Merge branch 'rc/fix-branches-api-endpoint' into 'master'
Fix the `/projects/:id/repository/branches endpoint` to handle dots in the branch name when the project full patch contains a `/` See merge request !13115
This commit is contained in:
commit
caad9334a7
14 changed files with 442 additions and 259 deletions
5
changelogs/unreleased/rc-fix-branches-api-endpoint.yml
Normal file
5
changelogs/unreleased/rc-fix-branches-api-endpoint.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix the /projects/:id/repository/branches endpoint to handle dots in the branch
|
||||
name when the project full patch contains a `/`
|
||||
merge_request: 13115
|
||||
author:
|
|
@ -340,7 +340,18 @@ URL-encoded.
|
|||
For example, `/` is represented by `%2F`:
|
||||
|
||||
```
|
||||
/api/v4/projects/diaspora%2Fdiaspora
|
||||
GET /api/v4/projects/diaspora%2Fdiaspora
|
||||
```
|
||||
|
||||
## Branches & tags name encoding
|
||||
|
||||
If your branch or tag contains a `/`, make sure the branch/tag name is
|
||||
URL-encoded.
|
||||
|
||||
For example, `/` is represented by `%2F`:
|
||||
|
||||
```
|
||||
GET /api/v4/projects/1/branches/my%2Fbranch/commits
|
||||
```
|
||||
|
||||
## `id` vs `iid`
|
||||
|
|
|
@ -86,6 +86,9 @@ module API
|
|||
helpers ::API::Helpers
|
||||
helpers ::API::Helpers::CommonHelpers
|
||||
|
||||
NO_SLASH_URL_PART_REGEX = %r{[^/]+}
|
||||
PROJECT_ENDPOINT_REQUIREMENTS = { id: NO_SLASH_URL_PART_REGEX }.freeze
|
||||
|
||||
# Keep in alphabetical order
|
||||
mount ::API::AccessRequests
|
||||
mount ::API::AwardEmoji
|
||||
|
|
|
@ -4,19 +4,21 @@ module API
|
|||
class Branches < Grape::API
|
||||
include PaginationParams
|
||||
|
||||
BRANCH_ENDPOINT_REQUIREMENTS = API::PROJECT_ENDPOINT_REQUIREMENTS.merge(branch: API::NO_SLASH_URL_PART_REGEX)
|
||||
|
||||
before { authorize! :download_code, user_project }
|
||||
|
||||
params do
|
||||
requires :id, type: String, desc: 'The ID of a project'
|
||||
end
|
||||
resource :projects, requirements: { id: %r{[^/]+} } do
|
||||
resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
|
||||
desc 'Get a project repository branches' do
|
||||
success Entities::RepoBranch
|
||||
end
|
||||
params do
|
||||
use :pagination
|
||||
end
|
||||
get ":id/repository/branches" do
|
||||
get ':id/repository/branches' do
|
||||
branches = ::Kaminari.paginate_array(user_project.repository.branches.sort_by(&:name))
|
||||
|
||||
present paginate(branches), with: Entities::RepoBranch, project: user_project
|
||||
|
@ -28,7 +30,7 @@ module API
|
|||
params do
|
||||
requires :branch, type: String, desc: 'The name of the branch'
|
||||
end
|
||||
get ':id/repository/branches/:branch', requirements: { branch: /.+/ } do
|
||||
get ':id/repository/branches/:branch', requirements: BRANCH_ENDPOINT_REQUIREMENTS do
|
||||
branch = user_project.repository.find_branch(params[:branch])
|
||||
not_found!("Branch") unless branch
|
||||
|
||||
|
@ -46,7 +48,7 @@ module API
|
|||
optional :developers_can_push, type: Boolean, desc: 'Flag if developers can push to that branch'
|
||||
optional :developers_can_merge, type: Boolean, desc: 'Flag if developers can merge to that branch'
|
||||
end
|
||||
put ':id/repository/branches/:branch/protect', requirements: { branch: /.+/ } do
|
||||
put ':id/repository/branches/:branch/protect', requirements: BRANCH_ENDPOINT_REQUIREMENTS do
|
||||
authorize_admin_project
|
||||
|
||||
branch = user_project.repository.find_branch(params[:branch])
|
||||
|
@ -81,7 +83,7 @@ module API
|
|||
params do
|
||||
requires :branch, type: String, desc: 'The name of the branch'
|
||||
end
|
||||
put ':id/repository/branches/:branch/unprotect', requirements: { branch: /.+/ } do
|
||||
put ':id/repository/branches/:branch/unprotect', requirements: BRANCH_ENDPOINT_REQUIREMENTS do
|
||||
authorize_admin_project
|
||||
|
||||
branch = user_project.repository.find_branch(params[:branch])
|
||||
|
@ -99,7 +101,7 @@ module API
|
|||
requires :branch, type: String, desc: 'The name of the branch'
|
||||
requires :ref, type: String, desc: 'Create branch from commit sha or existing branch'
|
||||
end
|
||||
post ":id/repository/branches" do
|
||||
post ':id/repository/branches' do
|
||||
authorize_push_project
|
||||
|
||||
result = CreateBranchService.new(user_project, current_user)
|
||||
|
@ -118,7 +120,7 @@ module API
|
|||
params do
|
||||
requires :branch, type: String, desc: 'The name of the branch'
|
||||
end
|
||||
delete ":id/repository/branches/:branch", requirements: { branch: /.+/ } do
|
||||
delete ':id/repository/branches/:branch', requirements: BRANCH_ENDPOINT_REQUIREMENTS do
|
||||
authorize_push_project
|
||||
|
||||
result = DeleteBranchService.new(user_project, current_user)
|
||||
|
@ -130,7 +132,7 @@ module API
|
|||
end
|
||||
|
||||
desc 'Delete all merged branches'
|
||||
delete ":id/repository/merged_branches" do
|
||||
delete ':id/repository/merged_branches' do
|
||||
DeleteMergedBranchesService.new(user_project, current_user).async_execute
|
||||
|
||||
accepted!
|
||||
|
|
20
spec/fixtures/api/schemas/public_api/v4/branch.json
vendored
Normal file
20
spec/fixtures/api/schemas/public_api/v4/branch.json
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"type": "object",
|
||||
"required" : [
|
||||
"name",
|
||||
"commit",
|
||||
"merged",
|
||||
"protected",
|
||||
"developers_can_push",
|
||||
"developers_can_merge"
|
||||
],
|
||||
"properties" : {
|
||||
"name": { "type": "string" },
|
||||
"commit": { "$ref": "commit/basic.json" },
|
||||
"merged": { "type": "boolean" },
|
||||
"protected": { "type": "boolean" },
|
||||
"developers_can_push": { "type": "boolean" },
|
||||
"developers_can_merge": { "type": "boolean" }
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
4
spec/fixtures/api/schemas/public_api/v4/branches.json
vendored
Normal file
4
spec/fixtures/api/schemas/public_api/v4/branches.json
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"type": "array",
|
||||
"items": { "$ref": "branch.json" }
|
||||
}
|
37
spec/fixtures/api/schemas/public_api/v4/commit/basic.json
vendored
Normal file
37
spec/fixtures/api/schemas/public_api/v4/commit/basic.json
vendored
Normal file
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"type": "object",
|
||||
"required" : [
|
||||
"id",
|
||||
"short_id",
|
||||
"title",
|
||||
"created_at",
|
||||
"parent_ids",
|
||||
"message",
|
||||
"author_name",
|
||||
"author_email",
|
||||
"authored_date",
|
||||
"committer_name",
|
||||
"committer_email",
|
||||
"committed_date"
|
||||
],
|
||||
"properties" : {
|
||||
"id": { "type": ["string", "null"] },
|
||||
"short_id": { "type": ["string", "null"] },
|
||||
"title": { "type": "string" },
|
||||
"created_at": { "type": "date" },
|
||||
"parent_ids": {
|
||||
"type": ["array", "null"],
|
||||
"items": {
|
||||
"type": "string",
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"message": { "type": "string" },
|
||||
"author_name": { "type": "string" },
|
||||
"author_email": { "type": "string" },
|
||||
"authored_date": { "type": "date" },
|
||||
"committer_name": { "type": "string" },
|
||||
"committer_email": { "type": "string" },
|
||||
"committed_date": { "type": "date" }
|
||||
}
|
||||
}
|
|
@ -1,25 +1,31 @@
|
|||
require 'spec_helper'
|
||||
require 'mime/types'
|
||||
|
||||
describe API::Branches do
|
||||
let(:user) { create(:user) }
|
||||
let!(:project) { create(:project, :repository, creator: user) }
|
||||
let!(:master) { create(:project_member, :master, user: user, project: project) }
|
||||
let(:guest) { create(:user).tap { |u| create(:project_member, :guest, user: u, project: project) } }
|
||||
let!(:branch_name) { 'feature' }
|
||||
let!(:branch_sha) { '0b4bc9a49b562e85de7cc9e834518ea6828729b9' }
|
||||
let(:branch_with_dot) { CreateBranchService.new(project, user).execute("with.1.2.3", "master")[:branch] }
|
||||
let(:guest) { create(:user).tap { |u| project.add_guest(u) } }
|
||||
let(:project) { create(:project, :repository, creator: user, path: 'my.project') }
|
||||
let(:branch_name) { 'feature' }
|
||||
let(:branch_sha) { '0b4bc9a49b562e85de7cc9e834518ea6828729b9' }
|
||||
let(:branch_with_dot) { project.repository.find_branch('ends-with.json') }
|
||||
let(:branch_with_slash) { project.repository.find_branch('improve/awesome') }
|
||||
|
||||
let(:project_id) { project.id }
|
||||
let(:current_user) { nil }
|
||||
|
||||
before do
|
||||
project.add_master(user)
|
||||
end
|
||||
|
||||
describe "GET /projects/:id/repository/branches" do
|
||||
let(:route) { "/projects/#{project.id}/repository/branches" }
|
||||
let(:route) { "/projects/#{project_id}/repository/branches" }
|
||||
|
||||
shared_examples_for 'repository branches' do
|
||||
it 'returns the repository branches' do
|
||||
get api(route, current_user), per_page: 100
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
expect(response).to have_gitlab_http_status(200)
|
||||
expect(response).to match_response_schema('public_api/v4/branches')
|
||||
expect(response).to include_pagination_headers
|
||||
expect(json_response).to be_an Array
|
||||
branch_names = json_response.map { |x| x['name'] }
|
||||
expect(branch_names).to match_array(project.repository.branch_names)
|
||||
end
|
||||
|
@ -34,10 +40,9 @@ describe API::Branches do
|
|||
end
|
||||
|
||||
context 'when unauthenticated', 'and project is public' do
|
||||
it_behaves_like 'repository branches' do
|
||||
let(:project) { create(:project, :public, :repository) }
|
||||
let(:current_user) { nil }
|
||||
end
|
||||
let(:project) { create(:project, :public, :repository) }
|
||||
|
||||
it_behaves_like 'repository branches'
|
||||
end
|
||||
|
||||
context 'when unauthenticated', 'and project is private' do
|
||||
|
@ -47,9 +52,15 @@ describe API::Branches do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when authenticated', 'as a developer' do
|
||||
it_behaves_like 'repository branches' do
|
||||
let(:current_user) { user }
|
||||
context 'when authenticated', 'as a master' do
|
||||
let(:current_user) { user }
|
||||
|
||||
it_behaves_like 'repository branches'
|
||||
|
||||
context 'requesting with the escaped project full path' do
|
||||
let(:project_id) { CGI.escape(project.full_path) }
|
||||
|
||||
it_behaves_like 'repository branches'
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -61,31 +72,15 @@ describe API::Branches do
|
|||
end
|
||||
|
||||
describe "GET /projects/:id/repository/branches/:branch" do
|
||||
let(:route) { "/projects/#{project.id}/repository/branches/#{branch_name}" }
|
||||
let(:route) { "/projects/#{project_id}/repository/branches/#{branch_name}" }
|
||||
|
||||
shared_examples_for 'repository branch' do |merged: false|
|
||||
shared_examples_for 'repository branch' do
|
||||
it 'returns the repository branch' do
|
||||
get api(route, current_user)
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
expect(json_response['name']).to eq(branch_name)
|
||||
expect(json_response['merged']).to eq(merged)
|
||||
expect(json_response['protected']).to eq(false)
|
||||
expect(json_response['developers_can_push']).to eq(false)
|
||||
expect(json_response['developers_can_merge']).to eq(false)
|
||||
|
||||
json_commit = json_response['commit']
|
||||
expect(json_commit['id']).to eq(branch_sha)
|
||||
expect(json_commit).to have_key('short_id')
|
||||
expect(json_commit).to have_key('title')
|
||||
expect(json_commit).to have_key('message')
|
||||
expect(json_commit).to have_key('author_name')
|
||||
expect(json_commit).to have_key('author_email')
|
||||
expect(json_commit).to have_key('authored_date')
|
||||
expect(json_commit).to have_key('committer_name')
|
||||
expect(json_commit).to have_key('committer_email')
|
||||
expect(json_commit).to have_key('committed_date')
|
||||
expect(json_commit).to have_key('parent_ids')
|
||||
expect(response).to have_gitlab_http_status(200)
|
||||
expect(response).to match_response_schema('public_api/v4/branch')
|
||||
expect(json_response['name']).to eq(CGI.unescape(branch_name))
|
||||
end
|
||||
|
||||
context 'when branch does not exist' do
|
||||
|
@ -107,10 +102,9 @@ describe API::Branches do
|
|||
end
|
||||
|
||||
context 'when unauthenticated', 'and project is public' do
|
||||
it_behaves_like 'repository branch' do
|
||||
let(:project) { create(:project, :public, :repository) }
|
||||
let(:current_user) { nil }
|
||||
end
|
||||
let(:project) { create(:project, :public, :repository) }
|
||||
|
||||
it_behaves_like 'repository branch'
|
||||
end
|
||||
|
||||
context 'when unauthenticated', 'and project is private' do
|
||||
|
@ -120,22 +114,41 @@ describe API::Branches do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when authenticated', 'as a developer' do
|
||||
context 'when authenticated', 'as a master' do
|
||||
let(:current_user) { user }
|
||||
|
||||
it_behaves_like 'repository branch'
|
||||
|
||||
context 'when branch contains a dot' do
|
||||
let(:branch_name) { branch_with_dot.name }
|
||||
let(:branch_sha) { project.commit('master').sha }
|
||||
|
||||
it_behaves_like 'repository branch'
|
||||
end
|
||||
|
||||
context 'when branch is merged' do
|
||||
let(:branch_name) { 'merge-test' }
|
||||
let(:branch_sha) { project.commit('merge-test').sha }
|
||||
context 'when branch contains a slash' do
|
||||
let(:branch_name) { branch_with_slash.name }
|
||||
|
||||
it_behaves_like 'repository branch', merged: true
|
||||
it_behaves_like '404 response' do
|
||||
let(:request) { get api(route, current_user) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when branch contains an escaped slash' do
|
||||
let(:branch_name) { CGI.escape(branch_with_slash.name) }
|
||||
|
||||
it_behaves_like 'repository branch'
|
||||
end
|
||||
|
||||
context 'requesting with the escaped project full path' do
|
||||
let(:project_id) { CGI.escape(project.full_path) }
|
||||
|
||||
it_behaves_like 'repository branch'
|
||||
|
||||
context 'when branch contains a dot' do
|
||||
let(:branch_name) { branch_with_dot.name }
|
||||
|
||||
it_behaves_like 'repository branch'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -147,268 +160,348 @@ describe API::Branches do
|
|||
end
|
||||
|
||||
describe 'PUT /projects/:id/repository/branches/:branch/protect' do
|
||||
context "when a protected branch doesn't already exist" do
|
||||
let(:route) { "/projects/#{project_id}/repository/branches/#{branch_name}/protect" }
|
||||
|
||||
shared_examples_for 'repository new protected branch' do
|
||||
it 'protects a single branch' do
|
||||
put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user)
|
||||
put api(route, current_user)
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
expect(json_response['name']).to eq(branch_name)
|
||||
expect(json_response['commit']['id']).to eq(branch_sha)
|
||||
expect(json_response['protected']).to eq(true)
|
||||
expect(json_response['developers_can_push']).to eq(false)
|
||||
expect(json_response['developers_can_merge']).to eq(false)
|
||||
end
|
||||
|
||||
it "protects a single branch with dots in the name" do
|
||||
put api("/projects/#{project.id}/repository/branches/#{branch_with_dot.name}/protect", user)
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
expect(json_response['name']).to eq(branch_with_dot.name)
|
||||
expect(response).to have_gitlab_http_status(200)
|
||||
expect(response).to match_response_schema('public_api/v4/branch')
|
||||
expect(json_response['name']).to eq(CGI.unescape(branch_name))
|
||||
expect(json_response['protected']).to eq(true)
|
||||
end
|
||||
|
||||
it 'protects a single branch and developers can push' do
|
||||
put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user),
|
||||
developers_can_push: true
|
||||
put api(route, current_user), developers_can_push: true
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
expect(json_response['name']).to eq(branch_name)
|
||||
expect(json_response['commit']['id']).to eq(branch_sha)
|
||||
expect(response).to have_gitlab_http_status(200)
|
||||
expect(response).to match_response_schema('public_api/v4/branch')
|
||||
expect(json_response['name']).to eq(CGI.unescape(branch_name))
|
||||
expect(json_response['protected']).to eq(true)
|
||||
expect(json_response['developers_can_push']).to eq(true)
|
||||
expect(json_response['developers_can_merge']).to eq(false)
|
||||
end
|
||||
|
||||
it 'protects a single branch and developers can merge' do
|
||||
put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user),
|
||||
developers_can_merge: true
|
||||
put api(route, current_user), developers_can_merge: true
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
expect(json_response['name']).to eq(branch_name)
|
||||
expect(json_response['commit']['id']).to eq(branch_sha)
|
||||
expect(response).to have_gitlab_http_status(200)
|
||||
expect(response).to match_response_schema('public_api/v4/branch')
|
||||
expect(json_response['name']).to eq(CGI.unescape(branch_name))
|
||||
expect(json_response['protected']).to eq(true)
|
||||
expect(json_response['developers_can_push']).to eq(false)
|
||||
expect(json_response['developers_can_merge']).to eq(true)
|
||||
end
|
||||
|
||||
it 'protects a single branch and developers can push and merge' do
|
||||
put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user),
|
||||
developers_can_push: true, developers_can_merge: true
|
||||
put api(route, current_user), developers_can_push: true, developers_can_merge: true
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
expect(json_response['name']).to eq(branch_name)
|
||||
expect(response).to have_gitlab_http_status(200)
|
||||
expect(response).to match_response_schema('public_api/v4/branch')
|
||||
expect(json_response['name']).to eq(CGI.unescape(branch_name))
|
||||
expect(json_response['protected']).to eq(true)
|
||||
expect(json_response['developers_can_push']).to eq(true)
|
||||
expect(json_response['developers_can_merge']).to eq(true)
|
||||
end
|
||||
|
||||
context 'when branch does not exist' do
|
||||
let(:branch_name) { 'unknown' }
|
||||
|
||||
it_behaves_like '404 response' do
|
||||
let(:request) { put api(route, current_user) }
|
||||
let(:message) { '404 Branch Not Found' }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when repository is disabled' do
|
||||
include_context 'disabled repository'
|
||||
|
||||
it_behaves_like '403 response' do
|
||||
let(:request) { put api(route, current_user) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when unauthenticated', 'and project is private' do
|
||||
it_behaves_like '404 response' do
|
||||
let(:request) { put api(route) }
|
||||
let(:message) { '404 Project Not Found' }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when authenticated', 'as a guest' do
|
||||
it_behaves_like '403 response' do
|
||||
let(:request) { put api(route, guest) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when authenticated', 'as a master' do
|
||||
let(:current_user) { user }
|
||||
|
||||
context "when a protected branch doesn't already exist" do
|
||||
it_behaves_like 'repository new protected branch'
|
||||
|
||||
context 'when branch contains a dot' do
|
||||
let(:branch_name) { branch_with_dot.name }
|
||||
|
||||
it_behaves_like 'repository new protected branch'
|
||||
end
|
||||
|
||||
context 'when branch contains a slash' do
|
||||
let(:branch_name) { branch_with_slash.name }
|
||||
|
||||
it_behaves_like '404 response' do
|
||||
let(:request) { put api(route, current_user) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when branch contains an escaped slash' do
|
||||
let(:branch_name) { CGI.escape(branch_with_slash.name) }
|
||||
|
||||
it_behaves_like 'repository new protected branch'
|
||||
end
|
||||
|
||||
context 'requesting with the escaped project full path' do
|
||||
let(:project_id) { CGI.escape(project.full_path) }
|
||||
|
||||
it_behaves_like 'repository new protected branch'
|
||||
|
||||
context 'when branch contains a dot' do
|
||||
let(:branch_name) { branch_with_dot.name }
|
||||
|
||||
it_behaves_like 'repository new protected branch'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when protected branch already exists' do
|
||||
before do
|
||||
project.repository.add_branch(user, protected_branch.name, 'master')
|
||||
end
|
||||
|
||||
context 'when developers can push and merge' do
|
||||
let(:protected_branch) { create(:protected_branch, :developers_can_push, :developers_can_merge, project: project, name: 'protected_branch') }
|
||||
|
||||
it 'updates that a developer cannot push or merge' do
|
||||
put api("/projects/#{project.id}/repository/branches/#{protected_branch.name}/protect", user),
|
||||
developers_can_push: false, developers_can_merge: false
|
||||
|
||||
expect(response).to have_gitlab_http_status(200)
|
||||
expect(response).to match_response_schema('public_api/v4/branch')
|
||||
expect(json_response['name']).to eq(protected_branch.name)
|
||||
expect(json_response['protected']).to eq(true)
|
||||
expect(json_response['developers_can_push']).to eq(false)
|
||||
expect(json_response['developers_can_merge']).to eq(false)
|
||||
expect(protected_branch.reload.push_access_levels.first.access_level).to eq(Gitlab::Access::MASTER)
|
||||
expect(protected_branch.reload.merge_access_levels.first.access_level).to eq(Gitlab::Access::MASTER)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when developers cannot push or merge' do
|
||||
let(:protected_branch) { create(:protected_branch, project: project, name: 'protected_branch') }
|
||||
|
||||
it 'updates that a developer can push and merge' do
|
||||
put api("/projects/#{project.id}/repository/branches/#{protected_branch.name}/protect", user),
|
||||
developers_can_push: true, developers_can_merge: true
|
||||
|
||||
expect(response).to have_gitlab_http_status(200)
|
||||
expect(response).to match_response_schema('public_api/v4/branch')
|
||||
expect(json_response['name']).to eq(protected_branch.name)
|
||||
expect(json_response['protected']).to eq(true)
|
||||
expect(json_response['developers_can_push']).to eq(true)
|
||||
expect(json_response['developers_can_merge']).to eq(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PUT /projects/:id/repository/branches/:branch/unprotect' do
|
||||
let(:route) { "/projects/#{project_id}/repository/branches/#{branch_name}/unprotect" }
|
||||
|
||||
shared_examples_for 'repository unprotected branch' do
|
||||
it 'unprotects a single branch' do
|
||||
put api(route, current_user)
|
||||
|
||||
expect(response).to have_gitlab_http_status(200)
|
||||
expect(response).to match_response_schema('public_api/v4/branch')
|
||||
expect(json_response['name']).to eq(CGI.unescape(branch_name))
|
||||
expect(json_response['protected']).to eq(false)
|
||||
end
|
||||
|
||||
context 'when branch does not exist' do
|
||||
let(:branch_name) { 'unknown' }
|
||||
|
||||
it_behaves_like '404 response' do
|
||||
let(:request) { put api(route, current_user) }
|
||||
let(:message) { '404 Branch Not Found' }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when repository is disabled' do
|
||||
include_context 'disabled repository'
|
||||
|
||||
it_behaves_like '403 response' do
|
||||
let(:request) { put api(route, current_user) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when unauthenticated', 'and project is private' do
|
||||
it_behaves_like '404 response' do
|
||||
let(:request) { put api(route) }
|
||||
let(:message) { '404 Project Not Found' }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when authenticated', 'as a guest' do
|
||||
it_behaves_like '403 response' do
|
||||
let(:request) { put api(route, guest) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when authenticated', 'as a master' do
|
||||
let(:current_user) { user }
|
||||
|
||||
context "when a protected branch doesn't already exist" do
|
||||
it_behaves_like 'repository unprotected branch'
|
||||
|
||||
context 'when branch contains a dot' do
|
||||
let(:branch_name) { branch_with_dot.name }
|
||||
|
||||
it_behaves_like 'repository unprotected branch'
|
||||
end
|
||||
|
||||
context 'when branch contains a slash' do
|
||||
let(:branch_name) { branch_with_slash.name }
|
||||
|
||||
it_behaves_like '404 response' do
|
||||
let(:request) { put api(route, current_user) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when branch contains an escaped slash' do
|
||||
let(:branch_name) { CGI.escape(branch_with_slash.name) }
|
||||
|
||||
it_behaves_like 'repository unprotected branch'
|
||||
end
|
||||
|
||||
context 'requesting with the escaped project full path' do
|
||||
let(:project_id) { CGI.escape(project.full_path) }
|
||||
|
||||
it_behaves_like 'repository unprotected branch'
|
||||
|
||||
context 'when branch contains a dot' do
|
||||
let(:branch_name) { branch_with_dot.name }
|
||||
|
||||
it_behaves_like 'repository unprotected branch'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /projects/:id/repository/branches' do
|
||||
let(:route) { "/projects/#{project_id}/repository/branches" }
|
||||
|
||||
shared_examples_for 'repository new branch' do
|
||||
it 'creates a new branch' do
|
||||
post api(route, current_user), branch: 'feature1', ref: branch_sha
|
||||
|
||||
expect(response).to have_gitlab_http_status(201)
|
||||
expect(response).to match_response_schema('public_api/v4/branch')
|
||||
expect(json_response['name']).to eq('feature1')
|
||||
expect(json_response['commit']['id']).to eq(branch_sha)
|
||||
expect(json_response['protected']).to eq(true)
|
||||
expect(json_response['developers_can_push']).to eq(true)
|
||||
expect(json_response['developers_can_merge']).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'for an existing protected branch' do
|
||||
before do
|
||||
project.repository.add_branch(user, protected_branch.name, 'master')
|
||||
end
|
||||
|
||||
context "when developers can push and merge" do
|
||||
let(:protected_branch) { create(:protected_branch, :developers_can_push, :developers_can_merge, project: project, name: 'protected_branch') }
|
||||
context 'when repository is disabled' do
|
||||
include_context 'disabled repository'
|
||||
|
||||
it 'updates that a developer cannot push or merge' do
|
||||
put api("/projects/#{project.id}/repository/branches/#{protected_branch.name}/protect", user),
|
||||
developers_can_push: false, developers_can_merge: false
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
expect(json_response['name']).to eq(protected_branch.name)
|
||||
expect(json_response['protected']).to eq(true)
|
||||
expect(json_response['developers_can_push']).to eq(false)
|
||||
expect(json_response['developers_can_merge']).to eq(false)
|
||||
end
|
||||
|
||||
it "doesn't result in 0 access levels when 'developers_can_push' is switched off" do
|
||||
put api("/projects/#{project.id}/repository/branches/#{protected_branch.name}/protect", user),
|
||||
developers_can_push: false
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
expect(json_response['name']).to eq(protected_branch.name)
|
||||
expect(protected_branch.reload.push_access_levels.first).to be_present
|
||||
expect(protected_branch.reload.push_access_levels.first.access_level).to eq(Gitlab::Access::MASTER)
|
||||
end
|
||||
|
||||
it "doesn't result in 0 access levels when 'developers_can_merge' is switched off" do
|
||||
put api("/projects/#{project.id}/repository/branches/#{protected_branch.name}/protect", user),
|
||||
developers_can_merge: false
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
expect(json_response['name']).to eq(protected_branch.name)
|
||||
expect(protected_branch.reload.merge_access_levels.first).to be_present
|
||||
expect(protected_branch.reload.merge_access_levels.first.access_level).to eq(Gitlab::Access::MASTER)
|
||||
end
|
||||
end
|
||||
|
||||
context "when developers cannot push or merge" do
|
||||
let(:protected_branch) { create(:protected_branch, project: project, name: 'protected_branch') }
|
||||
|
||||
it 'updates that a developer can push and merge' do
|
||||
put api("/projects/#{project.id}/repository/branches/#{protected_branch.name}/protect", user),
|
||||
developers_can_push: true, developers_can_merge: true
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
expect(json_response['name']).to eq(protected_branch.name)
|
||||
expect(json_response['protected']).to eq(true)
|
||||
expect(json_response['developers_can_push']).to eq(true)
|
||||
expect(json_response['developers_can_merge']).to eq(true)
|
||||
it_behaves_like '403 response' do
|
||||
let(:request) { post api(route, current_user) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "multiple API calls" do
|
||||
it "returns success when `protect` is called twice" do
|
||||
put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user)
|
||||
put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user)
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
expect(json_response['name']).to eq(branch_name)
|
||||
expect(json_response['protected']).to eq(true)
|
||||
expect(json_response['developers_can_push']).to eq(false)
|
||||
expect(json_response['developers_can_merge']).to eq(false)
|
||||
end
|
||||
|
||||
it "returns success when `protect` is called twice with `developers_can_push` turned on" do
|
||||
put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user), developers_can_push: true
|
||||
put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user), developers_can_push: true
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
expect(json_response['name']).to eq(branch_name)
|
||||
expect(json_response['protected']).to eq(true)
|
||||
expect(json_response['developers_can_push']).to eq(true)
|
||||
expect(json_response['developers_can_merge']).to eq(false)
|
||||
end
|
||||
|
||||
it "returns success when `protect` is called twice with `developers_can_merge` turned on" do
|
||||
put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user), developers_can_merge: true
|
||||
put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user), developers_can_merge: true
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
expect(json_response['name']).to eq(branch_name)
|
||||
expect(json_response['protected']).to eq(true)
|
||||
expect(json_response['developers_can_push']).to eq(false)
|
||||
expect(json_response['developers_can_merge']).to eq(true)
|
||||
context 'when unauthenticated', 'and project is private' do
|
||||
it_behaves_like '404 response' do
|
||||
let(:request) { post api(route) }
|
||||
let(:message) { '404 Project Not Found' }
|
||||
end
|
||||
end
|
||||
|
||||
it "returns a 404 error if branch not found" do
|
||||
put api("/projects/#{project.id}/repository/branches/unknown/protect", user)
|
||||
expect(response).to have_http_status(404)
|
||||
context 'when authenticated', 'as a guest' do
|
||||
it_behaves_like '403 response' do
|
||||
let(:request) { post api(route, guest) }
|
||||
end
|
||||
end
|
||||
|
||||
it "returns a 403 error if guest" do
|
||||
put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", guest)
|
||||
expect(response).to have_http_status(403)
|
||||
end
|
||||
end
|
||||
context 'when authenticated', 'as a master' do
|
||||
let(:current_user) { user }
|
||||
|
||||
describe "PUT /projects/:id/repository/branches/:branch/unprotect" do
|
||||
it "unprotects a single branch" do
|
||||
put api("/projects/#{project.id}/repository/branches/#{branch_name}/unprotect", user)
|
||||
expect(response).to have_http_status(200)
|
||||
context "when a protected branch doesn't already exist" do
|
||||
it_behaves_like 'repository new branch'
|
||||
|
||||
expect(json_response['name']).to eq(branch_name)
|
||||
expect(json_response['commit']['id']).to eq(branch_sha)
|
||||
expect(json_response['protected']).to eq(false)
|
||||
end
|
||||
context 'requesting with the escaped project full path' do
|
||||
let(:project_id) { CGI.escape(project.full_path) }
|
||||
|
||||
it "update branches with dots in branch name" do
|
||||
put api("/projects/#{project.id}/repository/branches/#{branch_with_dot.name}/unprotect", user)
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
expect(json_response['name']).to eq(branch_with_dot.name)
|
||||
expect(json_response['protected']).to eq(false)
|
||||
end
|
||||
|
||||
it "returns success when unprotect branch" do
|
||||
put api("/projects/#{project.id}/repository/branches/unknown/unprotect", user)
|
||||
expect(response).to have_http_status(404)
|
||||
end
|
||||
|
||||
it "returns success when unprotect branch again" do
|
||||
put api("/projects/#{project.id}/repository/branches/#{branch_name}/unprotect", user)
|
||||
put api("/projects/#{project.id}/repository/branches/#{branch_name}/unprotect", user)
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
end
|
||||
|
||||
describe "POST /projects/:id/repository/branches" do
|
||||
it "creates a new branch" do
|
||||
post api("/projects/#{project.id}/repository/branches", user),
|
||||
branch: 'feature1',
|
||||
ref: branch_sha
|
||||
|
||||
expect(response).to have_http_status(201)
|
||||
|
||||
expect(json_response['name']).to eq('feature1')
|
||||
expect(json_response['commit']['id']).to eq(branch_sha)
|
||||
end
|
||||
|
||||
it "denies for user without push access" do
|
||||
post api("/projects/#{project.id}/repository/branches", guest),
|
||||
branch: branch_name,
|
||||
ref: branch_sha
|
||||
expect(response).to have_http_status(403)
|
||||
it_behaves_like 'repository new branch'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns 400 if branch name is invalid' do
|
||||
post api("/projects/#{project.id}/repository/branches", user),
|
||||
branch: 'new design',
|
||||
ref: branch_sha
|
||||
expect(response).to have_http_status(400)
|
||||
post api(route, user), branch: 'new design', ref: branch_sha
|
||||
|
||||
expect(response).to have_gitlab_http_status(400)
|
||||
expect(json_response['message']).to eq('Branch name is invalid')
|
||||
end
|
||||
|
||||
it 'returns 400 if branch already exists' do
|
||||
post api("/projects/#{project.id}/repository/branches", user),
|
||||
branch: 'new_design1',
|
||||
ref: branch_sha
|
||||
expect(response).to have_http_status(201)
|
||||
post api(route, user), branch: 'new_design1', ref: branch_sha
|
||||
|
||||
post api("/projects/#{project.id}/repository/branches", user),
|
||||
branch: 'new_design1',
|
||||
ref: branch_sha
|
||||
expect(response).to have_http_status(400)
|
||||
expect(response).to have_gitlab_http_status(201)
|
||||
|
||||
post api(route, user), branch: 'new_design1', ref: branch_sha
|
||||
|
||||
expect(response).to have_gitlab_http_status(400)
|
||||
expect(json_response['message']).to eq('Branch already exists')
|
||||
end
|
||||
|
||||
it 'returns 400 if ref name is invalid' do
|
||||
post api("/projects/#{project.id}/repository/branches", user),
|
||||
branch: 'new_design3',
|
||||
ref: 'foo'
|
||||
expect(response).to have_http_status(400)
|
||||
post api(route, user), branch: 'new_design3', ref: 'foo'
|
||||
|
||||
expect(response).to have_gitlab_http_status(400)
|
||||
expect(json_response['message']).to eq('Invalid reference name')
|
||||
end
|
||||
end
|
||||
|
||||
describe "DELETE /projects/:id/repository/branches/:branch" do
|
||||
describe 'DELETE /projects/:id/repository/branches/:branch' do
|
||||
before do
|
||||
allow_any_instance_of(Repository).to receive(:rm_branch).and_return(true)
|
||||
end
|
||||
|
||||
it "removes branch" do
|
||||
it 'removes branch' do
|
||||
delete api("/projects/#{project.id}/repository/branches/#{branch_name}", user)
|
||||
|
||||
expect(response).to have_http_status(204)
|
||||
expect(response).to have_gitlab_http_status(204)
|
||||
end
|
||||
|
||||
it "removes a branch with dots in the branch name" do
|
||||
it 'removes a branch with dots in the branch name' do
|
||||
delete api("/projects/#{project.id}/repository/branches/#{branch_with_dot.name}", user)
|
||||
|
||||
expect(response).to have_http_status(204)
|
||||
expect(response).to have_gitlab_http_status(204)
|
||||
end
|
||||
|
||||
it 'returns 404 if branch not exists' do
|
||||
delete api("/projects/#{project.id}/repository/branches/foobar", user)
|
||||
expect(response).to have_http_status(404)
|
||||
|
||||
expect(response).to have_gitlab_http_status(404)
|
||||
end
|
||||
end
|
||||
|
||||
describe "DELETE /projects/:id/repository/merged_branches" do
|
||||
describe 'DELETE /projects/:id/repository/merged_branches' do
|
||||
before do
|
||||
allow_any_instance_of(Repository).to receive(:rm_branch).and_return(true)
|
||||
end
|
||||
|
@ -416,13 +509,14 @@ describe API::Branches do
|
|||
it 'returns 202 with json body' do
|
||||
delete api("/projects/#{project.id}/repository/merged_branches", user)
|
||||
|
||||
expect(response).to have_http_status(202)
|
||||
expect(response).to have_gitlab_http_status(202)
|
||||
expect(json_response['message']).to eql('202 Accepted')
|
||||
end
|
||||
|
||||
it 'returns a 403 error if guest' do
|
||||
delete api("/projects/#{project.id}/repository/merged_branches", guest)
|
||||
expect(response).to have_http_status(403)
|
||||
|
||||
expect(response).to have_gitlab_http_status(403)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -510,7 +510,7 @@ describe API::Groups do
|
|||
|
||||
describe "POST /groups/:id/projects/:project_id" do
|
||||
let(:project) { create(:empty_project) }
|
||||
let(:project_path) { project.full_path.gsub('/', '%2F') }
|
||||
let(:project_path) { CGI.escape(project.full_path) }
|
||||
|
||||
before(:each) do
|
||||
allow_any_instance_of(Projects::TransferService)
|
||||
|
|
|
@ -768,7 +768,7 @@ describe API::Projects do
|
|||
dot_user = create(:user, username: 'dot.user')
|
||||
project = create(:empty_project, creator_id: dot_user.id, namespace: dot_user.namespace)
|
||||
|
||||
get api("/projects/#{dot_user.namespace.name}%2F#{project.path}", dot_user)
|
||||
get api("/projects/#{CGI.escape(project.full_path)}", dot_user)
|
||||
expect(response).to have_http_status(200)
|
||||
expect(json_response['name']).to eq(project.name)
|
||||
end
|
||||
|
|
|
@ -502,7 +502,7 @@ describe API::V3::Groups do
|
|||
|
||||
describe "POST /groups/:id/projects/:project_id" do
|
||||
let(:project) { create(:empty_project) }
|
||||
let(:project_path) { "#{project.namespace.path}%2F#{project.path}" }
|
||||
let(:project_path) { CGI.escape(project.full_path) }
|
||||
|
||||
before(:each) do
|
||||
allow_any_instance_of(Projects::TransferService)
|
||||
|
|
|
@ -720,7 +720,7 @@ describe API::V3::Projects do
|
|||
dot_user = create(:user, username: 'dot.user')
|
||||
project = create(:empty_project, creator_id: dot_user.id, namespace: dot_user.namespace)
|
||||
|
||||
get v3_api("/projects/#{dot_user.namespace.name}%2F#{project.path}", dot_user)
|
||||
get v3_api("/projects/#{CGI.escape(project.full_path)}", dot_user)
|
||||
expect(response).to have_http_status(200)
|
||||
expect(json_response['name']).to eq(project.name)
|
||||
end
|
||||
|
|
|
@ -5,7 +5,14 @@ end
|
|||
|
||||
RSpec::Matchers.define :match_response_schema do |schema, **options|
|
||||
match do |response|
|
||||
JSON::Validator.validate!(schema_path(schema), response.body, options)
|
||||
@errors = JSON::Validator.fully_validate(schema_path(schema), response.body, options)
|
||||
|
||||
@errors.empty?
|
||||
end
|
||||
|
||||
failure_message do |response|
|
||||
"didn't match the schema defined by #{schema_path(schema)}" \
|
||||
" The validation errors were:\n#{@errors.join("\n")}"
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ shared_examples_for '400 response' do
|
|||
end
|
||||
|
||||
it 'returns 400' do
|
||||
expect(response).to have_http_status(400)
|
||||
expect(response).to have_gitlab_http_status(400)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -20,7 +20,7 @@ shared_examples_for '403 response' do
|
|||
end
|
||||
|
||||
it 'returns 403' do
|
||||
expect(response).to have_http_status(403)
|
||||
expect(response).to have_gitlab_http_status(403)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -32,7 +32,7 @@ shared_examples_for '404 response' do
|
|||
end
|
||||
|
||||
it 'returns 404' do
|
||||
expect(response).to have_http_status(404)
|
||||
expect(response).to have_gitlab_http_status(404)
|
||||
expect(json_response).to be_an Object
|
||||
|
||||
if message.present?
|
Loading…
Reference in a new issue