Commit 1ac79462 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent 5247fe0b
...@@ -50,6 +50,15 @@ ...@@ -50,6 +50,15 @@
variables: variables:
POSTGRES_HOST_AUTH_METHOD: trust 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: .use-pg9-ee:
services: services:
- name: postgres:9.6.17 - name: postgres:9.6.17
...@@ -69,6 +78,16 @@ ...@@ -69,6 +78,16 @@
variables: variables:
POSTGRES_HOST_AUTH_METHOD: trust 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: .as-if-foss:
variables: variables:
FOSS_ONLY: '1' FOSS_ONLY: '1'
...@@ -238,6 +238,12 @@ rspec quarantine pg9: ...@@ -238,6 +238,12 @@ rspec quarantine pg9:
- .rails:rules:master-refs-code-backstage - .rails:rules:master-refs-code-backstage
- .use-pg10 - .use-pg10
rspec migration pg10:
extends:
- .rspec-base-pg10
- .rspec-base-migration
parallel: 2
rspec unit pg10: rspec unit pg10:
extends: .rspec-base-pg10 extends: .rspec-base-pg10
parallel: 20 parallel: 20
...@@ -252,6 +258,34 @@ rspec system pg10: ...@@ -252,6 +258,34 @@ rspec system pg10:
# master-only jobs # # 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 # # ee + master-only jobs #
rspec-ee quarantine pg9: rspec-ee quarantine pg9:
......
...@@ -22,6 +22,9 @@ ...@@ -22,6 +22,9 @@
.if-merge-request: &if-merge-request .if-merge-request: &if-merge-request
if: '$CI_MERGE_REQUEST_IID' 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-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"' if: '$CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_NAMESPACE == "gitlab-org" && $CI_PIPELINE_SOURCE == "schedule"'
...@@ -343,6 +346,12 @@ ...@@ -343,6 +346,12 @@
changes: *code-backstage-patterns changes: *code-backstage-patterns
when: on_success 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: .rails:rules:ee-only:
rules: rules:
- <<: *if-not-ee - <<: *if-not-ee
......
<script> <script>
/* eslint-disable vue/require-default-prop */ /* eslint-disable vue/require-default-prop */
import _ from 'underscore'; import { isEmpty, isString } from 'lodash';
import Identicon from '~/vue_shared/components/identicon.vue'; import Identicon from '~/vue_shared/components/identicon.vue';
import highlight from '~/lib/utils/highlight'; import highlight from '~/lib/utils/highlight';
import { truncateNamespace } from '~/lib/utils/text_utility'; import { truncateNamespace } from '~/lib/utils/text_utility';
...@@ -39,7 +39,7 @@ export default { ...@@ -39,7 +39,7 @@ export default {
}, },
computed: { computed: {
hasAvatar() { hasAvatar() {
return _.isString(this.avatarUrl) && !_.isEmpty(this.avatarUrl); return isString(this.avatarUrl) && !isEmpty(this.avatarUrl);
}, },
truncatedNamespace() { truncatedNamespace() {
return truncateNamespace(this.namespace); return truncateNamespace(this.namespace);
......
<script> <script>
import _ from 'underscore'; import { debounce } from 'lodash';
import { mapActions } from 'vuex'; import { mapActions } from 'vuex';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
...@@ -21,7 +21,7 @@ export default { ...@@ -21,7 +21,7 @@ export default {
}, },
}, },
watch: { watch: {
searchQuery: _.debounce(function debounceSearchQuery() { searchQuery: debounce(function debounceSearchQuery() {
this.setSearchQuery(this.searchQuery); this.setSearchQuery(this.searchQuery);
}, 500), }, 500),
}, },
......
import _ from 'underscore'; import { take } from 'lodash';
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils'; import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
import sanitize from 'sanitize-html'; import sanitize from 'sanitize-html';
import { FREQUENT_ITEMS, HOUR_IN_MS } from './constants'; import { FREQUENT_ITEMS, HOUR_IN_MS } from './constants';
...@@ -31,7 +31,7 @@ export const getTopFrequentItems = items => { ...@@ -31,7 +31,7 @@ export const getTopFrequentItems = items => {
return 0; return 0;
}); });
return _.first(frequentItems, frequentItemsCount); return take(frequentItems, frequentItemsCount);
}; };
export const updateExistingFrequentItem = (frequentItem, item) => { export const updateExistingFrequentItem = (frequentItem, item) => {
......
<script> <script>
import _ from 'underscore';
import { GlTooltipDirective } from '@gitlab/ui'; import { GlTooltipDirective } from '@gitlab/ui';
import { __ } from '~/locale'; import { __ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
...@@ -48,7 +47,7 @@ export default { ...@@ -48,7 +47,7 @@ export default {
}, },
computed: { computed: {
isSearchEmpty() { isSearchEmpty() {
return _.isEmpty(this.search); return !this.search.length;
}, },
showSuggestions() { showSuggestions() {
return !this.isSearchEmpty && this.issues.length && !this.loading; return !this.isSearchEmpty && this.issues.length && !this.loading;
......
<script> <script>
/* eslint-disable @gitlab/vue-i18n/no-bare-strings */ /* eslint-disable @gitlab/vue-i18n/no-bare-strings */
import _ from 'underscore'; import { uniqueId } from 'lodash';
import { GlLink, GlTooltip, GlTooltipDirective } from '@gitlab/ui'; import { GlLink, GlTooltip, GlTooltipDirective } from '@gitlab/ui';
import { __ } from '~/locale'; import { __ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
...@@ -36,13 +36,13 @@ export default { ...@@ -36,13 +36,13 @@ export default {
counts() { counts() {
return [ return [
{ {
id: _.uniqueId(), id: uniqueId(),
icon: 'thumb-up', icon: 'thumb-up',
tooltipTitle: __('Upvotes'), tooltipTitle: __('Upvotes'),
count: this.suggestion.upvotes, count: this.suggestion.upvotes,
}, },
{ {
id: _.uniqueId(), id: uniqueId(),
icon: 'comment', icon: 'comment',
tooltipTitle: __('Comments'), tooltipTitle: __('Comments'),
count: this.suggestion.userNotesCount, count: this.suggestion.userNotesCount,
......
import _ from 'underscore';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import updateDescription from '../utils/update_description'; import updateDescription from '../utils/update_description';
...@@ -26,7 +25,7 @@ export default class Store { ...@@ -26,7 +25,7 @@ export default class Store {
'.detail-page-description.content-block', '.detail-page-description.content-block',
); );
const details = const details =
!_.isNull(descriptionSection) && descriptionSection.getElementsByTagName('details'); descriptionSection != null && descriptionSection.getElementsByTagName('details');
this.state.descriptionHtml = updateDescription(data.description, details); this.state.descriptionHtml = updateDescription(data.description, details);
this.state.titleHtml = data.title; this.state.titleHtml = data.title;
......
import _ from 'underscore';
/** /**
* Function that replaces the open attribute for the <details> element. * Function that replaces the open attribute for the <details> element.
* *
...@@ -10,7 +8,7 @@ import _ from 'underscore'; ...@@ -10,7 +8,7 @@ import _ from 'underscore';
const updateDescription = (descriptionHtml = '', details) => { const updateDescription = (descriptionHtml = '', details) => {
let detailNodes = details; let detailNodes = details;
if (_.isEmpty(details)) { if (!details.length) {
detailNodes = []; detailNodes = [];
} }
......
<script> <script>
import { mapState, mapActions } from 'vuex'; import { mapState, mapActions } from 'vuex';
import { GlButton, GlFormInput, GlFormGroup } from '@gitlab/ui'; import { GlButton, GlFormInput, GlFormGroup } from '@gitlab/ui';
import _ from 'underscore'; import { escape as esc } from 'lodash';
import { __, sprintf } from '~/locale'; import { __, sprintf } from '~/locale';
import MarkdownField from '~/vue_shared/components/markdown/field.vue'; import MarkdownField from '~/vue_shared/components/markdown/field.vue';
import autofocusonshow from '~/vue_shared/directives/autofocusonshow'; import autofocusonshow from '~/vue_shared/directives/autofocusonshow';
...@@ -50,7 +50,7 @@ export default { ...@@ -50,7 +50,7 @@ export default {
'Changing a Release tag is only supported via Releases API. %{linkStart}More information%{linkEnd}', 'Changing a Release tag is only supported via Releases API. %{linkStart}More information%{linkEnd}',
), ),
{ {
linkStart: `<a href="${_.escape( linkStart: `<a href="${esc(
this.updateReleaseApiDocsPath, this.updateReleaseApiDocsPath,
)}" target="_blank" rel="noopener noreferrer">`, )}" target="_blank" rel="noopener noreferrer">`,
linkEnd: '</a>', linkEnd: '</a>',
......
<script> <script>
import _ from 'underscore'; import { isEmpty } from 'lodash';
import $ from 'jquery'; import $ from 'jquery';
import { slugify } from '~/lib/utils/text_utility'; import { slugify } from '~/lib/utils/text_utility';
import { getLocationHash } from '~/lib/utils/url_utility'; import { getLocationHash } from '~/lib/utils/url_utility';
...@@ -64,7 +64,7 @@ export default { ...@@ -64,7 +64,7 @@ export default {
return !this.glFeatures.releaseIssueSummary; return !this.glFeatures.releaseIssueSummary;
}, },
shouldRenderMilestoneInfo() { shouldRenderMilestoneInfo() {
return Boolean(this.glFeatures.releaseIssueSummary && !_.isEmpty(this.release.milestones)); return Boolean(this.glFeatures.releaseIssueSummary && !isEmpty(this.release.milestones));
}, },
}, },
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
class Projects::ForksController < Projects::ApplicationController class Projects::ForksController < Projects::ApplicationController
include ContinueParams include ContinueParams
include RendersMemberAccess include RendersMemberAccess
include Gitlab::Utils::StrongMemoize
# Authorize # Authorize
before_action :whitelist_query_limiting, only: [:create] before_action :whitelist_query_limiting, only: [:create]
...@@ -10,6 +11,7 @@ class Projects::ForksController < Projects::ApplicationController ...@@ -10,6 +11,7 @@ class Projects::ForksController < Projects::ApplicationController
before_action :authorize_download_code! before_action :authorize_download_code!
before_action :authenticate_user!, only: [:new, :create] before_action :authenticate_user!, only: [:new, :create]
before_action :authorize_fork_project!, only: [:new, :create] before_action :authorize_fork_project!, only: [:new, :create]
before_action :authorize_fork_namespace!, only: [:create]
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def index def index
...@@ -37,18 +39,15 @@ class Projects::ForksController < Projects::ApplicationController ...@@ -37,18 +39,15 @@ class Projects::ForksController < Projects::ApplicationController
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
def new def new
@namespaces = current_user.manageable_namespaces @namespaces = fork_service.valid_fork_targets
@namespaces.delete(@project.namespace)
end end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def create def create
namespace = Namespace.find(params[:namespace_key]) @forked_project = fork_namespace.projects.find_by(path: project.path)
@forked_project = namespace.projects.find_by(path: project.path)
@forked_project = nil unless @forked_project && @forked_project.forked_from_project == project @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? if !@forked_project.saved? || !@forked_project.forked?
render :error render :error
...@@ -64,6 +63,22 @@ class Projects::ForksController < Projects::ApplicationController ...@@ -64,6 +63,22 @@ class Projects::ForksController < Projects::ApplicationController
private 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 def whitelist_query_limiting
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-foss/issues/42335') Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-foss/issues/42335')
end 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 ...@@ -68,6 +68,7 @@ class Namespace < ApplicationRecord
after_destroy :rm_dir after_destroy :rm_dir
scope :for_user, -> { where('type IS NULL') } scope :for_user, -> { where('type IS NULL') }
scope :sort_by_type, -> { order(Gitlab::Database.nulls_first_order(:type)) }
scope :with_statistics, -> do scope :with_statistics, -> do
joins('LEFT JOIN project_statistics ps ON ps.namespace_id = namespaces.id') joins('LEFT JOIN project_statistics ps ON ps.namespace_id = namespaces.id')
...@@ -326,7 +327,10 @@ class Namespace < ApplicationRecord ...@@ -326,7 +327,10 @@ class Namespace < ApplicationRecord
end end
def pages_virtual_domain 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 end
def closest_setting(name) def closest_setting(name)
......
...@@ -3,24 +3,25 @@ ...@@ -3,24 +3,25 @@
module Projects module Projects
class ForkService < BaseService class ForkService < BaseService
def execute(fork_to_project = nil) def execute(fork_to_project = nil)
forked_project = forked_project = fork_to_project ? link_existing_project(fork_to_project) : fork_new_project
if fork_to_project
link_existing_project(fork_to_project)
else
fork_new_project
end
refresh_forks_count if forked_project&.saved? refresh_forks_count if forked_project&.saved?
forked_project forked_project
end end
private def valid_fork_targets
@valid_fork_targets ||= ForkTargetsFinder.new(@project, current_user).execute
end
def allowed_fork? def valid_fork_target?
current_user.can?(:fork_project, @project) return true if current_user.admin?
valid_fork_targets.include?(target_namespace)
end end
private
def link_existing_project(fork_to_project) def link_existing_project(fork_to_project)
return if fork_to_project.forked? return if fork_to_project.forked?
...@@ -30,6 +31,21 @@ module Projects ...@@ -30,6 +31,21 @@ module Projects
end end
def fork_new_project 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 = { new_params = {
visibility_level: allowed_visibility_level, visibility_level: allowed_visibility_level,
description: @project.description, description: @project.description,
...@@ -57,18 +73,11 @@ module Projects ...@@ -57,18 +73,11 @@ module Projects
new_params.merge!(@project.object_pool_params) new_params.merge!(@project.object_pool_params)
new_project = CreateService.new(current_user, new_params).execute new_params
return new_project unless new_project.persisted? end
# 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 def allowed_fork?
current_user.can?(:fork_project, @project)
end end
def fork_network 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 ...@@ -3770,7 +3770,7 @@ ActiveRecord::Schema.define(version: 2020_02_13_220211) do
t.string "sso_url", null: false t.string "sso_url", null: false
t.boolean "enforced_sso", default: false, null: false t.boolean "enforced_sso", default: false, null: false
t.boolean "enforced_group_managed_accounts", 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" t.index ["group_id"], name: "index_saml_providers_on_group_id"
end end
......
...@@ -30,7 +30,7 @@ Implementing Geo provides the following benefits: ...@@ -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. - 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. - 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: In addition, it:
...@@ -249,6 +249,7 @@ This list of limitations only reflects the latest version of GitLab. If you are ...@@ -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. - [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. - 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. - [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 ### Limitations on replication/verification
......
...@@ -57,6 +57,7 @@ future GitLab releases.** ...@@ -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_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_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_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_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_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` | | `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 ...@@ -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-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-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-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-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-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. | | | `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 ...@@ -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/). [Amazon EBS pricing](https://aws.amazon.com/ebs/pricing/).
- **S3**: We will use S3 to store backups, artifacts, LFS objects, etc. See the - **S3**: We will use S3 to store backups, artifacts, LFS objects, etc. See the
[Amazon S3 pricing](https://aws.amazon.com/s3/pricing/). [Amazon S3 pricing](https://aws.amazon.com/s3/pricing/).
- **ALB**: An Application Load Balancer will be used to route requests to the - **ELB**: A Classic Load Balancer will be used to route requests to the
GitLab instance. See the [Amazon ELB pricing](https://aws.amazon.com/elasticloadbalancing/pricing/). GitLab instances. See the [Amazon ELB pricing](https://aws.amazon.com/elasticloadbalancing/pricing/).
- **RDS**: An Amazon Relational Database Service using PostgreSQL will be used - **RDS**: An Amazon Relational Database Service using PostgreSQL will be used
to provide a High Availability database configuration. See the to provide a High Availability database configuration. See the
[Amazon RDS pricing](https://aws.amazon.com/rds/postgresql/pricing/). [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. ...@@ -291,27 +291,30 @@ and add a custom TCP rule for port `6379` accessible within itself.
## Load Balancer ## 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. Click the **Create Load Balancer** button.
1. Choose the Application Load Balancer. 1. Choose the **Classic Load Balancer**.
1. Give it a name (`gitlab-loadbalancer`) and set the scheme to "internet-facing". 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, make sure it has HTTP and HTTPS. 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 "Availability Zones" section, select the `gitlab-vpc` we have created 1. In the **Select Subnets** section, select both public subnets from the list.
and associate the **public subnets**. 1. Click **Assign Security Groups** and select **Create a new security group**, give it a name
1. Click **Configure Security Settings** to go to the next section to (`gitlab-loadbalancer-sec-group`) and description, and allow both HTTP and HTTPS traffic
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
from anywhere (`0.0.0.0/0, ::/0`). from anywhere (`0.0.0.0/0, ::/0`).
1. In the next step, configure the routing and select an existing target group 1. Click **Configure Security Settings** and select an SSL/TLS certificate from ACM or upload a certificate to IAM.
(`gitlab-public`). The Load Balancer Health will allow us to indicate where to 1. Click **Configure Health Check** and set up a health check for your EC2 instances.
ping and what makes up a healthy or unhealthy instance. 1. For **Ping Protocol**, select HTTP.
1. Leave the "Register Targets" section as is, and finally review the settings 1. For **Ping Port**, enter 80.
and create the ELB. 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 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. you might have.
## Deploying GitLab inside an auto scaling group ## 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 ...@@ -64,7 +64,7 @@ We intend to add a similar SSO requirement for [Git and API activity](https://gi
#### Group-managed accounts #### 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. 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 ...@@ -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). 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 #### Assertions
When using group-managed accounts, the following user details need to be passed to GitLab as SAML 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 ...@@ -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 ## 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 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. 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), 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 ...@@ -267,18 +267,16 @@ module API
post ':id/fork' do post ':id/fork' do
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-foss/issues/42284') 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) 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? service = ::Projects::ForkService.new(user_project, current_user, fork_params)
fork_params[:namespace] = find_namespace(namespace_id)
unless fork_params[:namespace] && can?(current_user, :create_projects, fork_params[:namespace]) not_found!('Target Namespace') unless service.valid_fork_target?
not_found!('Target Namespace')
end
end
forked_project = ::Projects::ForkService.new(user_project, current_user, fork_params).execute forked_project = service.execute
if forked_project.errors.any? if forked_project.errors.any?
conflict!(forked_project.errors.messages) conflict!(forked_project.errors.messages)
......
...@@ -51,7 +51,7 @@ module Gitlab ...@@ -51,7 +51,7 @@ module Gitlab
# and all their ancestors (recursively). # and all their ancestors (recursively).
# #
# Passing an `upto` will stop the recursion once the specified parent_id is # 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. # included.
# #
# Passing a `hierarchy_order` with either `:asc` or `:desc` will cause the # Passing a `hierarchy_order` with either `:asc` or `:desc` will cause the
......
...@@ -209,6 +209,17 @@ describe Projects::ForksController do ...@@ -209,6 +209,17 @@ describe Projects::ForksController do
expect(response).to redirect_to(namespace_project_import_path(user.namespace, project)) expect(response).to redirect_to(namespace_project_import_path(user.namespace, project))
end 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 context 'continue params' do
let(: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 ...@@ -983,6 +983,24 @@ describe Namespace do
expect(virtual_domain.lookup_paths).not_to be_empty expect(virtual_domain.lookup_paths).not_to be_empty
end end
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
end end
......
...@@ -2698,20 +2698,14 @@ describe API::Projects do ...@@ -2698,20 +2698,14 @@ describe API::Projects do
create(:project, :repository, creator: user, namespace: user.namespace) create(:project, :repository, creator: user, namespace: user.namespace)
end end
let(:group) { create(:group) } let(:group) { create(:group, :public) }
let(:group2) do let(:group2) { create(:group, name: 'group2_name') }
group = create(:group, name: 'group2_name') let(:group3) { create(:group, name: 'group3_name', parent: group2) }
group.add_maintainer(user2)
group
end
let(:group3) do
group = create(:group, name: 'group3_name', parent: group2)
group.add_owner(user2)
group
end
before do before do
group.add_guest(user2)
group2.add_maintainer(user2)
group3.add_owner(user2)
project.add_reporter(user2) project.add_reporter(user2)
project2.add_reporter(user2) project2.add_reporter(user2)
end end
...@@ -2720,7 +2714,7 @@ describe API::Projects do ...@@ -2720,7 +2714,7 @@ describe API::Projects do
it 'forks if user has sufficient access to project' do it 'forks if user has sufficient access to project' do
post api("/projects/#{project.id}/fork", user2) 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['name']).to eq(project.name)
expect(json_response['path']).to eq(project.path) expect(json_response['path']).to eq(project.path)
expect(json_response['owner']['id']).to eq(user2.id) expect(json_response['owner']['id']).to eq(user2.id)
...@@ -2733,7 +2727,7 @@ describe API::Projects do ...@@ -2733,7 +2727,7 @@ describe API::Projects do
it 'forks if user is admin' do it 'forks if user is admin' do
post api("/projects/#{project.id}/fork", admin) 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['name']).to eq(project.name)
expect(json_response['path']).to eq(project.path) expect(json_response['path']).to eq(project.path)
expect(json_response['owner']['id']).to eq(admin.id) expect(json_response['owner']['id']).to eq(admin.id)
...@@ -2747,14 +2741,17 @@ describe API::Projects do ...@@ -2747,14 +2741,17 @@ describe API::Projects do
new_user = create(:user) new_user = create(:user)
post api("/projects/#{project.id}/fork", new_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') expect(json_response['message']).to eq('404 Project Not Found')
end end
it 'fails if forked project exists in the user namespace' do 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']['name']).to eq(['has already been taken'])
expect(json_response['message']['path']).to eq(['has already been taken']) expect(json_response['message']['path']).to eq(['has already been taken'])
end end
...@@ -2762,48 +2759,48 @@ describe API::Projects do ...@@ -2762,48 +2759,48 @@ describe API::Projects do
it 'fails if project to fork from does not exist' do it 'fails if project to fork from does not exist' do
post api('/projects/424242/fork', user) 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') expect(json_response['message']).to eq('404 Project Not Found')
end end
it 'forks with explicit own user namespace id' do it 'forks with explicit own user namespace id' do
post api("/projects/#{project.id}/fork", user2), params: { namespace: user2.namespace.id } 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) expect(json_response['owner']['id']).to eq(user2.id)
end end
it 'forks with explicit own user name as namespace' do it 'forks with explicit own user name as namespace' do
post api("/projects/#{project.id}/fork", user2), params: { namespace: user2.username } 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) expect(json_response['owner']['id']).to eq(user2.id)
end end
it 'forks to another user when admin' do it 'forks to another user when admin' do
post api("/projects/#{project.id}/fork", admin), params: { namespace: user2.username } 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) expect(json_response['owner']['id']).to eq(user2.id)
end end
it 'fails if trying to fork to another user when not admin' do 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 } 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 end
it 'fails if trying to fork to non-existent namespace' do it 'fails if trying to fork to non-existent namespace' do
post api("/projects/#{project.id}/fork", user2), params: { namespace: 42424242 } post api("/projects/#{project.id}/fork", user2), params: { namespace: 42424242 }
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') expect(json_response['message']).to eq('404 Namespace Not Found')
end end
it 'forks to owned group' do it 'forks to owned group' do
post api("/projects/#{project.id}/fork", user2), params: { namespace: group2.name } 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) expect(json_response['namespace']['name']).to eq(group2.name)
end end
...@@ -2811,7 +2808,7 @@ describe API::Projects do ...@@ -2811,7 +2808,7 @@ describe API::Projects do
full_path = "#{group2.path}/#{group3.path}" full_path = "#{group2.path}/#{group3.path}"
post api("/projects/#{project.id}/fork", user2), params: { namespace: full_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']['name']).to eq(group3.name)
expect(json_response['namespace']['full_path']).to eq(full_path) expect(json_response['namespace']['full_path']).to eq(full_path)
end end
...@@ -2819,20 +2816,21 @@ describe API::Projects do ...@@ -2819,20 +2816,21 @@ describe API::Projects do
it 'fails to fork to not owned group' do it 'fails to fork to not owned group' do
post api("/projects/#{project.id}/fork", user2), params: { namespace: group.name } 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 end
it 'forks to not owned group when admin' do it 'forks to not owned group when admin' do
post api("/projects/#{project.id}/fork", admin), params: { namespace: group.name } 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) expect(json_response['namespace']['name']).to eq(group.name)
end end
it 'accepts a path for the target project' do it 'accepts a path for the target project' do
post api("/projects/#{project.id}/fork", user2), params: { path: 'foobar' } 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['name']).to eq(project.name)
expect(json_response['path']).to eq('foobar') expect(json_response['path']).to eq('foobar')
expect(json_response['owner']['id']).to eq(user2.id) expect(json_response['owner']['id']).to eq(user2.id)
...@@ -2846,14 +2844,14 @@ describe API::Projects do ...@@ -2846,14 +2844,14 @@ describe API::Projects do
post api("/projects/#{project.id}/fork", user2), params: { path: 'foobar' } post api("/projects/#{project.id}/fork", user2), params: { path: 'foobar' }
post api("/projects/#{project2.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']) expect(json_response['message']['path']).to eq(['has already been taken'])
end end
it 'accepts a name for the target project' do it 'accepts a name for the target project' do
post api("/projects/#{project.id}/fork", user2), params: { name: 'My Random Project' } 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['name']).to eq('My Random Project')
expect(json_response['path']).to eq(project.path) expect(json_response['path']).to eq(project.path)
expect(json_response['owner']['id']).to eq(user2.id) expect(json_response['owner']['id']).to eq(user2.id)
...@@ -2867,7 +2865,7 @@ describe API::Projects do ...@@ -2867,7 +2865,7 @@ describe API::Projects do
post api("/projects/#{project.id}/fork", user2), params: { name: 'My Random Project' } post api("/projects/#{project.id}/fork", user2), params: { name: 'My Random Project' }
post api("/projects/#{project2.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']) expect(json_response['message']['name']).to eq(['has already been taken'])
end end
end end
...@@ -2876,7 +2874,7 @@ describe API::Projects do ...@@ -2876,7 +2874,7 @@ describe API::Projects do
it 'returns authentication error' do it 'returns authentication error' do
post api("/projects/#{project.id}/fork") 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') expect(json_response['message']).to eq('401 Unauthorized')
end end
end end
...@@ -2890,8 +2888,7 @@ describe API::Projects do ...@@ -2890,8 +2888,7 @@ describe API::Projects do
it 'denies project to be forked' do it 'denies project to be forked' do
post api("/projects/#{project.id}/fork", admin) post api("/projects/#{project.id}/fork", admin)
expect(response).to have_gitlab_http_status(409) expect(response).to have_gitlab_http_status(:not_found)
expect(json_response['message']['forked_from_project_id']).to eq(['is forbidden'])
end end
end end
end end
......
...@@ -275,6 +275,7 @@ describe Projects::ForkService do ...@@ -275,6 +275,7 @@ describe Projects::ForkService do
context 'fork project for group when user not owner' do context 'fork project for group when user not owner' do
it 'group developer fails to fork project into the group' do it 'group developer fails to fork project into the group' do
to_project = fork_project(@project, @developer, @opts) to_project = fork_project(@project, @developer, @opts)
expect(to_project.errors[:namespace]).to eq(['is not valid']) expect(to_project.errors[:namespace]).to eq(['is not valid'])
end end
end end
...@@ -336,7 +337,9 @@ describe Projects::ForkService do ...@@ -336,7 +337,9 @@ describe Projects::ForkService do
context 'when linking fork to an existing project' do context 'when linking fork to an existing project' do
let(:fork_from_project) { create(:project, :public) } let(:fork_from_project) { create(:project, :public) }
let(:fork_to_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) } subject { described_class.new(fork_from_project, user) }
...@@ -387,4 +390,54 @@ describe Projects::ForkService do ...@@ -387,4 +390,54 @@ describe Projects::ForkService do
end end
end 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 end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment