...@@ -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` |
... ...
......