提交 1ac79462 编写于 作者: G GitLab Bot

Add latest changes from gitlab-org/gitlab@master

上级 5247fe0b
......@@ -50,6 +50,15 @@
variables:
POSTGRES_HOST_AUTH_METHOD: trust
.use-pg11:
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.5-golang-1.12-git-2.24-lfs-2.9-chrome-73.0-node-12.x-yarn-1.21-postgresql-11-graphicsmagick-1.3.34"
services:
- name: postgres:11.6
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
- name: redis:alpine
variables:
POSTGRES_HOST_AUTH_METHOD: trust
.use-pg9-ee:
services:
- name: postgres:9.6.17
......@@ -69,6 +78,16 @@
variables:
POSTGRES_HOST_AUTH_METHOD: trust
.use-pg11-ee:
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.5-golang-1.12-git-2.24-lfs-2.9-chrome-73.0-node-12.x-yarn-1.21-postgresql-11-graphicsmagick-1.3.34"
services:
- name: postgres:11.6
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
- name: redis:alpine
- name: elasticsearch:6.4.2
variables:
POSTGRES_HOST_AUTH_METHOD: trust
.as-if-foss:
variables:
FOSS_ONLY: '1'
......@@ -238,6 +238,12 @@ rspec quarantine pg9:
- .rails:rules:master-refs-code-backstage
- .use-pg10
rspec migration pg10:
extends:
- .rspec-base-pg10
- .rspec-base-migration
parallel: 2
rspec unit pg10:
extends: .rspec-base-pg10
parallel: 20
......@@ -252,6 +258,34 @@ rspec system pg10:
# master-only jobs #
####################
############################
# nightly master-only jobs #
.rspec-base-pg11:
extends:
- .rspec-base
- .rails:rules:nightly-master-refs-code-backstage
- .use-pg11
rspec migration pg11:
extends:
- .rspec-base-pg11
- .rspec-base-migration
parallel: 2
rspec unit pg11:
extends: .rspec-base-pg11
parallel: 20
rspec integration pg11:
extends: .rspec-base-pg11
parallel: 8
rspec system pg11:
extends: .rspec-base-pg11
parallel: 24
# nightly master-only jobs #
############################
#########################
# ee + master-only jobs #
rspec-ee quarantine pg9:
......
......@@ -22,6 +22,9 @@
.if-merge-request: &if-merge-request
if: '$CI_MERGE_REQUEST_IID'
.if-nightly-master-schedule: &if-nightly-master-schedule
if: '$NIGHTLY && $CI_COMMIT_REF_NAME == "master" && $CI_PIPELINE_SOURCE == "schedule"'
.if-dot-com-gitlab-org-schedule: &if-dot-com-gitlab-org-schedule
if: '$CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_NAMESPACE == "gitlab-org" && $CI_PIPELINE_SOURCE == "schedule"'
......@@ -343,6 +346,12 @@
changes: *code-backstage-patterns
when: on_success
.rails:rules:nightly-master-refs-code-backstage:
rules:
- <<: *if-nightly-master-schedule
changes: *code-backstage-patterns
when: on_success
.rails:rules:ee-only:
rules:
- <<: *if-not-ee
......
<script>
/* eslint-disable vue/require-default-prop */
import _ from 'underscore';
import { isEmpty, isString } from 'lodash';
import Identicon from '~/vue_shared/components/identicon.vue';
import highlight from '~/lib/utils/highlight';
import { truncateNamespace } from '~/lib/utils/text_utility';
......@@ -39,7 +39,7 @@ export default {
},
computed: {
hasAvatar() {
return _.isString(this.avatarUrl) && !_.isEmpty(this.avatarUrl);
return isString(this.avatarUrl) && !isEmpty(this.avatarUrl);
},
truncatedNamespace() {
return truncateNamespace(this.namespace);
......
<script>
import _ from 'underscore';
import { debounce } from 'lodash';
import { mapActions } from 'vuex';
import Icon from '~/vue_shared/components/icon.vue';
import eventHub from '../event_hub';
......@@ -21,7 +21,7 @@ export default {
},
},
watch: {
searchQuery: _.debounce(function debounceSearchQuery() {
searchQuery: debounce(function debounceSearchQuery() {
this.setSearchQuery(this.searchQuery);
}, 500),
},
......
import _ from 'underscore';
import { take } from 'lodash';
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
import sanitize from 'sanitize-html';
import { FREQUENT_ITEMS, HOUR_IN_MS } from './constants';
......@@ -31,7 +31,7 @@ export const getTopFrequentItems = items => {
return 0;
});
return _.first(frequentItems, frequentItemsCount);
return take(frequentItems, frequentItemsCount);
};
export const updateExistingFrequentItem = (frequentItem, item) => {
......
<script>
import _ from 'underscore';
import { GlTooltipDirective } from '@gitlab/ui';
import { __ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue';
......@@ -48,7 +47,7 @@ export default {
},
computed: {
isSearchEmpty() {
return _.isEmpty(this.search);
return !this.search.length;
},
showSuggestions() {
return !this.isSearchEmpty && this.issues.length && !this.loading;
......
<script>
/* eslint-disable @gitlab/vue-i18n/no-bare-strings */
import _ from 'underscore';
import { uniqueId } from 'lodash';
import { GlLink, GlTooltip, GlTooltipDirective } from '@gitlab/ui';
import { __ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue';
......@@ -36,13 +36,13 @@ export default {
counts() {
return [
{
id: _.uniqueId(),
id: uniqueId(),
icon: 'thumb-up',
tooltipTitle: __('Upvotes'),
count: this.suggestion.upvotes,
},
{
id: _.uniqueId(),
id: uniqueId(),
icon: 'comment',
tooltipTitle: __('Comments'),
count: this.suggestion.userNotesCount,
......
import _ from 'underscore';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import updateDescription from '../utils/update_description';
......@@ -26,7 +25,7 @@ export default class Store {
'.detail-page-description.content-block',
);
const details =
!_.isNull(descriptionSection) && descriptionSection.getElementsByTagName('details');
descriptionSection != null && descriptionSection.getElementsByTagName('details');
this.state.descriptionHtml = updateDescription(data.description, details);
this.state.titleHtml = data.title;
......
import _ from 'underscore';
/**
* Function that replaces the open attribute for the <details> element.
*
......@@ -10,7 +8,7 @@ import _ from 'underscore';
const updateDescription = (descriptionHtml = '', details) => {
let detailNodes = details;
if (_.isEmpty(details)) {
if (!details.length) {
detailNodes = [];
}
......
<script>
import { mapState, mapActions } from 'vuex';
import { GlButton, GlFormInput, GlFormGroup } from '@gitlab/ui';
import _ from 'underscore';
import { escape as esc } from 'lodash';
import { __, sprintf } from '~/locale';
import MarkdownField from '~/vue_shared/components/markdown/field.vue';
import autofocusonshow from '~/vue_shared/directives/autofocusonshow';
......@@ -50,7 +50,7 @@ export default {
'Changing a Release tag is only supported via Releases API. %{linkStart}More information%{linkEnd}',
),
{
linkStart: `<a href="${_.escape(
linkStart: `<a href="${esc(
this.updateReleaseApiDocsPath,
)}" target="_blank" rel="noopener noreferrer">`,
linkEnd: '</a>',
......
<script>
import _ from 'underscore';
import { isEmpty } from 'lodash';
import $ from 'jquery';
import { slugify } from '~/lib/utils/text_utility';
import { getLocationHash } from '~/lib/utils/url_utility';
......@@ -64,7 +64,7 @@ export default {
return !this.glFeatures.releaseIssueSummary;
},
shouldRenderMilestoneInfo() {
return Boolean(this.glFeatures.releaseIssueSummary && !_.isEmpty(this.release.milestones));
return Boolean(this.glFeatures.releaseIssueSummary && !isEmpty(this.release.milestones));
},
},
......
......@@ -3,6 +3,7 @@
class Projects::ForksController < Projects::ApplicationController
include ContinueParams
include RendersMemberAccess
include Gitlab::Utils::StrongMemoize
# Authorize
before_action :whitelist_query_limiting, only: [:create]
......@@ -10,6 +11,7 @@ class Projects::ForksController < Projects::ApplicationController
before_action :authorize_download_code!
before_action :authenticate_user!, only: [:new, :create]
before_action :authorize_fork_project!, only: [:new, :create]
before_action :authorize_fork_namespace!, only: [:create]
# rubocop: disable CodeReuse/ActiveRecord
def index
......@@ -37,18 +39,15 @@ class Projects::ForksController < Projects::ApplicationController
# rubocop: enable CodeReuse/ActiveRecord
def new
@namespaces = current_user.manageable_namespaces
@namespaces.delete(@project.namespace)
@namespaces = fork_service.valid_fork_targets
end
# rubocop: disable CodeReuse/ActiveRecord
def create
namespace = Namespace.find(params[:namespace_key])
@forked_project = namespace.projects.find_by(path: project.path)
@forked_project = fork_namespace.projects.find_by(path: project.path)
@forked_project = nil unless @forked_project && @forked_project.forked_from_project == project
@forked_project ||= ::Projects::ForkService.new(project, current_user, namespace: namespace).execute
@forked_project ||= fork_service.execute
if !@forked_project.saved? || !@forked_project.forked?
render :error
......@@ -64,6 +63,22 @@ class Projects::ForksController < Projects::ApplicationController
private
def fork_service
strong_memoize(:fork_service) do
::Projects::ForkService.new(project, current_user, namespace: fork_namespace)
end
end
def fork_namespace
strong_memoize(:fork_namespace) do
Namespace.find(params[:namespace_key]) if params[:namespace_key].present?
end
end
def authorize_fork_namespace!
access_denied! unless fork_namespace && fork_service.valid_fork_target?
end
def whitelist_query_limiting
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-foss/issues/42335')
end
......
# frozen_string_literal: true
class ForkTargetsFinder
def initialize(project, user)
@project = project
@user = user
end
# rubocop: disable CodeReuse/ActiveRecord
def execute
::Namespace.where(id: user.manageable_namespaces).where.not(id: project.namespace).sort_by_type
end
# rubocop: enable CodeReuse/ActiveRecord
private
attr_reader :project, :user
end
ForkTargetsFinder.prepend_if_ee('EE::ForkTargetsFinder')
......@@ -68,6 +68,7 @@ class Namespace < ApplicationRecord
after_destroy :rm_dir
scope :for_user, -> { where('type IS NULL') }
scope :sort_by_type, -> { order(Gitlab::Database.nulls_first_order(:type)) }
scope :with_statistics, -> do
joins('LEFT JOIN project_statistics ps ON ps.namespace_id = namespaces.id')
......@@ -326,7 +327,10 @@ class Namespace < ApplicationRecord
end
def pages_virtual_domain
Pages::VirtualDomain.new(all_projects_with_pages, trim_prefix: full_path)
Pages::VirtualDomain.new(
all_projects_with_pages.includes(:route, :project_feature),
trim_prefix: full_path
)
end
def closest_setting(name)
......
......@@ -3,24 +3,25 @@
module Projects
class ForkService < BaseService
def execute(fork_to_project = nil)
forked_project =
if fork_to_project
link_existing_project(fork_to_project)
else
fork_new_project
end
forked_project = fork_to_project ? link_existing_project(fork_to_project) : fork_new_project
refresh_forks_count if forked_project&.saved?
forked_project
end
private
def valid_fork_targets
@valid_fork_targets ||= ForkTargetsFinder.new(@project, current_user).execute
end
def allowed_fork?
current_user.can?(:fork_project, @project)
def valid_fork_target?
return true if current_user.admin?
valid_fork_targets.include?(target_namespace)
end
private
def link_existing_project(fork_to_project)
return if fork_to_project.forked?
......@@ -30,6 +31,21 @@ module Projects
end
def fork_new_project
new_project = CreateService.new(current_user, new_fork_params).execute
return new_project unless new_project.persisted?
# Set the forked_from_project relation after saving to avoid having to
# reload the project to reset the association information and cause an
# extra query.
new_project.forked_from_project = @project
builds_access_level = @project.project_feature.builds_access_level
new_project.project_feature.update(builds_access_level: builds_access_level)
new_project
end
def new_fork_params
new_params = {
visibility_level: allowed_visibility_level,
description: @project.description,
......@@ -57,18 +73,11 @@ module Projects
new_params.merge!(@project.object_pool_params)
new_project = CreateService.new(current_user, new_params).execute
return new_project unless new_project.persisted?
# Set the forked_from_project relation after saving to avoid having to
# reload the project to reset the association information and cause an
# extra query.
new_project.forked_from_project = @project
builds_access_level = @project.project_feature.builds_access_level
new_project.project_feature.update(builds_access_level: builds_access_level)
new_params
end
new_project
def allowed_fork?
current_user.can?(:fork_project, @project)
end
def fork_network
......
# frozen_string_literal: true
class ChangeSamlProviderOuterForksDefault < ActiveRecord::Migration[6.0]
DOWNTIME = false
def up
change_column_null :saml_providers, :prohibited_outer_forks, false
change_column_default :saml_providers, :prohibited_outer_forks, true
end
def down
change_column_default :saml_providers, :prohibited_outer_forks, false
change_column_null :saml_providers, :prohibited_outer_forks, true
end
end
......@@ -3770,7 +3770,7 @@ ActiveRecord::Schema.define(version: 2020_02_13_220211) do
t.string "sso_url", null: false
t.boolean "enforced_sso", default: false, null: false
t.boolean "enforced_group_managed_accounts", default: false, null: false
t.boolean "prohibited_outer_forks", default: false
t.boolean "prohibited_outer_forks", default: true, null: false
t.index ["group_id"], name: "index_saml_providers_on_group_id"
end
......
......@@ -30,7 +30,7 @@ Implementing Geo provides the following benefits:
- Reduce from minutes to seconds the time taken for your distributed developers to clone and fetch large repositories and projects.
- Enable all of your developers to contribute ideas and work in parallel, no matter where they are.
- Balance the load between your **primary** and **secondary** nodes, or offload your automated tests to a **secondary** node.
- Balance the read-only load between your **primary** and **secondary** nodes.
In addition, it:
......@@ -249,6 +249,7 @@ This list of limitations only reflects the latest version of GitLab. If you are
- [Selective synchronization](configuration.md#selective-synchronization) applies only to files and repositories. Other datasets are replicated to the **secondary** node in full, making it inappropriate for use as an access control mechanism.
- Object pools for forked project deduplication work only on the **primary** node, and are duplicated on the **secondary** node.
- [External merge request diffs](../../merge_request_diffs.md) will not be replicated if they are on-disk, and viewing merge requests will fail. However, external MR diffs in object storage **are** supported. The default configuration (in-database) does work.
- GitLab Runners cannot register with a **secondary** node. Support for this is [planned for the future](https://gitlab.com/gitlab-org/gitlab/issues/3294).
### Limitations on replication/verification
......
......@@ -57,6 +57,7 @@ future GitLab releases.**
| `CI_EXTERNAL_PULL_REQUEST_TARGET_BRANCH_NAME` | 12.3 | all | The target branch name of the pull request if [the pipelines are for external pull requests](../ci_cd_for_external_repos/index.md#pipelines-for-external-pull-requests). Available only if `only: [external_pull_requests]` is used and the pull request is open. |
| `CI_EXTERNAL_PULL_REQUEST_TARGET_BRANCH_SHA` | 12.3 | all | The HEAD SHA of the target branch of the pull request if [the pipelines are for external pull requests](../ci_cd_for_external_repos/index.md#pipelines-for-external-pull-requests). Available only if `only: [external_pull_requests]` is used and the pull request is open. |
| `CI_JOB_ID` | 9.0 | all | The unique id of the current job that GitLab CI uses internally |
| `CI_JOB_IMAGE` | 12.9 | 12.9 | The name of the image running the CI job |
| `CI_JOB_MANUAL` | 8.12 | all | The flag to indicate that job was manually started |
| `CI_JOB_NAME` | 9.0 | 0.5 | The name of the job as defined in `.gitlab-ci.yml` |
| `CI_JOB_STAGE` | 9.0 | 0.5 | The name of the stage as defined in `.gitlab-ci.yml` |
......
......@@ -100,6 +100,7 @@ and included in `rules` definitions via [YAML anchors](../ci/yaml/README.md#anch
| `if-master-refs` | Matches if the current branch is `master`. | |
| `if-master-or-tag` | Matches if the pipeline is for the `master` branch or for a tag. | |
| `if-merge-request` | Matches if the pipeline is for a merge request. | |
| `if-nightly-master-schedule` | Matches if the pipeline is for a `master` scheduled pipeline with `$NIGHTLY` set. | |
| `if-dot-com-gitlab-org-schedule` | Limits jobs creation to scheduled pipelines for the `gitlab-org` group on GitLab.com. | |
| `if-dot-com-gitlab-org-master` | Limits jobs creation to the `master` branch for the `gitlab-org` group on GitLab.com. | |
| `if-dot-com-gitlab-org-merge-request` | Limits jobs creation to merge requests for the `gitlab-org` group on GitLab.com. | |
......
......@@ -53,8 +53,8 @@ Here's a list of the AWS services we will use, with links to pricing information
[Amazon EBS pricing](https://aws.amazon.com/ebs/pricing/).
- **S3**: We will use S3 to store backups, artifacts, LFS objects, etc. See the
[Amazon S3 pricing](https://aws.amazon.com/s3/pricing/).
- **ALB**: An Application Load Balancer will be used to route requests to the
GitLab instance. See the [Amazon ELB pricing](https://aws.amazon.com/elasticloadbalancing/pricing/).
- **ELB**: A Classic Load Balancer will be used to route requests to the
GitLab instances. See the [Amazon ELB pricing](https://aws.amazon.com/elasticloadbalancing/pricing/).
- **RDS**: An Amazon Relational Database Service using PostgreSQL will be used
to provide a High Availability database configuration. See the
[Amazon RDS pricing](https://aws.amazon.com/rds/postgresql/pricing/).
......@@ -291,27 +291,30 @@ and add a custom TCP rule for port `6379` accessible within itself.
## Load Balancer
On the EC2 dashboard, look for Load Balancer on the left column:
On the EC2 dashboard, look for Load Balancer in the left navigation bar:
1. Click the **Create Load Balancer** button.
1. Choose the Application Load Balancer.
1. Give it a name (`gitlab-loadbalancer`) and set the scheme to "internet-facing".
1. In the "Listeners" section, make sure it has HTTP and HTTPS.
1. In the "Availability Zones" section, select the `gitlab-vpc` we have created
and associate the **public subnets**.
1. Click **Configure Security Settings** to go to the next section to
select the TLS certificate. When done, go to the next step.
1. In the "Security Groups" section, create a new one by giving it a name
(`gitlab-loadbalancer-sec-group`) and allow both HTTP ad HTTPS traffic
1. Choose the **Classic Load Balancer**.
1. Give it a name (`gitlab-loadbalancer`) and for the **Create LB Inside** option, select `gitlab-vpc` from the dropdown menu.
1. In the **Listeners** section, set HTTP port 80, HTTPS port 443, and TCP port 22 for both load balancer and instance protocols and ports.
1. In the **Select Subnets** section, select both public subnets from the list.
1. Click **Assign Security Groups** and select **Create a new security group**, give it a name
(`gitlab-loadbalancer-sec-group`) and description, and allow both HTTP and HTTPS traffic
from anywhere (`0.0.0.0/0, ::/0`).
1. In the next step, configure the routing and select an existing target group
(`gitlab-public`). The Load Balancer Health will allow us to indicate where to
ping and what makes up a healthy or unhealthy instance.
1. Leave the "Register Targets" section as is, and finally review the settings
and create the ELB.
1. Click **Configure Security Settings** and select an SSL/TLS certificate from ACM or upload a certificate to IAM.
1. Click **Configure Health Check** and set up a health check for your EC2 instances.
1. For **Ping Protocol**, select HTTP.
1. For **Ping Port**, enter 80.
1. For **Ping Path**, enter `/explore`. (We use `/explore` as it's a public endpoint that does
not require authorization.)
1. Keep the default **Advanced Details** or adjust them according to your needs.
1. For now, don't click **Add EC2 Instances**, as we don't have any instances to add yet. Come back
to your load balancer after creating your GitLab instances and add them.
1. Click **Add Tags** and add any tags you need.
1. Click **Review and Create**, review all your settings, and click **Create** if you're happy.
After the Load Balancer is up and running, you can revisit your Security
Groups to refine the access only through the ELB and any other requirement
Groups to refine the access only through the ELB and any other requirements
you might have.
## Deploying GitLab inside an auto scaling group
......
......@@ -64,7 +64,7 @@ We intend to add a similar SSO requirement for [Git and API activity](https://gi
#### Group-managed accounts
[Introduced in GitLab 12.1](https://gitlab.com/groups/gitlab-org/-/epics/709).
> [Introduced in GitLab 12.1](https://gitlab.com/groups/gitlab-org/-/epics/709).
When SSO is being enforced, groups can enable an additional level of protection by enforcing the creation of dedicated user accounts to access the group.
......@@ -95,6 +95,14 @@ To access the Credentials inventory of a group, navigate to **{shield}** **Secur
This feature is similar to the [Credentials inventory for self-managed instances](../../admin_area/credentials_inventory.md).
##### Outer forks restriction for Group-managed accounts
> [Introduced in GitLab 12.9](https://gitlab.com/gitlab-org/gitlab/issues/34648)
Groups with enabled group-managed accounts can allow or disallow forking of projects outside of root group
by using separate toggle. If forking is disallowed any project of given root group or its subgroups can be forked to
a subgroup of the same root group only.
#### Assertions
When using group-managed accounts, the following user details need to be passed to GitLab as SAML
......
......@@ -49,9 +49,9 @@ Docker pulls and pushes and re-run any CI pipelines to retrieve any build artifa
## Migrating between two self-hosted GitLab instances
The best method for migrating a project from one GitLab instance to another,
The best method for migrating from one GitLab instance to another,
perhaps from an old server to a new server for example, is to
[back up the project](../../../raketasks/backup_restore.md),
[back up the instance](../../../raketasks/backup_restore.md),
then restore it on the new server.
In the event of merging two GitLab instances together (for example, both instances have existing data on them and one can't be wiped),
......
......@@ -267,18 +267,16 @@ module API
post ':id/fork' do
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-foss/issues/42284')
not_found! unless can?(current_user, :fork_project, user_project)
fork_params = declared_params(include_missing: false)
namespace_id = fork_params[:namespace]
fork_params[:namespace] = find_namespace!(fork_params[:namespace]) if fork_params[:namespace].present?
if namespace_id.present?
fork_params[:namespace] = find_namespace(namespace_id)
service = ::Projects::ForkService.new(user_project, current_user, fork_params)
unless fork_params[:namespace] && can?(current_user, :create_projects, fork_params[:namespace])
not_found!('Target Namespace')
end
end
not_found!('Target Namespace') unless service.valid_fork_target?
forked_project = ::Projects::ForkService.new(user_project, current_user, fork_params).execute
forked_project = service.execute
if forked_project.errors.any?
conflict!(forked_project.errors.messages)
......
......@@ -51,7 +51,7 @@ module Gitlab
# and all their ancestors (recursively).
#
# Passing an `upto` will stop the recursion once the specified parent_id is
# reached. So all ancestors *lower* than the specified acestor will be
# reached. So all ancestors *lower* than the specified ancestor will be
# included.
#
# Passing a `hierarchy_order` with either `:asc` or `:desc` will cause the
......
......@@ -209,6 +209,17 @@ describe Projects::ForksController do
expect(response).to redirect_to(namespace_project_import_path(user.namespace, project))
end
context 'when target namespace is not valid for forking' do
let(:params) { super().merge(namespace_key: another_group.id) }
let(:another_group) { create :group }
it 'responds with :not_found' do
subject
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'continue params' do
let(:params) do
{
......
# frozen_string_literal: true
require 'spec_helper'
describe ForkTargetsFinder do
subject(:finder) { described_class.new(project, user) }
let(:project) { create(:project, namespace: create(:group)) }
let(:user) { create(:user) }
let!(:maintained_group) do
create(:group).tap { |g| g.add_maintainer(user) }
end
let!(:owned_group) do
create(:group).tap { |g| g.add_owner(user) }
end
let!(:developer_group) do
create(:group).tap { |g| g.add_developer(user) }
end
let!(:reporter_group) do
create(:group).tap { |g| g.add_reporter(user) }
end
let!(:guest_group) do
create(:group).tap { |g| g.add_guest(user) }
end
before do
project.namespace.add_owner(user)
end
describe '#execute' do
it 'returns all user manageable namespaces except project namespace' do
expect(finder.execute).to match_array([user.namespace, maintained_group, owned_group])
end
end
end
......@@ -983,6 +983,24 @@ describe Namespace do
expect(virtual_domain.lookup_paths).not_to be_empty
end
end
it 'preloads project_feature and route' do
project2 = create(:project, namespace: namespace)
project3 = create(:project, namespace: namespace)
project.mark_pages_as_deployed
project2.mark_pages_as_deployed
project3.mark_pages_as_deployed
virtual_domain = namespace.pages_virtual_domain
queries = ActiveRecord::QueryRecorder.new { virtual_domain.lookup_paths }
# 1 to load projects
# 1 to preload project features
# 1 to load routes
expect(queries.count).to eq(3)
end
end
end
......
......@@ -2698,20 +2698,14 @@ describe API::Projects do
create(:project, :repository, creator: user, namespace: user.namespace)
end
let(:group) { create(:group) }
let(:group2) do
group = create(:group, name: 'group2_name')
group.add_maintainer(user2)
group
end
let(:group3) do
group = create(:group, name: 'group3_name', parent: group2)
group.add_owner(user2)
group
end
let(:group) { create(:group, :public) }
let(:group2) { create(:group, name: 'group2_name') }
let(:group3) { create(:group, name: 'group3_name', parent: group2) }
before do
group.add_guest(user2)
group2.add_maintainer(user2)
group3.add_owner(user2)
project.add_reporter(user2)
project2.add_reporter(user2)
end
......@@ -2720,7 +2714,7 @@ describe API::Projects do
it 'forks if user has sufficient access to project' do
post api("/projects/#{project.id}/fork", user2)
expect(response).to have_gitlab_http_status(201)
expect(response).to have_gitlab_http_status(:created)
expect(json_response['name']).to eq(project.name)
expect(json_response['path']).to eq(project.path)
expect(json_response['owner']['id']).to eq(user2.id)
......@@ -2733,7 +2727,7 @@ describe API::Projects do
it 'forks if user is admin' do
post api("/projects/#{project.id}/fork", admin)
expect(response).to have_gitlab_http_status(201)
expect(response).to have_gitlab_http_status(:created)
expect(json_response['name']).to eq(project.name)
expect(json_response['path']).to eq(project.path)
expect(json_response['owner']['id']).to eq(admin.id)
......@@ -2747,14 +2741,17 @@ describe API::Projects do
new_user = create(:user)
post api("/projects/#{project.id}/fork", new_user)
expect(response).to have_gitlab_http_status(404)
expect(response).to have_gitlab_http_status(:not_found)
expect(json_response['message']).to eq('404 Project Not Found')
end
it 'fails if forked project exists in the user namespace' do
post api("/projects/#{project.id}/fork", user)
new_project = create(:project, name: project.name, path: project.path)
new_project.add_reporter(user)
post api("/projects/#{new_project.id}/fork", user)
expect(response).to have_gitlab_http_status(409)
expect(response).to have_gitlab_http_status(:conflict)
expect(json_response['message']['name']).to eq(['has already been taken'])
expect(json_response['message']['path']).to eq(['has already been taken'])
end
......@@ -2762,48 +2759,48 @@ describe API::Projects do
it 'fails if project to fork from does not exist' do
post api('/projects/424242/fork', user)
expect(response).to have_gitlab_http_status(404)
expect(response).to have_gitlab_http_status(:not_found)
expect(json_response['message']).to eq('404 Project Not Found')
end
it 'forks with explicit own user namespace id' do
post api("/projects/#{project.id}/fork", user2), params: { namespace: user2.namespace.id }
expect(response).to have_gitlab_http_status(201)
expect(response).to have_gitlab_http_status(:created)
expect(json_response['owner']['id']).to eq(user2.id)
end
it 'forks with explicit own user name as namespace' do
post api("/projects/#{project.id}/fork", user2), params: { namespace: user2.username }
expect(response).to have_gitlab_http_status(201)
expect(response).to have_gitlab_http_status(:created)
expect(json_response['owner']['id']).to eq(user2.id)
end
it 'forks to another user when admin' do
post api("/projects/#{project.id}/fork", admin), params: { namespace: user2.username }
expect(response).to have_gitlab_http_status(201)
expect(response).to have_gitlab_http_status(:created)
expect(json_response['owner']['id']).to eq(user2.id)
end
it 'fails if trying to fork to another user when not admin' do
post api("/projects/#{project.id}/fork", user2), params: { namespace: admin.namespace.id }
expect(response).to have_gitlab_http_status(404)
expect(response).to have_gitlab_http_status(:not_found)
end
it 'fails if trying to fork to non-existent namespace' do
post api("/projects/#{project.id}/fork", user2), params: { namespace: 42424242 }
expect(response).to have_gitlab_http_status(404)
expect(json_response['message']).to eq('404 Target Namespace Not Found')
expect(response).to have_gitlab_http_status(:not_found)
expect(json_response['message']).to eq('404 Namespace Not Found')
end
it 'forks to owned group' do
post api("/projects/#{project.id}/fork", user2), params: { namespace: group2.name }
expect(response).to have_gitlab_http_status(201)
expect(response).to have_gitlab_http_status(:created)
expect(json_response['namespace']['name']).to eq(group2.name)
end
......@@ -2811,7 +2808,7 @@ describe API::Projects do
full_path = "#{group2.path}/#{group3.path}"
post api("/projects/#{project.id}/fork", user2), params: { namespace: full_path }
expect(response).to have_gitlab_http_status(201)
expect(response).to have_gitlab_http_status(:created)
expect(json_response['namespace']['name']).to eq(group3.name)
expect(json_response['namespace']['full_path']).to eq(full_path)
end
......@@ -2819,20 +2816,21 @@ describe API::Projects do
it 'fails to fork to not owned group' do
post api("/projects/#{project.id}/fork", user2), params: { namespace: group.name }
expect(response).to have_gitlab_http_status(404)
expect(response).to have_gitlab_http_status(:not_found)
expect(json_response['message']).to eq("404 Target Namespace Not Found")
end
it 'forks to not owned group when admin' do
post api("/projects/#{project.id}/fork", admin), params: { namespace: group.name }
expect(response).to have_gitlab_http_status(201)
expect(response).to have_gitlab_http_status(:created)
expect(json_response['namespace']['name']).to eq(group.name)
end
it 'accepts a path for the target project' do
post api("/projects/#{project.id}/fork", user2), params: { path: 'foobar' }
expect(response).to have_gitlab_http_status(201)
expect(response).to have_gitlab_http_status(:created)
expect(json_response['name']).to eq(project.name)
expect(json_response['path']).to eq('foobar')
expect(json_response['owner']['id']).to eq(user2.id)
......@@ -2846,14 +2844,14 @@ describe API::Projects do
post api("/projects/#{project.id}/fork", user2), params: { path: 'foobar' }
post api("/projects/#{project2.id}/fork", user2), params: { path: 'foobar' }
expect(response).to have_gitlab_http_status(409)
expect(response).to have_gitlab_http_status(:conflict)
expect(json_response['message']['path']).to eq(['has already been taken'])
end
it 'accepts a name for the target project' do
post api("/projects/#{project.id}/fork", user2), params: { name: 'My Random Project' }
expect(response).to have_gitlab_http_status(201)
expect(response).to have_gitlab_http_status(:created)
expect(json_response['name']).to eq('My Random Project')
expect(json_response['path']).to eq(project.path)
expect(json_response['owner']['id']).to eq(user2.id)
......@@ -2867,7 +2865,7 @@ describe API::Projects do
post api("/projects/#{project.id}/fork", user2), params: { name: 'My Random Project' }
post api("/projects/#{project2.id}/fork", user2), params: { name: 'My Random Project' }
expect(response).to have_gitlab_http_status(409)
expect(response).to have_gitlab_http_status(:conflict)
expect(json_response['message']['name']).to eq(['has already been taken'])
end
end
......@@ -2876,7 +2874,7 @@ describe API::Projects do
it 'returns authentication error' do
post api("/projects/#{project.id}/fork")
expect(response).to have_gitlab_http_status(401)
expect(response).to have_gitlab_http_status(:unauthorized)
expect(json_response['message']).to eq('401 Unauthorized')
end
end
......@@ -2890,8 +2888,7 @@ describe API::Projects do
it 'denies project to be forked' do
post api("/projects/#{project.id}/fork", admin)
expect(response).to have_gitlab_http_status(409)
expect(json_response['message']['forked_from_project_id']).to eq(['is forbidden'])
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
......
......@@ -275,6 +275,7 @@ describe Projects::ForkService do
context 'fork project for group when user not owner' do
it 'group developer fails to fork project into the group' do
to_project = fork_project(@project, @developer, @opts)
expect(to_project.errors[:namespace]).to eq(['is not valid'])
end
end
......@@ -336,7 +337,9 @@ describe Projects::ForkService do
context 'when linking fork to an existing project' do
let(:fork_from_project) { create(:project, :public) }
let(:fork_to_project) { create(:project, :public) }
let(:user) { create(:user) }
let(:user) do
create(:user).tap { |u| fork_to_project.add_maintainer(u) }
end
subject { described_class.new(fork_from_project, user) }
......@@ -387,4 +390,54 @@ describe Projects::ForkService do
end
end
end
describe '#valid_fork_targets' do
let(:finder_mock) { instance_double('ForkTargetsFinder', execute: ['finder_return_value']) }
let(:current_user) { instance_double('User') }
let(:project) { instance_double('Project') }
before do
allow(ForkTargetsFinder).to receive(:new).with(project, current_user).and_return(finder_mock)
end
it 'returns whatever finder returns' do
expect(described_class.new(project, current_user).valid_fork_targets).to eq ['finder_return_value']
end
end
describe '#valid_fork_target?' do
subject { described_class.new(project, user, params).valid_fork_target? }
let(:project) { Project.new }
let(:params) { {} }
context 'when current user is an admin' do
let(:user) { build(:user, :admin) }
it { is_expected.to be_truthy }
end
context 'when current_user is not an admin' do
let(:user) { create(:user) }
let(:finder_mock) { instance_double('ForkTargetsFinder', execute: [user.namespace]) }
let(:project) { create(:project) }
before do
allow(ForkTargetsFinder).to receive(:new).with(project, user).and_return(finder_mock)
end
context 'when target namespace is in valid fork targets' do
let(:params) { { namespace: user.namespace } }
it { is_expected.to be_truthy }
end
context 'when target namespace is not in valid fork targets' do
let(:params) { { namespace: create(:group) } }
it { is_expected.to be_falsey }
end
end
end
end
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册