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:
-rw-r--r--.gitlab-ci.yml25
-rw-r--r--CHANGELOG.md15
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock6
-rw-r--r--app/assets/javascripts/jobs/components/stages_dropdown.vue51
-rw-r--r--app/assets/javascripts/notes/components/discussion_filter.vue2
-rw-r--r--app/assets/javascripts/notes/components/note_actions/reply_button.vue2
-rw-r--r--app/assets/javascripts/notes/discussion_filters.js13
-rw-r--r--app/assets/javascripts/notes/index.js7
-rw-r--r--app/assets/javascripts/pipelines/components/pipeline_url.vue8
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_table_row.vue3
-rw-r--r--app/assets/javascripts/vue_shared/components/commit.vue70
-rw-r--r--app/assets/stylesheets/framework/blank.scss28
-rw-r--r--app/assets/stylesheets/framework/common.scss6
-rw-r--r--app/assets/stylesheets/framework/header.scss4
-rw-r--r--app/assets/stylesheets/framework/icons.scss1
-rw-r--r--app/assets/stylesheets/pages/builds.scss12
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss1
-rw-r--r--app/assets/stylesheets/pages/pipelines.scss7
-rw-r--r--app/assets/stylesheets/pages/profile.scss15
-rw-r--r--app/assets/stylesheets/pages/projects.scss3
-rw-r--r--app/assets/stylesheets/pages/status.scss1
-rw-r--r--app/controllers/concerns/notes_actions.rb2
-rw-r--r--app/controllers/omniauth_callbacks_controller.rb4
-rw-r--r--app/controllers/profiles/accounts_controller.rb2
-rw-r--r--app/helpers/auth_helper.rb8
-rw-r--r--app/helpers/projects_helper.rb5
-rw-r--r--app/models/broadcast_message.rb2
-rw-r--r--app/models/ci/build.rb26
-rw-r--r--app/models/ci/pipeline.rb13
-rw-r--r--app/models/ci/stage.rb7
-rw-r--r--app/models/clusters/platforms/kubernetes.rb2
-rw-r--r--app/models/commit_status.rb21
-rw-r--r--app/models/commit_status_enums.rb3
-rw-r--r--app/models/concerns/cache_markdown_field.rb22
-rw-r--r--app/models/concerns/has_status.rb17
-rw-r--r--app/models/deployment.rb4
-rw-r--r--app/models/diff_note.rb2
-rw-r--r--app/models/project_services/kubernetes_service.rb4
-rw-r--r--app/policies/identity_provider_policy.rb15
-rw-r--r--app/presenters/commit_status_presenter.rb3
-rw-r--r--app/services/ci/prepare_build_service.rb25
-rw-r--r--app/views/admin/dashboard/index.html.haml5
-rw-r--r--app/views/layouts/nav/_dashboard.html.haml4
-rw-r--r--app/views/profiles/_email_settings.html.haml16
-rw-r--r--app/views/profiles/accounts/_providers.html.haml21
-rw-r--r--app/views/profiles/accounts/show.html.haml19
-rw-r--r--app/views/profiles/notifications/_email_settings.html.haml6
-rw-r--r--app/views/profiles/notifications/show.html.haml4
-rw-r--r--app/views/profiles/show.html.haml13
-rw-r--r--app/views/projects/_new_project_fields.html.haml8
-rw-r--r--app/views/projects/protected_branches/shared/_index.html.haml2
-rw-r--r--app/views/search/_results.html.haml2
-rw-r--r--app/views/shared/projects/_project.html.haml2
-rw-r--r--app/workers/all_queues.yml1
-rw-r--r--app/workers/ci/build_prepare_worker.rb16
-rw-r--r--app/workers/cluster_configure_worker.rb2
-rw-r--r--app/workers/cluster_project_configure_worker.rb2
-rw-r--r--changelogs/unreleased/57115-just-in-time-k8s-resource-creation.yml5
-rw-r--r--changelogs/unreleased/58883-fix-fetching-comments.yml5
-rw-r--r--changelogs/unreleased/59057-buttons-on-top-from-a-user-profile-page-on-mobile.yml5
-rw-r--r--changelogs/unreleased/59117-inconsistent-hover-behavior-on-navbar-items.yml5
-rw-r--r--changelogs/unreleased/59189-long-names-in-project-path-namespace-dropdown-breaks-past-container.yml5
-rw-r--r--changelogs/unreleased/59208-fix-error-500-on-every-page-when-active-broadcast-message-present-after-upgrading-to-11-9-0.yml6
-rw-r--r--changelogs/unreleased/59289-fix-push-to-create-protected-branches.yml6
-rw-r--r--changelogs/unreleased/59293-android-template-is-missing-gitlab-ci-yml.yml5
-rw-r--r--changelogs/unreleased/avoid_es_loading_project_ci_status.yml5
-rw-r--r--changelogs/unreleased/gitaly-version-v1.29.0.yml5
-rw-r--r--changelogs/unreleased/k8s_new_deployment_labels.yml5
-rw-r--r--changelogs/unreleased/nfriend-update-job-detail-view-sidebar.yml5
-rw-r--r--changelogs/unreleased/nfriend-update-pipeline-list-view.yml5
-rw-r--r--changelogs/unreleased/osw-multi-line-suggestions-parsing.yml5
-rw-r--r--changelogs/unreleased/security-2826-fix-project-serialization-in-quick-actions.yml5
-rw-r--r--doc/administration/container_registry.md1
-rw-r--r--doc/administration/high_availability/redis.md3
-rw-r--r--doc/administration/job_artifacts.md2
-rw-r--r--doc/administration/monitoring/prometheus/index.md1
-rw-r--r--doc/administration/pages/index.md1
-rw-r--r--doc/administration/repository_storage_types.md5
-rw-r--r--doc/administration/uploads.md2
-rw-r--r--doc/api/group_milestones.md1
-rw-r--r--doc/api/project_snippets.md1
-rw-r--r--doc/api/services.md7
-rw-r--r--doc/ci/chatops/README.md4
-rw-r--r--doc/ci/docker/using_docker_build.md1
-rw-r--r--doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md18
-rw-r--r--doc/development/new_fe_guide/tips.md16
-rw-r--r--doc/development/testing_guide/frontend_testing.md10
-rw-r--r--doc/raketasks/backup_restore.md2
-rw-r--r--doc/topics/autodevops/index.md4
-rw-r--r--doc/user/discussions/index.md5
-rw-r--r--doc/user/group/index.md5
-rw-r--r--doc/user/profile/account/two_factor_authentication.md1
-rw-r--r--doc/user/project/bulk_editing.md1
-rw-r--r--doc/user/project/clusters/index.md25
-rw-r--r--doc/user/project/container_registry.md6
-rw-r--r--doc/user/project/integrations/jira.md2
-rw-r--r--doc/user/project/merge_requests/versions.md1
-rw-r--r--doc/user/project/new_ci_build_permissions_model.md1
-rw-r--r--doc/user/project/pages/introduction.md1
-rw-r--r--doc/user/project/pipelines/job_artifacts.md7
-rw-r--r--doc/user/project/pipelines/schedules.md1
-rw-r--r--doc/user/project/protected_branches.md2
-rw-r--r--lib/banzai/filter/output_safety.rb11
-rw-r--r--lib/banzai/filter/reference_filter.rb5
-rw-r--r--lib/banzai/filter/suggestion_filter.rb18
-rw-r--r--lib/banzai/filter/syntax_highlight_filter.rb21
-rw-r--r--lib/gitlab/auth/ldap/person.rb2
-rw-r--r--lib/gitlab/badge/pipeline/template.rb1
-rw-r--r--lib/gitlab/checks/branch_check.rb2
-rw-r--r--lib/gitlab/ci/build/prerequisite/base.rb27
-rw-r--r--lib/gitlab/ci/build/prerequisite/factory.rb33
-rw-r--r--lib/gitlab/ci/build/prerequisite/kubernetes_namespace.rb40
-rw-r--r--lib/gitlab/ci/status/build/factory.rb1
-rw-r--r--lib/gitlab/ci/status/build/failed.rb3
-rw-r--r--lib/gitlab/ci/status/build/preparing.rb28
-rw-r--r--lib/gitlab/ci/status/preparing.rb33
-rw-r--r--lib/gitlab/ci/templates/Android-Fastlane.gitlab-ci.yml1
-rw-r--r--lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml4
-rw-r--r--lib/gitlab/ci/templates/Bash.gitlab-ci.yml14
-rw-r--r--lib/gitlab/ci/templates/C++.gitlab-ci.yml6
-rw-r--r--lib/gitlab/ci/templates/Chef.gitlab-ci.yml2
-rw-r--r--lib/gitlab/ci/templates/Clojure.gitlab-ci.yml8
-rw-r--r--lib/gitlab/ci/templates/Code-Quality.gitlab-ci.yml17
-rw-r--r--lib/gitlab/ci/templates/Django.gitlab-ci.yml2
-rw-r--r--lib/gitlab/ci/templates/Gradle.gitlab-ci.yml2
-rw-r--r--lib/gitlab/ci/templates/Grails.gitlab-ci.yml10
-rw-r--r--lib/gitlab/ci/templates/Julia.gitlab-ci.yml2
-rw-r--r--lib/gitlab/ci/templates/Laravel.gitlab-ci.yml15
-rw-r--r--lib/gitlab/ci/templates/Maven.gitlab-ci.yml2
-rw-r--r--lib/gitlab/ci/templates/Mono.gitlab-ci.yml4
-rw-r--r--lib/gitlab/ci/templates/Pages/Brunch.gitlab-ci.yml1
-rw-r--r--lib/gitlab/ci/templates/Pages/Harp.gitlab-ci.yml1
-rw-r--r--lib/gitlab/ci/templates/Pages/Hugo.gitlab-ci.yml2
-rw-r--r--lib/gitlab/ci/templates/Pages/JBake.gitlab-ci.yml3
-rw-r--r--lib/gitlab/ci/templates/Pages/Jigsaw.gitlab-ci.yml13
-rw-r--r--lib/gitlab/ci/templates/Pages/Metalsmith.gitlab-ci.yml1
-rw-r--r--lib/gitlab/ci/templates/Python.gitlab-ci.yml4
-rw-r--r--lib/gitlab/ci/templates/Ruby.gitlab-ci.yml2
-rw-r--r--lib/gitlab/ci/templates/dotNET.gitlab-ci.yml2
-rw-r--r--lib/gitlab/ci/templates/iOS-Fastlane.gitlab-ci.yml2
-rw-r--r--lib/gitlab/diff/suggestions_parser.rb10
-rw-r--r--lib/gitlab/git/commit.rb9
-rw-r--r--lib/gitlab/json_cache.rb19
-rw-r--r--lib/gitlab/kubernetes.rb24
-rw-r--r--lib/sentry/client.rb2
-rw-r--r--locale/gitlab.pot34
-rw-r--r--package.json1
-rw-r--r--qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb6
-rw-r--r--spec/controllers/omniauth_callbacks_controller_spec.rb27
-rw-r--r--spec/controllers/projects/notes_controller_spec.rb31
-rw-r--r--spec/factories/ci/builds.rb4
-rw-r--r--spec/factories/ci/pipelines.rb8
-rw-r--r--spec/factories/clusters/clusters.rb4
-rw-r--r--spec/factories/commit_statuses.rb4
-rw-r--r--spec/features/issues_spec.rb11
-rw-r--r--spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb78
-rw-r--r--spec/features/projects/badges/pipeline_badge_spec.rb19
-rw-r--r--spec/features/projects/jobs_spec.rb109
-rw-r--r--spec/features/projects/pipelines/pipeline_spec.rb23
-rw-r--r--spec/features/projects/pipelines/pipelines_spec.rb119
-rw-r--r--spec/features/user_opens_link_to_comment.rb33
-rw-r--r--spec/helpers/auth_helper_spec.rb38
-rw-r--r--spec/javascripts/environments/environment_item_spec.js2
-rw-r--r--spec/javascripts/jobs/components/stages_dropdown_spec.js188
-rw-r--r--spec/javascripts/pipelines/pipeline_url_spec.js12
-rw-r--r--spec/javascripts/vue_shared/components/commit_spec.js90
-rw-r--r--spec/lib/banzai/filter/output_safety_spec.rb29
-rw-r--r--spec/lib/banzai/filter/suggestion_filter_spec.rb33
-rw-r--r--spec/lib/banzai/filter/syntax_highlight_filter_spec.rb32
-rw-r--r--spec/lib/gitlab/badge/pipeline/template_spec.rb10
-rw-r--r--spec/lib/gitlab/checks/branch_check_spec.rb80
-rw-r--r--spec/lib/gitlab/ci/build/prerequisite/factory_spec.rb34
-rw-r--r--spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb85
-rw-r--r--spec/lib/gitlab/ci/status/build/preparing_spec.rb33
-rw-r--r--spec/lib/gitlab/ci/status/preparing_spec.rb29
-rw-r--r--spec/lib/gitlab/json_cache_spec.rb54
-rw-r--r--spec/lib/gitlab/kubernetes_spec.rb34
-rw-r--r--spec/models/broadcast_message_spec.rb6
-rw-r--r--spec/models/ci/build_spec.rb87
-rw-r--r--spec/models/ci/pipeline_spec.rb68
-rw-r--r--spec/models/clusters/platforms/kubernetes_spec.rb6
-rw-r--r--spec/models/commit_status_spec.rb23
-rw-r--r--spec/models/concerns/cache_markdown_field_spec.rb14
-rw-r--r--spec/models/concerns/has_status_spec.rb22
-rw-r--r--spec/models/deployment_spec.rb28
-rw-r--r--spec/models/diff_note_spec.rb10
-rw-r--r--spec/models/project_services/kubernetes_service_spec.rb12
-rw-r--r--spec/policies/identity_provider_policy_spec.rb30
-rw-r--r--spec/requests/api/project_clusters_spec.rb1
-rw-r--r--spec/requests/api/runner_spec.rb9
-rw-r--r--spec/services/ci/prepare_build_service_spec.rb54
-rw-r--r--spec/services/projects/create_service_spec.rb1
-rw-r--r--spec/services/projects/transfer_service_spec.rb1
-rw-r--r--spec/support/helpers/kubernetes_helpers.rb16
-rw-r--r--spec/workers/ci/build_prepare_worker_spec.rb30
-rw-r--r--spec/workers/cluster_configure_worker_spec.rb16
-rw-r--r--spec/workers/cluster_project_configure_worker_spec.rb21
-rw-r--r--vendor/project_templates/android.tar.gzbin132152 -> 132592 bytes
200 files changed, 2504 insertions, 462 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 2de6c0d2cc7..33559812872 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,5 +1,8 @@
image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.5.3-golang-1.11-git-2.18-chrome-71.0-node-10.x-yarn-1.12-postgresql-9.6-graphicsmagick-1.3.29"
+include:
+ - local: /lib/gitlab/ci/templates/Code-Quality.gitlab-ci.yml
+
.dedicated-runner: &dedicated-runner
retry: 1
tags:
@@ -774,31 +777,14 @@ jest:
code_quality:
<<: *dedicated-no-docs-no-db-pull-cache-job
- image: docker:stable
- allow_failure: true
# gitlab-org runners set `privileged: false` but we need to have it set to true
# since we're using Docker in Docker
tags: []
before_script: []
- services:
- - docker:stable-dind
- variables:
- SETUP_DB: "false"
- DOCKER_DRIVER: overlay2
cache: {}
dependencies: []
- script:
- # Extract "MAJOR.MINOR" from CI_SERVER_VERSION and generate "MAJOR-MINOR-stable" for Security Products
- - export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')
- - docker run
- --env SOURCE_CODE="$PWD"
- --volume "$PWD":/code
- --volume /var/run/docker.sock:/var/run/docker.sock
- "registry.gitlab.com/gitlab-org/security-products/codequality:$SP_VERSION" /code
- artifacts:
- reports:
- codequality: gl-code-quality-report.json
- expire_in: 1 week
+ variables:
+ SETUP_DB: "false"
sast:
<<: *dedicated-no-docs-no-db-pull-cache-job
@@ -1014,7 +1000,6 @@ no_ee_check:
dependencies: []
cache: {}
variables:
- GIT_DEPTH: "1"
API_TOKEN: "${GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN}"
script:
- apk add --update openssl curl jq
diff --git a/CHANGELOG.md b/CHANGELOG.md
index bec6a8b3e21..0c33596d9c1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,13 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 11.8.3 (2019-03-19)
+
+### Security (1 change)
+
+- Remove project serialization in quick actions response.
+
+
## 11.8.2 (2019-03-13)
### Security (1 change)
@@ -264,6 +271,14 @@ entry.
- Creates mixin to reduce code duplication between CE and EE in graph component.
+## 11.7.7 (2019-03-19)
+
+### Security (2 changes)
+
+- Remove project serialization in quick actions response.
+- Fixed ability to see private groups by users not belonging to given group.
+
+
## 11.7.5 (2019-02-06)
### Fixed (8 changes)
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 5db08bf2dc5..5e57fb89558 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-1.27.0
+1.29.0
diff --git a/Gemfile b/Gemfile
index 1ae063d8558..d44a99b2913 100644
--- a/Gemfile
+++ b/Gemfile
@@ -170,7 +170,7 @@ gem 'gitlab-sidekiq-fetcher', '~> 0.4.0', require: 'sidekiq-reliable-fetch'
gem 'fugit', '~> 1.1'
# HTTP requests
-gem 'httparty', '~> 0.13.3'
+gem 'httparty', '~> 0.16.4'
# Colored output to console
gem 'rainbow', '~> 3.0'
diff --git a/Gemfile.lock b/Gemfile.lock
index face24489f1..6943428a7d8 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -379,8 +379,8 @@ GEM
domain_name (~> 0.5)
http-form_data (2.1.1)
http_parser.rb (0.6.0)
- httparty (0.13.7)
- json (~> 1.8)
+ httparty (0.16.4)
+ mime-types (~> 3.0)
multi_xml (>= 0.5.2)
httpclient (2.8.3)
i18n (1.2.0)
@@ -1040,7 +1040,7 @@ DEPENDENCIES
health_check (~> 2.6.0)
html-pipeline (~> 2.8)
html2text
- httparty (~> 0.13.3)
+ httparty (~> 0.16.4)
icalendar
influxdb (~> 0.2)
jaeger-client (~> 0.10.0)
diff --git a/app/assets/javascripts/jobs/components/stages_dropdown.vue b/app/assets/javascripts/jobs/components/stages_dropdown.vue
index c5076d65ff9..6e92b599b0a 100644
--- a/app/assets/javascripts/jobs/components/stages_dropdown.vue
+++ b/app/assets/javascripts/jobs/components/stages_dropdown.vue
@@ -1,5 +1,6 @@
<script>
import _ from 'underscore';
+import { GlLink } from '@gitlab/ui';
import CiIcon from '~/vue_shared/components/ci_icon.vue';
import Icon from '~/vue_shared/components/icon.vue';
@@ -7,6 +8,7 @@ export default {
components: {
CiIcon,
Icon,
+ GlLink,
},
props: {
pipeline: {
@@ -26,6 +28,12 @@ export default {
hasRef() {
return !_.isEmpty(this.pipeline.ref);
},
+ isTriggeredByMergeRequest() {
+ return Boolean(this.pipeline.merge_request);
+ },
+ isMergeRequestPipeline() {
+ return Boolean(this.pipeline.flags && this.pipeline.flags.merge_request_pipeline);
+ },
},
methods: {
onStageClick(stage) {
@@ -36,16 +44,41 @@ export default {
</script>
<template>
<div class="block-last dropdown">
- <ci-icon :status="pipeline.details.status" class="vertical-align-middle" />
+ <div class="js-pipeline-info">
+ <ci-icon :status="pipeline.details.status" class="vertical-align-middle" />
- <span class="font-weight-bold">{{ __('Pipeline') }}</span>
- <a :href="pipeline.path" class="js-pipeline-path link-commit qa-pipeline-path"
- >#{{ pipeline.id }}</a
- >
- <template v-if="hasRef">
- {{ __('from') }}
- <a :href="pipeline.ref.path" class="link-commit ref-name">{{ pipeline.ref.name }}</a>
- </template>
+ <span class="font-weight-bold">{{ s__('Job|Pipeline') }}</span>
+ <gl-link :href="pipeline.path" class="js-pipeline-path link-commit qa-pipeline-path"
+ >#{{ pipeline.id }}</gl-link
+ >
+ <template v-if="hasRef">
+ {{ s__('Job|for') }}
+
+ <template v-if="isTriggeredByMergeRequest">
+ <gl-link :href="pipeline.merge_request.path" class="link-commit ref-name js-mr-link"
+ >!{{ pipeline.merge_request.iid }}</gl-link
+ >
+ {{ s__('Job|with') }}
+ <gl-link
+ :href="pipeline.merge_request.source_branch_path"
+ class="link-commit ref-name js-source-branch-link"
+ >{{ pipeline.merge_request.source_branch }}</gl-link
+ >
+
+ <template v-if="isMergeRequestPipeline">
+ {{ s__('Job|into') }}
+ <gl-link
+ :href="pipeline.merge_request.target_branch_path"
+ class="link-commit ref-name js-target-branch-link"
+ >{{ pipeline.merge_request.target_branch }}</gl-link
+ >
+ </template>
+ </template>
+ <gl-link v-else :href="pipeline.ref.path" class="link-commit ref-name">{{
+ pipeline.ref.name
+ }}</gl-link>
+ </template>
+ </div>
<button
type="button"
diff --git a/app/assets/javascripts/notes/components/discussion_filter.vue b/app/assets/javascripts/notes/components/discussion_filter.vue
index 31164f74201..47951591e82 100644
--- a/app/assets/javascripts/notes/components/discussion_filter.vue
+++ b/app/assets/javascripts/notes/components/discussion_filter.vue
@@ -22,7 +22,7 @@ export default {
},
selectedValue: {
type: Number,
- default: null,
+ default: DISCUSSION_FILTERS_DEFAULT_VALUE,
required: false,
},
},
diff --git a/app/assets/javascripts/notes/components/note_actions/reply_button.vue b/app/assets/javascripts/notes/components/note_actions/reply_button.vue
index f50cab81efe..be8e42af9ea 100644
--- a/app/assets/javascripts/notes/components/note_actions/reply_button.vue
+++ b/app/assets/javascripts/notes/components/note_actions/reply_button.vue
@@ -18,7 +18,7 @@ export default {
<div class="note-actions-item">
<gl-button
ref="button"
- v-gl-tooltip.bottom
+ v-gl-tooltip
class="note-action-button"
variant="transparent"
:title="__('Reply to comment')"
diff --git a/app/assets/javascripts/notes/discussion_filters.js b/app/assets/javascripts/notes/discussion_filters.js
index 5c5f38a3fb0..cdf9a46c5aa 100644
--- a/app/assets/javascripts/notes/discussion_filters.js
+++ b/app/assets/javascripts/notes/discussion_filters.js
@@ -6,12 +6,16 @@ export default store => {
if (discussionFilterEl) {
const { defaultFilter, notesFilters } = discussionFilterEl.dataset;
- const selectedValue = defaultFilter ? parseInt(defaultFilter, 10) : null;
const filterValues = notesFilters ? JSON.parse(notesFilters) : {};
const filters = Object.keys(filterValues).map(entry => ({
title: entry,
value: filterValues[entry],
}));
+ const props = { filters };
+
+ if (defaultFilter) {
+ props.selectedValue = parseInt(defaultFilter, 10);
+ }
return new Vue({
el: discussionFilterEl,
@@ -21,12 +25,7 @@ export default store => {
},
store,
render(createElement) {
- return createElement('discussion-filter', {
- props: {
- filters,
- selectedValue,
- },
- });
+ return createElement('discussion-filter', { props });
},
});
}
diff --git a/app/assets/javascripts/notes/index.js b/app/assets/javascripts/notes/index.js
index 4883266dae5..30372103590 100644
--- a/app/assets/javascripts/notes/index.js
+++ b/app/assets/javascripts/notes/index.js
@@ -6,9 +6,8 @@ import createStore from './stores';
document.addEventListener('DOMContentLoaded', () => {
const store = createStore();
- initDiscussionFilters(store);
-
- return new Vue({
+ // eslint-disable-next-line no-new
+ new Vue({
el: '#js-vue-notes',
components: {
notesApp,
@@ -49,4 +48,6 @@ document.addEventListener('DOMContentLoaded', () => {
});
},
});
+
+ initDiscussionFilters(store);
});
diff --git a/app/assets/javascripts/pipelines/components/pipeline_url.vue b/app/assets/javascripts/pipelines/components/pipeline_url.vue
index 918622ef8dc..3e7bf20470c 100644
--- a/app/assets/javascripts/pipelines/components/pipeline_url.vue
+++ b/app/assets/javascripts/pipelines/components/pipeline_url.vue
@@ -110,12 +110,12 @@ export default {
{{ __('stuck') }}
</span>
<span
- v-if="pipeline.flags.merge_request"
+ v-if="pipeline.flags.detached_merge_request_pipeline"
v-gl-tooltip
- :title="__('This pipeline is run in a merge request context')"
- class="js-pipeline-url-mergerequest badge badge-info"
+ :title="__('This pipeline is run on the source branch')"
+ class="js-pipeline-url-detached badge badge-info"
>
- {{ __('merge request') }}
+ {{ __('detached') }}
</span>
</div>
</div>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_table_row.vue b/app/assets/javascripts/pipelines/components/pipelines_table_row.vue
index f6454a84ea5..1c44427e720 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_table_row.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_table_row.vue
@@ -272,10 +272,11 @@ export default {
:tag="commitTag"
:commit-ref="commitRef"
:commit-url="commitUrl"
+ :merge-request-ref="pipeline.merge_request"
:short-sha="commitShortSha"
:title="commitTitle"
:author="commitAuthor"
- :show-branch="!isChildView"
+ :show-ref-info="!isChildView"
/>
</div>
</div>
diff --git a/app/assets/javascripts/vue_shared/components/commit.vue b/app/assets/javascripts/vue_shared/components/commit.vue
index ee685a4b8cd..3f282138bdf 100644
--- a/app/assets/javascripts/vue_shared/components/commit.vue
+++ b/app/assets/javascripts/vue_shared/components/commit.vue
@@ -1,5 +1,6 @@
<script>
-import { GlTooltipDirective } from '@gitlab/ui';
+import _ from 'underscore';
+import { GlTooltipDirective, GlLink } from '@gitlab/ui';
import UserAvatarLink from './user_avatar/user_avatar_link.vue';
import Icon from '../../vue_shared/components/icon.vue';
@@ -10,6 +11,7 @@ export default {
components: {
UserAvatarLink,
Icon,
+ GlLink,
},
props: {
/**
@@ -33,6 +35,27 @@ export default {
required: false,
default: () => ({}),
},
+
+ /**
+ * If provided, is used the render the MR IID and link
+ * in place of the branch name. Must contains the
+ * following properties:
+ * - iid (number)
+ * - path (non-empty string)
+ *
+ * May optionally contain the following properties:
+ * - title (string): used in a tooltip if provided
+ *
+ * Any additional properties are ignored.
+ */
+ mergeRequestRef: {
+ type: Object,
+ required: false,
+ default: undefined,
+ validator: ref =>
+ _.isUndefined(ref) || (_.isFinite(ref.iid) && _.isString(ref.path) && !_.isEmpty(ref.path)),
+ },
+
/**
* Used to link to the commit sha.
*/
@@ -70,7 +93,11 @@ export default {
required: false,
default: () => ({}),
},
- showBranch: {
+
+ /**
+ * Indicates whether or not to show the branch/MR ref info
+ */
+ showRefInfo: {
type: Boolean,
required: false,
default: true,
@@ -78,14 +105,12 @@ export default {
},
computed: {
/**
- * Used to verify if all the properties needed to render the commit
- * ref section were provided.
- *
- * @returns {Boolean}
+ * Determines if we shoud render the ref info section based
*/
- hasCommitRef() {
- return this.commitRef && this.commitRef.name && this.commitRef.ref_url;
+ shouldShowRefInfo() {
+ return this.showRefInfo && (this.commitRef || this.mergeRequestRef);
},
+
/**
* Used to verify if all the properties needed to render the commit
* author section were provided.
@@ -109,18 +134,35 @@ export default {
</script>
<template>
<div class="branch-commit">
- <template v-if="hasCommitRef && showBranch">
+ <template v-if="shouldShowRefInfo">
<div class="icon-container">
- <i v-if="tag" class="fa fa-tag" aria-hidden="true"> </i> <icon v-if="!tag" name="fork" />
+ <icon v-if="tag" name="tag" />
+ <icon v-else-if="mergeRequestRef" name="git-merge" />
+ <icon v-else name="branch" />
</div>
- <a v-gl-tooltip :href="commitRef.ref_url" :title="commitRef.name" class="ref-name">
+ <gl-link
+ v-if="mergeRequestRef"
+ v-gl-tooltip
+ :href="mergeRequestRef.path"
+ :title="mergeRequestRef.title"
+ class="ref-name"
+ >
+ {{ mergeRequestRef.iid }}
+ </gl-link>
+ <gl-link
+ v-else
+ v-gl-tooltip
+ :href="commitRef.ref_url"
+ :title="commitRef.name"
+ class="ref-name"
+ >
{{ commitRef.name }}
- </a>
+ </gl-link>
</template>
<icon name="commit" class="commit-icon js-commit-icon" />
- <a :href="commitUrl" class="commit-sha"> {{ shortSha }} </a>
+ <gl-link :href="commitUrl" class="commit-sha"> {{ shortSha }} </gl-link>
<div class="commit-title flex-truncate-parent">
<span v-if="title" class="flex-truncate-child">
@@ -132,7 +174,7 @@ export default {
:tooltip-text="author.username"
class="avatar-image-container"
/>
- <a :href="commitUrl" class="commit-row-message"> {{ title }} </a>
+ <gl-link :href="commitUrl" class="commit-row-message"> {{ title }} </gl-link>
</span>
<span v-else> Can't find HEAD commit for this branch </span>
</div>
diff --git a/app/assets/stylesheets/framework/blank.scss b/app/assets/stylesheets/framework/blank.scss
index 91dbb2a6365..cbd390e7145 100644
--- a/app/assets/stylesheets/framework/blank.scss
+++ b/app/assets/stylesheets/framework/blank.scss
@@ -69,6 +69,7 @@
@include media-breakpoint-up(sm) {
display: flex;
+ height: 100%;
align-items: center;
padding: 50px 30px;
}
@@ -99,3 +100,30 @@
}
}
}
+
+@include media-breakpoint-up(lg) {
+ .column-large {
+ flex: 2;
+ }
+
+ .column-small {
+ flex: 1;
+ margin-bottom: 15px;
+
+ .blank-state {
+ max-width: 400px;
+ flex-wrap: wrap;
+ margin-left: 15px;
+ }
+
+ .blank-state-icon {
+ margin-bottom: 30px;
+ }
+ }
+}
+
+@include media-breakpoint-down(xs) {
+ .blank-state-icon svg {
+ width: 315px;
+ }
+}
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index 5efd484b51b..97a9a55c968 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -120,7 +120,7 @@ hr {
text-overflow: ellipsis;
white-space: nowrap;
- > div,
+ > div:not(.block),
.str-truncated {
display: inline;
}
@@ -381,6 +381,7 @@ img.emoji {
.prepend-left-5 { margin-left: 5px; }
.prepend-left-8 { margin-left: 8px; }
.prepend-left-10 { margin-left: 10px; }
+.prepend-left-15 { margin-left: 15px; }
.prepend-left-default { margin-left: $gl-padding; }
.prepend-left-20 { margin-left: 20px; }
.prepend-left-32 { margin-left: 32px; }
@@ -388,6 +389,7 @@ img.emoji {
.append-right-5 { margin-right: 5px; }
.append-right-8 { margin-right: 8px; }
.append-right-10 { margin-right: 10px; }
+.append-right-15 { margin-right: 15px; }
.append-right-default { margin-right: $gl-padding; }
.append-right-20 { margin-right: 20px; }
.prepend-right-32 { margin-right: 32px; }
@@ -402,6 +404,8 @@ img.emoji {
.prepend-bottom-32 { margin-bottom: 32px; }
.inline { display: inline-block; }
.center { text-align: center; }
+.block { display: block; }
+.flex { display: flex; }
.vertical-align-middle { vertical-align: middle; }
.vertical-align-sub { vertical-align: sub; }
.flex-align-self-center { align-self: center; }
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index f5ed6621c55..1e025b3a67d 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -304,7 +304,9 @@
}
}
-.caret-down {
+.caret-down,
+.btn .caret-down {
+ top: 0;
height: 11px;
width: 11px;
margin-left: 4px;
diff --git a/app/assets/stylesheets/framework/icons.scss b/app/assets/stylesheets/framework/icons.scss
index 49b9b7014ae..3ab61cc5c47 100644
--- a/app/assets/stylesheets/framework/icons.scss
+++ b/app/assets/stylesheets/framework/icons.scss
@@ -31,6 +31,7 @@
}
}
+.ci-status-icon-preparing,
.ci-status-icon-running {
svg {
fill: $blue-400;
diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss
index fa5a182243c..916f6cd3137 100644
--- a/app/assets/stylesheets/pages/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
@@ -50,7 +50,6 @@
position: relative;
}
-
.build-trace {
@include build-trace();
}
@@ -392,3 +391,14 @@
right: 0;
margin-top: -17px;
}
+
+@include media-breakpoint-down(sm) {
+ .top-bar {
+ .truncated-info {
+ white-space: nowrap;
+ overflow: hidden;
+ max-width: 220px;
+ text-overflow: ellipsis;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index df3a4be6559..7f8b8ea8100 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -166,6 +166,7 @@
float: left;
.accept-merge-request {
+ &.ci-preparing,
&.ci-pending,
&.ci-running {
@include btn-blue;
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index 2b6319ddd4f..bb08440fda8 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -498,7 +498,8 @@
list-style: none;
}
- &:last-child {
+ // when downstream pipelines are present, the last stage isn't the last column
+ &:last-child:not(.has-downstream) {
.build {
// Remove right connecting horizontal line from first build in last stage
&:first-child::after {
@@ -515,7 +516,8 @@
}
}
- &:first-child {
+ // when upstream pipelines are present, the first stage isn't the first column
+ &:first-child:not(.has-upstream) {
.build {
// Remove left curved connectors from all builds in first stage
&:not(:first-child)::before {
@@ -793,6 +795,7 @@
@include mini-pipeline-graph-color($white, $orange-100, $orange-200, $orange-500, $orange-600, $orange-700);
}
+ &.ci-status-icon-preparing,
&.ci-status-icon-running {
@include mini-pipeline-graph-color($white, $blue-100, $blue-200, $blue-500, $blue-600, $blue-700);
}
diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss
index ab26259c007..8e933b62dd9 100644
--- a/app/assets/stylesheets/pages/profile.scss
+++ b/app/assets/stylesheets/pages/profile.scss
@@ -266,21 +266,6 @@
padding-top: 20px;
}
- .cover-controls {
- position: static;
- padding: 0 16px;
- margin-bottom: 20px;
- display: flex;
-
- .btn {
- flex-grow: 1;
-
- &:first-child {
- margin-left: 0;
- }
- }
- }
-
.user-profile-nav {
a {
margin-right: 0;
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 8e53876eb4f..bcb306d97d5 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -18,12 +18,9 @@
}
.input-group {
- display: flex;
-
.select2-container {
display: unset;
max-width: unset;
- width: unset !important;
flex-grow: 1;
}
diff --git a/app/assets/stylesheets/pages/status.scss b/app/assets/stylesheets/pages/status.scss
index f4d568d02ac..a59bb31bdcb 100644
--- a/app/assets/stylesheets/pages/status.scss
+++ b/app/assets/stylesheets/pages/status.scss
@@ -44,6 +44,7 @@
}
&.ci-info,
+ &.ci-preparing,
&.ci-running {
@include status-color($blue-100, $blue-500, $blue-600);
}
diff --git a/app/controllers/concerns/notes_actions.rb b/app/controllers/concerns/notes_actions.rb
index b4fee93713b..f96d1821095 100644
--- a/app/controllers/concerns/notes_actions.rb
+++ b/app/controllers/concerns/notes_actions.rb
@@ -48,7 +48,7 @@ module NotesActions
respond_to do |format|
format.json do
json = {
- commands_changes: @note.commands_changes
+ commands_changes: @note.commands_changes&.slice(:emoji_award, :time_estimate, :spend_time)
}
if @note.persisted? && return_discussion?
diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb
index cc2bb99f55b..e90e8278c13 100644
--- a/app/controllers/omniauth_callbacks_controller.rb
+++ b/app/controllers/omniauth_callbacks_controller.rb
@@ -3,6 +3,7 @@
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
include AuthenticatesWithTwoFactor
include Devise::Controllers::Rememberable
+ include AuthHelper
protect_from_forgery except: [:kerberos, :saml, :cas3, :failure], with: :exception, prepend: true
@@ -80,10 +81,11 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
end
if current_user
+ return render_403 unless link_provider_allowed?(oauth['provider'])
+
log_audit_event(current_user, with: oauth['provider'])
identity_linker ||= auth_module::IdentityLinker.new(current_user, oauth)
-
identity_linker.link
if identity_linker.changed?
diff --git a/app/controllers/profiles/accounts_controller.rb b/app/controllers/profiles/accounts_controller.rb
index b0d65f284af..0d2a6145d0e 100644
--- a/app/controllers/profiles/accounts_controller.rb
+++ b/app/controllers/profiles/accounts_controller.rb
@@ -14,7 +14,7 @@ class Profiles::AccountsController < Profiles::ApplicationController
return render_404 unless identity
- if unlink_allowed?(provider)
+ if unlink_provider_allowed?(provider)
identity.destroy
else
flash[:alert] = "You are not allowed to unlink your primary login account"
diff --git a/app/helpers/auth_helper.rb b/app/helpers/auth_helper.rb
index 2b1d6f49878..b4ee648361c 100644
--- a/app/helpers/auth_helper.rb
+++ b/app/helpers/auth_helper.rb
@@ -100,8 +100,12 @@ module AuthHelper
end
# rubocop: enable CodeReuse/ActiveRecord
- def unlink_allowed?(provider)
- %w(saml cas3).exclude?(provider.to_s)
+ def unlink_provider_allowed?(provider)
+ IdentityProviderPolicy.new(current_user, provider).can?(:unlink)
+ end
+
+ def link_provider_allowed?(provider)
+ IdentityProviderPolicy.new(current_user, provider).can?(:link)
end
extend self
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 5496aa4908c..f2abb241753 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -169,7 +169,7 @@ module ProjectsHelper
translation.html_safe
end
- def project_list_cache_key(project)
+ def project_list_cache_key(project, pipeline_status: true)
key = [
project.route.cache_key,
project.cache_key,
@@ -179,10 +179,11 @@ module ProjectsHelper
Gitlab::CurrentSettings.cache_key,
"cross-project:#{can?(current_user, :read_cross_project)}",
max_project_member_access_cache_key(project),
+ pipeline_status,
'v2.6'
]
- key << pipeline_status_cache_key(project.pipeline_status) if project.pipeline_status.has_status?
+ key << pipeline_status_cache_key(project.pipeline_status) if pipeline_status && project.pipeline_status.has_status?
key
end
diff --git a/app/models/broadcast_message.rb b/app/models/broadcast_message.rb
index 2d237383e60..1c95abdd9ee 100644
--- a/app/models/broadcast_message.rb
+++ b/app/models/broadcast_message.rb
@@ -4,7 +4,7 @@ class BroadcastMessage < ActiveRecord::Base
include CacheMarkdownField
include Sortable
- cache_markdown_field :message, pipeline: :broadcast_message
+ cache_markdown_field :message, pipeline: :broadcast_message, whitelisted: true
validates :message, presence: true
validates :starts_at, presence: true
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index a629db82c19..59f47effff7 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -172,6 +172,10 @@ module Ci
end
state_machine :status do
+ event :enqueue do
+ transition [:created, :skipped, :manual, :scheduled] => :preparing, if: :any_unmet_prerequisites?
+ end
+
event :actionize do
transition created: :manual
end
@@ -185,8 +189,12 @@ module Ci
end
event :enqueue_scheduled do
+ transition scheduled: :preparing, if: ->(build) do
+ build.scheduled_at&.past? && build.any_unmet_prerequisites?
+ end
+
transition scheduled: :pending, if: ->(build) do
- build.scheduled_at && build.scheduled_at < Time.now
+ build.scheduled_at&.past? && !build.any_unmet_prerequisites?
end
end
@@ -204,6 +212,12 @@ module Ci
end
end
+ after_transition any => [:preparing] do |build|
+ build.run_after_commit do
+ Ci::BuildPrepareWorker.perform_async(id)
+ end
+ end
+
after_transition any => [:pending] do |build|
build.run_after_commit do
BuildQueueWorker.perform_async(id)
@@ -355,6 +369,16 @@ module Ci
!retried?
end
+ def any_unmet_prerequisites?
+ return false unless Feature.enabled?(:ci_preparing_state, default_enabled: true)
+
+ prerequisites.present?
+ end
+
+ def prerequisites
+ Gitlab::Ci::Build::Prerequisite::Factory.new(self).unmet
+ end
+
def expanded_environment_name
return unless has_environment?
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index adffdc0355e..ae74f569415 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -82,10 +82,14 @@ module Ci
state_machine :status, initial: :created do
event :enqueue do
- transition [:created, :skipped, :scheduled] => :pending
+ transition [:created, :preparing, :skipped, :scheduled] => :pending
transition [:success, :failed, :canceled] => :running
end
+ event :prepare do
+ transition any - [:preparing] => :preparing
+ end
+
event :run do
transition any - [:running] => :running
end
@@ -118,7 +122,7 @@ module Ci
# Do not add any operations to this state_machine
# Create a separate worker for each new operation
- before_transition [:created, :pending] => :running do |pipeline|
+ before_transition [:created, :preparing, :pending] => :running do |pipeline|
pipeline.started_at = Time.now
end
@@ -141,7 +145,7 @@ module Ci
end
end
- after_transition [:created, :pending] => :running do |pipeline|
+ after_transition [:created, :preparing, :pending] => :running do |pipeline|
pipeline.run_after_commit { PipelineMetricsWorker.perform_async(pipeline.id) }
end
@@ -149,7 +153,7 @@ module Ci
pipeline.run_after_commit { PipelineMetricsWorker.perform_async(pipeline.id) }
end
- after_transition [:created, :pending, :running] => :success do |pipeline|
+ after_transition [:created, :preparing, :pending, :running] => :success do |pipeline|
pipeline.run_after_commit { PipelineSuccessWorker.perform_async(pipeline.id) }
end
@@ -597,6 +601,7 @@ module Ci
retry_optimistic_lock(self) do
case latest_builds_status.to_s
when 'created' then nil
+ when 'preparing' then prepare
when 'pending' then enqueue
when 'running' then run
when 'success' then succeed
diff --git a/app/models/ci/stage.rb b/app/models/ci/stage.rb
index 0389945191e..098f5189517 100644
--- a/app/models/ci/stage.rb
+++ b/app/models/ci/stage.rb
@@ -39,10 +39,14 @@ module Ci
state_machine :status, initial: :created do
event :enqueue do
- transition created: :pending
+ transition [:created, :preparing] => :pending
transition [:success, :failed, :canceled, :skipped] => :running
end
+ event :prepare do
+ transition any - [:preparing] => :preparing
+ end
+
event :run do
transition any - [:running] => :running
end
@@ -76,6 +80,7 @@ module Ci
retry_optimistic_lock(self) do
case statuses.latest.status
when 'created' then nil
+ when 'preparing' then prepare
when 'pending' then enqueue
when 'running' then run
when 'success' then succeed
diff --git a/app/models/clusters/platforms/kubernetes.rb b/app/models/clusters/platforms/kubernetes.rb
index fb2221b601f..7786b48429c 100644
--- a/app/models/clusters/platforms/kubernetes.rb
+++ b/app/models/clusters/platforms/kubernetes.rb
@@ -110,7 +110,7 @@ module Clusters
# short time later
def terminals(environment)
with_reactive_cache do |data|
- pods = filter_by_label(data[:pods], app: environment.slug)
+ pods = filter_by_project_environment(data[:pods], project.full_path_slug, environment.slug)
terminals = pods.flat_map { |pod| terminals_for_pod(api_url, actual_namespace, pod) }.compact
terminals.each { |terminal| add_terminal_auth(terminal, terminal_auth) }
end
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index 7f6562b63e5..5f66a661324 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -66,7 +66,10 @@ class CommitStatus < ActiveRecord::Base
end
event :enqueue do
- transition [:created, :skipped, :manual, :scheduled] => :pending
+ # A CommitStatus will never have prerequisites, but this event
+ # is shared by Ci::Build, which cannot progress unless prerequisites
+ # are satisfied.
+ transition [:created, :preparing, :skipped, :manual, :scheduled] => :pending, unless: :any_unmet_prerequisites?
end
event :run do
@@ -74,26 +77,26 @@ class CommitStatus < ActiveRecord::Base
end
event :skip do
- transition [:created, :pending] => :skipped
+ transition [:created, :preparing, :pending] => :skipped
end
event :drop do
- transition [:created, :pending, :running, :scheduled] => :failed
+ transition [:created, :preparing, :pending, :running, :scheduled] => :failed
end
event :success do
- transition [:created, :pending, :running] => :success
+ transition [:created, :preparing, :pending, :running] => :success
end
event :cancel do
- transition [:created, :pending, :running, :manual, :scheduled] => :canceled
+ transition [:created, :preparing, :pending, :running, :manual, :scheduled] => :canceled
end
- before_transition [:created, :skipped, :manual, :scheduled] => :pending do |commit_status|
+ before_transition [:created, :preparing, :skipped, :manual, :scheduled] => :pending do |commit_status|
commit_status.queued_at = Time.now
end
- before_transition [:created, :pending] => :running do |commit_status|
+ before_transition [:created, :preparing, :pending] => :running do |commit_status|
commit_status.started_at = Time.now
end
@@ -180,6 +183,10 @@ class CommitStatus < ActiveRecord::Base
false
end
+ def any_unmet_prerequisites?
+ false
+ end
+
def auto_canceled?
canceled? && auto_canceled_by_id?
end
diff --git a/app/models/commit_status_enums.rb b/app/models/commit_status_enums.rb
index 152105d9429..45e08fa18fe 100644
--- a/app/models/commit_status_enums.rb
+++ b/app/models/commit_status_enums.rb
@@ -14,7 +14,8 @@ module CommitStatusEnums
runner_unsupported: 6,
stale_schedule: 7,
job_execution_timeout: 8,
- archived_failure: 9
+ archived_failure: 9,
+ unmet_prerequisites: 10
}
end
end
diff --git a/app/models/concerns/cache_markdown_field.rb b/app/models/concerns/cache_markdown_field.rb
index 1a8570b80c3..15d8d58b9b5 100644
--- a/app/models/concerns/cache_markdown_field.rb
+++ b/app/models/concerns/cache_markdown_field.rb
@@ -7,6 +7,7 @@
# cache_markdown_field :foo
# cache_markdown_field :bar
# cache_markdown_field :baz, pipeline: :single_line
+# cache_markdown_field :baz, whitelisted: true
#
# Corresponding foo_html, bar_html and baz_html fields should exist.
module CacheMarkdownField
@@ -37,7 +38,15 @@ module CacheMarkdownField
end
def html_fields
- markdown_fields.map {|field| html_field(field) }
+ markdown_fields.map { |field| html_field(field) }
+ end
+
+ def html_fields_whitelisted
+ markdown_fields.each_with_object([]) do |field, fields|
+ if @data[field].fetch(:whitelisted, false)
+ fields << html_field(field)
+ end
+ end
end
end
@@ -149,13 +158,18 @@ module CacheMarkdownField
alias_method :attributes_before_markdown_cache, :attributes
def attributes
attrs = attributes_before_markdown_cache
+ html_fields = cached_markdown_fields.html_fields
+ whitelisted = cached_markdown_fields.html_fields_whitelisted
+ exclude_fields = html_fields - whitelisted
- attrs.delete('cached_markdown_version')
-
- cached_markdown_fields.html_fields.each do |field|
+ exclude_fields.each do |field|
attrs.delete(field)
end
+ if whitelisted.empty?
+ attrs.delete('cached_markdown_version')
+ end
+
attrs
end
diff --git a/app/models/concerns/has_status.rb b/app/models/concerns/has_status.rb
index 0d2be4c61ab..8882f48c281 100644
--- a/app/models/concerns/has_status.rb
+++ b/app/models/concerns/has_status.rb
@@ -5,14 +5,14 @@ module HasStatus
DEFAULT_STATUS = 'created'.freeze
BLOCKED_STATUS = %w[manual scheduled].freeze
- AVAILABLE_STATUSES = %w[created pending running success failed canceled skipped manual scheduled].freeze
+ AVAILABLE_STATUSES = %w[created preparing pending running success failed canceled skipped manual scheduled].freeze
STARTED_STATUSES = %w[running success failed skipped manual scheduled].freeze
- ACTIVE_STATUSES = %w[pending running].freeze
+ ACTIVE_STATUSES = %w[preparing pending running].freeze
COMPLETED_STATUSES = %w[success failed canceled skipped].freeze
- ORDERED_STATUSES = %w[failed pending running manual scheduled canceled success skipped created].freeze
+ ORDERED_STATUSES = %w[failed preparing pending running manual scheduled canceled success skipped created].freeze
STATUSES_ENUM = { created: 0, pending: 1, running: 2, success: 3,
failed: 4, canceled: 5, skipped: 6, manual: 7,
- scheduled: 8 }.freeze
+ scheduled: 8, preparing: 9 }.freeze
UnknownStatusError = Class.new(StandardError)
@@ -26,6 +26,7 @@ module HasStatus
success = scope_relevant.success.select('count(*)').to_sql
manual = scope_relevant.manual.select('count(*)').to_sql
scheduled = scope_relevant.scheduled.select('count(*)').to_sql
+ preparing = scope_relevant.preparing.select('count(*)').to_sql
pending = scope_relevant.pending.select('count(*)').to_sql
running = scope_relevant.running.select('count(*)').to_sql
skipped = scope_relevant.skipped.select('count(*)').to_sql
@@ -37,12 +38,14 @@ module HasStatus
WHEN (#{builds})=(#{skipped}) THEN 'skipped'
WHEN (#{builds})=(#{success}) THEN 'success'
WHEN (#{builds})=(#{created}) THEN 'created'
+ WHEN (#{builds})=(#{preparing}) THEN 'preparing'
WHEN (#{builds})=(#{success})+(#{skipped}) THEN 'success'
WHEN (#{builds})=(#{success})+(#{skipped})+(#{canceled}) THEN 'canceled'
WHEN (#{builds})=(#{created})+(#{skipped})+(#{pending}) THEN 'pending'
WHEN (#{running})+(#{pending})>0 THEN 'running'
WHEN (#{manual})>0 THEN 'manual'
WHEN (#{scheduled})>0 THEN 'scheduled'
+ WHEN (#{preparing})>0 THEN 'preparing'
WHEN (#{created})>0 THEN 'running'
ELSE 'failed'
END)"
@@ -70,6 +73,7 @@ module HasStatus
state_machine :status, initial: :created do
state :created, value: 'created'
+ state :preparing, value: 'preparing'
state :pending, value: 'pending'
state :running, value: 'running'
state :failed, value: 'failed'
@@ -81,6 +85,7 @@ module HasStatus
end
scope :created, -> { where(status: 'created') }
+ scope :preparing, -> { where(status: 'preparing') }
scope :relevant, -> { where(status: AVAILABLE_STATUSES - ['created']) }
scope :running, -> { where(status: 'running') }
scope :pending, -> { where(status: 'pending') }
@@ -90,14 +95,14 @@ module HasStatus
scope :skipped, -> { where(status: 'skipped') }
scope :manual, -> { where(status: 'manual') }
scope :scheduled, -> { where(status: 'scheduled') }
- scope :alive, -> { where(status: [:created, :pending, :running]) }
+ scope :alive, -> { where(status: [:created, :preparing, :pending, :running]) }
scope :created_or_pending, -> { where(status: [:created, :pending]) }
scope :running_or_pending, -> { where(status: [:running, :pending]) }
scope :finished, -> { where(status: [:success, :failed, :canceled]) }
scope :failed_or_canceled, -> { where(status: [:failed, :canceled]) }
scope :cancelable, -> do
- where(status: [:running, :pending, :created, :scheduled])
+ where(status: [:running, :preparing, :pending, :created, :scheduled])
end
end
diff --git a/app/models/deployment.rb b/app/models/deployment.rb
index 811e623b7f7..428edfd88de 100644
--- a/app/models/deployment.rb
+++ b/app/models/deployment.rb
@@ -78,6 +78,10 @@ class Deployment < ActiveRecord::Base
Commit.truncate_sha(sha)
end
+ def cluster
+ project.deployment_platform(environment: environment.name)&.cluster
+ end
+
def last?
self == environment.last_deployment
end
diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb
index 805092e527a..87755cf3f3d 100644
--- a/app/models/diff_note.rb
+++ b/app/models/diff_note.rb
@@ -88,7 +88,7 @@ class DiffNote < Note
end
def banzai_render_context(field)
- super.merge(suggestions_filter_enabled: supports_suggestion?)
+ super.merge(project: project, suggestions_filter_enabled: supports_suggestion?)
end
private
diff --git a/app/models/project_services/kubernetes_service.rb b/app/models/project_services/kubernetes_service.rb
index 4cf3a7f3d84..f650dbd3726 100644
--- a/app/models/project_services/kubernetes_service.rb
+++ b/app/models/project_services/kubernetes_service.rb
@@ -131,8 +131,8 @@ class KubernetesService < DeploymentService
# short time later
def terminals(environment)
with_reactive_cache do |data|
- pods = filter_by_label(data[:pods], app: environment.slug)
- terminals = pods.flat_map { |pod| terminals_for_pod(api_url, actual_namespace, pod) }
+ pods = filter_by_project_environment(data[:pods], project.full_path_slug, environment.slug)
+ terminals = pods.flat_map { |pod| terminals_for_pod(api_url, actual_namespace, pod) }.compact
terminals.each { |terminal| add_terminal_auth(terminal, terminal_auth) }
end
end
diff --git a/app/policies/identity_provider_policy.rb b/app/policies/identity_provider_policy.rb
new file mode 100644
index 00000000000..d34cdd5bdd4
--- /dev/null
+++ b/app/policies/identity_provider_policy.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class IdentityProviderPolicy < BasePolicy
+ desc "Provider is SAML or CAS3"
+ condition(:protected_provider, scope: :subject, score: 0) { %w(saml cas3).include?(@subject.to_s) }
+
+ rule { anonymous }.prevent_all
+
+ rule { default }.policy do
+ enable :unlink
+ enable :link
+ end
+
+ rule { protected_provider }.prevent(:unlink)
+end
diff --git a/app/presenters/commit_status_presenter.rb b/app/presenters/commit_status_presenter.rb
index 0cd77da6303..28a25c8b7a3 100644
--- a/app/presenters/commit_status_presenter.rb
+++ b/app/presenters/commit_status_presenter.rb
@@ -11,7 +11,8 @@ class CommitStatusPresenter < Gitlab::View::Presenter::Delegated
runner_unsupported: 'Your runner is outdated, please upgrade your runner',
stale_schedule: 'Delayed job could not be executed by some reason, please try again',
job_execution_timeout: 'The script exceeded the maximum execution time set for the job',
- archived_failure: 'The job is archived and cannot be run'
+ archived_failure: 'The job is archived and cannot be run',
+ unmet_prerequisites: 'The job failed to complete prerequisite tasks'
}.freeze
private_constant :CALLOUT_FAILURE_MESSAGES
diff --git a/app/services/ci/prepare_build_service.rb b/app/services/ci/prepare_build_service.rb
new file mode 100644
index 00000000000..32f11438b79
--- /dev/null
+++ b/app/services/ci/prepare_build_service.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Ci
+ class PrepareBuildService
+ attr_reader :build
+
+ def initialize(build)
+ @build = build
+ end
+
+ def execute
+ prerequisites.each(&:complete!)
+
+ unless build.enqueue
+ build.drop!(:unmet_prerequisites)
+ end
+ end
+
+ private
+
+ def prerequisites
+ build.prerequisites
+ end
+ end
+end
diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml
index 6756299cf43..2a1d2c2aeab 100644
--- a/app/views/admin/dashboard/index.html.haml
+++ b/app/views/admin/dashboard/index.html.haml
@@ -22,9 +22,10 @@
%h3.text-center
Users:
= approximate_count_with_delimiters(@counts, User)
- = render_if_exists 'admin/dashboard/users_statistics'
%hr
- = link_to 'New user', new_admin_user_path, class: "btn btn-success"
+ .btn-group.d-flex{ role: 'group' }
+ = link_to 'New user', new_admin_user_path, class: "btn btn-success"
+ = render_if_exists 'admin/dashboard/users_statistics'
.col-sm-4
.info-well.dark-well
.well-segment.well-centered
diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml
index 1ec368f8910..5a27237bf76 100644
--- a/app/views/layouts/nav/_dashboard.html.haml
+++ b/app/views/layouts/nav/_dashboard.html.haml
@@ -3,7 +3,7 @@
%ul.list-unstyled.navbar-sub-nav
- if dashboard_nav_link?(:projects)
= nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: { id: 'nav-projects-dropdown', class: "home dropdown header-projects qa-projects-dropdown", data: { track_label: "projects_dropdown", track_event: "click_dropdown" } }) do
- %button{ type: 'button', data: { toggle: "dropdown" } }
+ %button.btn{ type: 'button', data: { toggle: "dropdown" } }
= _('Projects')
= sprite_icon('angle-down', css_class: 'caret-down')
.dropdown-menu.frequent-items-dropdown-menu
@@ -11,7 +11,7 @@
- if dashboard_nav_link?(:groups)
= nav_link(controller: ['dashboard/groups', 'explore/groups'], html_options: { id: 'nav-groups-dropdown', class: "home dropdown header-groups qa-groups-dropdown", data: { track_label: "groups_dropdown", track_event: "click_dropdown" } }) do
- %button{ type: 'button', data: { toggle: "dropdown" } }
+ %button.btn{ type: 'button', data: { toggle: "dropdown" } }
= _('Groups')
= sprite_icon('angle-down', css_class: 'caret-down')
.dropdown-menu.frequent-items-dropdown-menu
diff --git a/app/views/profiles/_email_settings.html.haml b/app/views/profiles/_email_settings.html.haml
new file mode 100644
index 00000000000..fb4da08e129
--- /dev/null
+++ b/app/views/profiles/_email_settings.html.haml
@@ -0,0 +1,16 @@
+- form = local_assigns.fetch(:form)
+- readonly = @user.read_only_attribute?(:email)
+- email_change_disabled = local_assigns.fetch(:email_change_disabled, nil)
+- read_only_help_text = readonly ? s_("Profiles|Your email address was automatically set based on your %{provider_label} account") % { provider_label: attribute_provider_label(:email) } : user_email_help_text(@user)
+- help_text = email_change_disabled ? s_("Your account uses dedicated credentials for the \"%{group_name}\" group and can only be updated through SSO.") % { group_name: @user.managing_group.name } : read_only_help_text
+
+= form.text_field :email, required: true, class: 'input-lg', value: (@user.email unless @user.temp_oauth_email?), help: help_text.html_safe, readonly: readonly || email_change_disabled
+= form.select :public_email, options_for_select(@user.all_emails, selected: @user.public_email),
+ { help: s_("Profiles|This email will be displayed on your public profile"), include_blank: s_("Profiles|Do not show on profile") },
+ control_class: 'select2 input-lg', disabled: email_change_disabled
+- commit_email_link_url = help_page_path('user/profile/index', anchor: 'commit-email', target: '_blank')
+- commit_email_link_start = '<a href="%{url}">'.html_safe % { url: commit_email_link_url }
+- commit_email_docs_link = s_('Profiles|This email will be used for web based operations, such as edits and merges. %{commit_email_link_start}Learn more%{commit_email_link_end}').html_safe % { commit_email_link_start: commit_email_link_start, commit_email_link_end: '</a>'.html_safe }
+= form.select :commit_email, options_for_select(commit_email_select_options(@user), selected: selected_commit_email(@user)),
+ { help: commit_email_docs_link },
+ control_class: 'select2 input-lg', disabled: email_change_disabled
diff --git a/app/views/profiles/accounts/_providers.html.haml b/app/views/profiles/accounts/_providers.html.haml
new file mode 100644
index 00000000000..068f9cc70f7
--- /dev/null
+++ b/app/views/profiles/accounts/_providers.html.haml
@@ -0,0 +1,21 @@
+%label.label-bold
+ = s_('Profiles|Connected Accounts')
+ %p= s_('Profiles|Click on icon to activate signin with one of the following services')
+ - providers.each do |provider|
+ - unlink_allowed = unlink_provider_allowed?(provider)
+ - link_allowed = link_provider_allowed?(provider)
+ - if unlink_allowed || link_allowed
+ .provider-btn-group
+ .provider-btn-image
+ = provider_image_tag(provider)
+ - if auth_active?(provider)
+ - if unlink_allowed
+ = link_to unlink_profile_account_path(provider: provider), method: :delete, class: 'provider-btn' do
+ = s_('Profiles|Disconnect')
+ - else
+ %a.provider-btn
+ = s_('Profiles|Active')
+ - elsif link_allowed
+ = link_to omniauth_authorize_path(:user, provider), method: :post, class: 'provider-btn not-active' do
+ = s_('Profiles|Connect')
+ = render_if_exists 'profiles/accounts/group_saml_unlink_buttons', group_saml_identities: group_saml_identities
diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml
index ee2c5a13b8a..e6380817c8f 100644
--- a/app/views/profiles/accounts/show.html.haml
+++ b/app/views/profiles/accounts/show.html.haml
@@ -29,24 +29,7 @@
%p
= s_('Profiles|Activate signin with one of the following services')
.col-lg-8
- %label.label-bold
- = s_('Profiles|Connected Accounts')
- %p= s_('Profiles|Click on icon to activate signin with one of the following services')
- - button_based_providers.each do |provider|
- .provider-btn-group
- .provider-btn-image
- = provider_image_tag(provider)
- - if auth_active?(provider)
- - if unlink_allowed?(provider)
- = link_to unlink_profile_account_path(provider: provider), method: :delete, class: 'provider-btn' do
- = s_('Profiles|Disconnect')
- - else
- %a.provider-btn
- = s_('Profiles|Active')
- - else
- = link_to omniauth_authorize_path(:user, provider), method: :post, class: 'provider-btn not-active' do
- = s_('Profiles|Connect')
- = render_if_exists 'profiles/accounts/group_saml_unlink_buttons', group_saml_identities: local_assigns[:group_saml_identities]
+ = render 'providers', providers: button_based_providers, group_saml_identities: local_assigns[:group_saml_identities]
%hr
- if current_user.can_change_username?
.row.prepend-top-default
diff --git a/app/views/profiles/notifications/_email_settings.html.haml b/app/views/profiles/notifications/_email_settings.html.haml
new file mode 100644
index 00000000000..34dcf8f5402
--- /dev/null
+++ b/app/views/profiles/notifications/_email_settings.html.haml
@@ -0,0 +1,6 @@
+- form = local_assigns.fetch(:form)
+.form-group
+ = form.label :notification_email, class: "label-bold"
+ = form.select :notification_email, @user.all_emails, { include_blank: false }, class: "select2", disabled: local_assigns.fetch(:email_change_disabled, nil)
+ .help-block
+ = local_assigns.fetch(:help_text, nil)
diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml
index 712eb2a4573..e616e5546b3 100644
--- a/app/views/profiles/notifications/show.html.haml
+++ b/app/views/profiles/notifications/show.html.haml
@@ -22,9 +22,7 @@
Global notification settings
= form_for @user, url: profile_notifications_path, method: :put, html: { class: 'update-notifications prepend-top-default' } do |f|
- .form-group
- = f.label :notification_email, class: "label-bold"
- = f.select :notification_email, @user.all_emails, { include_blank: false }, class: "select2"
+ = render_if_exists 'profiles/notifications/email_settings', form: f
= label_tag :global_notification_level, "Global notification level", class: "label-bold"
%br
diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml
index 4d3d92d09c0..1fffea08dae 100644
--- a/app/views/profiles/show.html.haml
+++ b/app/views/profiles/show.html.haml
@@ -83,18 +83,7 @@
= f.text_field :name, label: 'Full name', required: true, title: s_("Profiles|Using emojis in names seems fun, but please try to set a status message instead"), wrapper: { class: 'col-md-9 qa-full-name' }, help: s_("Profiles|Enter your name, so people you know can recognize you")
= f.text_field :id, readonly: true, label: 'User ID', wrapper: { class: 'col-md-3' }
- - if @user.read_only_attribute?(:email)
- = f.text_field :email, required: true, class: 'input-lg', readonly: true, help: s_("Profiles|Your email address was automatically set based on your %{provider_label} account") % { provider_label: attribute_provider_label(:email) }
- - else
- = f.text_field :email, required: true, class: 'input-lg', value: (@user.email unless @user.temp_oauth_email?),
- help: user_email_help_text(@user)
- = f.select :public_email, options_for_select(@user.all_emails, selected: @user.public_email),
- { help: s_("Profiles|This email will be displayed on your public profile"), include_blank: s_("Profiles|Do not show on profile") },
- control_class: 'select2 input-lg'
- - commit_email_docs_link = link_to s_('Profiles|Learn more'), help_page_path('user/profile/index', anchor: 'commit-email', target: '_blank')
- = f.select :commit_email, options_for_select(commit_email_select_options(@user), selected: selected_commit_email(@user)),
- { help: s_("Profiles|This email will be used for web based operations, such as edits and merges. %{learn_more}").html_safe % { learn_more: commit_email_docs_link } },
- control_class: 'select2 input-lg'
+ = render_if_exists 'profiles/email_settings', form: f
= f.text_field :skype, class: 'input-md', placeholder: s_("Profiles|username")
= f.text_field :linkedin, class: 'input-md', help: s_("Profiles|Your LinkedIn profile name from linkedin.com/in/profilename")
= f.text_field :twitter, class: 'input-md', placeholder: s_("Profiles|@username")
diff --git a/app/views/projects/_new_project_fields.html.haml b/app/views/projects/_new_project_fields.html.haml
index 276363df7da..5129f11875c 100644
--- a/app/views/projects/_new_project_fields.html.haml
+++ b/app/views/projects/_new_project_fields.html.haml
@@ -12,9 +12,9 @@
.form-group.project-path.col-sm-6
= f.label :namespace_id, class: 'label-bold' do
%span= s_("Project URL")
- .input-group
+ .input-group.flex-nowrap
- if current_user.can_select_namespace?
- .input-group-prepend.has-tooltip{ title: root_url }
+ .input-group-prepend.flex-shrink-0.has-tooltip{ title: root_url }
.input-group-text
= root_url
- namespace_id = namespace_id_from(params)
@@ -23,10 +23,10 @@
display_path: true,
extra_group: namespace_id),
{},
- { class: 'select2 js-select-namespace qa-project-namespace-select', tabindex: 1, data: { track_label: "#{track_label}", track_event: "activate_form_input", track_property: "project_path", track_value: "" }})
+ { class: 'select2 js-select-namespace qa-project-namespace-select block-truncated', tabindex: 1, data: { track_label: "#{track_label}", track_event: "activate_form_input", track_property: "project_path", track_value: "" }})
- else
- .input-group-prepend.static-namespace.has-tooltip{ title: user_url(current_user.username) + '/' }
+ .input-group-prepend.static-namespace.flex-shrink-0.has-tooltip{ title: user_url(current_user.username) + '/' }
.input-group-text.border-0
#{user_url(current_user.username)}/
= f.hidden_field :namespace_id, value: current_user.namespace_id
diff --git a/app/views/projects/protected_branches/shared/_index.html.haml b/app/views/projects/protected_branches/shared/_index.html.haml
index 4997770321e..539b184e5c2 100644
--- a/app/views/projects/protected_branches/shared/_index.html.haml
+++ b/app/views/projects/protected_branches/shared/_index.html.haml
@@ -12,7 +12,7 @@
%p
By default, protected branches are designed to:
%ul
- %li prevent their creation, if not already created, from everybody except users who are allowed to merge
+ %li prevent their creation, if not already created, from everybody except Maintainers
%li prevent pushes from everybody except Maintainers
%li prevent <strong>anyone</strong> from force pushing to the branch
%li prevent <strong>anyone</strong> from deleting the branch
diff --git a/app/views/search/_results.html.haml b/app/views/search/_results.html.haml
index be7a2436d16..2e62039b90a 100644
--- a/app/views/search/_results.html.haml
+++ b/app/views/search/_results.html.haml
@@ -20,7 +20,7 @@
.search-results
- if @scope == 'projects'
.term
- = render 'shared/projects/list', projects: @search_objects
+ = render 'shared/projects/list', projects: @search_objects, pipeline_status: false
- else
= render partial: "search/results/#{@scope.singularize}", collection: @search_objects
diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml
index d2b1be29eb9..90fb067e75d 100644
--- a/app/views/shared/projects/_project.html.haml
+++ b/app/views/shared/projects/_project.html.haml
@@ -10,7 +10,7 @@
- show_last_commit_as_description = false unless local_assigns[:show_last_commit_as_description] == true && can_show_last_commit_in_list?(project)
- css_class = '' unless local_assigns[:css_class]
- css_class += " no-description" if project.description.blank? && !show_last_commit_as_description
-- cache_key = project_list_cache_key(project)
+- cache_key = project_list_cache_key(project, pipeline_status: pipeline_status)
- updated_tooltip = time_ago_with_tooltip(project.last_activity_date)
- css_controls_class = compact_mode ? "" : "flex-lg-row justify-content-lg-between"
- avatar_container_class = project.creator && use_creator_avatar ? '' : 'rect-avatar'
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index b2d88567e0e..6ebd756d3da 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -71,6 +71,7 @@
- pipeline_hooks:build_hooks
- pipeline_hooks:pipeline_hooks
- pipeline_processing:build_finished
+- pipeline_processing:ci_build_prepare
- pipeline_processing:build_queue
- pipeline_processing:build_success
- pipeline_processing:pipeline_process
diff --git a/app/workers/ci/build_prepare_worker.rb b/app/workers/ci/build_prepare_worker.rb
new file mode 100644
index 00000000000..1a35a74ae53
--- /dev/null
+++ b/app/workers/ci/build_prepare_worker.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module Ci
+ class BuildPrepareWorker
+ include ApplicationWorker
+ include PipelineQueue
+
+ queue_namespace :pipeline_processing
+
+ def perform(build_id)
+ Ci::Build.find_by_id(build_id).try do |build|
+ Ci::PrepareBuildService.new(build).execute
+ end
+ end
+ end
+end
diff --git a/app/workers/cluster_configure_worker.rb b/app/workers/cluster_configure_worker.rb
index 63e6cc147be..b984dee5b21 100644
--- a/app/workers/cluster_configure_worker.rb
+++ b/app/workers/cluster_configure_worker.rb
@@ -5,6 +5,8 @@ class ClusterConfigureWorker
include ClusterQueue
def perform(cluster_id)
+ return if Feature.enabled?(:ci_preparing_state, default_enabled: true)
+
Clusters::Cluster.find_by_id(cluster_id).try do |cluster|
Clusters::RefreshService.create_or_update_namespaces_for_cluster(cluster)
end
diff --git a/app/workers/cluster_project_configure_worker.rb b/app/workers/cluster_project_configure_worker.rb
index 497e57c0d0b..d7bea69a01c 100644
--- a/app/workers/cluster_project_configure_worker.rb
+++ b/app/workers/cluster_project_configure_worker.rb
@@ -5,6 +5,8 @@ class ClusterProjectConfigureWorker
include ClusterQueue
def perform(project_id)
+ return if Feature.enabled?(:ci_preparing_state, default_enabled: true)
+
project = Project.find(project_id)
::Clusters::RefreshService.create_or_update_namespaces_for_project(project)
diff --git a/changelogs/unreleased/57115-just-in-time-k8s-resource-creation.yml b/changelogs/unreleased/57115-just-in-time-k8s-resource-creation.yml
new file mode 100644
index 00000000000..2141c75ec72
--- /dev/null
+++ b/changelogs/unreleased/57115-just-in-time-k8s-resource-creation.yml
@@ -0,0 +1,5 @@
+---
+title: Create Kubernetes resources for projects when their deployment jobs run.
+merge_request: 25586
+author:
+type: changed
diff --git a/changelogs/unreleased/58883-fix-fetching-comments.yml b/changelogs/unreleased/58883-fix-fetching-comments.yml
new file mode 100644
index 00000000000..14c0f1687f2
--- /dev/null
+++ b/changelogs/unreleased/58883-fix-fetching-comments.yml
@@ -0,0 +1,5 @@
+---
+title: Fix error shown when loading links to specific comments
+merge_request: 26092
+author:
+type: fixed
diff --git a/changelogs/unreleased/59057-buttons-on-top-from-a-user-profile-page-on-mobile.yml b/changelogs/unreleased/59057-buttons-on-top-from-a-user-profile-page-on-mobile.yml
new file mode 100644
index 00000000000..febbbce2139
--- /dev/null
+++ b/changelogs/unreleased/59057-buttons-on-top-from-a-user-profile-page-on-mobile.yml
@@ -0,0 +1,5 @@
+---
+title: Improve mobile UI on User Profile page
+merge_request: 26240
+author: Takuya Noguchi
+type: other
diff --git a/changelogs/unreleased/59117-inconsistent-hover-behavior-on-navbar-items.yml b/changelogs/unreleased/59117-inconsistent-hover-behavior-on-navbar-items.yml
new file mode 100644
index 00000000000..eb9dcef4a89
--- /dev/null
+++ b/changelogs/unreleased/59117-inconsistent-hover-behavior-on-navbar-items.yml
@@ -0,0 +1,5 @@
+---
+title: Fix hover animation consistency in top navbar items
+merge_request: 26345
+author:
+type: fixed
diff --git a/changelogs/unreleased/59189-long-names-in-project-path-namespace-dropdown-breaks-past-container.yml b/changelogs/unreleased/59189-long-names-in-project-path-namespace-dropdown-breaks-past-container.yml
new file mode 100644
index 00000000000..bed7fcf2651
--- /dev/null
+++ b/changelogs/unreleased/59189-long-names-in-project-path-namespace-dropdown-breaks-past-container.yml
@@ -0,0 +1,5 @@
+---
+title: Prevent namespace dropdown in new project form from exceeding container
+merge_request: 26343
+author:
+type: fixed
diff --git a/changelogs/unreleased/59208-fix-error-500-on-every-page-when-active-broadcast-message-present-after-upgrading-to-11-9-0.yml b/changelogs/unreleased/59208-fix-error-500-on-every-page-when-active-broadcast-message-present-after-upgrading-to-11-9-0.yml
new file mode 100644
index 00000000000..3c9feae5a04
--- /dev/null
+++ b/changelogs/unreleased/59208-fix-error-500-on-every-page-when-active-broadcast-message-present-after-upgrading-to-11-9-0.yml
@@ -0,0 +1,6 @@
+---
+title: Gracefully handles excluded fields from attributes during serialization on
+ JsonCache
+merge_request: 26368
+author:
+type: fixed
diff --git a/changelogs/unreleased/59289-fix-push-to-create-protected-branches.yml b/changelogs/unreleased/59289-fix-push-to-create-protected-branches.yml
new file mode 100644
index 00000000000..76dd63fef7a
--- /dev/null
+++ b/changelogs/unreleased/59289-fix-push-to-create-protected-branches.yml
@@ -0,0 +1,6 @@
+---
+title: Allow users who can push to protected branches to create protected branches
+ via CLI
+merge_request: 26413
+author:
+type: fixed
diff --git a/changelogs/unreleased/59293-android-template-is-missing-gitlab-ci-yml.yml b/changelogs/unreleased/59293-android-template-is-missing-gitlab-ci-yml.yml
new file mode 100644
index 00000000000..ea559e74a2d
--- /dev/null
+++ b/changelogs/unreleased/59293-android-template-is-missing-gitlab-ci-yml.yml
@@ -0,0 +1,5 @@
+---
+title: Add missing .gitlab-ci.yml to Android template
+merge_request: 26415
+author:
+type: fixed
diff --git a/changelogs/unreleased/avoid_es_loading_project_ci_status.yml b/changelogs/unreleased/avoid_es_loading_project_ci_status.yml
new file mode 100644
index 00000000000..514909c730d
--- /dev/null
+++ b/changelogs/unreleased/avoid_es_loading_project_ci_status.yml
@@ -0,0 +1,5 @@
+---
+title: Avoid loading pipeline status in project search
+merge_request: 26342
+author:
+type: performance
diff --git a/changelogs/unreleased/gitaly-version-v1.29.0.yml b/changelogs/unreleased/gitaly-version-v1.29.0.yml
new file mode 100644
index 00000000000..b6ce14c33a2
--- /dev/null
+++ b/changelogs/unreleased/gitaly-version-v1.29.0.yml
@@ -0,0 +1,5 @@
+---
+title: Upgrade to Gitaly v1.29.0
+merge_request: 26406
+author:
+type: changed
diff --git a/changelogs/unreleased/k8s_new_deployment_labels.yml b/changelogs/unreleased/k8s_new_deployment_labels.yml
new file mode 100644
index 00000000000..e9ef3ee0082
--- /dev/null
+++ b/changelogs/unreleased/k8s_new_deployment_labels.yml
@@ -0,0 +1,5 @@
+---
+title: Update deploy boards to additionally select on "app.gitlab.com" annotations
+merge_request: 25623
+author:
+type: changed
diff --git a/changelogs/unreleased/nfriend-update-job-detail-view-sidebar.yml b/changelogs/unreleased/nfriend-update-job-detail-view-sidebar.yml
new file mode 100644
index 00000000000..5364d29710a
--- /dev/null
+++ b/changelogs/unreleased/nfriend-update-job-detail-view-sidebar.yml
@@ -0,0 +1,5 @@
+---
+title: Update job detail sidebar to accommodate post-merge pipeline information
+merge_request: 25777
+author:
+type: added
diff --git a/changelogs/unreleased/nfriend-update-pipeline-list-view.yml b/changelogs/unreleased/nfriend-update-pipeline-list-view.yml
new file mode 100644
index 00000000000..34e43162b5c
--- /dev/null
+++ b/changelogs/unreleased/nfriend-update-pipeline-list-view.yml
@@ -0,0 +1,5 @@
+---
+title: Update pipeline list view to accommodate post-merge pipeline information
+merge_request: 25690
+author:
+type: added
diff --git a/changelogs/unreleased/osw-multi-line-suggestions-parsing.yml b/changelogs/unreleased/osw-multi-line-suggestions-parsing.yml
new file mode 100644
index 00000000000..985b01e9254
--- /dev/null
+++ b/changelogs/unreleased/osw-multi-line-suggestions-parsing.yml
@@ -0,0 +1,5 @@
+---
+title: Prepare multi-line suggestions for rendering in Markdown
+merge_request: 26107
+author:
+type: other
diff --git a/changelogs/unreleased/security-2826-fix-project-serialization-in-quick-actions.yml b/changelogs/unreleased/security-2826-fix-project-serialization-in-quick-actions.yml
new file mode 100644
index 00000000000..272f8a95957
--- /dev/null
+++ b/changelogs/unreleased/security-2826-fix-project-serialization-in-quick-actions.yml
@@ -0,0 +1,5 @@
+---
+title: Remove project serialization in quick actions response
+merge_request:
+author:
+type: security
diff --git a/doc/administration/container_registry.md b/doc/administration/container_registry.md
index a1ac4a2a57c..b21bfafc096 100644
--- a/doc/administration/container_registry.md
+++ b/doc/administration/container_registry.md
@@ -1,6 +1,7 @@
# GitLab Container Registry administration
> **Notes:**
+>
> - [Introduced][ce-4040] in GitLab 8.8.
> - Container Registry manifest `v1` support was added in GitLab 8.9 to support
> Docker versions earlier than 1.10.
diff --git a/doc/administration/high_availability/redis.md b/doc/administration/high_availability/redis.md
index a52bc5c3b02..3daebc4d84b 100644
--- a/doc/administration/high_availability/redis.md
+++ b/doc/administration/high_availability/redis.md
@@ -14,6 +14,7 @@ a hosted cloud solution or you can use the one that comes bundled with
Omnibus GitLab packages.
> **Notes:**
+>
> - Redis requires authentication for High Availability. See
> [Redis Security](https://redis.io/topics/security) documentation for more
> information. We recommend using a combination of a Redis password and tight
@@ -55,6 +56,7 @@ components below.
### High Availability with Sentinel
> **Notes:**
+>
> - Starting with GitLab `8.11`, you can configure a list of Redis Sentinel
> servers that will monitor a group of Redis servers to provide failover support.
> - Starting with GitLab `8.14`, the Omnibus GitLab Enterprise Edition package
@@ -231,6 +233,7 @@ Pick the one that suits your needs.
This is the section where we install and set up the new Redis instances.
> **Notes:**
+>
> - We assume that you have installed GitLab and all HA components from scratch. If you
> already have it installed and running, read how to
> [switch from a single-machine installation to Redis HA](#switching-from-an-existing-single-machine-installation-to-redis-ha).
diff --git a/doc/administration/job_artifacts.md b/doc/administration/job_artifacts.md
index 8522d046a92..e7792106f81 100644
--- a/doc/administration/job_artifacts.md
+++ b/doc/administration/job_artifacts.md
@@ -1,6 +1,7 @@
# Jobs artifacts administration
> **Notes:**
+>
> - Introduced in GitLab 8.2 and GitLab Runner 0.7.0.
> - Starting with GitLab 8.4 and GitLab Runner 1.0, the artifacts archive format changed to `ZIP`.
> - Starting with GitLab 8.17, builds are renamed to jobs.
@@ -86,6 +87,7 @@ _The artifacts are stored by default in
### Using object storage
> **Notes:**
+>
> - [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/1762) in
> [GitLab Premium](https://about.gitlab.com/pricing/) 9.4.
> - Since version 9.5, artifacts are [browsable](../user/project/pipelines/job_artifacts.md#browsing-artifacts),
diff --git a/doc/administration/monitoring/prometheus/index.md b/doc/administration/monitoring/prometheus/index.md
index 20d7ef9bb74..f2ac155a694 100644
--- a/doc/administration/monitoring/prometheus/index.md
+++ b/doc/administration/monitoring/prometheus/index.md
@@ -1,6 +1,7 @@
# Monitoring GitLab with Prometheus
> **Notes:**
+>
> - Prometheus and the various exporters listed in this page are bundled in the
> Omnibus GitLab package. Check each exporter's documentation for the timeline
> they got added. For installations from source you will have to install them
diff --git a/doc/administration/pages/index.md b/doc/administration/pages/index.md
index 279ad018aed..288ce376687 100644
--- a/doc/administration/pages/index.md
+++ b/doc/administration/pages/index.md
@@ -5,6 +5,7 @@ description: 'Learn how to administer GitLab Pages.'
# GitLab Pages administration
> **Notes:**
+>
> - [Introduced][ee-80] in GitLab EE 8.3.
> - Custom CNAMEs with TLS support were [introduced][ee-173] in GitLab EE 8.5.
> - GitLab Pages [were ported][ce-14605] to Community Edition in GitLab 8.17.
diff --git a/doc/administration/repository_storage_types.md b/doc/administration/repository_storage_types.md
index 40f7c5566ac..25c3d564560 100644
--- a/doc/administration/repository_storage_types.md
+++ b/doc/administration/repository_storage_types.md
@@ -86,6 +86,11 @@ by another folder with the next 2 characters. They are both stored in a special
### Hashed object pools
+CAUTION: **Beta:**
+Hashed objects pools are considered beta, and are not ready for production use.
+Follow [gitaly#1548](https://gitlab.com/gitlab-org/gitaly/issues/1548) for
+updates.
+
For deduplication of public forks and their parent repository, objects are pooled
in an object pool. These object pools are a third repository where shared objects
are stored.
diff --git a/doc/administration/uploads.md b/doc/administration/uploads.md
index 8c0c7a36736..708b59a273b 100644
--- a/doc/administration/uploads.md
+++ b/doc/administration/uploads.md
@@ -18,7 +18,7 @@ below.
>**Notes:**
For historical reasons, uploads are stored into a base directory, which by default is `uploads/-/system`. It is strongly discouraged to change this configuration option on an existing GitLab installation.
-_The uploads are stored by default in `/var/opt/gitlab/gitlab-rails/uploads/-/system`._
+_The uploads are stored by default in `/var/opt/gitlab/gitlab-rails/uploads`._
1. To change the storage path for example to `/mnt/storage/uploads`, edit
`/etc/gitlab/gitlab.rb` and add the following line:
diff --git a/doc/api/group_milestones.md b/doc/api/group_milestones.md
index eb974267084..1c2f56581eb 100644
--- a/doc/api/group_milestones.md
+++ b/doc/api/group_milestones.md
@@ -1,6 +1,5 @@
# Group milestones API
-> **Notes:**
> [Introduced][ce-12819] in GitLab 9.5.
## List group milestones
diff --git a/doc/api/project_snippets.md b/doc/api/project_snippets.md
index f02674adfe2..0ccb0517e08 100644
--- a/doc/api/project_snippets.md
+++ b/doc/api/project_snippets.md
@@ -121,7 +121,6 @@ Parameters:
## Get user agent details
-> **Notes:**
> [Introduced][ce-29508] in GitLab 9.4.
Available only for admins.
diff --git a/doc/api/services.md b/doc/api/services.md
index c44f5cc5781..03d0a80aa64 100644
--- a/doc/api/services.md
+++ b/doc/api/services.md
@@ -505,10 +505,9 @@ GET /projects/:id/services/jira
Set JIRA service for a project.
-> **Notes:**
-> - Starting with GitLab 8.14, `api_url`, `issues_url`, `new_issue_url` and
-> `project_url` are replaced by `project_key`, `url`. If you are using an
-> older version, [follow this documentation][old-jira-api].
+> Starting with GitLab 8.14, `api_url`, `issues_url`, `new_issue_url` and
+> `project_url` are replaced by `project_key`, `url`. If you are using an
+> older version, [follow this documentation][old-jira-api].
```
PUT /projects/:id/services/jira
diff --git a/doc/ci/chatops/README.md b/doc/ci/chatops/README.md
index df7fb8a4912..a06fe6961a7 100644
--- a/doc/ci/chatops/README.md
+++ b/doc/ci/chatops/README.md
@@ -2,9 +2,9 @@
> **Notes:**
>
-> * [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/4466) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 10.6. [Moved](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/24780) to [GitLab Core](https://about.gitlab.com/pricing/) in 11.9.
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/4466) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 10.6. [Moved](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/24780) to [GitLab Core](https://about.gitlab.com/pricing/) in 11.9.
>
-> * ChatOps is currently in alpha, with some important features missing like access control.
+> - ChatOps is currently in alpha, with some important features missing like access control.
GitLab ChatOps provides a method to interact with CI/CD jobs through chat services like Slack. Many organizations' discussion, collaboration, and troubleshooting is taking place in chat services these days, and having a method to run CI/CD jobs with output posted back to the channel can significantly augment a team's workflow.
diff --git a/doc/ci/docker/using_docker_build.md b/doc/ci/docker/using_docker_build.md
index 4f9efb57b8d..9266c4511be 100644
--- a/doc/ci/docker/using_docker_build.md
+++ b/doc/ci/docker/using_docker_build.md
@@ -389,6 +389,7 @@ If you're running multiple Runners you will have to modify all configuration fil
## Using the GitLab Container Registry
> **Notes:**
+>
> - This feature requires GitLab 8.8 and GitLab Runner 1.2.
> - Starting from GitLab 8.12, if you have [2FA] enabled in your account, you need
> to pass a [personal access token][pat] instead of your password in order to
diff --git a/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md b/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md
index 90a8f5917f8..908cf85980e 100644
--- a/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md
+++ b/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md
@@ -4,7 +4,7 @@ author_gitlab: blitzgren
level: intermediate
article_type: tutorial
date: 2018-03-07
-last_updated: 2019-03-06
+last_updated: 2019-03-11
---
# DevOps and Game Dev with GitLab CI/CD
@@ -21,7 +21,7 @@ and the basics of game development.
Our [demo game](http://gitlab-game-demo.s3-website-us-east-1.amazonaws.com/) consists of a simple spaceship traveling in space that shoots by clicking the mouse in a given direction.
-Creating a strong CI/CD pipeline at the beginning of developing another game, [Dark Nova](http://darknova.io/about),
+Creating a strong CI/CD pipeline at the beginning of developing another game, [Dark Nova](http://darknova.io/),
was essential for the fast pace the team worked at. This tutorial will build upon my
[previous introductory article](https://ryanhallcs.wordpress.com/2017/03/15/devops-and-game-dev/) and go through the following steps:
@@ -38,8 +38,8 @@ This will also provide
boilerplate code for starting a browser-based game with the following components:
- Written in [Typescript](https://www.typescriptlang.org/) and [PhaserJs](https://phaser.io)
-- Building, running, and testing with [Gulp](http://gulpjs.com/)
-- Unit tests with [Chai](http://chaijs.com/) and [Mocha](https://mochajs.org/)
+- Building, running, and testing with [Gulp](https://gulpjs.com)
+- Unit tests with [Chai](https://www.chaijs.com) and [Mocha](https://mochajs.org/)
- CI/CD with GitLab
- Hosting the codebase on GitLab.com
- Hosting the game on AWS
@@ -318,7 +318,7 @@ allowing us to run our tests by every push.
Our entire `.gitlab-ci.yml` file should now look like this:
```yml
-image: node:6
+image: node:10
build:
stage: build
@@ -413,7 +413,7 @@ use root security credentials. Proper IAM credential management is beyond the sc
article, but AWS will remind you that using root credentials is unadvised and against their
best practices, as they should. Feel free to follow best practices and use a custom IAM user's
credentials, which will be the same two credentials (Key ID and Secret). It's a good idea to
-fully understand [IAM Best Practices in AWS](http://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html). We need to add these credentials to GitLab:
+fully understand [IAM Best Practices in AWS](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html). We need to add these credentials to GitLab:
1. Log into your AWS account and go to the [Security Credentials page](https://console.aws.amazon.com/iam/home#/security_credential)
1. Click the **Access Keys** section and **Create New Access Key**. Create the key and keep the id and secret around, you'll need them later
@@ -455,7 +455,7 @@ Be sure to update the region and S3 URL in that last script command to fit your
Our final configuration file `.gitlab-ci.yml` looks like:
```yml
-image: node:6
+image: node:10
build:
stage: build
@@ -503,7 +503,7 @@ deploy:
## Conclusion
Within the [demo repository](https://gitlab.com/blitzgren/gitlab-game-demo) you can also find a handful of boilerplate code to get
-[Typescript](https://www.typescriptlang.org/), [Mocha](https://mochajs.org/), [Gulp](http://gulpjs.com/) and [Phaser](https://phaser.io) all playing
+[Typescript](https://www.typescriptlang.org/), [Mocha](https://mochajs.org/), [Gulp](https://gulpjs.com/) and [Phaser](https://phaser.io) all playing
together nicely with GitLab CI/CD, which is the result of lessons learned while making [Dark Nova](http://darknova.io/).
Using a combination of free and open source software, we have a full CI/CD pipeline, a game foundation,
and unit tests, all running and deployed at every push to master - with shockingly little code.
@@ -522,6 +522,6 @@ Here are some ideas to further investigate that can speed up or improve your pip
- [Yarn](https://yarnpkg.com) instead of npm
- Set up a custom [Docker](../../../ci/docker/using_docker_images.md#define-image-and-services-from-gitlab-ciyml) image that can preload dependencies and tools (like AWS CLI)
-- Forward a [custom domain](http://docs.aws.amazon.com/AmazonS3/latest/dev/website-hosting-custom-domain-walkthrough.html) to your game's S3 static website
+- Forward a [custom domain](https:/docs.aws.amazon.com/AmazonS3/latest/dev/website-hosting-custom-domain-walkthrough.html) to your game's S3 static website
- Combine jobs if you find it unnecessary for a small project
- Avoid the queues and set up your own [custom GitLab CI/CD runner](https://about.gitlab.com/2016/03/01/gitlab-runner-with-docker/)
diff --git a/doc/development/new_fe_guide/tips.md b/doc/development/new_fe_guide/tips.md
index 881ad1662ae..889a5aab2b7 100644
--- a/doc/development/new_fe_guide/tips.md
+++ b/doc/development/new_fe_guide/tips.md
@@ -7,3 +7,19 @@ To clear production compiled assets created with `yarn webpack-prod` you can run
```
yarn clean
```
+
+## Creating feature flags in development
+
+The process for creating a feature flag is the same as [enabling a feature flag in development](https://docs.gitlab.com/ee/development/feature_flags.html#enabling-a-feature-flag-in-development).
+
+Your feature flag can now be:
+
+- [made available to the frontend](https://docs.gitlab.com/ee/development/feature_flags.html#frontend) via the `gon`
+- queried in [tests](https://docs.gitlab.com/ee/development/feature_flags.html#specs)
+- queried in HAML templates and ruby files via the `Feature.enabled?(:my_shiny_new_feature_flag)` method
+
+### More on feature flags
+
+- [Deleting a feature flag](https://docs.gitlab.com/ee/api/features.html#delete-a-feature)
+- [Manage feature flags](https://docs.gitlab.com/ee/development/feature_flags.html)
+- [Feature flags API](https://docs.gitlab.com/ee/api/features.html)
diff --git a/doc/development/testing_guide/frontend_testing.md b/doc/development/testing_guide/frontend_testing.md
index 3634e648219..71c9637e72c 100644
--- a/doc/development/testing_guide/frontend_testing.md
+++ b/doc/development/testing_guide/frontend_testing.md
@@ -225,14 +225,12 @@ See this [section][vue-test].
### Running frontend tests
-`rake karma` runs the frontend-only (JavaScript) tests.
-It consists of two subtasks:
+For running the frontend tests, you need the following commands:
-- `rake karma:fixtures` (re-)generates fixtures
-- `rake karma:tests` actually executes the tests
+- `rake karma:fixtures` (re-)generates fixtures.
+- `yarn test` executes the tests.
-As long as the fixtures don't change, `rake karma:tests` (or `yarn karma`)
-is sufficient (and saves you some time).
+As long as the fixtures don't change, `yarn test` is sufficient (and saves you some time).
### Live testing and focused testing
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index c78cfc41678..574ba961cb0 100644
--- a/doc/raketasks/backup_restore.md
+++ b/doc/raketasks/backup_restore.md
@@ -409,6 +409,8 @@ an access key from the Google console first:
1. Select "Interoperability" and create an access key
1. Make note of the "Access Key" and "Secret" and replace them in the
configurations below
+1. In the buckets advanced settings ensure the Access Control option "Set object-level
+ and bucket-level permissions" is selected
1. Make sure you already have a bucket created
For Omnibus GitLab packages:
diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md
index 43aa4163ab1..fb3f9711711 100644
--- a/doc/topics/autodevops/index.md
+++ b/doc/topics/autodevops/index.md
@@ -1022,6 +1022,10 @@ planned for a subsequent release.
buildpack](#custom-buildpacks).
- Auto Test may fail because of a mismatch between testing frameworks. In this
case, you may need to customize your `.gitlab-ci.yml` with your test commands.
+- Auto Deploy may fail if it is unable to create a Kubernetes namespace and
+ service account for your project. See the
+ [troubleshooting failed deployments](../../user/project/clusters/index.md#troubleshooting-failed-deployment-jobs)
+ section to debug why these resources were not created.
### Disable the banner instance wide
diff --git a/doc/user/discussions/index.md b/doc/user/discussions/index.md
index a15e1a59921..cf0dc51ea23 100644
--- a/doc/user/discussions/index.md
+++ b/doc/user/discussions/index.md
@@ -19,13 +19,14 @@ comment at any time, and anyone with [Maintainer access level][permissions] or
higher can also edit a comment made by someone else.
You can also reply to a comment notification email to reply to the comment if
-[Reply by email] is configured for your GitLab instance. Replying to a standard comment
+[Reply by email] is configured for your GitLab instance. Replying to a standard comment
creates another standard comment. Replying to a discussion comment creates a reply in the
discussion thread. Email replies support [Markdown] and [quick actions], just as if you replied from the web.
## Resolvable comments and discussions
> **Notes:**
+>
> - The main feature was [introduced][ce-5022] in GitLab 8.11.
> - Resolvable discussions can be added only to merge request diffs.
@@ -357,7 +358,7 @@ Clicking on the **Reply to comment** button will bring the reply area into focus
![Reply to comment feature](img/reply_to_comment.gif)
-Relying to a non-discussion comment will convert the non-discussion comment to a
+Relying to a non-discussion comment will convert the non-discussion comment to a
threaded discussion once the reply is submitted. This conversion is considered an edit
to the original comment, so a note about when it was last edited will appear underneath it.
diff --git a/doc/user/group/index.md b/doc/user/group/index.md
index 1fe8017adbc..3bcfd30079d 100644
--- a/doc/user/group/index.md
+++ b/doc/user/group/index.md
@@ -63,9 +63,8 @@ together in a single list view.
## Create a new group
-> **Notes:**
-> - For a list of words that are not allowed to be used as group names see the
-> [reserved names](../reserved_names.md).
+> For a list of words that are not allowed to be used as group names see the
+> [reserved names](../reserved_names.md).
You can create a group in GitLab from:
diff --git a/doc/user/profile/account/two_factor_authentication.md b/doc/user/profile/account/two_factor_authentication.md
index 8e5fe4b0fb9..b74bd81d467 100644
--- a/doc/user/profile/account/two_factor_authentication.md
+++ b/doc/user/profile/account/two_factor_authentication.md
@@ -59,6 +59,7 @@ of recovery codes.
### Enable 2FA via U2F device
> **Notes:**
+>
> - GitLab officially only supports [Yubikey] U2F devices.
> - Support for U2F devices was added in GitLab 8.8.
diff --git a/doc/user/project/bulk_editing.md b/doc/user/project/bulk_editing.md
index fead99c5e88..d0c7daf4692 100644
--- a/doc/user/project/bulk_editing.md
+++ b/doc/user/project/bulk_editing.md
@@ -1,6 +1,7 @@
# Bulk editing issues and merge requests
> **Notes:**
+>
> - A permission level of `Reporter` or higher is required in order to manage
> issues.
> - A permission level of `Developer` or higher is required in order to manage
diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md
index b05e8777b40..44b9e5cccb5 100644
--- a/doc/user/project/clusters/index.md
+++ b/doc/user/project/clusters/index.md
@@ -556,26 +556,27 @@ NOTE: **NOTE:**
Prior to GitLab 11.5, `KUBE_TOKEN` was the Kubernetes token of the main
service account of the cluster integration.
-### Troubleshooting missing `KUBECONFIG` or `KUBE_TOKEN`
+### Troubleshooting failed deployment jobs
-GitLab will create a new service account specifically for your CI builds. The
-new service account is created when the cluster is added to the project.
-Sometimes there may be errors that cause the service account creation to fail.
+GitLab will create a namespace and service account specifically for your
+deployment jobs. These resources are created just before the deployment
+job starts. Sometimes there may be errors that cause their creation to fail.
-In such instances, your build will not be passed the `KUBECONFIG` or
-`KUBE_TOKEN` variables and, if you are using Auto DevOps, your Auto DevOps
-pipelines will no longer trigger a `production` deploy build. You will need to
-check the [logs](../../../administration/logs.md) to debug why the service
-account creation failed.
+In such instances, your job will fail with the message:
+
+```The job failed to complete prerequisite tasks```
+
+You will need to check the [logs](../../../administration/logs.md) to debug
+why the namespace and service account creation failed.
A common reason for failure is that the token you gave GitLab did not have
[`cluster-admin`](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#user-facing-roles)
privileges as GitLab expects.
-Another common problem for why these variables are not being passed to your
-builds is that they must have a matching
+Another common problem is caused by a missing `KUBECONFIG` or `KUBE_TOKEN`.
+To be passed to your job, it must have a matching
[`environment:name`](../../../ci/environments.md#defining-environments). If
-your build has no `environment:name` set, it will not be passed the Kubernetes
+your job has no `environment:name` set, it will not be passed the Kubernetes
credentials.
## Monitoring your Kubernetes cluster **[ULTIMATE]**
diff --git a/doc/user/project/container_registry.md b/doc/user/project/container_registry.md
index dec6eac2508..83b268db967 100644
--- a/doc/user/project/container_registry.md
+++ b/doc/user/project/container_registry.md
@@ -1,7 +1,8 @@
# GitLab Container Registry
> **Notes:**
-> [Introduced][ce-4040] in GitLab 8.8.
+>
+> - [Introduced][ce-4040] in GitLab 8.8.
> - Docker Registry manifest `v1` support was added in GitLab 8.9 to support Docker
> versions earlier than 1.10.
> - This document is about the user guide. To learn how to enable GitLab Container
@@ -10,7 +11,7 @@
> - Starting from GitLab 8.12, if you have 2FA enabled in your account, you need
> to pass a [personal access token][pat] instead of your password in order to
> login to GitLab's Container Registry.
-> - Multiple level image names support was added in GitLab 9.1
+> - Multiple level image names support was added in GitLab 9.1.
With the Docker Container Registry integrated into GitLab, every project can
have its own space to store its Docker images.
@@ -41,6 +42,7 @@ to enable it.
## Build and push images
> **Notes:**
+>
> - Moving or renaming existing container registry repositories is not supported
> once you have pushed images because the images are signed, and the
> signature includes the repository name.
diff --git a/doc/user/project/integrations/jira.md b/doc/user/project/integrations/jira.md
index 754711f5919..a90167b9767 100644
--- a/doc/user/project/integrations/jira.md
+++ b/doc/user/project/integrations/jira.md
@@ -56,6 +56,7 @@ When connecting to **JIRA Cloud**, which supports authentication via API token,
### Configuring GitLab
> **Notes:**
+>
> - The currently supported Jira versions are `v6.x` and `v7.x.`. GitLab 7.8 or
> higher is required.
> - GitLab 8.14 introduced a new way to integrate with Jira which greatly simplified
@@ -142,6 +143,7 @@ the same goal:
where `PROJECT-1` is the issue ID of the Jira project.
> **Notes:**
+>
> - Only commits and merges into the project's default branch (usually **master**) will
> close an issue in Jira. You can change your projects default branch under
> [project settings](img/jira_project_settings.png).
diff --git a/doc/user/project/merge_requests/versions.md b/doc/user/project/merge_requests/versions.md
index 90500fd9c21..70bd1e60594 100644
--- a/doc/user/project/merge_requests/versions.md
+++ b/doc/user/project/merge_requests/versions.md
@@ -1,6 +1,7 @@
# Merge requests versions
> **Notes:**
+>
> - [Introduced][ce-5467] in GitLab 8.12.
> - Comments are disabled while viewing outdated merge versions or comparing to
> versions other than base.
diff --git a/doc/user/project/new_ci_build_permissions_model.md b/doc/user/project/new_ci_build_permissions_model.md
index d41b65f7985..6c3fa5eb463 100644
--- a/doc/user/project/new_ci_build_permissions_model.md
+++ b/doc/user/project/new_ci_build_permissions_model.md
@@ -205,6 +205,7 @@ With the update permission model we also extended the support for accessing
Container Registries for private projects.
> **Notes:**
+>
> - GitLab Runner versions prior to 1.8 don't incorporate the introduced changes
> for permissions. This makes the `image:` directive to not work with private
> projects automatically and it needs to be configured manually on Runner's host
diff --git a/doc/user/project/pages/introduction.md b/doc/user/project/pages/introduction.md
index f67ef1e6a46..ccb0300e23e 100644
--- a/doc/user/project/pages/introduction.md
+++ b/doc/user/project/pages/introduction.md
@@ -1,6 +1,7 @@
# Exploring GitLab Pages
> **Notes:**
+>
> - This feature was [introduced][ee-80] in GitLab EE 8.3.
> - Custom CNAMEs with TLS support were [introduced][ee-173] in GitLab EE 8.5.
> - GitLab Pages [was ported][ce-14605] to Community Edition in GitLab 8.17.
diff --git a/doc/user/project/pipelines/job_artifacts.md b/doc/user/project/pipelines/job_artifacts.md
index 5271c76fc24..a3f40c20192 100644
--- a/doc/user/project/pipelines/job_artifacts.md
+++ b/doc/user/project/pipelines/job_artifacts.md
@@ -1,6 +1,7 @@
# Introduction to job artifacts
> **Notes:**
+>
> - Since GitLab 8.2 and GitLab Runner 0.7.0, job artifacts that are created by
> GitLab Runner are uploaded to GitLab and are downloadable as a single archive
> (`tar.gz`) using the GitLab UI.
@@ -152,7 +153,7 @@ For example:
https://gitlab.com/gitlab-org/gitlab-ce/-/jobs/artifacts/master/browse?job=coverage
```
-There is also a URL to specific files, including html files that
+There is also a URL to specific files, including html files that
are shown in [GitLab Pages](../../../administration/pages/index.md):
```
@@ -191,9 +192,9 @@ artifacts and the job's trace.
1. Click the trash icon at the top right of the job's trace.
1. Confirm the deletion.
-## Retrieve artifacts of private projects when using GitLab CI
+## Retrieve artifacts of private projects when using GitLab CI
In order to retrieve a job artifact of a different project, you might need to use a private token in order to [authenticate and download](../../../api/jobs.md#get-job-artifacts) the artifacts.
[expiry date]: ../../../ci/yaml/README.md#artifactsexpire_in
-[ce-14399]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14399 \ No newline at end of file
+[ce-14399]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14399
diff --git a/doc/user/project/pipelines/schedules.md b/doc/user/project/pipelines/schedules.md
index 58a0fbc97cd..07ce4f3f5da 100644
--- a/doc/user/project/pipelines/schedules.md
+++ b/doc/user/project/pipelines/schedules.md
@@ -1,6 +1,7 @@
# Pipeline schedules
> **Notes**:
+>
> - This feature was introduced in 9.1 as [Trigger Schedule][ce-10533].
> - In 9.2, the feature was [renamed to Pipeline Schedule][ce-10853].
> - Cron notation is parsed by [Fugit](https://github.com/floraison/fugit).
diff --git a/doc/user/project/protected_branches.md b/doc/user/project/protected_branches.md
index 480cc921d76..2060b5dd4a2 100644
--- a/doc/user/project/protected_branches.md
+++ b/doc/user/project/protected_branches.md
@@ -10,7 +10,7 @@ created protected branches.
By default, a protected branch does four simple things:
- it prevents its creation, if not already created, from everybody except users
- who are allowed to merge
+ with Maintainer permission
- it prevents pushes from everybody except users with Maintainer permission
- it prevents **anyone** from force pushing to the branch
- it prevents **anyone** from deleting the branch
diff --git a/lib/banzai/filter/output_safety.rb b/lib/banzai/filter/output_safety.rb
new file mode 100644
index 00000000000..d4ebce5d9c9
--- /dev/null
+++ b/lib/banzai/filter/output_safety.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module Banzai
+ module Filter
+ module OutputSafety
+ def escape_once(html)
+ html.html_safe? ? html : ERB::Util.html_escape_once(html)
+ end
+ end
+ end
+end
diff --git a/lib/banzai/filter/reference_filter.rb b/lib/banzai/filter/reference_filter.rb
index 42f9b3a689c..b3ce9200b49 100644
--- a/lib/banzai/filter/reference_filter.rb
+++ b/lib/banzai/filter/reference_filter.rb
@@ -12,6 +12,7 @@ module Banzai
# :only_path - Generate path-only links.
class ReferenceFilter < HTML::Pipeline::Filter
include RequestStoreReferenceCache
+ include OutputSafety
class << self
attr_accessor :reference_type
@@ -43,10 +44,6 @@ module Banzai
end.join(' ')
end
- def escape_once(html)
- html.html_safe? ? html : ERB::Util.html_escape_once(html)
- end
-
def ignore_ancestor_query
@ignore_ancestor_query ||= begin
parents = %w(pre code a style)
diff --git a/lib/banzai/filter/suggestion_filter.rb b/lib/banzai/filter/suggestion_filter.rb
index 9950db373d8..848aca10a20 100644
--- a/lib/banzai/filter/suggestion_filter.rb
+++ b/lib/banzai/filter/suggestion_filter.rb
@@ -6,11 +6,15 @@ module Banzai
class SuggestionFilter < HTML::Pipeline::Filter
# Class used for tagging elements that should be rendered
TAG_CLASS = 'js-render-suggestion'.freeze
+ SUGGESTION_REGEX = Gitlab::Diff::SuggestionsParser::SUGGESTION_CONTEXT
def call
return doc unless suggestions_filter_enabled?
doc.search('pre.suggestion > code').each do |node|
+ # TODO: Remove once multi-line suggestions FF get removed (#59178).
+ remove_multi_line_params(node.parent)
+
node.add_class(TAG_CLASS)
end
@@ -20,6 +24,20 @@ module Banzai
def suggestions_filter_enabled?
context[:suggestions_filter_enabled]
end
+
+ private
+
+ def project
+ context[:project]
+ end
+
+ def remove_multi_line_params(node)
+ return if Feature.enabled?(:multi_line_suggestions, project)
+
+ if node[SyntaxHighlightFilter::LANG_PARAMS_ATTR]&.match?(SUGGESTION_REGEX)
+ node.remove_attribute(SyntaxHighlightFilter::LANG_PARAMS_ATTR)
+ end
+ end
end
end
end
diff --git a/lib/banzai/filter/syntax_highlight_filter.rb b/lib/banzai/filter/syntax_highlight_filter.rb
index 9ffde52b5f2..fe56f9a1e33 100644
--- a/lib/banzai/filter/syntax_highlight_filter.rb
+++ b/lib/banzai/filter/syntax_highlight_filter.rb
@@ -8,6 +8,11 @@ module Banzai
# HTML Filter to highlight fenced code blocks
#
class SyntaxHighlightFilter < HTML::Pipeline::Filter
+ include OutputSafety
+
+ PARAMS_DELIMITER = ':'.freeze
+ LANG_PARAMS_ATTR = 'data-lang-params'.freeze
+
def call
doc.search('pre > code').each do |node|
highlight_node(node)
@@ -18,7 +23,7 @@ module Banzai
def highlight_node(node)
css_classes = +'code highlight js-syntax-highlight'
- lang = node.attr('lang')
+ lang, lang_params = parse_lang_params(node.attr('lang'))
retried = false
if use_rouge?(lang)
@@ -46,7 +51,10 @@ module Banzai
retry
end
- highlighted = %(<pre class="#{css_classes}" lang="#{language}" v-pre="true"><code>#{code}</code></pre>)
+ highlighted = %(<pre class="#{css_classes}"
+ lang="#{language}"
+ #{lang_params}
+ v-pre="true"><code>#{code}</code></pre>)
# Extracted to a method to measure it
replace_parent_pre_element(node, highlighted)
@@ -54,6 +62,15 @@ module Banzai
private
+ def parse_lang_params(language)
+ return unless language
+
+ lang, params = language.split(PARAMS_DELIMITER, 2)
+ formatted_params = %(#{LANG_PARAMS_ATTR}="#{escape_once(params)}") if params
+
+ [lang, formatted_params]
+ end
+
# Separate method so it can be instrumented.
def lex(lexer, code)
lexer.lex(code)
diff --git a/lib/gitlab/auth/ldap/person.rb b/lib/gitlab/auth/ldap/person.rb
index 13d67e0f871..c1517222956 100644
--- a/lib/gitlab/auth/ldap/person.rb
+++ b/lib/gitlab/auth/ldap/person.rb
@@ -69,7 +69,7 @@ module Gitlab
end
def name
- attribute_value(:name).first
+ attribute_value(:name)&.first
end
def uid
diff --git a/lib/gitlab/badge/pipeline/template.rb b/lib/gitlab/badge/pipeline/template.rb
index 64c3dfcd10b..2c5f9654496 100644
--- a/lib/gitlab/badge/pipeline/template.rb
+++ b/lib/gitlab/badge/pipeline/template.rb
@@ -15,6 +15,7 @@ module Gitlab
failed: '#e05d44',
running: '#dfb317',
pending: '#dfb317',
+ preparing: '#dfb317',
canceled: '#9f9f9f',
skipped: '#9f9f9f',
unknown: '#9f9f9f'
diff --git a/lib/gitlab/checks/branch_check.rb b/lib/gitlab/checks/branch_check.rb
index ad926739752..1dbd564fb6f 100644
--- a/lib/gitlab/checks/branch_check.rb
+++ b/lib/gitlab/checks/branch_check.rb
@@ -59,6 +59,8 @@ module Gitlab
def protected_branch_creation_checks
logger.log_timed(LOG_MESSAGES[:protected_branch_creation_checks]) do
+ break if user_access.can_push_to_branch?(branch_name)
+
unless user_access.can_merge_to_branch?(branch_name)
raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:create_protected_branch]
end
diff --git a/lib/gitlab/ci/build/prerequisite/base.rb b/lib/gitlab/ci/build/prerequisite/base.rb
new file mode 100644
index 00000000000..156aa22d95b
--- /dev/null
+++ b/lib/gitlab/ci/build/prerequisite/base.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module Build
+ module Prerequisite
+ class Base
+ include Utils::StrongMemoize
+
+ attr_reader :build
+
+ def initialize(build)
+ @build = build
+ end
+
+ def unmet?
+ raise NotImplementedError
+ end
+
+ def complete!
+ raise NotImplementedError
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/build/prerequisite/factory.rb b/lib/gitlab/ci/build/prerequisite/factory.rb
new file mode 100644
index 00000000000..60cdf7af418
--- /dev/null
+++ b/lib/gitlab/ci/build/prerequisite/factory.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module Build
+ module Prerequisite
+ class Factory
+ attr_reader :build
+
+ def self.prerequisites
+ [KubernetesNamespace]
+ end
+
+ def initialize(build)
+ @build = build
+ end
+
+ def unmet
+ build_prerequisites.select(&:unmet?)
+ end
+
+ private
+
+ def build_prerequisites
+ self.class.prerequisites.map do |prerequisite|
+ prerequisite.new(build)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/build/prerequisite/kubernetes_namespace.rb b/lib/gitlab/ci/build/prerequisite/kubernetes_namespace.rb
new file mode 100644
index 00000000000..41135ae62bb
--- /dev/null
+++ b/lib/gitlab/ci/build/prerequisite/kubernetes_namespace.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module Build
+ module Prerequisite
+ class KubernetesNamespace < Base
+ def unmet?
+ deployment_cluster.present? && kubernetes_namespace.new_record?
+ end
+
+ def complete!
+ return unless unmet?
+
+ create_or_update_namespace
+ end
+
+ private
+
+ def deployment_cluster
+ build.deployment&.cluster
+ end
+
+ def kubernetes_namespace
+ strong_memoize(:kubernetes_namespace) do
+ deployment_cluster.find_or_initialize_kubernetes_namespace_for_project(build.project)
+ end
+ end
+
+ def create_or_update_namespace
+ Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService.new(
+ cluster: deployment_cluster,
+ kubernetes_namespace: kubernetes_namespace
+ ).execute
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/status/build/factory.rb b/lib/gitlab/ci/status/build/factory.rb
index 6e4bfe23f2b..f7d0715e617 100644
--- a/lib/gitlab/ci/status/build/factory.rb
+++ b/lib/gitlab/ci/status/build/factory.rb
@@ -11,6 +11,7 @@ module Gitlab
Status::Build::Manual,
Status::Build::Canceled,
Status::Build::Created,
+ Status::Build::Preparing,
Status::Build::Pending,
Status::Build::Skipped],
[Status::Build::Cancelable,
diff --git a/lib/gitlab/ci/status/build/failed.rb b/lib/gitlab/ci/status/build/failed.rb
index d40454df737..76dfe7b7639 100644
--- a/lib/gitlab/ci/status/build/failed.rb
+++ b/lib/gitlab/ci/status/build/failed.rb
@@ -15,7 +15,8 @@ module Gitlab
runner_unsupported: 'unsupported runner',
stale_schedule: 'stale schedule',
job_execution_timeout: 'job execution timeout',
- archived_failure: 'archived failure'
+ archived_failure: 'archived failure',
+ unmet_prerequisites: 'unmet prerequisites'
}.freeze
private_constant :REASONS
diff --git a/lib/gitlab/ci/status/build/preparing.rb b/lib/gitlab/ci/status/build/preparing.rb
new file mode 100644
index 00000000000..1fddcb05f79
--- /dev/null
+++ b/lib/gitlab/ci/status/build/preparing.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module Status
+ module Build
+ class Preparing < Status::Extended
+ ##
+ # TODO: image is shared with 'pending'
+ # until we get a dedicated one
+ #
+ def illustration
+ {
+ image: 'illustrations/job_not_triggered.svg',
+ size: 'svg-306',
+ title: _('This job is preparing to start'),
+ content: _('This job is performing tasks that must complete before it can start')
+ }
+ end
+
+ def self.matches?(build, _)
+ build.preparing?
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/status/preparing.rb b/lib/gitlab/ci/status/preparing.rb
new file mode 100644
index 00000000000..62985d0a9f9
--- /dev/null
+++ b/lib/gitlab/ci/status/preparing.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module Status
+ class Preparing < Status::Core
+ def text
+ s_('CiStatusText|preparing')
+ end
+
+ def label
+ s_('CiStatusLabel|preparing')
+ end
+
+ ##
+ # TODO: shared with 'created'
+ # until we get one for 'preparing'
+ #
+ def icon
+ 'status_created'
+ end
+
+ ##
+ # TODO: shared with 'created'
+ # until we get one for 'preparing'
+ #
+ def favicon
+ 'favicon_status_created'
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/templates/Android-Fastlane.gitlab-ci.yml b/lib/gitlab/ci/templates/Android-Fastlane.gitlab-ci.yml
index 9c534b2b8e7..8e767b22360 100644
--- a/lib/gitlab/ci/templates/Android-Fastlane.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Android-Fastlane.gitlab-ci.yml
@@ -118,4 +118,3 @@ promoteProduction:
- master
script:
- bundle exec fastlane promote_beta_to_production
- \ No newline at end of file
diff --git a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
index cbe466a1c37..7ec786b6d5d 100644
--- a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
@@ -697,6 +697,8 @@ rollout 100%:
helm upgrade --install \
--wait \
--set service.enabled="$service_enabled" \
+ --set gitlab.app="$CI_PROJECT_PATH_SLUG" \
+ --set gitlab.env="$CI_ENVIRONMENT_SLUG" \
--set releaseOverride="$CI_ENVIRONMENT_SLUG" \
--set image.repository="$CI_APPLICATION_REPOSITORY" \
--set image.tag="$CI_APPLICATION_TAG" \
@@ -734,6 +736,8 @@ rollout 100%:
helm upgrade --install \
--wait \
--set service.enabled="$service_enabled" \
+ --set gitlab.app="$CI_PROJECT_PATH_SLUG" \
+ --set gitlab.env="$CI_ENVIRONMENT_SLUG" \
--set releaseOverride="$CI_ENVIRONMENT_SLUG" \
--set image.repository="$CI_APPLICATION_REPOSITORY" \
--set image.tag="$CI_APPLICATION_TAG" \
diff --git a/lib/gitlab/ci/templates/Bash.gitlab-ci.yml b/lib/gitlab/ci/templates/Bash.gitlab-ci.yml
index 2d218b2e164..368069844ea 100644
--- a/lib/gitlab/ci/templates/Bash.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Bash.gitlab-ci.yml
@@ -7,28 +7,28 @@ before_script:
- echo "Before script section"
- echo "For example you might run an update here or install a build dependency"
- echo "Or perhaps you might print out some debugging details"
-
+
after_script:
- echo "After script section"
- echo "For example you might do some cleanup here"
-
+
build1:
stage: build
script:
- echo "Do your build here"
-
+
test1:
stage: test
- script:
+ script:
- echo "Do a test here"
- echo "For example run a test suite"
-
+
test2:
stage: test
- script:
+ script:
- echo "Do another parallel test here"
- echo "For example run a lint test"
-
+
deploy1:
stage: deploy
script:
diff --git a/lib/gitlab/ci/templates/C++.gitlab-ci.yml b/lib/gitlab/ci/templates/C++.gitlab-ci.yml
index c83c49d8c95..9a8fa9d7091 100644
--- a/lib/gitlab/ci/templates/C++.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/C++.gitlab-ci.yml
@@ -7,9 +7,9 @@ build:
stage: build
# instead of calling g++ directly you can also use some build toolkit like make
# install the necessary build tools when needed
- # before_script:
- # - apt update && apt -y install make autoconf
- script:
+ # before_script:
+ # - apt update && apt -y install make autoconf
+ script:
- g++ helloworld.cpp -o mybinary
artifacts:
paths:
diff --git a/lib/gitlab/ci/templates/Chef.gitlab-ci.yml b/lib/gitlab/ci/templates/Chef.gitlab-ci.yml
index 4d5b6484d6e..33507aa58e4 100644
--- a/lib/gitlab/ci/templates/Chef.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Chef.gitlab-ci.yml
@@ -41,7 +41,7 @@ chefspec:
# - apt-get -y install rsync
# script:
# - kitchen verify default-centos-6 --destroy=always
-#
+#
#verify-centos-7:
# stage: functional
# before_script:
diff --git a/lib/gitlab/ci/templates/Clojure.gitlab-ci.yml b/lib/gitlab/ci/templates/Clojure.gitlab-ci.yml
index f066285b1ad..0610cb9ccc0 100644
--- a/lib/gitlab/ci/templates/Clojure.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Clojure.gitlab-ci.yml
@@ -5,9 +5,9 @@ image: clojure:lein-2.7.0
# Make sure you configure the connection as well
before_script:
- # If you need to install any external applications, like a
+ # If you need to install any external applications, like a
# postgres client, you may want to uncomment the line below:
- #
+ #
#- apt-get update -y
#
# Retrieve project dependencies
@@ -17,6 +17,6 @@ before_script:
test:
script:
- # If you need to run any migrations or configure the database, this
- # would be the point to do it.
+ # If you need to run any migrations or configure the database, this
+ # would be the point to do it.
- lein test
diff --git a/lib/gitlab/ci/templates/Code-Quality.gitlab-ci.yml b/lib/gitlab/ci/templates/Code-Quality.gitlab-ci.yml
new file mode 100644
index 00000000000..10b25af904f
--- /dev/null
+++ b/lib/gitlab/ci/templates/Code-Quality.gitlab-ci.yml
@@ -0,0 +1,17 @@
+code_quality:
+ image: docker:stable
+ allow_failure: true
+ services:
+ - docker:stable-dind
+ variables:
+ DOCKER_DRIVER: overlay2
+ script:
+ - docker run
+ --env SOURCE_CODE="$PWD"
+ --volume "$PWD":/code
+ --volume /var/run/docker.sock:/var/run/docker.sock
+ "registry.gitlab.com/gitlab-org/security-products/codequality:11-8-stable" /code
+ artifacts:
+ reports:
+ codequality: gl-code-quality-report.json
+ expire_in: 1 week
diff --git a/lib/gitlab/ci/templates/Django.gitlab-ci.yml b/lib/gitlab/ci/templates/Django.gitlab-ci.yml
index 57afcbbe8b5..1d8be6f017e 100644
--- a/lib/gitlab/ci/templates/Django.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Django.gitlab-ci.yml
@@ -21,7 +21,7 @@ cache:
# This is a basic example for a gem or script which doesn't use
# services such as redis or postgres
before_script:
- - python -V # Print out python version for debugging
+ - python -V # Print out python version for debugging
# Uncomment next line if your Django app needs a JS runtime:
# - apt-get update -q && apt-get install nodejs -yqq
- pip install -r requirements.txt
diff --git a/lib/gitlab/ci/templates/Gradle.gitlab-ci.yml b/lib/gitlab/ci/templates/Gradle.gitlab-ci.yml
index 48d98dddfad..cbf4d58bdad 100644
--- a/lib/gitlab/ci/templates/Gradle.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Gradle.gitlab-ci.yml
@@ -23,7 +23,6 @@ build:
- build
- .gradle
-
test:
stage: test
script: gradle check
@@ -33,4 +32,3 @@ test:
paths:
- build
- .gradle
-
diff --git a/lib/gitlab/ci/templates/Grails.gitlab-ci.yml b/lib/gitlab/ci/templates/Grails.gitlab-ci.yml
index 7fc698d50cf..dbc868238f8 100644
--- a/lib/gitlab/ci/templates/Grails.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Grails.gitlab-ci.yml
@@ -13,7 +13,7 @@ image: java:8
variables:
GRAILS_VERSION: "3.1.9"
GRADLE_VERSION: "2.13"
-
+
# We use SDKMan as tool for managing versions
before_script:
- apt-get update -qq && apt-get install -y -qq unzip
@@ -23,10 +23,10 @@ before_script:
- sdk install gradle $GRADLE_VERSION < /dev/null
- sdk use gradle $GRADLE_VERSION
# As it's not a good idea to version gradle.properties feel free to add your
-# environments variable here
+# environments variable here
- echo grailsVersion=$GRAILS_VERSION > gradle.properties
- echo gradleWrapperVersion=2.14 >> gradle.properties
-# refresh dependencies from your project
+# refresh dependencies from your project
- ./gradlew --refresh-dependencies
# Be aware that if you are using Angular profile,
# Bower cannot be run as root if you don't allow it before.
@@ -36,5 +36,5 @@ before_script:
# This build job does the full grails pipeline
# (compile, test, integrationTest, war, assemble).
build:
- script:
- - ./gradlew build \ No newline at end of file
+ script:
+ - ./gradlew build
diff --git a/lib/gitlab/ci/templates/Julia.gitlab-ci.yml b/lib/gitlab/ci/templates/Julia.gitlab-ci.yml
index 04c21b4725d..2c4683fbfbb 100644
--- a/lib/gitlab/ci/templates/Julia.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Julia.gitlab-ci.yml
@@ -30,7 +30,7 @@
test:0.7:
image: julia:0.7
<<: *test_definition
-
+
test:1.0:
image: julia:1.0
<<: *test_definition
diff --git a/lib/gitlab/ci/templates/Laravel.gitlab-ci.yml b/lib/gitlab/ci/templates/Laravel.gitlab-ci.yml
index d0cad285572..e1cd29ecc94 100644
--- a/lib/gitlab/ci/templates/Laravel.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Laravel.gitlab-ci.yml
@@ -22,33 +22,25 @@ cache:
# This is a basic example for a gem or script which doesn't use
# services such as redis or postgres
before_script:
- # Update packages
+ # Update packages
- apt-get update -yqq
-
# Prep for Node
- apt-get install gnupg -yqq
-
# Upgrade to Node 8
- curl -sL https://deb.nodesource.com/setup_8.x | bash -
-
# Install dependencies
- apt-get install git nodejs libcurl4-gnutls-dev libicu-dev libmcrypt-dev libvpx-dev libjpeg-dev libpng-dev libxpm-dev zlib1g-dev libfreetype6-dev libxml2-dev libexpat1-dev libbz2-dev libgmp3-dev libldap2-dev unixodbc-dev libpq-dev libsqlite3-dev libaspell-dev libsnmp-dev libpcre3-dev libtidy-dev -yqq
-
# Install php extensions
- docker-php-ext-install mbstring pdo_mysql curl json intl gd xml zip bz2 opcache
-
# Install & enable Xdebug for code coverage reports
- pecl install xdebug
- docker-php-ext-enable xdebug
-
# Install Composer and project dependencies.
- curl -sS https://getcomposer.org/installer | php
- php composer.phar install
-
# Install Node dependencies.
# comment this out if you don't have a node dependency
- npm install
-
# Copy over testing configuration.
# Don't forget to set the database config in .env.testing correctly
# DB_HOST=mysql
@@ -56,20 +48,16 @@ before_script:
# DB_USERNAME=root
# DB_PASSWORD=secret
- cp .env.testing .env
-
# Run npm build
# comment this out if you don't have a frontend build
# you can change this to to your frontend building script like
# npm run build
- npm run dev
-
# Generate an application key. Re-cache.
- php artisan key:generate
- php artisan config:cache
-
# Run database migrations.
- php artisan migrate
-
# Run database seed
- php artisan db:seed
@@ -77,7 +65,6 @@ test:
script:
# run laravel tests
- php vendor/bin/phpunit --coverage-text --colors=never
-
# run frontend tests
# if you have any task for testing frontend
# set it in your package.json script
diff --git a/lib/gitlab/ci/templates/Maven.gitlab-ci.yml b/lib/gitlab/ci/templates/Maven.gitlab-ci.yml
index 492b3d03db2..c9838c7a7ff 100644
--- a/lib/gitlab/ci/templates/Maven.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Maven.gitlab-ci.yml
@@ -66,7 +66,6 @@ verify:jdk8:
<<: *verify
image: maven:3.3.9-jdk-8
-
# For `master` branch run `mvn deploy` automatically.
# Here you need to decide whether you want to use JDK7 or 8.
# To get this working you need to define a volume while configuring your gitlab-ci-multi-runner.
@@ -85,7 +84,6 @@ deploy:jdk8:
- target/staging
image: maven:3.3.9-jdk-8
-
pages:
image: busybox:latest
stage: deploy
diff --git a/lib/gitlab/ci/templates/Mono.gitlab-ci.yml b/lib/gitlab/ci/templates/Mono.gitlab-ci.yml
index 3585f99760f..86d62b93313 100644
--- a/lib/gitlab/ci/templates/Mono.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Mono.gitlab-ci.yml
@@ -32,11 +32,11 @@ release:
# The output path is relative to the position of the csproj-file
- msbuild /p:Configuration="Release" /p:Platform="Any CPU"
/p:OutputPath="./../../build/release/" "MyProject.sln"
-
+
debug:
stage: test
script:
# The output path is relative to the position of the csproj-file
- msbuild /p:Configuration="Debug" /p:Platform="Any CPU"
/p:OutputPath="./../../build/debug/" "MyProject.sln"
- - mono packages/NUnit.ConsoleRunner.3.6.0/tools/nunit3-console.exe build/debug/MyProject.Test.dll \ No newline at end of file
+ - mono packages/NUnit.ConsoleRunner.3.6.0/tools/nunit3-console.exe build/debug/MyProject.Test.dll
diff --git a/lib/gitlab/ci/templates/Pages/Brunch.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Brunch.gitlab-ci.yml
index 7fcc0b436b5..d6de8cab5d1 100644
--- a/lib/gitlab/ci/templates/Pages/Brunch.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Pages/Brunch.gitlab-ci.yml
@@ -5,7 +5,6 @@ pages:
cache:
paths:
- node_modules/
-
script:
- npm install -g brunch
- brunch build --production
diff --git a/lib/gitlab/ci/templates/Pages/Harp.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Harp.gitlab-ci.yml
index dd3ef149668..4b58003ee10 100644
--- a/lib/gitlab/ci/templates/Pages/Harp.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Pages/Harp.gitlab-ci.yml
@@ -5,7 +5,6 @@ pages:
cache:
paths:
- node_modules
-
script:
- npm install -g harp
- harp compile ./ public
diff --git a/lib/gitlab/ci/templates/Pages/Hugo.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Hugo.gitlab-ci.yml
index b8cfb0f56f6..f9ddcc6fb0a 100644
--- a/lib/gitlab/ci/templates/Pages/Hugo.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Pages/Hugo.gitlab-ci.yml
@@ -9,7 +9,7 @@ pages:
- public
only:
- master
-
+
test:
script:
- hugo
diff --git a/lib/gitlab/ci/templates/Pages/JBake.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/JBake.gitlab-ci.yml
index 7abfaf53e8e..7a485f8d135 100644
--- a/lib/gitlab/ci/templates/Pages/JBake.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Pages/JBake.gitlab-ci.yml
@@ -13,7 +13,6 @@ image: java:8
variables:
JBAKE_VERSION: 2.5.1
-
# We use SDKMan as tool for managing versions
before_script:
- apt-get update -qq && apt-get install -y -qq unzip zip
@@ -29,4 +28,4 @@ pages:
- jbake . public
artifacts:
paths:
- - public \ No newline at end of file
+ - public
diff --git a/lib/gitlab/ci/templates/Pages/Jigsaw.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Jigsaw.gitlab-ci.yml
index 0e5fb410a4e..5ca4619e200 100644
--- a/lib/gitlab/ci/templates/Pages/Jigsaw.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Pages/Jigsaw.gitlab-ci.yml
@@ -11,24 +11,19 @@ cache:
- node_modules/
before_script:
- # Update packages
+ # Update packages
- apt-get update -yqq
-
# Install dependencies
- apt-get install -yqq gnupg zlib1g-dev libpng-dev
-
# Install Node 8
- curl -sL https://deb.nodesource.com/setup_8.x | bash -
- apt-get install -yqq nodejs
-
# Install php extensions
- docker-php-ext-install zip
-
- # Install Composer and project dependencies.
+ # Install Composer and project dependencies
- curl -sS https://getcomposer.org/installer | php
- - php composer.phar install
-
- # Install Node dependencies.
+ - php composer.phar install
+ # Install Node dependencies
- npm install
pages:
diff --git a/lib/gitlab/ci/templates/Pages/Metalsmith.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Metalsmith.gitlab-ci.yml
index 50e8b7ccd46..c6ded272150 100644
--- a/lib/gitlab/ci/templates/Pages/Metalsmith.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Pages/Metalsmith.gitlab-ci.yml
@@ -5,7 +5,6 @@ pages:
cache:
paths:
- node_modules/
-
script:
- npm install -g metalsmith
- npm install
diff --git a/lib/gitlab/ci/templates/Python.gitlab-ci.yml b/lib/gitlab/ci/templates/Python.gitlab-ci.yml
index 098abe4daf5..3eaed4e91cd 100644
--- a/lib/gitlab/ci/templates/Python.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Python.gitlab-ci.yml
@@ -18,7 +18,7 @@ cache:
- venv/
before_script:
- - python -V # Print out python version for debugging
+ - python -V # Print out python version for debugging
- pip install virtualenv
- virtualenv venv
- source venv/bin/activate
@@ -26,7 +26,7 @@ before_script:
test:
script:
- python setup.py test
- - pip install tox flake8 # you can also use tox
+ - pip install tox flake8 # you can also use tox
- tox -e py36,flake8
run:
diff --git a/lib/gitlab/ci/templates/Ruby.gitlab-ci.yml b/lib/gitlab/ci/templates/Ruby.gitlab-ci.yml
index 0d12cbc6460..93196dbd475 100644
--- a/lib/gitlab/ci/templates/Ruby.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Ruby.gitlab-ci.yml
@@ -21,7 +21,7 @@ cache:
# This is a basic example for a gem or script which doesn't use
# services such as redis or postgres
before_script:
- - ruby -v # Print out ruby version for debugging
+ - ruby -v # Print out ruby version for debugging
# Uncomment next line if your rails app needs a JS runtime:
# - apt-get update -q && apt-get install nodejs -yqq
- bundle install -j $(nproc) --path vendor # Install dependencies into ./vendor/ruby
diff --git a/lib/gitlab/ci/templates/dotNET.gitlab-ci.yml b/lib/gitlab/ci/templates/dotNET.gitlab-ci.yml
index 25a32ba0f74..5e128b793d0 100644
--- a/lib/gitlab/ci/templates/dotNET.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/dotNET.gitlab-ci.yml
@@ -26,7 +26,6 @@ variables:
MSI_RELEASE_FOLDER: 'Setup\bin\Release'
TEST_FOLDER: 'Tests\bin\Release'
DEPLOY_FOLDER: 'P:\Projects\YourApp\Builds'
-
NUGET_PATH: 'C:\NuGet\nuget.exe'
MSBUILD_PATH: 'C:\Program Files (x86)\MSBuild\14.0\Bin\msbuild.exe'
NUNIT_PATH: 'C:\Program Files (x86)\NUnit.org\nunit-console\nunit3-console.exe'
@@ -84,4 +83,3 @@ deploy_job:
dependencies:
- build_job
- test_job
- \ No newline at end of file
diff --git a/lib/gitlab/ci/templates/iOS-Fastlane.gitlab-ci.yml b/lib/gitlab/ci/templates/iOS-Fastlane.gitlab-ci.yml
index 245e6bec60a..df6ac4d340d 100644
--- a/lib/gitlab/ci/templates/iOS-Fastlane.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/iOS-Fastlane.gitlab-ci.yml
@@ -17,7 +17,7 @@ variables:
LC_ALL: "en_US.UTF-8"
LANG: "en_US.UTF-8"
GIT_STRATEGY: clone
-
+
build:
stage: build
script:
diff --git a/lib/gitlab/diff/suggestions_parser.rb b/lib/gitlab/diff/suggestions_parser.rb
new file mode 100644
index 00000000000..043bd9a4bcb
--- /dev/null
+++ b/lib/gitlab/diff/suggestions_parser.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Diff
+ class SuggestionsParser
+ # Matches for instance "-1", "+1" or "-1+2".
+ SUGGESTION_CONTEXT = /^(\-(?<above>\d+))?(\+(?<below>\d+))?$/.freeze
+ end
+ end
+end
diff --git a/lib/gitlab/git/commit.rb b/lib/gitlab/git/commit.rb
index e5bbd500e98..88ff9fbceb4 100644
--- a/lib/gitlab/git/commit.rb
+++ b/lib/gitlab/git/commit.rb
@@ -184,11 +184,12 @@ module Gitlab
end
end
- def initialize(repository, raw_commit, head = nil)
+ def initialize(repository, raw_commit, head = nil, lazy_load_parents: false)
raise "Nil as raw commit passed" unless raw_commit
@repository = repository
@head = head
+ @lazy_load_parents = lazy_load_parents
init_commit(raw_commit)
end
@@ -225,6 +226,12 @@ module Gitlab
author_name != committer_name || author_email != committer_email
end
+ def parent_ids
+ return @parent_ids unless @lazy_load_parents
+
+ @parent_ids ||= @repository.commit(id).parent_ids
+ end
+
def parent_id
parent_ids.first
end
diff --git a/lib/gitlab/json_cache.rb b/lib/gitlab/json_cache.rb
index 24daad638f4..e4bc437d787 100644
--- a/lib/gitlab/json_cache.rb
+++ b/lib/gitlab/json_cache.rb
@@ -80,8 +80,23 @@ module Gitlab
# when the new_record? method incorrectly returns false.
#
# See https://gitlab.com/gitlab-org/gitlab-ee/issues/9903#note_145329964
- attributes = klass.attributes_builder.build_from_database(raw, {})
- klass.allocate.init_with("attributes" => attributes, "new_record" => new_record?(raw, klass))
+ klass
+ .allocate
+ .init_with(
+ "attributes" => attributes_for(klass, raw),
+ "new_record" => new_record?(raw, klass)
+ )
+ end
+
+ def attributes_for(klass, raw)
+ # We have models that leave out some fields from the JSON export for
+ # security reasons, e.g. models that include the CacheMarkdownField.
+ # The ActiveRecord::AttributeSet we build from raw does know about
+ # these columns so we need manually set them.
+ missing_attributes = (klass.columns.map(&:name) - raw.keys)
+ missing_attributes.each { |column| raw[column] = nil }
+
+ klass.attributes_builder.build_from_database(raw, {})
end
def new_record?(raw, klass)
diff --git a/lib/gitlab/kubernetes.rb b/lib/gitlab/kubernetes.rb
index a9957a85d48..d46b5e3aee3 100644
--- a/lib/gitlab/kubernetes.rb
+++ b/lib/gitlab/kubernetes.rb
@@ -24,6 +24,30 @@ module Gitlab
end
end
+ # Filters an array of pods (as returned by the kubernetes API) by their annotations
+ def filter_by_annotation(items, annotations = {})
+ items.select do |item|
+ metadata = item.fetch("metadata", {})
+ item_annotations = metadata.fetch("annotations", nil)
+ next unless item_annotations
+
+ annotations.all? { |k, v| item_annotations[k.to_s] == v }
+ end
+ end
+
+ # Filters an array of pods (as returned by the kubernetes API) by their project and environment
+ def filter_by_project_environment(items, app, env)
+ pods = filter_by_annotation(items, {
+ 'app.gitlab.com/app' => app,
+ 'app.gitlab.com/env' => env
+ })
+ return pods unless pods.empty?
+
+ filter_by_label(items, {
+ 'app' => env, # deprecated: replaced by app.gitlab.com/env
+ })
+ end
+
# Converts a pod (as returned by the kubernetes API) into a terminal
def terminals_for_pod(api_url, namespace, pod)
metadata = pod.fetch("metadata", {})
diff --git a/lib/sentry/client.rb b/lib/sentry/client.rb
index 49ec196b103..5e0c9101de5 100644
--- a/lib/sentry/client.rb
+++ b/lib/sentry/client.rb
@@ -57,7 +57,7 @@ module Sentry
raise Client::Error, "Sentry response status code: #{response.code}"
end
- response.as_json
+ response
end
def projects_api_url
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index f7fb66b5f5d..5ab7de81bb6 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -1542,6 +1542,9 @@ msgstr ""
msgid "CiStatusLabel|pending"
msgstr ""
+msgid "CiStatusLabel|preparing"
+msgstr ""
+
msgid "CiStatusLabel|skipped"
msgstr ""
@@ -1575,6 +1578,9 @@ msgstr ""
msgid "CiStatusText|pending"
msgstr ""
+msgid "CiStatusText|preparing"
+msgstr ""
+
msgid "CiStatusText|skipped"
msgstr ""
@@ -4502,6 +4508,9 @@ msgstr ""
msgid "Job|Keep"
msgstr ""
+msgid "Job|Pipeline"
+msgstr ""
+
msgid "Job|Scroll to bottom"
msgstr ""
@@ -4520,6 +4529,15 @@ msgstr ""
msgid "Job|This job is stuck because the project doesn't have any runners online assigned to it."
msgstr ""
+msgid "Job|for"
+msgstr ""
+
+msgid "Job|into"
+msgstr ""
+
+msgid "Job|with"
+msgstr ""
+
msgid "Jul"
msgstr ""
@@ -6073,7 +6091,7 @@ msgstr ""
msgid "Profiles|This email will be displayed on your public profile"
msgstr ""
-msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{learn_more}"
+msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{commit_email_link_start}Learn more%{commit_email_link_end}"
msgstr ""
msgid "Profiles|This emoji and message will appear on your profile and throughout the interface."
@@ -8008,6 +8026,12 @@ msgstr ""
msgid "This job is in pending state and is waiting to be picked by a runner"
msgstr ""
+msgid "This job is performing tasks that must complete before it can start"
+msgstr ""
+
+msgid "This job is preparing to start"
+msgstr ""
+
msgid "This job is stuck because you don't have any active runners online with any of these tags assigned to them:"
msgstr ""
@@ -8041,7 +8065,7 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
-msgid "This pipeline is run in a merge request context"
+msgid "This pipeline is run on the source branch"
msgstr ""
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
@@ -9200,6 +9224,9 @@ msgstr ""
msgid "Your U2F device was registered!"
msgstr ""
+msgid "Your account uses dedicated credentials for the \"%{group_name}\" group and can only be updated through SSO."
+msgstr ""
+
msgid "Your applications (%{size})"
msgstr ""
@@ -9295,6 +9322,9 @@ msgstr ""
msgid "deploy token"
msgstr ""
+msgid "detached"
+msgstr ""
+
msgid "disabled"
msgstr ""
diff --git a/package.json b/package.json
index fa980fa173e..d830d83b963 100644
--- a/package.json
+++ b/package.json
@@ -17,6 +17,7 @@
"prettier-all": "node ./scripts/frontend/prettier.js check-all",
"prettier-all-save": "node ./scripts/frontend/prettier.js save-all",
"stylelint": "node node_modules/stylelint/bin/stylelint.js app/assets/stylesheets/**/*.* --custom-formatter node_modules/stylelint-error-string-formatter",
+ "test": "yarn jest && yarn karma",
"webpack": "webpack --config config/webpack.config.js",
"webpack-prod": "NODE_ENV=production webpack --config config/webpack.config.js"
},
diff --git a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb
index 16aca7c0e22..0b92ea29ca4 100644
--- a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb
+++ b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb
@@ -116,12 +116,6 @@ module QA
p.description = 'Project with AutoDevOps'
end
- project.visit!
-
- Page::Alert::AutoDevopsAlert.perform do |alert|
- expect(alert).to have_text(/.*The Auto DevOps pipeline has been enabled.*/)
- end
-
# Create AutoDevOps repo
Resource::Repository::ProjectPush.fabricate! do |push|
push.project = project
diff --git a/spec/controllers/omniauth_callbacks_controller_spec.rb b/spec/controllers/omniauth_callbacks_controller_spec.rb
index e0da23ca0b8..06c6f49f7cc 100644
--- a/spec/controllers/omniauth_callbacks_controller_spec.rb
+++ b/spec/controllers/omniauth_callbacks_controller_spec.rb
@@ -113,6 +113,33 @@ describe OmniauthCallbacksController, type: :controller do
expect(request.env['warden']).to be_authenticated
end
+ context 'when user has no linked provider' do
+ let(:user) { create(:user) }
+
+ before do
+ sign_in user
+ end
+
+ it 'links identity' do
+ expect do
+ post provider
+ user.reload
+ end.to change { user.identities.count }.by(1)
+ end
+
+ context 'and is not allowed to link the provider' do
+ before do
+ allow_any_instance_of(IdentityProviderPolicy).to receive(:can?).with(:link).and_return(false)
+ end
+
+ it 'returns 403' do
+ post provider
+
+ expect(response).to have_gitlab_http_status(403)
+ end
+ end
+ end
+
shared_context 'sign_up' do
let(:user) { double(email: 'new@example.com') }
diff --git a/spec/controllers/projects/notes_controller_spec.rb b/spec/controllers/projects/notes_controller_spec.rb
index 0b0f5117784..deecb7fefe9 100644
--- a/spec/controllers/projects/notes_controller_spec.rb
+++ b/spec/controllers/projects/notes_controller_spec.rb
@@ -413,6 +413,37 @@ describe Projects::NotesController do
end
end
end
+
+ context 'when creating a note with quick actions' do
+ context 'with commands that return changes' do
+ let(:note_text) { "/award :thumbsup:\n/estimate 1d\n/spend 3h" }
+
+ it 'includes changes in commands_changes ' do
+ post :create, params: request_params.merge(note: { note: note_text }, format: :json)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['commands_changes']).to include('emoji_award', 'time_estimate', 'spend_time')
+ expect(json_response['commands_changes']).not_to include('target_project', 'title')
+ end
+ end
+
+ context 'with commands that do not return changes' do
+ let(:issue) { create(:issue, project: project) }
+ let(:other_project) { create(:project) }
+ let(:note_text) { "/move #{other_project.full_path}\n/title AAA" }
+
+ before do
+ other_project.add_developer(user)
+ end
+
+ it 'does not include changes in commands_changes' do
+ post :create, params: request_params.merge(note: { note: note_text }, target_type: 'issue', target_id: issue.id, format: :json)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['commands_changes']).not_to include('target_project', 'title')
+ end
+ end
+ end
end
describe 'PUT update' do
diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb
index 0b3e67b4987..067391c1179 100644
--- a/spec/factories/ci/builds.rb
+++ b/spec/factories/ci/builds.rb
@@ -75,6 +75,10 @@ FactoryBot.define do
status 'created'
end
+ trait :preparing do
+ status 'preparing'
+ end
+
trait :scheduled do
schedulable
status 'scheduled'
diff --git a/spec/factories/ci/pipelines.rb b/spec/factories/ci/pipelines.rb
index ee5d27355f1..aa5ccbda6cd 100644
--- a/spec/factories/ci/pipelines.rb
+++ b/spec/factories/ci/pipelines.rb
@@ -50,6 +50,14 @@ FactoryBot.define do
failure_reason :config_error
end
+ trait :created do
+ status :created
+ end
+
+ trait :preparing do
+ status :preparing
+ end
+
trait :blocked do
status :manual
end
diff --git a/spec/factories/clusters/clusters.rb b/spec/factories/clusters/clusters.rb
index a2e5f4862db..1cc3c0e03d8 100644
--- a/spec/factories/clusters/clusters.rb
+++ b/spec/factories/clusters/clusters.rb
@@ -12,7 +12,7 @@ FactoryBot.define do
cluster_type { Clusters::Cluster.cluster_types[:project_type] }
before(:create) do |cluster, evaluator|
- cluster.projects << create(:project, :repository)
+ cluster.projects << create(:project, :repository) unless cluster.projects.present?
end
end
@@ -20,7 +20,7 @@ FactoryBot.define do
cluster_type { Clusters::Cluster.cluster_types[:group_type] }
before(:create) do |cluster, evalutor|
- cluster.groups << create(:group)
+ cluster.groups << create(:group) unless cluster.groups.present?
end
end
diff --git a/spec/factories/commit_statuses.rb b/spec/factories/commit_statuses.rb
index 381bf07f6a0..848a31e96c1 100644
--- a/spec/factories/commit_statuses.rb
+++ b/spec/factories/commit_statuses.rb
@@ -33,6 +33,10 @@ FactoryBot.define do
status 'pending'
end
+ trait :preparing do
+ status 'preparing'
+ end
+
trait :created do
status 'created'
end
diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb
index 9bc340ed4bb..51508b78649 100644
--- a/spec/features/issues_spec.rb
+++ b/spec/features/issues_spec.rb
@@ -497,12 +497,21 @@ describe 'Issues' do
it 'allows user to unselect themselves', :js do
issue2 = create(:issue, project: project, author: user)
+
visit project_issue_path(project, issue2)
+ def close_dropdown_menu_if_visible
+ find('.dropdown-menu-toggle', visible: :all).tap do |toggle|
+ toggle.click if toggle.visible?
+ end
+ end
+
page.within '.assignee' do
click_link 'Edit'
click_link user.name
+ close_dropdown_menu_if_visible
+
page.within '.value .author' do
expect(page).to have_content user.name
end
@@ -510,6 +519,8 @@ describe 'Issues' do
click_link 'Edit'
click_link user.name
+ close_dropdown_menu_if_visible
+
page.within '.value .assign-yourself' do
expect(page).to have_content "No assignee"
end
diff --git a/spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb b/spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb
index 97b2aa82fce..28f88718ec1 100644
--- a/spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb
+++ b/spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb
@@ -2,7 +2,7 @@
require 'rails_helper'
-describe 'Merge request > User sees merge request pipelines', :js do
+describe 'Merge request > User sees pipelines triggered by merge request', :js do
include ProjectForksHelper
include TestReportsHelper
@@ -47,7 +47,7 @@ describe 'Merge request > User sees merge request pipelines', :js do
.execute(:push)
end
- let!(:merge_request_pipeline) do
+ let!(:detached_merge_request_pipeline) do
Ci::CreatePipelineService.new(project, user, ref: 'feature')
.execute(:merge_request_event, merge_request: merge_request)
end
@@ -60,16 +60,16 @@ describe 'Merge request > User sees merge request pipelines', :js do
end
end
- it 'sees branch pipelines and merge request pipelines in correct order' do
+ it 'sees branch pipelines and detached merge request pipelines in correct order' do
page.within('.ci-table') do
expect(page).to have_selector('.ci-pending', count: 2)
- expect(first('.js-pipeline-url-link')).to have_content("##{merge_request_pipeline.id}")
+ expect(first('.js-pipeline-url-link')).to have_content("##{detached_merge_request_pipeline.id}")
end
end
- it 'sees the latest merge request pipeline as the head pipeline' do
+ it 'sees the latest detached merge request pipeline as the head pipeline' do
page.within('.ci-widget-content') do
- expect(page).to have_content("##{merge_request_pipeline.id}")
+ expect(page).to have_content("##{detached_merge_request_pipeline.id}")
end
end
@@ -79,7 +79,7 @@ describe 'Merge request > User sees merge request pipelines', :js do
.execute(:push)
end
- let!(:merge_request_pipeline_2) do
+ let!(:detached_merge_request_pipeline_2) do
Ci::CreatePipelineService.new(project, user, ref: 'feature')
.execute(:merge_request_event, merge_request: merge_request)
end
@@ -92,15 +92,15 @@ describe 'Merge request > User sees merge request pipelines', :js do
end
end
- it 'sees branch pipelines and merge request pipelines in correct order' do
+ it 'sees branch pipelines and detached merge request pipelines in correct order' do
page.within('.ci-table') do
expect(page).to have_selector('.ci-pending', count: 4)
expect(all('.js-pipeline-url-link')[0])
- .to have_content("##{merge_request_pipeline_2.id}")
+ .to have_content("##{detached_merge_request_pipeline_2.id}")
expect(all('.js-pipeline-url-link')[1])
- .to have_content("##{merge_request_pipeline.id}")
+ .to have_content("##{detached_merge_request_pipeline.id}")
expect(all('.js-pipeline-url-link')[2])
.to have_content("##{push_pipeline_2.id}")
@@ -110,25 +110,25 @@ describe 'Merge request > User sees merge request pipelines', :js do
end
end
- it 'sees merge request tag for merge request pipelines' do
+ it 'sees detached tag for detached merge request pipelines' do
page.within('.ci-table') do
expect(all('.pipeline-tags')[0])
- .to have_content("merge request")
+ .to have_content("detached")
expect(all('.pipeline-tags')[1])
- .to have_content("merge request")
+ .to have_content("detached")
expect(all('.pipeline-tags')[2])
- .not_to have_content("merge request")
+ .not_to have_content("detached")
expect(all('.pipeline-tags')[3])
- .not_to have_content("merge request")
+ .not_to have_content("detached")
end
end
- it 'sees the latest merge request pipeline as the head pipeline' do
+ it 'sees the latest detached merge request pipeline as the head pipeline' do
page.within('.ci-widget-content') do
- expect(page).to have_content("##{merge_request_pipeline_2.id}")
+ expect(page).to have_content("##{detached_merge_request_pipeline_2.id}")
end
end
end
@@ -140,16 +140,16 @@ describe 'Merge request > User sees merge request pipelines', :js do
wait_for_requests
end
- context 'when merge request pipeline is pending' do
+ context 'when detached merge request pipeline is pending' do
it 'waits the head pipeline' do
expect(page).to have_content('to be merged automatically when the pipeline succeeds')
expect(page).to have_link('Cancel automatic merge')
end
end
- context 'when merge request pipeline succeeds' do
+ context 'when detached merge request pipeline succeeds' do
before do
- merge_request_pipeline.succeed!
+ detached_merge_request_pipeline.succeed!
wait_for_requests
end
@@ -218,7 +218,7 @@ describe 'Merge request > User sees merge request pipelines', :js do
.execute(:push)
end
- let!(:merge_request_pipeline) do
+ let!(:detached_merge_request_pipeline) do
Ci::CreatePipelineService.new(forked_project, user2, ref: 'feature')
.execute(:merge_request_event, merge_request: merge_request)
end
@@ -236,16 +236,16 @@ describe 'Merge request > User sees merge request pipelines', :js do
end
end
- it 'sees branch pipelines and merge request pipelines in correct order' do
+ it 'sees branch pipelines and detached merge request pipelines in correct order' do
page.within('.ci-table') do
expect(page).to have_selector('.ci-pending', count: 2)
- expect(first('.js-pipeline-url-link')).to have_content("##{merge_request_pipeline.id}")
+ expect(first('.js-pipeline-url-link')).to have_content("##{detached_merge_request_pipeline.id}")
end
end
- it 'sees the latest merge request pipeline as the head pipeline' do
+ it 'sees the latest detached merge request pipeline as the head pipeline' do
page.within('.ci-widget-content') do
- expect(page).to have_content("##{merge_request_pipeline.id}")
+ expect(page).to have_content("##{detached_merge_request_pipeline.id}")
end
end
@@ -261,7 +261,7 @@ describe 'Merge request > User sees merge request pipelines', :js do
.execute(:push)
end
- let!(:merge_request_pipeline_2) do
+ let!(:detached_merge_request_pipeline_2) do
Ci::CreatePipelineService.new(forked_project, user2, ref: 'feature')
.execute(:merge_request_event, merge_request: merge_request)
end
@@ -274,15 +274,15 @@ describe 'Merge request > User sees merge request pipelines', :js do
end
end
- it 'sees branch pipelines and merge request pipelines in correct order' do
+ it 'sees branch pipelines and detached merge request pipelines in correct order' do
page.within('.ci-table') do
expect(page).to have_selector('.ci-pending', count: 4)
expect(all('.js-pipeline-url-link')[0])
- .to have_content("##{merge_request_pipeline_2.id}")
+ .to have_content("##{detached_merge_request_pipeline_2.id}")
expect(all('.js-pipeline-url-link')[1])
- .to have_content("##{merge_request_pipeline.id}")
+ .to have_content("##{detached_merge_request_pipeline.id}")
expect(all('.js-pipeline-url-link')[2])
.to have_content("##{push_pipeline_2.id}")
@@ -292,25 +292,25 @@ describe 'Merge request > User sees merge request pipelines', :js do
end
end
- it 'sees merge request tag for merge request pipelines' do
+ it 'sees detached tag for detached merge request pipelines' do
page.within('.ci-table') do
expect(all('.pipeline-tags')[0])
- .to have_content("merge request")
+ .to have_content("detached")
expect(all('.pipeline-tags')[1])
- .to have_content("merge request")
+ .to have_content("detached")
expect(all('.pipeline-tags')[2])
- .not_to have_content("merge request")
+ .not_to have_content("detached")
expect(all('.pipeline-tags')[3])
- .not_to have_content("merge request")
+ .not_to have_content("detached")
end
end
- it 'sees the latest merge request pipeline as the head pipeline' do
+ it 'sees the latest detached merge request pipeline as the head pipeline' do
page.within('.ci-widget-content') do
- expect(page).to have_content("##{merge_request_pipeline_2.id}")
+ expect(page).to have_content("##{detached_merge_request_pipeline_2.id}")
end
end
@@ -328,16 +328,16 @@ describe 'Merge request > User sees merge request pipelines', :js do
wait_for_requests
end
- context 'when merge request pipeline is pending' do
+ context 'when detached merge request pipeline is pending' do
it 'waits the head pipeline' do
expect(page).to have_content('to be merged automatically when the pipeline succeeds')
expect(page).to have_link('Cancel automatic merge')
end
end
- context 'when merge request pipeline succeeds' do
+ context 'when detached merge request pipeline succeeds' do
before do
- merge_request_pipeline.succeed!
+ detached_merge_request_pipeline.succeed!
wait_for_requests
end
diff --git a/spec/features/projects/badges/pipeline_badge_spec.rb b/spec/features/projects/badges/pipeline_badge_spec.rb
index dee81898928..4ac4e8f0fcb 100644
--- a/spec/features/projects/badges/pipeline_badge_spec.rb
+++ b/spec/features/projects/badges/pipeline_badge_spec.rb
@@ -41,6 +41,25 @@ describe 'Pipeline Badge' do
end
end
+ context 'when the pipeline is preparing' do
+ let!(:job) { create(:ci_build, status: 'created', pipeline: pipeline) }
+
+ before do
+ # Prevent skipping directly to 'pending'
+ allow(Ci::BuildPrepareWorker).to receive(:perform_async)
+ allow(job).to receive(:prerequisites).and_return([double])
+ end
+
+ it 'displays the preparing badge' do
+ job.enqueue
+
+ visit pipeline_project_badges_path(project, ref: ref, format: :svg)
+
+ expect(page.status_code).to eq(200)
+ expect_badge('preparing')
+ end
+ end
+
context 'when the pipeline is running' do
it 'shows displays so on the badge' do
create(:ci_build, pipeline: pipeline, name: 'second build', status_event: 'run')
diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb
index 65ce872363f..224375daf71 100644
--- a/spec/features/projects/jobs_spec.rb
+++ b/spec/features/projects/jobs_spec.rb
@@ -2,6 +2,9 @@ require 'spec_helper'
require 'tempfile'
describe 'Jobs', :clean_gitlab_redis_shared_state do
+ include Gitlab::Routing
+ include ProjectForksHelper
+
let(:user) { create(:user) }
let(:user_access_level) { :developer }
let(:project) { create(:project, :repository) }
@@ -121,6 +124,112 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
end
end
+ context 'pipeline info block', :js do
+ it 'shows pipeline id and source branch' do
+ visit project_job_path(project, job)
+
+ within '.js-pipeline-info' do
+ expect(page).to have_content("Pipeline ##{pipeline.id} for #{pipeline.ref}")
+ end
+ end
+
+ context 'when pipeline is detached merge request pipeline' do
+ let(:merge_request) do
+ create(:merge_request,
+ :with_detached_merge_request_pipeline,
+ target_project: target_project,
+ source_project: source_project)
+ end
+
+ let(:source_project) { project }
+ let(:target_project) { project }
+ let(:pipeline) { merge_request.all_pipelines.last }
+ let(:job) { create(:ci_build, pipeline: pipeline) }
+
+ it 'shows merge request iid and source branch' do
+ visit project_job_path(project, job)
+
+ within '.js-pipeline-info' do
+ expect(page).to have_content("for !#{pipeline.merge_request.iid} " \
+ "with #{pipeline.merge_request.source_branch}")
+ expect(page).to have_link("!#{pipeline.merge_request.iid}",
+ href: project_merge_request_path(project, merge_request))
+ expect(page).to have_link(pipeline.merge_request.source_branch,
+ href: project_commits_path(project, merge_request.source_branch))
+ end
+ end
+
+ context 'when source project is a forked project' do
+ let(:source_project) { fork_project(project, user, repository: true) }
+ let(:target_project) { project }
+
+ it 'shows merge request iid and source branch' do
+ visit project_job_path(source_project, job)
+
+ within '.js-pipeline-info' do
+ expect(page).to have_content("for !#{pipeline.merge_request.iid} " \
+ "with #{pipeline.merge_request.source_branch}")
+ expect(page).to have_link("!#{pipeline.merge_request.iid}",
+ href: project_merge_request_path(project, merge_request))
+ expect(page).to have_link(pipeline.merge_request.source_branch,
+ href: project_commits_path(source_project, merge_request.source_branch))
+ end
+ end
+ end
+ end
+
+ context 'when pipeline is merge request pipeline' do
+ let(:merge_request) do
+ create(:merge_request,
+ :with_merge_request_pipeline,
+ target_project: target_project,
+ source_project: source_project)
+ end
+
+ let(:source_project) { project }
+ let(:target_project) { project }
+ let(:pipeline) { merge_request.all_pipelines.last }
+ let(:job) { create(:ci_build, pipeline: pipeline) }
+
+ it 'shows merge request iid and source branch' do
+ visit project_job_path(project, job)
+
+ within '.js-pipeline-info' do
+ expect(page).to have_content("for !#{pipeline.merge_request.iid} " \
+ "with #{pipeline.merge_request.source_branch} " \
+ "into #{pipeline.merge_request.target_branch}")
+ expect(page).to have_link("!#{pipeline.merge_request.iid}",
+ href: project_merge_request_path(project, merge_request))
+ expect(page).to have_link(pipeline.merge_request.source_branch,
+ href: project_commits_path(project, merge_request.source_branch))
+ expect(page).to have_link(pipeline.merge_request.target_branch,
+ href: project_commits_path(project, merge_request.target_branch))
+ end
+ end
+
+ context 'when source project is a forked project' do
+ let(:source_project) { fork_project(project, user, repository: true) }
+ let(:target_project) { project }
+
+ it 'shows merge request iid and source branch' do
+ visit project_job_path(source_project, job)
+
+ within '.js-pipeline-info' do
+ expect(page).to have_content("for !#{pipeline.merge_request.iid} " \
+ "with #{pipeline.merge_request.source_branch} " \
+ "into #{pipeline.merge_request.target_branch}")
+ expect(page).to have_link("!#{pipeline.merge_request.iid}",
+ href: project_merge_request_path(project, merge_request))
+ expect(page).to have_link(pipeline.merge_request.source_branch,
+ href: project_commits_path(source_project, merge_request.source_branch))
+ expect(page).to have_link(pipeline.merge_request.target_branch,
+ href: project_commits_path(project, merge_request.target_branch))
+ end
+ end
+ end
+ end
+ end
+
context 'sidebar', :js do
let(:job) { create(:ci_build, :success, :trace_live, pipeline: pipeline, name: '<img src=x onerror=alert(document.domain)>') }
diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb
index 5d2a99f40b7..9fdf78baa1e 100644
--- a/spec/features/projects/pipelines/pipeline_spec.rb
+++ b/spec/features/projects/pipelines/pipeline_spec.rb
@@ -24,6 +24,11 @@ describe 'Pipeline', :js do
pipeline: pipeline, stage: 'test', name: 'test')
end
+ let!(:build_preparing) do
+ create(:ci_build, :preparing,
+ pipeline: pipeline, stage: 'deploy', name: 'prepare')
+ end
+
let!(:build_running) do
create(:ci_build, :running,
pipeline: pipeline, stage: 'deploy', name: 'deploy')
@@ -109,6 +114,24 @@ describe 'Pipeline', :js do
end
end
+ context 'when pipeline has preparing builds' do
+ it 'shows a preparing icon and a cancel action' do
+ page.within('#ci-badge-prepare') do
+ expect(page).to have_selector('.js-ci-status-icon-preparing')
+ expect(page).to have_selector('.js-icon-cancel')
+ expect(page).to have_content('prepare')
+ end
+ end
+
+ it 'cancels the preparing build and shows retry button' do
+ find('#ci-badge-deploy .ci-action-icon-container').click
+
+ page.within('#ci-badge-deploy') do
+ expect(page).to have_css('.js-icon-retry')
+ end
+ end
+ end
+
context 'when pipeline has successful builds' do
it 'shows the success icon and a retry action for the successful build' do
page.within('#ci-badge-build') do
diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb
index 88d7c9ef8bd..7ca3b3d8edd 100644
--- a/spec/features/projects/pipelines/pipelines_spec.rb
+++ b/spec/features/projects/pipelines/pipelines_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe 'Pipelines', :js do
+ include ProjectForksHelper
+
let(:project) { create(:project) }
context 'when user is logged in' do
@@ -165,6 +167,99 @@ describe 'Pipelines', :js do
end
end
+ context 'when pipeline is detached merge request pipeline' do
+ let(:merge_request) do
+ create(:merge_request,
+ :with_detached_merge_request_pipeline,
+ source_project: source_project,
+ target_project: target_project)
+ end
+
+ let!(:pipeline) { merge_request.all_pipelines.first }
+ let(:source_project) { project }
+ let(:target_project) { project }
+
+ before do
+ visit project_pipelines_path(source_project)
+ end
+
+ shared_examples_for 'showing detached merge request pipeline information' do
+ it 'shows detached tag for the pipeline' do
+ within '.pipeline-tags' do
+ expect(page).to have_content('detached')
+ end
+ end
+
+ it 'shows the link of the merge request' do
+ within '.branch-commit' do
+ expect(page).to have_link(merge_request.iid,
+ href: project_merge_request_path(project, merge_request))
+ end
+ end
+
+ it 'does not show the ref of the pipeline' do
+ within '.branch-commit' do
+ expect(page).not_to have_link(pipeline.ref)
+ end
+ end
+ end
+
+ it_behaves_like 'showing detached merge request pipeline information'
+
+ context 'when source project is a forked project' do
+ let(:source_project) { fork_project(project, user, repository: true) }
+
+ it_behaves_like 'showing detached merge request pipeline information'
+ end
+ end
+
+ context 'when pipeline is merge request pipeline' do
+ let(:merge_request) do
+ create(:merge_request,
+ :with_merge_request_pipeline,
+ source_project: source_project,
+ target_project: target_project,
+ merge_sha: target_project.commit.sha)
+ end
+
+ let!(:pipeline) { merge_request.all_pipelines.first }
+ let(:source_project) { project }
+ let(:target_project) { project }
+
+ before do
+ visit project_pipelines_path(source_project)
+ end
+
+ shared_examples_for 'Correct merge request pipeline information' do
+ it 'does not show detached tag for the pipeline' do
+ within '.pipeline-tags' do
+ expect(page).not_to have_content('detached')
+ end
+ end
+
+ it 'shows the link of the merge request' do
+ within '.branch-commit' do
+ expect(page).to have_link(merge_request.iid,
+ href: project_merge_request_path(project, merge_request))
+ end
+ end
+
+ it 'does not show the ref of the pipeline' do
+ within '.branch-commit' do
+ expect(page).not_to have_link(pipeline.ref)
+ end
+ end
+ end
+
+ it_behaves_like 'Correct merge request pipeline information'
+
+ context 'when source project is a forked project' do
+ let(:source_project) { fork_project(project, user, repository: true) }
+
+ it_behaves_like 'Correct merge request pipeline information'
+ end
+ end
+
context 'when pipeline has configuration errors' do
let(:pipeline) do
create(:ci_pipeline, :invalid, project: project)
@@ -282,6 +377,30 @@ describe 'Pipelines', :js do
end
context 'for generic statuses' do
+ context 'when preparing' do
+ let!(:pipeline) do
+ create(:ci_empty_pipeline,
+ status: 'preparing', project: project)
+ end
+
+ let!(:status) do
+ create(:generic_commit_status,
+ :preparing, pipeline: pipeline)
+ end
+
+ before do
+ visit_project_pipelines
+ end
+
+ it 'is cancelable' do
+ expect(page).to have_selector('.js-pipelines-cancel-button')
+ end
+
+ it 'shows the pipeline as preparing' do
+ expect(page).to have_selector('.ci-preparing')
+ end
+ end
+
context 'when running' do
let!(:running) do
create(:generic_commit_status,
diff --git a/spec/features/user_opens_link_to_comment.rb b/spec/features/user_opens_link_to_comment.rb
new file mode 100644
index 00000000000..f1e07e55799
--- /dev/null
+++ b/spec/features/user_opens_link_to_comment.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'User opens link to comment', :js do
+ let(:project) { create(:project, :public) }
+ let(:note) { create(:note_on_issue, project: project) }
+
+ context 'authenticated user' do
+ let(:user) { create(:user) }
+
+ before do
+ sign_in(user)
+ end
+
+ it 'switches to all activity and does not show error message' do
+ create(:user_preference, user: user, issue_notes_filter: UserPreference::NOTES_FILTERS[:only_activity])
+
+ visit Gitlab::UrlBuilder.build(note)
+
+ expect(page.find('#discussion-filter-dropdown')).to have_content('Show all activity')
+ expect(page).not_to have_content('Something went wrong while fetching comments')
+ end
+ end
+
+ context 'anonymous user' do
+ it 'does not show error message' do
+ visit Gitlab::UrlBuilder.build(note)
+
+ expect(page).not_to have_content('Something went wrong while fetching comments')
+ end
+ end
+end
diff --git a/spec/helpers/auth_helper_spec.rb b/spec/helpers/auth_helper_spec.rb
index f0c2e4768ec..2ba8b3dbf22 100644
--- a/spec/helpers/auth_helper_spec.rb
+++ b/spec/helpers/auth_helper_spec.rb
@@ -97,17 +97,37 @@ describe AuthHelper do
end
end
- describe 'unlink_allowed?' do
- [:saml, :cas3].each do |provider|
- it "returns true if the provider is #{provider}" do
- expect(helper.unlink_allowed?(provider)).to be false
- end
+ describe '#link_provider_allowed?' do
+ let(:policy) { instance_double('IdentityProviderPolicy') }
+ let(:current_user) { instance_double('User') }
+ let(:provider) { double }
+
+ before do
+ allow(helper).to receive(:current_user).and_return(current_user)
+ allow(IdentityProviderPolicy).to receive(:new).with(current_user, provider).and_return(policy)
end
- [:twitter, :facebook, :google_oauth2, :gitlab, :github, :bitbucket, :crowd, :auth0, :authentiq].each do |provider|
- it "returns false if the provider is #{provider}" do
- expect(helper.unlink_allowed?(provider)).to be true
- end
+ it 'delegates to identity provider policy' do
+ allow(policy).to receive(:can?).with(:link).and_return('policy_link_result')
+
+ expect(helper.link_provider_allowed?(provider)).to eq 'policy_link_result'
+ end
+ end
+
+ describe '#unlink_provider_allowed?' do
+ let(:policy) { instance_double('IdentityProviderPolicy') }
+ let(:current_user) { instance_double('User') }
+ let(:provider) { double }
+
+ before do
+ allow(helper).to receive(:current_user).and_return(current_user)
+ allow(IdentityProviderPolicy).to receive(:new).with(current_user, provider).and_return(policy)
+ end
+
+ it 'delegates to identity provider policy' do
+ allow(policy).to receive(:can?).with(:unlink).and_return('policy_unlink_result')
+
+ expect(helper.unlink_provider_allowed?(provider)).to eq 'policy_unlink_result'
end
end
end
diff --git a/spec/javascripts/environments/environment_item_spec.js b/spec/javascripts/environments/environment_item_spec.js
index 8b877994515..388d7063d13 100644
--- a/spec/javascripts/environments/environment_item_spec.js
+++ b/spec/javascripts/environments/environment_item_spec.js
@@ -60,7 +60,7 @@ describe('Environment item', () => {
sha: '500aabcb17c97bdcf2d0c410b70cb8556f0362dd',
ref: {
name: 'master',
- ref_path: 'root/ci-folders/tree/master',
+ ref_url: 'root/ci-folders/tree/master',
},
tag: true,
'last?': true,
diff --git a/spec/javascripts/jobs/components/stages_dropdown_spec.js b/spec/javascripts/jobs/components/stages_dropdown_spec.js
index 9c731ae2f68..eccb4e13d67 100644
--- a/spec/javascripts/jobs/components/stages_dropdown_spec.js
+++ b/spec/javascripts/jobs/components/stages_dropdown_spec.js
@@ -1,59 +1,167 @@
import Vue from 'vue';
import component from '~/jobs/components/stages_dropdown.vue';
+import { trimText } from 'spec/helpers/vue_component_helper';
import mountComponent from '../../helpers/vue_mount_component_helper';
describe('Stages Dropdown', () => {
const Component = Vue.extend(component);
let vm;
- beforeEach(() => {
- vm = mountComponent(Component, {
- pipeline: {
- id: 28029444,
- details: {
- status: {
- details_path: '/gitlab-org/gitlab-ce/pipelines/28029444',
- group: 'success',
- has_details: true,
- icon: 'status_success',
- label: 'passed',
- text: 'passed',
- tooltip: 'passed',
- },
- },
- path: 'pipeline/28029444',
+ const mockPipelineData = {
+ id: 28029444,
+ details: {
+ status: {
+ details_path: '/gitlab-org/gitlab-ce/pipelines/28029444',
+ group: 'success',
+ has_details: true,
+ icon: 'status_success',
+ label: 'passed',
+ text: 'passed',
+ tooltip: 'passed',
},
- stages: [
- {
- name: 'build',
- },
- {
- name: 'test',
- },
- ],
- selectedStage: 'deploy',
+ },
+ path: 'pipeline/28029444',
+ flags: {
+ merge_request_pipeline: true,
+ detached_merge_request_pipeline: false,
+ },
+ merge_request: {
+ iid: 1234,
+ path: '/root/detached-merge-request-pipelines/merge_requests/1',
+ title: 'Update README.md',
+ source_branch: 'feature-1234',
+ source_branch_path: '/root/detached-merge-request-pipelines/branches/feature-1234',
+ target_branch: 'master',
+ target_branch_path: '/root/detached-merge-request-pipelines/branches/master',
+ },
+ ref: {
+ name: 'test-branch',
+ },
+ };
+
+ describe('without a merge request pipeline', () => {
+ let pipeline;
+
+ beforeEach(() => {
+ pipeline = JSON.parse(JSON.stringify(mockPipelineData));
+ delete pipeline.merge_request;
+ delete pipeline.flags.merge_request_pipeline;
+ delete pipeline.flags.detached_merge_request_pipeline;
+
+ vm = mountComponent(Component, {
+ pipeline,
+ stages: [{ name: 'build' }, { name: 'test' }],
+ selectedStage: 'deploy',
+ });
});
- });
- afterEach(() => {
- vm.$destroy();
- });
+ afterEach(() => {
+ vm.$destroy();
+ });
- it('renders pipeline status', () => {
- expect(vm.$el.querySelector('.js-ci-status-icon-success')).not.toBeNull();
- });
+ it('renders pipeline status', () => {
+ expect(vm.$el.querySelector('.js-ci-status-icon-success')).not.toBeNull();
+ });
+
+ it('renders pipeline link', () => {
+ expect(vm.$el.querySelector('.js-pipeline-path').getAttribute('href')).toEqual(
+ 'pipeline/28029444',
+ );
+ });
+
+ it('renders dropdown with stages', () => {
+ expect(vm.$el.querySelector('.dropdown .js-stage-item').textContent).toContain('build');
+ });
+
+ it('rendes selected stage', () => {
+ expect(vm.$el.querySelector('.dropdown .js-selected-stage').textContent).toContain('deploy');
+ });
- it('renders pipeline link', () => {
- expect(vm.$el.querySelector('.js-pipeline-path').getAttribute('href')).toEqual(
- 'pipeline/28029444',
- );
+ it(`renders the pipeline info text like "Pipeline #123 for source_branch"`, () => {
+ const expected = `Pipeline #${pipeline.id} for ${pipeline.ref.name}`;
+ const actual = trimText(vm.$el.querySelector('.js-pipeline-info').innerText);
+
+ expect(actual).toBe(expected);
+ });
});
- it('renders dropdown with stages', () => {
- expect(vm.$el.querySelector('.dropdown .js-stage-item').textContent).toContain('build');
+ describe('with an "attached" merge request pipeline', () => {
+ let pipeline;
+
+ beforeEach(() => {
+ pipeline = JSON.parse(JSON.stringify(mockPipelineData));
+ pipeline.flags.merge_request_pipeline = true;
+ pipeline.flags.detached_merge_request_pipeline = false;
+
+ vm = mountComponent(Component, {
+ pipeline,
+ stages: [],
+ selectedStage: 'deploy',
+ });
+ });
+
+ it(`renders the pipeline info text like "Pipeline #123 for !456 with source_branch into target_branch"`, () => {
+ const expected = `Pipeline #${pipeline.id} for !${pipeline.merge_request.iid} with ${
+ pipeline.merge_request.source_branch
+ } into ${pipeline.merge_request.target_branch}`;
+ const actual = trimText(vm.$el.querySelector('.js-pipeline-info').innerText);
+
+ expect(actual).toBe(expected);
+ });
+
+ it(`renders the correct merge request link`, () => {
+ const actual = vm.$el.querySelector('.js-mr-link').href;
+
+ expect(actual).toContain(pipeline.merge_request.path);
+ });
+
+ it(`renders the correct source branch link`, () => {
+ const actual = vm.$el.querySelector('.js-source-branch-link').href;
+
+ expect(actual).toContain(pipeline.merge_request.source_branch_path);
+ });
+
+ it(`renders the correct target branch link`, () => {
+ const actual = vm.$el.querySelector('.js-target-branch-link').href;
+
+ expect(actual).toContain(pipeline.merge_request.target_branch_path);
+ });
});
- it('rendes selected stage', () => {
- expect(vm.$el.querySelector('.dropdown .js-selected-stage').textContent).toContain('deploy');
+ describe('with a detached merge request pipeline', () => {
+ let pipeline;
+
+ beforeEach(() => {
+ pipeline = JSON.parse(JSON.stringify(mockPipelineData));
+ pipeline.flags.merge_request_pipeline = false;
+ pipeline.flags.detached_merge_request_pipeline = true;
+
+ vm = mountComponent(Component, {
+ pipeline,
+ stages: [],
+ selectedStage: 'deploy',
+ });
+ });
+
+ it(`renders the pipeline info like "Pipeline #123 for !456 with source_branch"`, () => {
+ const expected = `Pipeline #${pipeline.id} for !${pipeline.merge_request.iid} with ${
+ pipeline.merge_request.source_branch
+ }`;
+ const actual = trimText(vm.$el.querySelector('.js-pipeline-info').innerText);
+
+ expect(actual).toBe(expected);
+ });
+
+ it(`renders the correct merge request link`, () => {
+ const actual = vm.$el.querySelector('.js-mr-link').href;
+
+ expect(actual).toContain(pipeline.merge_request.path);
+ });
+
+ it(`renders the correct source branch link`, () => {
+ const actual = vm.$el.querySelector('.js-source-branch-link').href;
+
+ expect(actual).toContain(pipeline.merge_request.source_branch_path);
+ });
});
});
diff --git a/spec/javascripts/pipelines/pipeline_url_spec.js b/spec/javascripts/pipelines/pipeline_url_spec.js
index ea917b36526..faad49a78b0 100644
--- a/spec/javascripts/pipelines/pipeline_url_spec.js
+++ b/spec/javascripts/pipelines/pipeline_url_spec.js
@@ -100,7 +100,8 @@ describe('Pipeline Url Component', () => {
latest: true,
yaml_errors: true,
stuck: true,
- merge_request: true,
+ merge_request_pipeline: true,
+ detached_merge_request_pipeline: true,
},
},
autoDevopsHelpPath: 'foo',
@@ -108,15 +109,16 @@ describe('Pipeline Url Component', () => {
}).$mount();
expect(component.$el.querySelector('.js-pipeline-url-latest').textContent).toContain('latest');
+
expect(component.$el.querySelector('.js-pipeline-url-yaml').textContent).toContain(
'yaml invalid',
);
- expect(component.$el.querySelector('.js-pipeline-url-mergerequest').textContent).toContain(
- 'merge request',
- );
-
expect(component.$el.querySelector('.js-pipeline-url-stuck').textContent).toContain('stuck');
+
+ expect(component.$el.querySelector('.js-pipeline-url-detached').textContent).toContain(
+ 'detached',
+ );
});
it('should render a badge for autodevops', () => {
diff --git a/spec/javascripts/vue_shared/components/commit_spec.js b/spec/javascripts/vue_shared/components/commit_spec.js
index 18fcdf7ede1..f2e20f626b5 100644
--- a/spec/javascripts/vue_shared/components/commit_spec.js
+++ b/spec/javascripts/vue_shared/components/commit_spec.js
@@ -61,7 +61,7 @@ describe('Commit component', () => {
});
it('should render a tag icon if it represents a tag', () => {
- expect(component.$el.querySelector('.icon-container i').classList).toContain('fa-tag');
+ expect(component.$el.querySelector('.icon-container svg.ic-tag')).not.toBeNull();
});
it('should render a link to the ref url', () => {
@@ -143,4 +143,92 @@ describe('Commit component', () => {
);
});
});
+
+ describe('When commit ref is provided, but merge ref is not', () => {
+ it('should render the commit ref', () => {
+ props = {
+ tag: false,
+ commitRef: {
+ name: 'master',
+ ref_url: 'http://localhost/namespace2/gitlabhq/tree/master',
+ },
+ commitUrl:
+ 'https://gitlab.com/gitlab-org/gitlab-ce/commit/b7836eddf62d663c665769e1b0960197fd215067',
+ shortSha: 'b7836edd',
+ title: null,
+ author: {},
+ };
+
+ component = mountComponent(CommitComponent, props);
+ const refEl = component.$el.querySelector('.ref-name');
+
+ expect(refEl.textContent).toContain('master');
+
+ expect(refEl.href).toBe(props.commitRef.ref_url);
+
+ expect(refEl.getAttribute('data-original-title')).toBe(props.commitRef.name);
+
+ expect(component.$el.querySelector('.icon-container .ic-branch')).not.toBeNull();
+ });
+ });
+
+ describe('When both commit and merge ref are provided', () => {
+ it('should render the merge ref', () => {
+ props = {
+ tag: false,
+ commitRef: {
+ name: 'master',
+ ref_url: 'http://localhost/namespace2/gitlabhq/tree/master',
+ },
+ commitUrl:
+ 'https://gitlab.com/gitlab-org/gitlab-ce/commit/b7836eddf62d663c665769e1b0960197fd215067',
+ mergeRequestRef: {
+ iid: 1234,
+ path: 'https://example.com/path/to/mr',
+ title: 'Test MR',
+ },
+ shortSha: 'b7836edd',
+ title: null,
+ author: {},
+ };
+
+ component = mountComponent(CommitComponent, props);
+ const refEl = component.$el.querySelector('.ref-name');
+
+ expect(refEl.textContent).toContain('1234');
+
+ expect(refEl.href).toBe(props.mergeRequestRef.path);
+
+ expect(refEl.getAttribute('data-original-title')).toBe(props.mergeRequestRef.title);
+
+ expect(component.$el.querySelector('.icon-container .ic-git-merge')).not.toBeNull();
+ });
+ });
+
+ describe('When showRefInfo === false', () => {
+ it('should not render any ref info', () => {
+ props = {
+ tag: false,
+ commitRef: {
+ name: 'master',
+ ref_url: 'http://localhost/namespace2/gitlabhq/tree/master',
+ },
+ commitUrl:
+ 'https://gitlab.com/gitlab-org/gitlab-ce/commit/b7836eddf62d663c665769e1b0960197fd215067',
+ mergeRequestRef: {
+ iid: 1234,
+ path: '/path/to/mr',
+ title: 'Test MR',
+ },
+ shortSha: 'b7836edd',
+ title: null,
+ author: {},
+ showRefInfo: false,
+ };
+
+ component = mountComponent(CommitComponent, props);
+
+ expect(component.$el.querySelector('.ref-name')).toBeNull();
+ });
+ });
});
diff --git a/spec/lib/banzai/filter/output_safety_spec.rb b/spec/lib/banzai/filter/output_safety_spec.rb
new file mode 100644
index 00000000000..5ffe591c9a4
--- /dev/null
+++ b/spec/lib/banzai/filter/output_safety_spec.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Banzai::Filter::OutputSafety do
+ subject do
+ Class.new do
+ include Banzai::Filter::OutputSafety
+ end.new
+ end
+
+ let(:content) { '<pre><code>foo</code></pre>' }
+
+ context 'when given HTML is safe' do
+ let(:html) { content.html_safe }
+
+ it 'returns safe HTML' do
+ expect(subject.escape_once(html)).to eq(html)
+ end
+ end
+
+ context 'when given HTML is not safe' do
+ let(:html) { content }
+
+ it 'returns escaped HTML' do
+ expect(subject.escape_once(html)).to eq(ERB::Util.html_escape_once(html))
+ end
+ end
+end
diff --git a/spec/lib/banzai/filter/suggestion_filter_spec.rb b/spec/lib/banzai/filter/suggestion_filter_spec.rb
index b13c90b54bd..af6f002fa30 100644
--- a/spec/lib/banzai/filter/suggestion_filter_spec.rb
+++ b/spec/lib/banzai/filter/suggestion_filter_spec.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
describe Banzai::Filter::SuggestionFilter do
include FilterSpecHelper
- let(:input) { "<pre class='code highlight js-syntax-highlight suggestion'><code>foo\n</code></pre>" }
+ let(:input) { %(<pre class="code highlight js-syntax-highlight suggestion"><code>foo\n</code></pre>) }
let(:default_context) do
{ suggestions_filter_enabled: true }
end
@@ -23,4 +23,35 @@ describe Banzai::Filter::SuggestionFilter do
expect(result[:class]).to be_nil
end
+
+ context 'multi-line suggestions' do
+ let(:data_attr) { Banzai::Filter::SyntaxHighlightFilter::LANG_PARAMS_ATTR }
+ let(:input) { %(<pre class="code highlight js-syntax-highlight suggestion" #{data_attr}="-3+2"><code>foo\n</code></pre>) }
+
+ context 'feature disabled' do
+ before do
+ stub_feature_flags(multi_line_suggestions: false)
+ end
+
+ it 'removes data-lang-params if it matches a multi-line suggestion param' do
+ doc = filter(input, default_context)
+ pre = doc.css('pre').first
+
+ expect(pre[data_attr]).to be_nil
+ end
+ end
+
+ context 'feature enabled' do
+ before do
+ stub_feature_flags(multi_line_suggestions: true)
+ end
+
+ it 'keeps data-lang-params' do
+ doc = filter(input, default_context)
+ pre = doc.css('pre').first
+
+ expect(pre[data_attr]).to eq('-3+2')
+ end
+ end
+ end
end
diff --git a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb
index ef52c572898..05057789cc1 100644
--- a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb
+++ b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb
@@ -45,7 +45,10 @@ describe Banzai::Filter::SyntaxHighlightFilter do
end
context "languages that should be passed through" do
- %w(math mermaid plantuml).each do |lang|
+ let(:delimiter) { described_class::PARAMS_DELIMITER }
+ let(:data_attr) { described_class::LANG_PARAMS_ATTR }
+
+ %w(math mermaid plantuml suggestion).each do |lang|
context "when #{lang} is specified" do
it "highlights as plaintext but with the correct language attribute and class" do
result = filter(%{<pre><code lang="#{lang}">This is a test</code></pre>})
@@ -55,6 +58,33 @@ describe Banzai::Filter::SyntaxHighlightFilter do
include_examples "XSS prevention", lang
end
+
+ context "when #{lang} has extra params" do
+ let(:lang_params) { 'foo-bar-kux' }
+
+ it "includes data-lang-params tag with extra information" do
+ result = filter(%{<pre><code lang="#{lang}#{delimiter}#{lang_params}">This is a test</code></pre>})
+
+ expect(result.to_html).to eq(%{<pre class="code highlight js-syntax-highlight #{lang}" lang="#{lang}" #{data_attr}="#{lang_params}" v-pre="true"><code><span id="LC1" class="line" lang="#{lang}">This is a test</span></code></pre>})
+ end
+
+ include_examples "XSS prevention", lang
+ include_examples "XSS prevention",
+ "#{lang}#{described_class::PARAMS_DELIMITER}&lt;script&gt;alert(1)&lt;/script&gt;"
+ include_examples "XSS prevention",
+ "#{lang}#{described_class::PARAMS_DELIMITER}<script>alert(1)</script>"
+ end
+ end
+
+ context 'when multiple param delimiters are used' do
+ let(:lang) { 'suggestion' }
+ let(:lang_params) { '-1+10' }
+
+ it "delimits on the first appearence" do
+ result = filter(%{<pre><code lang="#{lang}#{delimiter}#{lang_params}#{delimiter}more-things">This is a test</code></pre>})
+
+ expect(result.to_html).to eq(%{<pre class="code highlight js-syntax-highlight #{lang}" lang="#{lang}" #{data_attr}="#{lang_params}#{delimiter}more-things" v-pre="true"><code><span id="LC1" class="line" lang="#{lang}">This is a test</span></code></pre>})
+ end
end
end
diff --git a/spec/lib/gitlab/badge/pipeline/template_spec.rb b/spec/lib/gitlab/badge/pipeline/template_spec.rb
index 20fa4f879c3..bcef0b7e120 100644
--- a/spec/lib/gitlab/badge/pipeline/template_spec.rb
+++ b/spec/lib/gitlab/badge/pipeline/template_spec.rb
@@ -59,6 +59,16 @@ describe Gitlab::Badge::Pipeline::Template do
end
end
+ context 'when status is preparing' do
+ before do
+ allow(badge).to receive(:status).and_return('preparing')
+ end
+
+ it 'has expected color' do
+ expect(template.value_color).to eq '#dfb317'
+ end
+ end
+
context 'when status is unknown' do
before do
allow(badge).to receive(:status).and_return('unknown')
diff --git a/spec/lib/gitlab/checks/branch_check_spec.rb b/spec/lib/gitlab/checks/branch_check_spec.rb
index 12beeecd470..8d5ab27a17c 100644
--- a/spec/lib/gitlab/checks/branch_check_spec.rb
+++ b/spec/lib/gitlab/checks/branch_check_spec.rb
@@ -108,64 +108,86 @@ describe Gitlab::Checks::BranchCheck do
end
context 'protected branch creation feature is enabled' do
- context 'user is not allowed to create protected branches' do
+ context 'user can push to branch' do
before do
allow(user_access)
- .to receive(:can_merge_to_branch?)
+ .to receive(:can_push_to_branch?)
.with('feature')
- .and_return(false)
+ .and_return(true)
end
- it 'raises an error' do
- expect { subject.validate! }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to create protected branches on this project.')
+ it 'does not raise an error' do
+ expect { subject.validate! }.not_to raise_error
end
end
- context 'user is allowed to create protected branches' do
+ context 'user cannot push to branch' do
before do
allow(user_access)
- .to receive(:can_merge_to_branch?)
+ .to receive(:can_push_to_branch?)
.with('feature')
- .and_return(true)
-
- allow(project.repository)
- .to receive(:branch_names_contains_sha)
- .with(newrev)
- .and_return(['branch'])
+ .and_return(false)
end
- context "newrev isn't in any protected branches" do
+ context 'user cannot merge to branch' do
before do
- allow(ProtectedBranch)
- .to receive(:any_protected?)
- .with(project, ['branch'])
+ allow(user_access)
+ .to receive(:can_merge_to_branch?)
+ .with('feature')
.and_return(false)
end
it 'raises an error' do
- expect { subject.validate! }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You can only use an existing protected branch ref as the basis of a new protected branch.')
+ expect { subject.validate! }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to create protected branches on this project.')
end
end
- context 'newrev is included in a protected branch' do
+ context 'user can merge to branch' do
before do
- allow(ProtectedBranch)
- .to receive(:any_protected?)
- .with(project, ['branch'])
+ allow(user_access)
+ .to receive(:can_merge_to_branch?)
+ .with('feature')
.and_return(true)
+
+ allow(project.repository)
+ .to receive(:branch_names_contains_sha)
+ .with(newrev)
+ .and_return(['branch'])
end
- context 'via web interface' do
- let(:protocol) { 'web' }
+ context "newrev isn't in any protected branches" do
+ before do
+ allow(ProtectedBranch)
+ .to receive(:any_protected?)
+ .with(project, ['branch'])
+ .and_return(false)
+ end
- it 'allows branch creation' do
- expect { subject.validate! }.not_to raise_error
+ it 'raises an error' do
+ expect { subject.validate! }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You can only use an existing protected branch ref as the basis of a new protected branch.')
end
end
- context 'via SSH' do
- it 'raises an error' do
- expect { subject.validate! }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You can only create protected branches using the web interface and API.')
+ context 'newrev is included in a protected branch' do
+ before do
+ allow(ProtectedBranch)
+ .to receive(:any_protected?)
+ .with(project, ['branch'])
+ .and_return(true)
+ end
+
+ context 'via web interface' do
+ let(:protocol) { 'web' }
+
+ it 'allows branch creation' do
+ expect { subject.validate! }.not_to raise_error
+ end
+ end
+
+ context 'via SSH' do
+ it 'raises an error' do
+ expect { subject.validate! }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You can only create protected branches using the web interface and API.')
+ end
end
end
end
diff --git a/spec/lib/gitlab/ci/build/prerequisite/factory_spec.rb b/spec/lib/gitlab/ci/build/prerequisite/factory_spec.rb
new file mode 100644
index 00000000000..5187f99a441
--- /dev/null
+++ b/spec/lib/gitlab/ci/build/prerequisite/factory_spec.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Ci::Build::Prerequisite::Factory do
+ let(:build) { create(:ci_build) }
+
+ describe '.for_build' do
+ let(:kubernetes_namespace) do
+ instance_double(
+ Gitlab::Ci::Build::Prerequisite::KubernetesNamespace,
+ unmet?: unmet)
+ end
+
+ subject { described_class.new(build).unmet }
+
+ before do
+ expect(Gitlab::Ci::Build::Prerequisite::KubernetesNamespace)
+ .to receive(:new).with(build).and_return(kubernetes_namespace)
+ end
+
+ context 'prerequisite is unmet' do
+ let(:unmet) { true }
+
+ it { is_expected.to eq [kubernetes_namespace] }
+ end
+
+ context 'prerequisite is met' do
+ let(:unmet) { false }
+
+ it { is_expected.to be_empty }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb b/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb
new file mode 100644
index 00000000000..62dcd80fad7
--- /dev/null
+++ b/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Ci::Build::Prerequisite::KubernetesNamespace do
+ let(:build) { create(:ci_build) }
+
+ describe '#unmet?' do
+ subject { described_class.new(build).unmet? }
+
+ context 'build has no deployment' do
+ before do
+ expect(build.deployment).to be_nil
+ end
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'build has a deployment' do
+ let!(:deployment) { create(:deployment, deployable: build) }
+
+ context 'and a cluster to deploy to' do
+ let(:cluster) { create(:cluster, projects: [build.project]) }
+
+ before do
+ allow(build.deployment).to receive(:cluster).and_return(cluster)
+ end
+
+ it { is_expected.to be_truthy }
+
+ context 'and a namespace is already created for this project' do
+ let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, cluster: cluster, project: build.project) }
+
+ it { is_expected.to be_falsey }
+ end
+ end
+
+ context 'and no cluster to deploy to' do
+ before do
+ expect(deployment.cluster).to be_nil
+ end
+
+ it { is_expected.to be_falsey }
+ end
+ end
+ end
+
+ describe '#complete!' do
+ let!(:deployment) { create(:deployment, deployable: build) }
+ let(:service) { double(execute: true) }
+
+ subject { described_class.new(build).complete! }
+
+ context 'completion is required' do
+ let(:cluster) { create(:cluster, projects: [build.project]) }
+
+ before do
+ allow(build.deployment).to receive(:cluster).and_return(cluster)
+ end
+
+ it 'creates a kubernetes namespace' do
+ expect(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService)
+ .to receive(:new)
+ .with(cluster: cluster, kubernetes_namespace: instance_of(Clusters::KubernetesNamespace))
+ .and_return(service)
+
+ expect(service).to receive(:execute).once
+
+ subject
+ end
+ end
+
+ context 'completion is not required' do
+ before do
+ expect(deployment.cluster).to be_nil
+ end
+
+ it 'does not create a namespace' do
+ expect(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService).not_to receive(:new)
+
+ subject
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/status/build/preparing_spec.rb b/spec/lib/gitlab/ci/status/build/preparing_spec.rb
new file mode 100644
index 00000000000..4d8945845ba
--- /dev/null
+++ b/spec/lib/gitlab/ci/status/build/preparing_spec.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Ci::Status::Build::Preparing do
+ subject do
+ described_class.new(double('subject'))
+ end
+
+ describe '#illustration' do
+ it { expect(subject.illustration).to include(:image, :size, :title, :content) }
+ end
+
+ describe '.matches?' do
+ subject { described_class.matches?(build, nil) }
+
+ context 'when build is preparing' do
+ let(:build) { create(:ci_build, :preparing) }
+
+ it 'is a correct match' do
+ expect(subject).to be true
+ end
+ end
+
+ context 'when build is not preparing' do
+ let(:build) { create(:ci_build, :success) }
+
+ it 'does not match' do
+ expect(subject).to be false
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/status/preparing_spec.rb b/spec/lib/gitlab/ci/status/preparing_spec.rb
new file mode 100644
index 00000000000..7211c0e506d
--- /dev/null
+++ b/spec/lib/gitlab/ci/status/preparing_spec.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Ci::Status::Preparing do
+ subject do
+ described_class.new(double('subject'), nil)
+ end
+
+ describe '#text' do
+ it { expect(subject.text).to eq 'preparing' }
+ end
+
+ describe '#label' do
+ it { expect(subject.label).to eq 'preparing' }
+ end
+
+ describe '#icon' do
+ it { expect(subject.icon).to eq 'status_created' }
+ end
+
+ describe '#favicon' do
+ it { expect(subject.favicon).to eq 'favicon_status_created' }
+ end
+
+ describe '#group' do
+ it { expect(subject.group).to eq 'preparing' }
+ end
+end
diff --git a/spec/lib/gitlab/json_cache_spec.rb b/spec/lib/gitlab/json_cache_spec.rb
index 2cae8ec031a..b82c09af306 100644
--- a/spec/lib/gitlab/json_cache_spec.rb
+++ b/spec/lib/gitlab/json_cache_spec.rb
@@ -7,7 +7,7 @@ describe Gitlab::JsonCache do
let(:namespace) { 'geo' }
let(:key) { 'foo' }
let(:expanded_key) { "#{namespace}:#{key}:#{Rails.version}" }
- let(:broadcast_message) { create(:broadcast_message) }
+ set(:broadcast_message) { create(:broadcast_message) }
subject(:cache) { described_class.new(namespace: namespace, backend: backend) }
@@ -146,6 +146,18 @@ describe Gitlab::JsonCache do
expect(cache.read(key, BroadcastMessage)).to be_nil
end
+
+ it 'gracefully handles excluded fields from attributes during serialization' do
+ allow(backend).to receive(:read)
+ .with(expanded_key)
+ .and_return(broadcast_message.attributes.except("message_html").to_json)
+
+ result = cache.read(key, BroadcastMessage)
+
+ BroadcastMessage.cached_markdown_fields.html_fields.each do |field|
+ expect(result.public_send(field)).to be_nil
+ end
+ end
end
context 'when the cached value is an array' do
@@ -321,6 +333,46 @@ describe Gitlab::JsonCache do
expect(result).to be_new_record
end
+
+ it 'gracefully handles bad cached entry' do
+ allow(backend).to receive(:read)
+ .with(expanded_key)
+ .and_return('{')
+
+ result = cache.fetch(key, as: BroadcastMessage) { 'block result' }
+
+ expect(result).to eq 'block result'
+ end
+
+ it 'gracefully handles an empty hash' do
+ allow(backend).to receive(:read)
+ .with(expanded_key)
+ .and_return('{}')
+
+ expect(cache.fetch(key, as: BroadcastMessage)).to be_a(BroadcastMessage)
+ end
+
+ it 'gracefully handles unknown attributes' do
+ allow(backend).to receive(:read)
+ .with(expanded_key)
+ .and_return(broadcast_message.attributes.merge(unknown_attribute: 1).to_json)
+
+ result = cache.fetch(key, as: BroadcastMessage) { 'block result' }
+
+ expect(result).to eq 'block result'
+ end
+
+ it 'gracefully handles excluded fields from attributes during serialization' do
+ allow(backend).to receive(:read)
+ .with(expanded_key)
+ .and_return(broadcast_message.attributes.except("message_html").to_json)
+
+ result = cache.fetch(key, as: BroadcastMessage) { 'block result' }
+
+ BroadcastMessage.cached_markdown_fields.html_fields.each do |field|
+ expect(result.public_send(field)).to be_nil
+ end
+ end
end
it "returns the result of the block when 'as' option is nil" do
diff --git a/spec/lib/gitlab/kubernetes_spec.rb b/spec/lib/gitlab/kubernetes_spec.rb
index f326d57e9c6..57b570a9166 100644
--- a/spec/lib/gitlab/kubernetes_spec.rb
+++ b/spec/lib/gitlab/kubernetes_spec.rb
@@ -40,10 +40,40 @@ describe Gitlab::Kubernetes do
describe '#filter_by_label' do
it 'returns matching labels' do
- matching_items = [kube_pod(app: 'foo')]
+ matching_items = [kube_pod(track: 'foo'), kube_deployment(track: 'foo')]
+ items = matching_items + [kube_pod, kube_deployment]
+
+ expect(filter_by_label(items, 'track' => 'foo')).to eq(matching_items)
+ end
+ end
+
+ describe '#filter_by_annotation' do
+ it 'returns matching labels' do
+ matching_items = [kube_pod(environment_slug: 'foo'), kube_deployment(environment_slug: 'foo')]
+ items = matching_items + [kube_pod, kube_deployment]
+
+ expect(filter_by_annotation(items, 'app.gitlab.com/env' => 'foo')).to eq(matching_items)
+ end
+ end
+
+ describe '#filter_by_project_environment' do
+ let(:matching_pod) { kube_pod(environment_slug: 'production', project_slug: 'my-cool-app') }
+
+ it 'returns matching legacy env label' do
+ matching_pod['metadata']['annotations'].delete('app.gitlab.com/app')
+ matching_pod['metadata']['annotations'].delete('app.gitlab.com/env')
+ matching_pod['metadata']['labels']['app'] = 'production'
+ matching_items = [matching_pod]
+ items = matching_items + [kube_pod]
+
+ expect(filter_by_project_environment(items, 'my-cool-app', 'production')).to eq(matching_items)
+ end
+
+ it 'returns matching env label' do
+ matching_items = [matching_pod]
items = matching_items + [kube_pod]
- expect(filter_by_label(items, app: 'foo')).to eq(matching_items)
+ expect(filter_by_project_environment(items, 'my-cool-app', 'production')).to eq(matching_items)
end
end
diff --git a/spec/models/broadcast_message_spec.rb b/spec/models/broadcast_message_spec.rb
index 89839709131..30ca07d5d2c 100644
--- a/spec/models/broadcast_message_spec.rb
+++ b/spec/models/broadcast_message_spec.rb
@@ -95,6 +95,12 @@ describe BroadcastMessage do
end
end
+ describe '#attributes' do
+ it 'includes message_html field' do
+ expect(subject.attributes.keys).to include("cached_markdown_version", "message_html")
+ end
+ end
+
describe '#active?' do
it 'is truthy when started and not ended' do
message = build(:broadcast_message)
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 9ca4241d7d8..7500e6ae5b1 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -186,6 +186,37 @@ describe Ci::Build do
end
end
+ describe '#enqueue' do
+ let(:build) { create(:ci_build, :created) }
+
+ subject { build.enqueue }
+
+ before do
+ allow(build).to receive(:any_unmet_prerequisites?).and_return(has_prerequisites)
+ allow(Ci::PrepareBuildService).to receive(:perform_async)
+ end
+
+ context 'build has unmet prerequisites' do
+ let(:has_prerequisites) { true }
+
+ it 'transitions to preparing' do
+ subject
+
+ expect(build).to be_preparing
+ end
+ end
+
+ context 'build has no prerequisites' do
+ let(:has_prerequisites) { false }
+
+ it 'transitions to pending' do
+ subject
+
+ expect(build).to be_pending
+ end
+ end
+ end
+
describe '#actionize' do
context 'when build is a created' do
before do
@@ -344,6 +375,18 @@ describe Ci::Build do
expect(build).to be_pending
end
+
+ context 'build has unmet prerequisites' do
+ before do
+ allow(build).to receive(:prerequisites).and_return([double])
+ end
+
+ it 'transits to preparing' do
+ subject
+
+ expect(build).to be_preparing
+ end
+ end
end
end
@@ -2876,6 +2919,36 @@ describe Ci::Build do
end
end
+ describe '#any_unmet_prerequisites?' do
+ let(:build) { create(:ci_build, :created) }
+
+ subject { build.any_unmet_prerequisites? }
+
+ context 'build has prerequisites' do
+ before do
+ allow(build).to receive(:prerequisites).and_return([double])
+ end
+
+ it { is_expected.to be_truthy }
+
+ context 'and the ci_preparing_state feature is disabled' do
+ before do
+ stub_feature_flags(ci_preparing_state: false)
+ end
+
+ it { is_expected.to be_falsey }
+ end
+ end
+
+ context 'build does not have prerequisites' do
+ before do
+ allow(build).to receive(:prerequisites).and_return([])
+ end
+
+ it { is_expected.to be_falsey }
+ end
+ end
+
describe '#yaml_variables' do
let(:build) { create(:ci_build, pipeline: pipeline, yaml_variables: variables) }
@@ -2928,6 +3001,20 @@ describe Ci::Build do
end
end
+ describe 'state transition: any => [:preparing]' do
+ let(:build) { create(:ci_build, :created) }
+
+ before do
+ allow(build).to receive(:prerequisites).and_return([double])
+ end
+
+ it 'queues BuildPrepareWorker' do
+ expect(Ci::BuildPrepareWorker).to receive(:perform_async).with(build.id)
+
+ build.enqueue
+ end
+ end
+
describe 'state transition: any => [:pending]' do
let(:build) { create(:ci_build, :created) }
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 7eeaa7a18ef..2ac056f63b2 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -1201,16 +1201,28 @@ describe Ci::Pipeline, :mailer do
end
describe '#started_at' do
- it 'updates on transitioning to running' do
- build.run
+ let(:pipeline) { create(:ci_empty_pipeline, status: from_status) }
+
+ %i[created preparing pending].each do |status|
+ context "from #{status}" do
+ let(:from_status) { status }
- expect(pipeline.reload.started_at).not_to be_nil
+ it 'updates on transitioning to running' do
+ pipeline.run
+
+ expect(pipeline.started_at).not_to be_nil
+ end
+ end
end
- it 'does not update on transitioning to success' do
- build.success
+ context 'from created' do
+ let(:from_status) { :created }
+
+ it 'does not update on transitioning to success' do
+ pipeline.succeed
- expect(pipeline.reload.started_at).to be_nil
+ expect(pipeline.started_at).to be_nil
+ end
end
end
@@ -1229,27 +1241,49 @@ describe Ci::Pipeline, :mailer do
end
describe 'merge request metrics' do
- let(:project) { create(:project, :repository) }
- let(:pipeline) { FactoryBot.create(:ci_empty_pipeline, status: 'created', project: project, ref: 'master', sha: project.repository.commit('master').id) }
- let!(:merge_request) { create(:merge_request, source_project: project, source_branch: pipeline.ref) }
+ let(:pipeline) { create(:ci_empty_pipeline, status: from_status) }
before do
expect(PipelineMetricsWorker).to receive(:perform_async).with(pipeline.id)
end
context 'when transitioning to running' do
- it 'schedules metrics workers' do
- pipeline.run
+ %i[created preparing pending].each do |status|
+ context "from #{status}" do
+ let(:from_status) { status }
+
+ it 'schedules metrics workers' do
+ pipeline.run
+ end
+ end
end
end
context 'when transitioning to success' do
+ let(:from_status) { 'created' }
+
it 'schedules metrics workers' do
pipeline.succeed
end
end
end
+ describe 'merge on success' do
+ let(:pipeline) { create(:ci_empty_pipeline, status: from_status) }
+
+ %i[created preparing pending running].each do |status|
+ context "from #{status}" do
+ let(:from_status) { status }
+
+ it 'schedules pipeline success worker' do
+ expect(PipelineSuccessWorker).to receive(:perform_async).with(pipeline.id)
+
+ pipeline.succeed
+ end
+ end
+ end
+ end
+
describe 'pipeline caching' do
it 'performs ExpirePipelinesCacheWorker' do
expect(ExpirePipelineCacheWorker).to receive(:perform_async).with(pipeline.id)
@@ -1768,6 +1802,18 @@ describe Ci::Pipeline, :mailer do
subject { pipeline.reload.status }
+ context 'on prepare' do
+ before do
+ # Prevent skipping directly to 'pending'
+ allow(build).to receive(:prerequisites).and_return([double])
+ allow(Ci::BuildPrepareWorker).to receive(:perform_async)
+
+ build.enqueue
+ end
+
+ it { is_expected.to eq('preparing') }
+ end
+
context 'on queuing' do
before do
build.enqueue
diff --git a/spec/models/clusters/platforms/kubernetes_spec.rb b/spec/models/clusters/platforms/kubernetes_spec.rb
index cc93a1b4965..af65530e663 100644
--- a/spec/models/clusters/platforms/kubernetes_spec.rb
+++ b/spec/models/clusters/platforms/kubernetes_spec.rb
@@ -375,14 +375,14 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching
end
context 'with valid pods' do
- let(:pod) { kube_pod(app: environment.slug) }
- let(:pod_with_no_terminal) { kube_pod(app: environment.slug, status: "Pending") }
+ let(:pod) { kube_pod(environment_slug: environment.slug, project_slug: project.full_path_slug) }
+ let(:pod_with_no_terminal) { kube_pod(environment_slug: environment.slug, project_slug: project.full_path_slug, status: "Pending") }
let(:terminals) { kube_terminals(service, pod) }
before do
stub_reactive_cache(
service,
- pods: [pod, pod, pod_with_no_terminal, kube_pod(app: "should-be-filtered-out")]
+ pods: [pod, pod, pod_with_no_terminal, kube_pod(environment_slug: "should-be-filtered-out")]
)
end
diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb
index 8b7c88805c1..e2b7f5c6ee2 100644
--- a/spec/models/commit_status_spec.rb
+++ b/spec/models/commit_status_spec.rb
@@ -49,6 +49,16 @@ describe CommitStatus do
commit_status.success!
end
+
+ describe 'transitioning to running' do
+ let(:commit_status) { create(:commit_status, :pending, started_at: nil) }
+
+ it 'records the started at time' do
+ commit_status.run!
+
+ expect(commit_status.started_at).to be_present
+ end
+ end
end
describe '#started?' do
@@ -479,6 +489,12 @@ describe CommitStatus do
it { is_expected.to be_script_failure }
end
+
+ context 'when failure_reason is unmet_prerequisites' do
+ let(:reason) { :unmet_prerequisites }
+
+ it { is_expected.to be_unmet_prerequisites }
+ end
end
describe 'ensure stage assignment' do
@@ -555,6 +571,7 @@ describe CommitStatus do
before do
allow(Time).to receive(:now).and_return(current_time)
+ expect(commit_status.any_unmet_prerequisites?).to eq false
end
shared_examples 'commit status enqueued' do
@@ -569,6 +586,12 @@ describe CommitStatus do
it_behaves_like 'commit status enqueued'
end
+ context 'when initial state is :preparing' do
+ let(:commit_status) { create(:commit_status, :preparing) }
+
+ it_behaves_like 'commit status enqueued'
+ end
+
context 'when initial state is :skipped' do
let(:commit_status) { create(:commit_status, :skipped) }
diff --git a/spec/models/concerns/cache_markdown_field_spec.rb b/spec/models/concerns/cache_markdown_field_spec.rb
index 447279f19a8..7d555f15e39 100644
--- a/spec/models/concerns/cache_markdown_field_spec.rb
+++ b/spec/models/concerns/cache_markdown_field_spec.rb
@@ -23,6 +23,7 @@ describe CacheMarkdownField do
include CacheMarkdownField
cache_markdown_field :foo
cache_markdown_field :baz, pipeline: :single_line
+ cache_markdown_field :zoo, whitelisted: true
def self.add_attr(name)
self.attribute_names += [name]
@@ -35,7 +36,7 @@ describe CacheMarkdownField do
add_attr :cached_markdown_version
- [:foo, :foo_html, :bar, :baz, :baz_html].each do |name|
+ [:foo, :foo_html, :bar, :baz, :baz_html, :zoo, :zoo_html].each do |name|
add_attr(name)
end
@@ -84,8 +85,8 @@ describe CacheMarkdownField do
end
describe '.attributes' do
- it 'excludes cache attributes' do
- expect(thing.attributes.keys.sort).to eq(%w[bar baz foo])
+ it 'excludes cache attributes that is blacklisted by default' do
+ expect(thing.attributes.keys.sort).to eq(%w[bar baz cached_markdown_version foo zoo zoo_html])
end
end
@@ -297,7 +298,12 @@ describe CacheMarkdownField do
it 'saves the changes using #update_columns' do
expect(thing).to receive(:persisted?).and_return(true)
expect(thing).to receive(:update_columns)
- .with("foo_html" => updated_html, "baz_html" => "", "cached_markdown_version" => cache_version)
+ .with(
+ "foo_html" => updated_html,
+ "baz_html" => "",
+ "zoo_html" => "",
+ "cached_markdown_version" => cache_version
+ )
thing.refresh_markdown_cache!
end
diff --git a/spec/models/concerns/has_status_spec.rb b/spec/models/concerns/has_status_spec.rb
index 6b1038cb8fd..e8b1eba67cc 100644
--- a/spec/models/concerns/has_status_spec.rb
+++ b/spec/models/concerns/has_status_spec.rb
@@ -34,6 +34,22 @@ describe HasStatus do
it { is_expected.to eq 'running' }
end
+ context 'all preparing' do
+ let!(:statuses) do
+ [create(type, status: :preparing), create(type, status: :preparing)]
+ end
+
+ it { is_expected.to eq 'preparing' }
+ end
+
+ context 'at least one preparing' do
+ let!(:statuses) do
+ [create(type, status: :success), create(type, status: :preparing)]
+ end
+
+ it { is_expected.to eq 'preparing' }
+ end
+
context 'success and failed but allowed to fail' do
let!(:statuses) do
[create(type, status: :success),
@@ -188,7 +204,7 @@ describe HasStatus do
end
end
- %i[created running pending success
+ %i[created preparing running pending success
failed canceled skipped].each do |status|
it_behaves_like 'having a job', status
end
@@ -234,7 +250,7 @@ describe HasStatus do
describe '.alive' do
subject { CommitStatus.alive }
- %i[running pending created].each do |status|
+ %i[running pending preparing created].each do |status|
it_behaves_like 'containing the job', status
end
@@ -270,7 +286,7 @@ describe HasStatus do
describe '.cancelable' do
subject { CommitStatus.cancelable }
- %i[running pending created scheduled].each do |status|
+ %i[running pending preparing created scheduled].each do |status|
it_behaves_like 'containing the job', status
end
diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb
index a8d53cfcd7d..5fce9504334 100644
--- a/spec/models/deployment_spec.rb
+++ b/spec/models/deployment_spec.rb
@@ -356,4 +356,32 @@ describe Deployment do
end
end
end
+
+ describe '#cluster' do
+ let(:deployment) { create(:deployment) }
+ let(:project) { deployment.project }
+ let(:environment) { deployment.environment }
+
+ subject { deployment.cluster }
+
+ before do
+ expect(project).to receive(:deployment_platform)
+ .with(environment: environment.name).and_call_original
+ end
+
+ context 'project has no deployment platform' do
+ before do
+ expect(project.clusters).to be_empty
+ end
+
+ it { is_expected.to be_nil }
+ end
+
+ context 'project has a deployment platform' do
+ let!(:cluster) { create(:cluster, projects: [project]) }
+ let!(:platform) { create(:cluster_platform_kubernetes, cluster: cluster) }
+
+ it { is_expected.to eq cluster }
+ end
+ end
end
diff --git a/spec/models/diff_note_spec.rb b/spec/models/diff_note_spec.rb
index fda00a693f0..67e5f4f7e41 100644
--- a/spec/models/diff_note_spec.rb
+++ b/spec/models/diff_note_spec.rb
@@ -336,6 +336,16 @@ describe DiffNote do
end
end
+ describe '#banzai_render_context' do
+ let(:note) { create(:diff_note_on_merge_request) }
+
+ it 'includes expected context' do
+ context = note.banzai_render_context(:note)
+
+ expect(context).to include(suggestions_filter_enabled: true, noteable: note.noteable, project: note.project)
+ end
+ end
+
describe "image diff notes" do
subject { build(:image_diff_note_on_merge_request, project: project, noteable: merge_request) }
diff --git a/spec/models/project_services/kubernetes_service_spec.rb b/spec/models/project_services/kubernetes_service_spec.rb
index 47f70e6648a..56e587262ef 100644
--- a/spec/models/project_services/kubernetes_service_spec.rb
+++ b/spec/models/project_services/kubernetes_service_spec.rb
@@ -323,13 +323,14 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do
end
context 'with valid pods' do
- let(:pod) { kube_pod(app: environment.slug) }
+ let(:pod) { kube_pod(environment_slug: environment.slug, project_slug: project.full_path_slug) }
+ let(:pod_with_no_terminal) { kube_pod(environment_slug: environment.slug, project_slug: project.full_path_slug, status: "Pending") }
let(:terminals) { kube_terminals(service, pod) }
before do
stub_reactive_cache(
service,
- pods: [pod, pod, kube_pod(app: "should-be-filtered-out")]
+ pods: [pod, pod, pod_with_no_terminal, kube_pod(environment_slug: "should-be-filtered-out")]
)
end
@@ -360,14 +361,16 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do
context 'when kubernetes responds with valid pods' do
before do
stub_kubeclient_pods
+ stub_kubeclient_deployments # Used by EE
end
- it { is_expected.to eq(pods: [kube_pod]) }
+ it { is_expected.to include(pods: [kube_pod]) }
end
context 'when kubernetes responds with 500s' do
before do
stub_kubeclient_pods(status: 500)
+ stub_kubeclient_deployments(status: 500) # Used by EE
end
it { expect { subject }.to raise_error(Kubeclient::HttpError) }
@@ -376,9 +379,10 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do
context 'when kubernetes responds with 404s' do
before do
stub_kubeclient_pods(status: 404)
+ stub_kubeclient_deployments(status: 404) # Used by EE
end
- it { is_expected.to eq(pods: []) }
+ it { is_expected.to include(pods: []) }
end
end
diff --git a/spec/policies/identity_provider_policy_spec.rb b/spec/policies/identity_provider_policy_spec.rb
new file mode 100644
index 00000000000..2520469d4e7
--- /dev/null
+++ b/spec/policies/identity_provider_policy_spec.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe IdentityProviderPolicy do
+ subject(:policy) { described_class.new(user, provider) }
+ let(:user) { User.new }
+ let(:provider) { :a_provider }
+
+ describe '#rules' do
+ it { is_expected.to be_allowed(:link) }
+ it { is_expected.to be_allowed(:unlink) }
+
+ context 'when user is anonymous' do
+ let(:user) { nil }
+
+ it { is_expected.not_to be_allowed(:link) }
+ it { is_expected.not_to be_allowed(:unlink) }
+ end
+
+ %w[saml cas3].each do |provider_name|
+ context "when provider is #{provider_name}" do
+ let(:provider) { provider_name }
+
+ it { is_expected.to be_allowed(:link) }
+ it { is_expected.not_to be_allowed(:unlink) }
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/project_clusters_spec.rb b/spec/requests/api/project_clusters_spec.rb
index 9bab1f95150..4e42e233b4c 100644
--- a/spec/requests/api/project_clusters_spec.rb
+++ b/spec/requests/api/project_clusters_spec.rb
@@ -331,7 +331,6 @@ describe API::ProjectClusters do
it 'should update cluster attributes' do
expect(cluster.platform_kubernetes.namespace).to eq('new-namespace')
- expect(cluster.kubernetes_namespace.namespace).to eq('new-namespace')
end
end
diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb
index 9087cccb759..3ccedd8dd06 100644
--- a/spec/requests/api/runner_spec.rb
+++ b/spec/requests/api/runner_spec.rb
@@ -918,6 +918,15 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
it { expect(job).to be_job_execution_timeout }
end
+
+ context 'when failure_reason is unmet_prerequisites' do
+ before do
+ update_job(state: 'failed', failure_reason: 'unmet_prerequisites')
+ job.reload
+ end
+
+ it { expect(job).to be_unmet_prerequisites }
+ end
end
context 'when trace is given' do
diff --git a/spec/services/ci/prepare_build_service_spec.rb b/spec/services/ci/prepare_build_service_spec.rb
new file mode 100644
index 00000000000..1797f8f964f
--- /dev/null
+++ b/spec/services/ci/prepare_build_service_spec.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Ci::PrepareBuildService do
+ describe '#execute' do
+ let(:build) { create(:ci_build, :preparing) }
+
+ subject { described_class.new(build).execute }
+
+ before do
+ allow(build).to receive(:prerequisites).and_return(prerequisites)
+ end
+
+ shared_examples 'build enqueueing' do
+ it 'enqueues the build' do
+ expect(build).to receive(:enqueue).once
+
+ subject
+ end
+ end
+
+ context 'build has unmet prerequisites' do
+ let(:prerequisite) { double(complete!: true) }
+ let(:prerequisites) { [prerequisite] }
+
+ it 'completes each prerequisite' do
+ expect(prerequisites).to all(receive(:complete!))
+
+ subject
+ end
+
+ include_examples 'build enqueueing'
+
+ context 'prerequisites fail to complete' do
+ before do
+ allow(build).to receive(:enqueue).and_return(false)
+ end
+
+ it 'drops the build' do
+ expect(build).to receive(:drop!).with(:unmet_prerequisites).once
+
+ subject
+ end
+ end
+ end
+
+ context 'build has no prerequisites' do
+ let(:prerequisites) { [] }
+
+ include_examples 'build enqueueing'
+ end
+ end
+end
diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb
index d1b110b9806..e8418b09dc2 100644
--- a/spec/services/projects/create_service_spec.rb
+++ b/spec/services/projects/create_service_spec.rb
@@ -276,6 +276,7 @@ describe Projects::CreateService, '#execute' do
before do
group.add_owner(user)
+ stub_feature_flags(ci_preparing_state: false)
expect(Clusters::Gcp::Kubernetes::CreateOrUpdateServiceAccountService).to receive(:namespace_creator).and_return(service_account_creator)
expect(Clusters::Gcp::Kubernetes::FetchKubernetesTokenService).to receive(:new).and_return(secrets_fetcher)
end
diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb
index aae50d5307f..4efd360cb30 100644
--- a/spec/services/projects/transfer_service_spec.rb
+++ b/spec/services/projects/transfer_service_spec.rb
@@ -83,6 +83,7 @@ describe Projects::TransferService do
subject { transfer_project(project, user, group) }
before do
+ stub_feature_flags(ci_preparing_state: false)
expect(Clusters::Gcp::Kubernetes::CreateOrUpdateServiceAccountService).to receive(:namespace_creator).and_return(service_account_creator)
expect(Clusters::Gcp::Kubernetes::FetchKubernetesTokenService).to receive(:new).and_return(secrets_fetcher)
end
diff --git a/spec/support/helpers/kubernetes_helpers.rb b/spec/support/helpers/kubernetes_helpers.rb
index cca11e112c9..ac52acb6570 100644
--- a/spec/support/helpers/kubernetes_helpers.rb
+++ b/spec/support/helpers/kubernetes_helpers.rb
@@ -250,16 +250,19 @@ module KubernetesHelpers
# This is a partial response, it will have many more elements in reality but
# these are the ones we care about at the moment
- def kube_pod(name: "kube-pod", app: "valid-pod-label", status: "Running", track: nil)
+ def kube_pod(name: "kube-pod", environment_slug: "production", project_slug: "project-path-slug", status: "Running", track: nil)
{
"metadata" => {
"name" => name,
"generate_name" => "generated-name-with-suffix",
"creationTimestamp" => "2016-11-25T19:55:19Z",
+ "annotations" => {
+ "app.gitlab.com/env" => environment_slug,
+ "app.gitlab.com/app" => project_slug
+ },
"labels" => {
- "app" => app,
"track" => track
- }
+ }.compact
},
"spec" => {
"containers" => [
@@ -293,13 +296,16 @@ module KubernetesHelpers
}
end
- def kube_deployment(name: "kube-deployment", app: "valid-deployment-label", track: nil)
+ def kube_deployment(name: "kube-deployment", environment_slug: "production", project_slug: "project-path-slug", track: nil)
{
"metadata" => {
"name" => name,
"generation" => 4,
+ "annotations" => {
+ "app.gitlab.com/env" => environment_slug,
+ "app.gitlab.com/app" => project_slug
+ },
"labels" => {
- "app" => app,
"track" => track
}.compact
},
diff --git a/spec/workers/ci/build_prepare_worker_spec.rb b/spec/workers/ci/build_prepare_worker_spec.rb
new file mode 100644
index 00000000000..9f76696ee66
--- /dev/null
+++ b/spec/workers/ci/build_prepare_worker_spec.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Ci::BuildPrepareWorker do
+ subject { described_class.new.perform(build_id) }
+
+ context 'build exists' do
+ let(:build) { create(:ci_build) }
+ let(:build_id) { build.id }
+ let(:service) { double(execute: true) }
+
+ it 'calls the prepare build service' do
+ expect(Ci::PrepareBuildService).to receive(:new).with(build).and_return(service)
+ expect(service).to receive(:execute).once
+
+ subject
+ end
+ end
+
+ context 'build does not exist' do
+ let(:build_id) { -1 }
+
+ it 'does not attempt to prepare the build' do
+ expect(Ci::PrepareBuildService).not_to receive(:new)
+
+ subject
+ end
+ end
+end
diff --git a/spec/workers/cluster_configure_worker_spec.rb b/spec/workers/cluster_configure_worker_spec.rb
index 6918ee3d7d8..83f76809435 100644
--- a/spec/workers/cluster_configure_worker_spec.rb
+++ b/spec/workers/cluster_configure_worker_spec.rb
@@ -4,6 +4,11 @@ require 'spec_helper'
describe ClusterConfigureWorker, '#perform' do
let(:worker) { described_class.new }
+ let(:ci_preparing_state_enabled) { false }
+
+ before do
+ stub_feature_flags(ci_preparing_state: ci_preparing_state_enabled)
+ end
context 'when group cluster' do
let(:cluster) { create(:cluster, :group, :provided_by_gcp) }
@@ -66,4 +71,15 @@ describe ClusterConfigureWorker, '#perform' do
described_class.new.perform(123)
end
end
+
+ context 'ci_preparing_state feature is enabled' do
+ let(:cluster) { create(:cluster) }
+ let(:ci_preparing_state_enabled) { true }
+
+ it 'does not configure the cluster' do
+ expect(Clusters::RefreshService).not_to receive(:create_or_update_namespaces_for_cluster)
+
+ described_class.new.perform(cluster.id)
+ end
+ end
end
diff --git a/spec/workers/cluster_project_configure_worker_spec.rb b/spec/workers/cluster_project_configure_worker_spec.rb
new file mode 100644
index 00000000000..afdea55adf4
--- /dev/null
+++ b/spec/workers/cluster_project_configure_worker_spec.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ClusterProjectConfigureWorker, '#perform' do
+ let(:worker) { described_class.new }
+
+ context 'ci_preparing_state feature is enabled' do
+ let(:cluster) { create(:cluster) }
+
+ before do
+ stub_feature_flags(ci_preparing_state: true)
+ end
+
+ it 'does not configure the cluster' do
+ expect(Clusters::RefreshService).not_to receive(:create_or_update_namespaces_for_project)
+
+ described_class.new.perform(cluster.id)
+ end
+ end
+end
diff --git a/vendor/project_templates/android.tar.gz b/vendor/project_templates/android.tar.gz
index 3df17a0d9a6..277aedaa1ca 100644
--- a/vendor/project_templates/android.tar.gz
+++ b/vendor/project_templates/android.tar.gz
Binary files differ