Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-02-20 12:09:13 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-02-20 12:09:13 +0300
commit1ac794623a8be5dee111716a44dd04ff708f3541 (patch)
tree6c18f9fbe0bd9978bd3e8d9b083d3a0ca180686e
parent5247fe0bef72fa922841a79d5dbefb47d95112fa (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitlab/ci/global.gitlab-ci.yml19
-rw-r--r--.gitlab/ci/rails.gitlab-ci.yml34
-rw-r--r--.gitlab/ci/rules.gitlab-ci.yml9
-rw-r--r--app/assets/javascripts/frequent_items/components/frequent_items_list_item.vue4
-rw-r--r--app/assets/javascripts/frequent_items/components/frequent_items_search_input.vue4
-rw-r--r--app/assets/javascripts/frequent_items/utils.js4
-rw-r--r--app/assets/javascripts/issuable_suggestions/components/app.vue3
-rw-r--r--app/assets/javascripts/issuable_suggestions/components/item.vue6
-rw-r--r--app/assets/javascripts/issue_show/stores/index.js3
-rw-r--r--app/assets/javascripts/issue_show/utils/update_description.js4
-rw-r--r--app/assets/javascripts/releases/components/app_edit.vue4
-rw-r--r--app/assets/javascripts/releases/components/release_block.vue4
-rw-r--r--app/controllers/projects/forks_controller.rb27
-rw-r--r--app/finders/fork_targets_finder.rb20
-rw-r--r--app/models/namespace.rb6
-rw-r--r--app/services/projects/fork_service.rb49
-rw-r--r--db/migrate/20200206141511_change_saml_provider_outer_forks_default.rb15
-rw-r--r--db/schema.rb2
-rw-r--r--doc/administration/geo/replication/index.md3
-rw-r--r--doc/ci/variables/predefined_variables.md1
-rw-r--r--doc/development/pipelines.md1
-rw-r--r--doc/install/aws/img/aws_ha_architecture_diagram.pngbin133747 -> 133100 bytes
-rw-r--r--doc/install/aws/index.md39
-rw-r--r--doc/user/group/saml_sso/index.md10
-rw-r--r--doc/user/project/import/index.md4
-rw-r--r--lib/api/projects.rb14
-rw-r--r--lib/gitlab/object_hierarchy.rb2
-rw-r--r--spec/controllers/projects/forks_controller_spec.rb11
-rw-r--r--spec/finders/fork_targets_finder_spec.rb35
-rw-r--r--spec/frontend/create_cluster/gke_cluster/stores/getters_spec.js (renamed from spec/javascripts/create_cluster/gke_cluster/stores/getters_spec.js)0
-rw-r--r--spec/models/namespace_spec.rb18
-rw-r--r--spec/requests/api/projects_spec.rb67
-rw-r--r--spec/services/projects/fork_service_spec.rb55
33 files changed, 362 insertions, 115 deletions
diff --git a/.gitlab/ci/global.gitlab-ci.yml b/.gitlab/ci/global.gitlab-ci.yml
index ffdc115cff7..bd2e49bde31 100644
--- a/.gitlab/ci/global.gitlab-ci.yml
+++ b/.gitlab/ci/global.gitlab-ci.yml
@@ -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'
diff --git a/.gitlab/ci/rails.gitlab-ci.yml b/.gitlab/ci/rails.gitlab-ci.yml
index 58c4ef0e02a..26f7febb59b 100644
--- a/.gitlab/ci/rails.gitlab-ci.yml
+++ b/.gitlab/ci/rails.gitlab-ci.yml
@@ -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:
diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml
index a4285f47b6c..4facbfd2b6a 100644
--- a/.gitlab/ci/rules.gitlab-ci.yml
+++ b/.gitlab/ci/rules.gitlab-ci.yml
@@ -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
diff --git a/app/assets/javascripts/frequent_items/components/frequent_items_list_item.vue b/app/assets/javascripts/frequent_items/components/frequent_items_list_item.vue
index 6188d41ae96..3276d85f1cd 100644
--- a/app/assets/javascripts/frequent_items/components/frequent_items_list_item.vue
+++ b/app/assets/javascripts/frequent_items/components/frequent_items_list_item.vue
@@ -1,6 +1,6 @@
<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);
diff --git a/app/assets/javascripts/frequent_items/components/frequent_items_search_input.vue b/app/assets/javascripts/frequent_items/components/frequent_items_search_input.vue
index c69e1b792dc..40add09f25d 100644
--- a/app/assets/javascripts/frequent_items/components/frequent_items_search_input.vue
+++ b/app/assets/javascripts/frequent_items/components/frequent_items_search_input.vue
@@ -1,5 +1,5 @@
<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),
},
diff --git a/app/assets/javascripts/frequent_items/utils.js b/app/assets/javascripts/frequent_items/utils.js
index 5188d6118ac..a992480c22b 100644
--- a/app/assets/javascripts/frequent_items/utils.js
+++ b/app/assets/javascripts/frequent_items/utils.js
@@ -1,4 +1,4 @@
-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) => {
diff --git a/app/assets/javascripts/issuable_suggestions/components/app.vue b/app/assets/javascripts/issuable_suggestions/components/app.vue
index d435460e38f..67d10b797fb 100644
--- a/app/assets/javascripts/issuable_suggestions/components/app.vue
+++ b/app/assets/javascripts/issuable_suggestions/components/app.vue
@@ -1,5 +1,4 @@
<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;
diff --git a/app/assets/javascripts/issuable_suggestions/components/item.vue b/app/assets/javascripts/issuable_suggestions/components/item.vue
index 66a4cc44d51..9f3508fb937 100644
--- a/app/assets/javascripts/issuable_suggestions/components/item.vue
+++ b/app/assets/javascripts/issuable_suggestions/components/item.vue
@@ -1,6 +1,6 @@
<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,
diff --git a/app/assets/javascripts/issue_show/stores/index.js b/app/assets/javascripts/issue_show/stores/index.js
index 688ba7b268d..0cd094243b9 100644
--- a/app/assets/javascripts/issue_show/stores/index.js
+++ b/app/assets/javascripts/issue_show/stores/index.js
@@ -1,4 +1,3 @@
-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;
diff --git a/app/assets/javascripts/issue_show/utils/update_description.js b/app/assets/javascripts/issue_show/utils/update_description.js
index 315f6c23b02..c5811290e61 100644
--- a/app/assets/javascripts/issue_show/utils/update_description.js
+++ b/app/assets/javascripts/issue_show/utils/update_description.js
@@ -1,5 +1,3 @@
-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 = [];
}
diff --git a/app/assets/javascripts/releases/components/app_edit.vue b/app/assets/javascripts/releases/components/app_edit.vue
index bdc2b3abb8c..f6a4d00692e 100644
--- a/app/assets/javascripts/releases/components/app_edit.vue
+++ b/app/assets/javascripts/releases/components/app_edit.vue
@@ -1,7 +1,7 @@
<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>',
diff --git a/app/assets/javascripts/releases/components/release_block.vue b/app/assets/javascripts/releases/components/release_block.vue
index e6bb5325120..bc3f2c3bf30 100644
--- a/app/assets/javascripts/releases/components/release_block.vue
+++ b/app/assets/javascripts/releases/components/release_block.vue
@@ -1,5 +1,5 @@
<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));
},
},
diff --git a/app/controllers/projects/forks_controller.rb b/app/controllers/projects/forks_controller.rb
index 9806b91c7e8..e0e8fb177ba 100644
--- a/app/controllers/projects/forks_controller.rb
+++ b/app/controllers/projects/forks_controller.rb
@@ -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
diff --git a/app/finders/fork_targets_finder.rb b/app/finders/fork_targets_finder.rb
new file mode 100644
index 00000000000..9003c593757
--- /dev/null
+++ b/app/finders/fork_targets_finder.rb
@@ -0,0 +1,20 @@
+# 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')
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index efe14a3e614..99212d09b8e 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -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)
diff --git a/app/services/projects/fork_service.rb b/app/services/projects/fork_service.rb
index fcfea567885..6ac53b15ef9 100644
--- a/app/services/projects/fork_service.rb
+++ b/app/services/projects/fork_service.rb
@@ -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
diff --git a/db/migrate/20200206141511_change_saml_provider_outer_forks_default.rb b/db/migrate/20200206141511_change_saml_provider_outer_forks_default.rb
new file mode 100644
index 00000000000..971cc4da921
--- /dev/null
+++ b/db/migrate/20200206141511_change_saml_provider_outer_forks_default.rb
@@ -0,0 +1,15 @@
+# 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
diff --git a/db/schema.rb b/db/schema.rb
index d2ceceed88d..0796eea9bbe 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -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
diff --git a/doc/administration/geo/replication/index.md b/doc/administration/geo/replication/index.md
index e3699f1544b..4eaf6a88575 100644
--- a/doc/administration/geo/replication/index.md
+++ b/doc/administration/geo/replication/index.md
@@ -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
diff --git a/doc/ci/variables/predefined_variables.md b/doc/ci/variables/predefined_variables.md
index dd15b8c37b1..a9f57d97316 100644
--- a/doc/ci/variables/predefined_variables.md
+++ b/doc/ci/variables/predefined_variables.md
@@ -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` |
diff --git a/doc/development/pipelines.md b/doc/development/pipelines.md
index e228845bc72..302e2a2a6ee 100644
--- a/doc/development/pipelines.md
+++ b/doc/development/pipelines.md
@@ -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. | |
diff --git a/doc/install/aws/img/aws_ha_architecture_diagram.png b/doc/install/aws/img/aws_ha_architecture_diagram.png
index 8cff5658b32..1b30a244778 100644
--- a/doc/install/aws/img/aws_ha_architecture_diagram.png
+++ b/doc/install/aws/img/aws_ha_architecture_diagram.png
Binary files differ
diff --git a/doc/install/aws/index.md b/doc/install/aws/index.md
index aa94dc1a2a5..63308f10421 100644
--- a/doc/install/aws/index.md
+++ b/doc/install/aws/index.md
@@ -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
diff --git a/doc/user/group/saml_sso/index.md b/doc/user/group/saml_sso/index.md
index 6fa7a0397f4..73ac54905e3 100644
--- a/doc/user/group/saml_sso/index.md
+++ b/doc/user/group/saml_sso/index.md
@@ -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
diff --git a/doc/user/project/import/index.md b/doc/user/project/import/index.md
index 571968dd065..8fd4325b5cd 100644
--- a/doc/user/project/import/index.md
+++ b/doc/user/project/import/index.md
@@ -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),
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 2271131ced3..6f96ffde0a7 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -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)
diff --git a/lib/gitlab/object_hierarchy.rb b/lib/gitlab/object_hierarchy.rb
index 74057bbc493..41d80fe9aa6 100644
--- a/lib/gitlab/object_hierarchy.rb
+++ b/lib/gitlab/object_hierarchy.rb
@@ -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
diff --git a/spec/controllers/projects/forks_controller_spec.rb b/spec/controllers/projects/forks_controller_spec.rb
index e6a68459a84..e362790cd3c 100644
--- a/spec/controllers/projects/forks_controller_spec.rb
+++ b/spec/controllers/projects/forks_controller_spec.rb
@@ -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
{
diff --git a/spec/finders/fork_targets_finder_spec.rb b/spec/finders/fork_targets_finder_spec.rb
new file mode 100644
index 00000000000..e7110c33071
--- /dev/null
+++ b/spec/finders/fork_targets_finder_spec.rb
@@ -0,0 +1,35 @@
+# 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
diff --git a/spec/javascripts/create_cluster/gke_cluster/stores/getters_spec.js b/spec/frontend/create_cluster/gke_cluster/stores/getters_spec.js
index 39106c3f6ca..39106c3f6ca 100644
--- a/spec/javascripts/create_cluster/gke_cluster/stores/getters_spec.js
+++ b/spec/frontend/create_cluster/gke_cluster/stores/getters_spec.js
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index 740385bbd54..54e54366c5a 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -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
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 97b61b44856..c78a1d4ec62 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -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
diff --git a/spec/services/projects/fork_service_spec.rb b/spec/services/projects/fork_service_spec.rb
index e14f1abf018..fa6d42369c8 100644
--- a/spec/services/projects/fork_service_spec.rb
+++ b/spec/services/projects/fork_service_spec.rb
@@ -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