Commit caad9334 authored by Sean McGivern's avatar Sean McGivern

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
parents 0ed42f4b 4e3e0dc8
---
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. ...@@ -340,7 +340,18 @@ URL-encoded.
For example, `/` is represented by `%2F`: 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` ## `id` vs `iid`
......
...@@ -86,6 +86,9 @@ module API ...@@ -86,6 +86,9 @@ module API
helpers ::API::Helpers helpers ::API::Helpers
helpers ::API::Helpers::CommonHelpers helpers ::API::Helpers::CommonHelpers
NO_SLASH_URL_PART_REGEX = %r{[^/]+}
PROJECT_ENDPOINT_REQUIREMENTS = { id: NO_SLASH_URL_PART_REGEX }.freeze
# Keep in alphabetical order # Keep in alphabetical order
mount ::API::AccessRequests mount ::API::AccessRequests
mount ::API::AwardEmoji mount ::API::AwardEmoji
......
...@@ -4,19 +4,21 @@ module API ...@@ -4,19 +4,21 @@ module API
class Branches < Grape::API class Branches < Grape::API
include PaginationParams include PaginationParams
BRANCH_ENDPOINT_REQUIREMENTS = API::PROJECT_ENDPOINT_REQUIREMENTS.merge(branch: API::NO_SLASH_URL_PART_REGEX)
before { authorize! :download_code, user_project } before { authorize! :download_code, user_project }
params do params do
requires :id, type: String, desc: 'The ID of a project' requires :id, type: String, desc: 'The ID of a project'
end end
resource :projects, requirements: { id: %r{[^/]+} } do resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
desc 'Get a project repository branches' do desc 'Get a project repository branches' do
success Entities::RepoBranch success Entities::RepoBranch
end end
params do params do
use :pagination use :pagination
end end
get ":id/repository/branches" do get ':id/repository/branches' do
branches = ::Kaminari.paginate_array(user_project.repository.branches.sort_by(&:name)) branches = ::Kaminari.paginate_array(user_project.repository.branches.sort_by(&:name))
present paginate(branches), with: Entities::RepoBranch, project: user_project present paginate(branches), with: Entities::RepoBranch, project: user_project
...@@ -28,7 +30,7 @@ module API ...@@ -28,7 +30,7 @@ module API
params do params do
requires :branch, type: String, desc: 'The name of the branch' requires :branch, type: String, desc: 'The name of the branch'
end 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]) branch = user_project.repository.find_branch(params[:branch])
not_found!("Branch") unless branch not_found!("Branch") unless branch
...@@ -46,7 +48,7 @@ module API ...@@ -46,7 +48,7 @@ module API
optional :developers_can_push, type: Boolean, desc: 'Flag if developers can push to that branch' 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' optional :developers_can_merge, type: Boolean, desc: 'Flag if developers can merge to that branch'
end end
put ':id/repository/branches/:branch/protect', requirements: { branch: /.+/ } do put ':id/repository/branches/:branch/protect', requirements: BRANCH_ENDPOINT_REQUIREMENTS do
authorize_admin_project authorize_admin_project
branch = user_project.repository.find_branch(params[:branch]) branch = user_project.repository.find_branch(params[:branch])
...@@ -81,7 +83,7 @@ module API ...@@ -81,7 +83,7 @@ module API
params do params do
requires :branch, type: String, desc: 'The name of the branch' requires :branch, type: String, desc: 'The name of the branch'
end end
put ':id/repository/branches/:branch/unprotect', requirements: { branch: /.+/ } do put ':id/repository/branches/:branch/unprotect', requirements: BRANCH_ENDPOINT_REQUIREMENTS do
authorize_admin_project authorize_admin_project
branch = user_project.repository.find_branch(params[:branch]) branch = user_project.repository.find_branch(params[:branch])
...@@ -99,7 +101,7 @@ module API ...@@ -99,7 +101,7 @@ module API
requires :branch, type: String, desc: 'The name of the branch' requires :branch, type: String, desc: 'The name of the branch'
requires :ref, type: String, desc: 'Create branch from commit sha or existing branch' requires :ref, type: String, desc: 'Create branch from commit sha or existing branch'
end end
post ":id/repository/branches" do post ':id/repository/branches' do
authorize_push_project authorize_push_project
result = CreateBranchService.new(user_project, current_user) result = CreateBranchService.new(user_project, current_user)
...@@ -118,7 +120,7 @@ module API ...@@ -118,7 +120,7 @@ module API
params do params do
requires :branch, type: String, desc: 'The name of the branch' requires :branch, type: String, desc: 'The name of the branch'
end end
delete ":id/repository/branches/:branch", requirements: { branch: /.+/ } do delete ':id/repository/branches/:branch', requirements: BRANCH_ENDPOINT_REQUIREMENTS do
authorize_push_project authorize_push_project
result = DeleteBranchService.new(user_project, current_user) result = DeleteBranchService.new(user_project, current_user)
...@@ -130,7 +132,7 @@ module API ...@@ -130,7 +132,7 @@ module API
end end
desc 'Delete all merged branches' 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 DeleteMergedBranchesService.new(user_project, current_user).async_execute
accepted! accepted!
......
{
"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
}
{
"type": "array",
"items": { "$ref": "branch.json" }
}
{
"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" }
}
}
This diff is collapsed.
...@@ -510,7 +510,7 @@ describe API::Groups do ...@@ -510,7 +510,7 @@ describe API::Groups do
describe "POST /groups/:id/projects/:project_id" do describe "POST /groups/:id/projects/:project_id" do
let(:project) { create(:empty_project) } let(:project) { create(:empty_project) }
let(:project_path) { project.full_path.gsub('/', '%2F') } let(:project_path) { CGI.escape(project.full_path) }
before(:each) do before(:each) do
allow_any_instance_of(Projects::TransferService) allow_any_instance_of(Projects::TransferService)
......
...@@ -768,7 +768,7 @@ describe API::Projects do ...@@ -768,7 +768,7 @@ describe API::Projects do
dot_user = create(:user, username: 'dot.user') dot_user = create(:user, username: 'dot.user')
project = create(:empty_project, creator_id: dot_user.id, namespace: dot_user.namespace) 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(response).to have_http_status(200)
expect(json_response['name']).to eq(project.name) expect(json_response['name']).to eq(project.name)
end end
......
...@@ -502,7 +502,7 @@ describe API::V3::Groups do ...@@ -502,7 +502,7 @@ describe API::V3::Groups do
describe "POST /groups/:id/projects/:project_id" do describe "POST /groups/:id/projects/:project_id" do
let(:project) { create(:empty_project) } 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 before(:each) do
allow_any_instance_of(Projects::TransferService) allow_any_instance_of(Projects::TransferService)
......
...@@ -720,7 +720,7 @@ describe API::V3::Projects do ...@@ -720,7 +720,7 @@ describe API::V3::Projects do
dot_user = create(:user, username: 'dot.user') dot_user = create(:user, username: 'dot.user')
project = create(:empty_project, creator_id: dot_user.id, namespace: dot_user.namespace) 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(response).to have_http_status(200)
expect(json_response['name']).to eq(project.name) expect(json_response['name']).to eq(project.name)
end end
......
...@@ -5,7 +5,14 @@ end ...@@ -5,7 +5,14 @@ end
RSpec::Matchers.define :match_response_schema do |schema, **options| RSpec::Matchers.define :match_response_schema do |schema, **options|
match do |response| 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
end end
......
...@@ -9,7 +9,7 @@ shared_examples_for '400 response' do ...@@ -9,7 +9,7 @@ shared_examples_for '400 response' do
end end
it 'returns 400' do it 'returns 400' do
expect(response).to have_http_status(400) expect(response).to have_gitlab_http_status(400)
end end
end end
...@@ -20,7 +20,7 @@ shared_examples_for '403 response' do ...@@ -20,7 +20,7 @@ shared_examples_for '403 response' do
end end
it 'returns 403' do it 'returns 403' do
expect(response).to have_http_status(403) expect(response).to have_gitlab_http_status(403)
end end
end end
...@@ -32,7 +32,7 @@ shared_examples_for '404 response' do ...@@ -32,7 +32,7 @@ shared_examples_for '404 response' do
end end
it 'returns 404' do 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 expect(json_response).to be_an Object
if message.present? if message.present?
......
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