Merge branch '20049-projects-api-forks' into 'master'
Resolve "make project data via API report forks of this project" Closes #20049 See merge request gitlab-org/gitlab-ce!14355
This commit is contained in:
commit
d247841b48
|
@ -9,14 +9,12 @@ class Projects::ForksController < Projects::ApplicationController
|
|||
def index
|
||||
base_query = project.forks.includes(:creator)
|
||||
|
||||
@forks = base_query.merge(ProjectsFinder.new(current_user: current_user).execute)
|
||||
forks = ForkProjectsFinder.new(project, params: params.merge(search: params[:filter_projects]), current_user: current_user).execute
|
||||
@total_forks_count = base_query.size
|
||||
@private_forks_count = @total_forks_count - @forks.size
|
||||
@private_forks_count = @total_forks_count - forks.size
|
||||
@public_forks_count = @total_forks_count - @private_forks_count
|
||||
|
||||
@sort = params[:sort] || 'id_desc'
|
||||
@forks = @forks.search(params[:filter_projects]) if params[:filter_projects].present?
|
||||
@forks = @forks.order_by(@sort).page(params[:page])
|
||||
@forks = forks.page(params[:page])
|
||||
|
||||
respond_to do |format|
|
||||
format.html
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
class ForkProjectsFinder < ProjectsFinder
|
||||
def initialize(project, params: {}, current_user: nil)
|
||||
project_ids = project.forks.includes(:creator).select(:id)
|
||||
super(params: params, current_user: current_user, project_ids_relation: project_ids)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add an API endpoint to determine the forks of a project
|
||||
merge_request:
|
||||
author:
|
||||
type: added
|
|
@ -635,6 +635,98 @@ POST /projects/:id/fork
|
|||
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
|
||||
| `namespace` | integer/string | yes | The ID or path of the namespace that the project will be forked to |
|
||||
|
||||
## List Forks of a project
|
||||
|
||||
>**Note:** This feature was introduced in GitLab 10.1
|
||||
|
||||
List the projects accessible to the calling user that have an established, forked relationship with the specified project
|
||||
|
||||
```
|
||||
GET /projects/:id/forks
|
||||
```
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
|
||||
| `archived` | boolean | no | Limit by archived status |
|
||||
| `visibility` | string | no | Limit by visibility `public`, `internal`, or `private` |
|
||||
| `order_by` | string | no | Return projects ordered by `id`, `name`, `path`, `created_at`, `updated_at`, or `last_activity_at` fields. Default is `created_at` |
|
||||
| `sort` | string | no | Return projects sorted in `asc` or `desc` order. Default is `desc` |
|
||||
| `search` | string | no | Return list of projects matching the search criteria |
|
||||
| `simple` | boolean | no | Return only the ID, URL, name, and path of each project |
|
||||
| `owned` | boolean | no | Limit by projects owned by the current user |
|
||||
| `membership` | boolean | no | Limit by projects that the current user is a member of |
|
||||
| `starred` | boolean | no | Limit by projects starred by the current user |
|
||||
| `statistics` | boolean | no | Include project statistics |
|
||||
| `with_issues_enabled` | boolean | no | Limit by enabled issues feature |
|
||||
| `with_merge_requests_enabled` | boolean | no | Limit by enabled merge requests feature |
|
||||
|
||||
```bash
|
||||
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/5/forks"
|
||||
```
|
||||
|
||||
Example responses:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": 3,
|
||||
"description": null,
|
||||
"default_branch": "master",
|
||||
"visibility": "internal",
|
||||
"ssh_url_to_repo": "git@example.com:diaspora/diaspora-project-site.git",
|
||||
"http_url_to_repo": "http://example.com/diaspora/diaspora-project-site.git",
|
||||
"web_url": "http://example.com/diaspora/diaspora-project-site",
|
||||
"tag_list": [
|
||||
"example",
|
||||
"disapora project"
|
||||
],
|
||||
"name": "Diaspora Project Site",
|
||||
"name_with_namespace": "Diaspora / Diaspora Project Site",
|
||||
"path": "diaspora-project-site",
|
||||
"path_with_namespace": "diaspora/diaspora-project-site",
|
||||
"issues_enabled": true,
|
||||
"open_issues_count": 1,
|
||||
"merge_requests_enabled": true,
|
||||
"jobs_enabled": true,
|
||||
"wiki_enabled": true,
|
||||
"snippets_enabled": false,
|
||||
"resolve_outdated_diff_discussions": false,
|
||||
"container_registry_enabled": false,
|
||||
"created_at": "2013-09-30T13:46:02Z",
|
||||
"last_activity_at": "2013-09-30T13:46:02Z",
|
||||
"creator_id": 3,
|
||||
"namespace": {
|
||||
"id": 3,
|
||||
"name": "Diaspora",
|
||||
"path": "diaspora",
|
||||
"kind": "group",
|
||||
"full_path": "diaspora"
|
||||
},
|
||||
"import_status": "none",
|
||||
"archived": true,
|
||||
"avatar_url": "http://example.com/uploads/project/avatar/3/uploads/avatar.png",
|
||||
"shared_runners_enabled": true,
|
||||
"forks_count": 0,
|
||||
"star_count": 1,
|
||||
"public_jobs": true,
|
||||
"shared_with_groups": [],
|
||||
"only_allow_merge_if_pipeline_succeeds": false,
|
||||
"only_allow_merge_if_all_discussions_are_resolved": false,
|
||||
"request_access_enabled": false,
|
||||
"_links": {
|
||||
"self": "http://example.com/api/v4/projects",
|
||||
"issues": "http://example.com/api/v4/projects/1/issues",
|
||||
"merge_requests": "http://example.com/api/v4/projects/1/merge_requests",
|
||||
"repo_branches": "http://example.com/api/v4/projects/1/repository_branches",
|
||||
"labels": "http://example.com/api/v4/projects/1/labels",
|
||||
"events": "http://example.com/api/v4/projects/1/events",
|
||||
"members": "http://example.com/api/v4/projects/1/members"
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## Star a project
|
||||
|
||||
Stars a given project. Returns status code `304` if the project is already starred.
|
||||
|
|
|
@ -70,8 +70,11 @@ module API
|
|||
optional :import_url, type: String, desc: 'URL from which the project is imported'
|
||||
end
|
||||
|
||||
def present_projects(options = {})
|
||||
projects = ProjectsFinder.new(current_user: current_user, params: project_finder_params).execute
|
||||
def load_projects
|
||||
ProjectsFinder.new(current_user: current_user, params: project_finder_params).execute
|
||||
end
|
||||
|
||||
def present_projects(projects, options = {})
|
||||
projects = reorder_projects(projects)
|
||||
projects = projects.with_statistics if params[:statistics]
|
||||
projects = projects.with_issues_enabled if params[:with_issues_enabled]
|
||||
|
@ -111,7 +114,7 @@ module API
|
|||
|
||||
params[:user] = user
|
||||
|
||||
present_projects
|
||||
present_projects load_projects
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -124,7 +127,7 @@ module API
|
|||
use :statistics_params
|
||||
end
|
||||
get do
|
||||
present_projects
|
||||
present_projects load_projects
|
||||
end
|
||||
|
||||
desc 'Create new project' do
|
||||
|
@ -229,6 +232,18 @@ module API
|
|||
end
|
||||
end
|
||||
|
||||
desc 'List forks of this project' do
|
||||
success Entities::Project
|
||||
end
|
||||
params do
|
||||
use :collection_params
|
||||
end
|
||||
get ':id/forks' do
|
||||
forks = ForkProjectsFinder.new(user_project, params: project_finder_params, current_user: current_user).execute
|
||||
|
||||
present_projects forks
|
||||
end
|
||||
|
||||
desc 'Update an existing project' do
|
||||
success Entities::Project
|
||||
end
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe ForkProjectsFinder do
|
||||
let(:source_project) { create(:project, :empty_repo) }
|
||||
let(:private_fork) { create(:project, :private, :empty_repo, name: 'A') }
|
||||
let(:internal_fork) { create(:project, :internal, :empty_repo, name: 'B') }
|
||||
let(:public_fork) { create(:project, :public, :empty_repo, name: 'C') }
|
||||
|
||||
let(:non_member) { create(:user) }
|
||||
let(:private_fork_member) { create(:user) }
|
||||
|
||||
before do
|
||||
private_fork.add_developer(private_fork_member)
|
||||
|
||||
source_project.forks << private_fork
|
||||
source_project.forks << internal_fork
|
||||
source_project.forks << public_fork
|
||||
end
|
||||
|
||||
describe '#execute' do
|
||||
let(:finder) { described_class.new(source_project, params: {}, current_user: current_user) }
|
||||
|
||||
subject { finder.execute }
|
||||
|
||||
describe 'without a user' do
|
||||
let(:current_user) { nil }
|
||||
|
||||
it { is_expected.to eq([public_fork]) }
|
||||
end
|
||||
|
||||
describe 'with a user' do
|
||||
let(:current_user) { non_member }
|
||||
|
||||
it { is_expected.to eq([public_fork, internal_fork]) }
|
||||
end
|
||||
|
||||
describe 'with a member' do
|
||||
let(:current_user) { private_fork_member }
|
||||
|
||||
it { is_expected.to eq([public_fork, internal_fork, private_fork]) }
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1181,6 +1181,59 @@ describe API::Projects do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /projects/:id/forks' do
|
||||
let(:private_fork) { create(:project, :private, :empty_repo) }
|
||||
let(:member) { create(:user) }
|
||||
let(:non_member) { create(:user) }
|
||||
|
||||
before do
|
||||
private_fork.add_developer(member)
|
||||
end
|
||||
|
||||
context 'for a forked project' do
|
||||
before do
|
||||
post api("/projects/#{private_fork.id}/fork/#{project_fork_source.id}", admin)
|
||||
private_fork.reload
|
||||
expect(private_fork.forked_from_project).not_to be_nil
|
||||
expect(private_fork.forked?).to be_truthy
|
||||
project_fork_source.reload
|
||||
expect(project_fork_source.forks.length).to eq(1)
|
||||
expect(project_fork_source.forks).to include(private_fork)
|
||||
end
|
||||
|
||||
context 'for a user that can access the forks' do
|
||||
it 'returns the forks' do
|
||||
get api("/projects/#{project_fork_source.id}/forks", member)
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
expect(response).to include_pagination_headers
|
||||
expect(json_response.length).to eq(1)
|
||||
expect(json_response[0]['name']).to eq(private_fork.name)
|
||||
end
|
||||
end
|
||||
|
||||
context 'for a user that cannot access the forks' do
|
||||
it 'returns an empty array' do
|
||||
get api("/projects/#{project_fork_source.id}/forks", non_member)
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
expect(response).to include_pagination_headers
|
||||
expect(json_response.length).to eq(0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'for a non-forked project' do
|
||||
it 'returns an empty array' do
|
||||
get api("/projects/#{project_fork_source.id}/forks")
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
expect(response).to include_pagination_headers
|
||||
expect(json_response.length).to eq(0)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "POST /projects/:id/share" do
|
||||
|
|
Loading…
Reference in New Issue