summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md4
-rw-r--r--app/assets/javascripts/boards/components/boards_selector.vue4
-rw-r--r--app/assets/javascripts/project_select.js17
-rw-r--r--app/assets/javascripts/repository/components/last_commit.vue11
-rw-r--r--app/assets/javascripts/repository/components/table/index.vue1
-rw-r--r--app/assets/javascripts/repository/components/table/row.vue6
-rw-r--r--app/assets/javascripts/repository/log_tree.js9
-rw-r--r--app/assets/javascripts/repository/queries/getFiles.query.graphql1
-rw-r--r--app/assets/javascripts/repository/queries/pathLastCommit.query.graphql1
-rw-r--r--app/assets/javascripts/vue_shared/components/file_row.vue2
-rw-r--r--app/assets/stylesheets/framework/filters.scss16
-rw-r--r--app/assets/stylesheets/pages/boards.scss8
-rw-r--r--app/graphql/types/commit_type.rb2
-rw-r--r--app/graphql/types/issue_sort_enum.rb1
-rw-r--r--app/graphql/types/tree/entry_type.rb1
-rw-r--r--app/models/ci/build.rb6
-rw-r--r--app/models/ci/pipeline.rb91
-rw-r--r--app/models/concerns/storage/legacy_namespace.rb32
-rw-r--r--app/models/project_ci_cd_setting.rb3
-rw-r--r--app/presenters/project_presenter.rb22
-rw-r--r--app/services/ci/create_pipeline_service.rb6
-rw-r--r--app/views/projects/pipelines/_with_tabs.html.haml4
-rw-r--r--app/views/shared/issuable/_search_bar.html.haml321
-rw-r--r--app/workers/gitlab_shell_worker.rb4
-rw-r--r--changelogs/unreleased/28302-move-add-license-button.yml5
-rw-r--r--changelogs/unreleased/29713-graphql-add-issue-relative-position-sort-2.yml5
-rw-r--r--changelogs/unreleased/Update-boards_selector-vue-to-use-boardsStore.yml5
-rw-r--r--changelogs/unreleased/remove_var_from_project_select_js.yml5
-rw-r--r--changelogs/unreleased/sh-add-exception-backtrace-production-log.yml5
-rw-r--r--changelogs/unreleased/sh-support-project-template-id-in-api.yml5
-rw-r--r--config/initializers/lograge.rb19
-rw-r--r--doc/administration/logs.md42
-rw-r--r--doc/api/graphql/reference/gitlab_schema.graphql30
-rw-r--r--doc/api/graphql/reference/gitlab_schema.json92
-rw-r--r--doc/api/graphql/reference/index.md4
-rw-r--r--doc/api/projects.md1
-rw-r--r--doc/development/fe_guide/graphql.md8
-rw-r--r--lib/api/projects.rb3
-rw-r--r--lib/gitlab/ci/pipeline/chain/base.rb2
-rw-r--r--lib/gitlab/ci/pipeline/chain/build.rb2
-rw-r--r--lib/gitlab/ci/pipeline/chain/command.rb4
-rw-r--r--lib/gitlab/ci/pipeline/chain/config/content.rb59
-rw-r--r--lib/gitlab/ci/pipeline/chain/config/process.rb41
-rw-r--r--lib/gitlab/ci/pipeline/chain/evaluate_workflow_rules.rb2
-rw-r--r--lib/gitlab/ci/pipeline/chain/populate.rb21
-rw-r--r--lib/gitlab/ci/pipeline/chain/remove_unwanted_chat_jobs.rb6
-rw-r--r--lib/gitlab/ci/pipeline/chain/seed.rb64
-rw-r--r--lib/gitlab/ci/pipeline/chain/validate/config.rb33
-rw-r--r--lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces.rb10
-rw-r--r--lib/gitlab/gitaly_client/namespace_service.rb15
-rw-r--r--locale/gitlab.pot13
-rw-r--r--qa/qa/vendor/github/page/login.rb2
-rw-r--r--spec/features/commits_spec.rb33
-rw-r--r--spec/features/issuables/sorting_list_spec.rb24
-rw-r--r--spec/features/projects/commits/user_browses_commits_spec.rb4
-rw-r--r--spec/features/projects/files/files_sort_submodules_with_folders_spec.rb4
-rw-r--r--spec/features/projects/files/project_owner_creates_license_file_spec.rb3
-rw-r--r--spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb2
-rw-r--r--spec/features/projects/files/user_browses_files_spec.rb24
-rw-r--r--spec/features/projects/files/user_browses_lfs_files_spec.rb2
-rw-r--r--spec/features/projects/files/user_creates_directory_spec.rb2
-rw-r--r--spec/features/projects/files/user_creates_files_spec.rb16
-rw-r--r--spec/features/projects/files/user_deletes_files_spec.rb2
-rw-r--r--spec/features/projects/files/user_edits_files_spec.rb1
-rw-r--r--spec/features/projects/files/user_replaces_files_spec.rb2
-rw-r--r--spec/features/projects/files/user_uploads_files_spec.rb2
-rw-r--r--spec/features/projects/show/user_sees_collaboration_links_spec.rb23
-rw-r--r--spec/features/projects/show/user_sees_last_commit_ci_status_spec.rb8
-rw-r--r--spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb6
-rw-r--r--spec/features/projects/tree/tree_show_spec.rb1
-rw-r--r--spec/features/projects_spec.rb12
-rw-r--r--spec/features/signed_commits_spec.rb8
-rw-r--r--spec/frontend/repository/components/table/index_spec.js2
-rw-r--r--spec/frontend/repository/components/table/row_spec.js9
-rw-r--r--spec/frontend/repository/log_tree_spec.js9
-rw-r--r--spec/graphql/resolvers/issues_resolver_spec.rb13
-rw-r--r--spec/graphql/types/commit_type_spec.rb2
-rw-r--r--spec/graphql/types/issue_sort_enum_spec.rb2
-rw-r--r--spec/graphql/types/tree/blob_type_spec.rb2
-rw-r--r--spec/graphql/types/tree/submodule_type_spec.rb2
-rw-r--r--spec/graphql/types/tree/tree_entry_type_spec.rb2
-rw-r--r--spec/initializers/lograge_spec.rb48
-rw-r--r--spec/javascripts/boards/components/boards_selector_spec.js7
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/build_spec.rb7
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb37
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/remove_unwanted_chat_jobs_spec.rb34
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb161
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb165
-rw-r--r--spec/lib/gitlab/shell_spec.rb14
-rw-r--r--spec/models/ci/build_spec.rb10
-rw-r--r--spec/models/ci/pipeline_spec.rb342
-rw-r--r--spec/presenters/project_presenter_spec.rb60
-rw-r--r--spec/requests/api/graphql/project/issues_spec.rb46
-rw-r--r--spec/requests/api/merge_requests_spec.rb14
-rw-r--r--spec/services/ci/create_pipeline_service_spec.rb80
-rw-r--r--spec/support/helpers/stub_gitlab_calls.rb9
-rw-r--r--spec/views/projects/show.html.haml_spec.rb16
-rw-r--r--spec/views/projects/tree/show.html.haml_spec.rb11
98 files changed, 1249 insertions, 1110 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4de0606589f..fda536ae157 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,10 +2,6 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
-## 12.4.3
-
-- No changes.
-
## 12.4.2
### Fixed (10 changes)
diff --git a/app/assets/javascripts/boards/components/boards_selector.vue b/app/assets/javascripts/boards/components/boards_selector.vue
index 334c162954e..32491dfbcb6 100644
--- a/app/assets/javascripts/boards/components/boards_selector.vue
+++ b/app/assets/javascripts/boards/components/boards_selector.vue
@@ -168,7 +168,7 @@ export default {
}
const recentBoardsPromise = new Promise((resolve, reject) =>
- gl.boardService
+ boardsStore
.recentBoards()
.then(resolve)
.catch(err => {
@@ -184,7 +184,7 @@ export default {
}),
);
- Promise.all([gl.boardService.allBoards(), recentBoardsPromise])
+ Promise.all([boardsStore.allBoards(), recentBoardsPromise])
.then(([allBoards, recentBoards]) => [allBoards.data, recentBoards.data])
.then(([allBoardsJson, recentBoardsJson]) => {
this.loading = false;
diff --git a/app/assets/javascripts/project_select.js b/app/assets/javascripts/project_select.js
index 6a32cef79bc..66ce1ab5659 100644
--- a/app/assets/javascripts/project_select.js
+++ b/app/assets/javascripts/project_select.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, no-var, one-var, no-else-return */
+/* eslint-disable func-names, no-else-return */
import $ from 'jquery';
import Api from './api';
@@ -7,7 +7,7 @@ import { s__ } from './locale';
const projectSelect = () => {
$('.ajax-project-select').each(function(i, select) {
- var placeholder;
+ let placeholder;
const simpleFilter = $(select).data('simpleFilter') || false;
const isInstantiated = $(select).data('select2');
this.groupId = $(select).data('groupId');
@@ -31,20 +31,17 @@ const projectSelect = () => {
placeholder,
minimumInputLength: 0,
query: query => {
- var finalCallback, projectsCallback;
- finalCallback = function(projects) {
- var data;
- data = {
+ let projectsCallback;
+ const finalCallback = function(projects) {
+ const data = {
results: projects,
};
return query.callback(data);
};
if (this.includeGroups) {
projectsCallback = function(projects) {
- var groupsCallback;
- groupsCallback = function(groups) {
- var data;
- data = groups.concat(projects);
+ const groupsCallback = function(groups) {
+ const data = groups.concat(projects);
return finalCallback(data);
};
return Api.groups(query.term, {}, groupsCallback);
diff --git a/app/assets/javascripts/repository/components/last_commit.vue b/app/assets/javascripts/repository/components/last_commit.vue
index 26c1f5813f5..70678b0db37 100644
--- a/app/assets/javascripts/repository/components/last_commit.vue
+++ b/app/assets/javascripts/repository/components/last_commit.vue
@@ -1,5 +1,6 @@
<script>
import { GlTooltipDirective, GlLink, GlButton, GlLoadingIcon } from '@gitlab/ui';
+import defaultAvatarUrl from 'images/no_avatar.png';
import { sprintf, s__ } from '~/locale';
import Icon from '../../vue_shared/components/icon.vue';
import UserAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
@@ -83,6 +84,7 @@ export default {
this.showDescription = !this.showDescription;
},
},
+ defaultAvatarUrl,
};
</script>
@@ -97,6 +99,9 @@ export default {
:img-size="40"
class="avatar-cell"
/>
+ <span v-else class="avatar-cell user-avatar-link">
+ <img :src="$options.defaultAvatarUrl" width="40" height="40" class="avatar s40" />
+ </span>
<div class="commit-detail flex-list">
<div class="commit-content qa-commit-content">
<gl-link :href="commit.webUrl" class="commit-row-message item-title">
@@ -119,6 +124,9 @@ export default {
>
{{ commit.author.name }}
</gl-link>
+ <template v-else>
+ {{ commit.authorName }}
+ </template>
{{ s__('LastCommit|authored') }}
<timeago-tooltip :time="commit.authoredDate" tooltip-placement="bottom" />
</div>
@@ -132,9 +140,8 @@ export default {
</div>
<div class="commit-actions flex-row">
<div v-if="commit.signatureHtml" v-html="commit.signatureHtml"></div>
- <div class="ci-status-link">
+ <div v-if="commit.pipeline" class="ci-status-link">
<gl-link
- v-if="commit.pipeline"
v-gl-tooltip.left
:href="commit.pipeline.detailedStatus.detailsPath"
:title="statusTitle"
diff --git a/app/assets/javascripts/repository/components/table/index.vue b/app/assets/javascripts/repository/components/table/index.vue
index ac20549acb8..8f2e9264bca 100644
--- a/app/assets/javascripts/repository/components/table/index.vue
+++ b/app/assets/javascripts/repository/components/table/index.vue
@@ -75,6 +75,7 @@ export default {
v-for="entry in val"
:id="entry.id"
:key="`${entry.flatPath}-${entry.id}`"
+ :sha="entry.sha"
:project-path="projectPath"
:current-path="path"
:name="entry.name"
diff --git a/app/assets/javascripts/repository/components/table/row.vue b/app/assets/javascripts/repository/components/table/row.vue
index 52f861fbb61..cf0457a2abf 100644
--- a/app/assets/javascripts/repository/components/table/row.vue
+++ b/app/assets/javascripts/repository/components/table/row.vue
@@ -37,6 +37,10 @@ export default {
type: String,
required: true,
},
+ sha: {
+ type: String,
+ required: true,
+ },
projectPath: {
type: String,
required: true,
@@ -98,7 +102,7 @@ export default {
return this.path.replace(new RegExp(`^${this.currentPath}/`), '');
},
shortSha() {
- return this.id.slice(0, 8);
+ return this.sha.slice(0, 8);
},
hasLockLabel() {
return this.commit && this.commit.lockLabel;
diff --git a/app/assets/javascripts/repository/log_tree.js b/app/assets/javascripts/repository/log_tree.js
index 87310278b9e..5bf30e625a0 100644
--- a/app/assets/javascripts/repository/log_tree.js
+++ b/app/assets/javascripts/repository/log_tree.js
@@ -26,9 +26,12 @@ export function fetchLogsTree(client, path, offset, resolver = null) {
const { ref } = client.readQuery({ query: getRef });
fetchpromise = axios
- .get(`${gon.gitlab_url}/${projectPath}/refs/${ref}/logs_tree/${path.replace(/^\//, '')}`, {
- params: { format: 'json', offset },
- })
+ .get(
+ `${gon.relative_url_root}/${projectPath}/refs/${ref}/logs_tree/${path.replace(/^\//, '')}`,
+ {
+ params: { format: 'json', offset },
+ },
+ )
.then(({ data, headers }) => {
const headerLogsOffset = headers['more-logs-offset'];
const { commits } = client.readQuery({ query: getCommits });
diff --git a/app/assets/javascripts/repository/queries/getFiles.query.graphql b/app/assets/javascripts/repository/queries/getFiles.query.graphql
index c4814f8e63a..2aaf5066b4a 100644
--- a/app/assets/javascripts/repository/queries/getFiles.query.graphql
+++ b/app/assets/javascripts/repository/queries/getFiles.query.graphql
@@ -2,6 +2,7 @@
fragment TreeEntry on Entry {
id
+ sha
name
flatPath
type
diff --git a/app/assets/javascripts/repository/queries/pathLastCommit.query.graphql b/app/assets/javascripts/repository/queries/pathLastCommit.query.graphql
index 4bb959a8001..9be025afe39 100644
--- a/app/assets/javascripts/repository/queries/pathLastCommit.query.graphql
+++ b/app/assets/javascripts/repository/queries/pathLastCommit.query.graphql
@@ -8,6 +8,7 @@ query pathLastCommit($projectPath: ID!, $path: String, $ref: String!) {
description
webUrl
authoredDate
+ authorName
author {
name
avatarUrl
diff --git a/app/assets/javascripts/vue_shared/components/file_row.vue b/app/assets/javascripts/vue_shared/components/file_row.vue
index 341c9534763..611001df32f 100644
--- a/app/assets/javascripts/vue_shared/components/file_row.vue
+++ b/app/assets/javascripts/vue_shared/components/file_row.vue
@@ -218,7 +218,7 @@ export default {
display: inline-block;
flex: 1;
max-width: inherit;
- height: 18px;
+ height: 19px;
line-height: 16px;
text-overflow: ellipsis;
white-space: nowrap;
diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss
index a669e004d3a..2d826064569 100644
--- a/app/assets/stylesheets/framework/filters.scss
+++ b/app/assets/stylesheets/framework/filters.scss
@@ -357,12 +357,18 @@
}
}
- .filter-dropdown-container > div {
- margin: 0;
+ .filter-dropdown-container {
+ > div {
+ margin: 0;
- > .btn {
- margin: 0 0 10px;
- width: 100%;
+ > .btn {
+ margin: 0 0 10px;
+ width: 100%;
+ }
+ }
+
+ .board-labels-toggle-wrapper {
+ margin-bottom: $gl-input-padding;
}
}
diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss
index 45d0579052a..d26979bc174 100644
--- a/app/assets/stylesheets/pages/boards.scss
+++ b/app/assets/stylesheets/pages/boards.scss
@@ -497,7 +497,7 @@
.add-issues-footer-to-list {
padding-left: $gl-vert-padding;
padding-right: $gl-vert-padding;
- line-height: 34px;
+ line-height: $input-height;
}
.issue-card-selected {
@@ -551,9 +551,5 @@
* Make the wrapper the same height as a button so it aligns properly when the
* filtered-search-box input element increases in size on Linux smaller breakpoints
*/
- height: 34px;
-
- @include media-breakpoint-down(sm) {
- margin-bottom: 10px;
- }
+ height: $input-height;
}
diff --git a/app/graphql/types/commit_type.rb b/app/graphql/types/commit_type.rb
index dcf4a2802c7..87f84ec576f 100644
--- a/app/graphql/types/commit_type.rb
+++ b/app/graphql/types/commit_type.rb
@@ -24,6 +24,8 @@ module Types
description: 'Web URL of the commit'
field :signature_html, type: GraphQL::STRING_TYPE, null: true, calls_gitaly: true,
description: 'Rendered HTML of the commit signature'
+ field :author_name, type: GraphQL::STRING_TYPE, null: true,
+ description: 'Commit authors name'
# models/commit lazy loads the author by email
field :author, type: Types::UserType, null: true,
diff --git a/app/graphql/types/issue_sort_enum.rb b/app/graphql/types/issue_sort_enum.rb
index f71dd114d1f..48ff5819286 100644
--- a/app/graphql/types/issue_sort_enum.rb
+++ b/app/graphql/types/issue_sort_enum.rb
@@ -8,6 +8,7 @@ module Types
value 'DUE_DATE_ASC', 'Due date by ascending order', value: 'due_date_asc'
value 'DUE_DATE_DESC', 'Due date by descending order', value: 'due_date_desc'
+ value 'RELATIVE_POSITION_ASC', 'Relative position by ascending order', value: 'relative_position_asc'
end
# rubocop: enable Graphql/AuthorizeTypes
end
diff --git a/app/graphql/types/tree/entry_type.rb b/app/graphql/types/tree/entry_type.rb
index 10c2ad8815e..87a3eced896 100644
--- a/app/graphql/types/tree/entry_type.rb
+++ b/app/graphql/types/tree/entry_type.rb
@@ -5,6 +5,7 @@ module Types
include Types::BaseInterface
field :id, GraphQL::ID_TYPE, null: false # rubocop:disable Graphql/Descriptions
+ field :sha, GraphQL::STRING_TYPE, null: false, description: "Last commit sha for entry", method: :id
field :name, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions
field :type, Tree::TypeEnum, null: false # rubocop:disable Graphql/Descriptions
field :path, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 4804364d404..59a2c09bd28 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -898,12 +898,6 @@ module Ci
value.with_indifferent_access
end
end
-
- def build_attributes_from_config
- return {} unless pipeline.config_processor
-
- pipeline.config_processor.build_attributes(name)
- end
end
end
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index f9840e13e03..f730b949ee9 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -551,23 +551,6 @@ module Ci
end
end
- def stage_seeds
- return [] unless config_processor
-
- strong_memoize(:stage_seeds) do
- seeds = config_processor.stages_attributes.inject([]) do |previous_stages, attributes|
- seed = Gitlab::Ci::Pipeline::Seed::Stage.new(self, attributes, previous_stages)
- previous_stages + [seed]
- end
-
- seeds.select(&:included?)
- end
- end
-
- def seeds_size
- stage_seeds.sum(&:size)
- end
-
def has_kubernetes_active?
project.deployment_platform&.active?
end
@@ -587,62 +570,14 @@ module Ci
end
end
- def set_config_source
- if ci_yaml_from_repo
- self.config_source = :repository_source
- elsif implied_ci_yaml_file
- self.config_source = :auto_devops_source
- end
- end
-
- ##
- # TODO, setting yaml_errors should be moved to the pipeline creation chain.
- #
- def config_processor
- return unless ci_yaml_file
- return @config_processor if defined?(@config_processor)
-
- @config_processor ||= begin
- ::Gitlab::Ci::YamlProcessor.new(ci_yaml_file, { project: project, sha: sha, user: user })
- rescue Gitlab::Ci::YamlProcessor::ValidationError => e
- self.yaml_errors = e.message
- nil
- rescue => ex
- self.yaml_errors = "Undefined error (#{Labkit::Correlation::CorrelationId.current_id})"
-
- Gitlab::Sentry.track_acceptable_exception(ex, extra: {
- project_id: project.id,
- sha: sha,
- ci_yaml_file: ci_yaml_file_path
- })
- nil
- end
- end
-
- def ci_yaml_file_path
+ # TODO: this logic is duplicate with Pipeline::Chain::Config::Content
+ # we should persist this is `ci_pipelines.config_path`
+ def config_path
return unless repository_source? || unknown_source?
project.ci_config_path.presence || '.gitlab-ci.yml'
end
- def ci_yaml_file
- return @ci_yaml_file if defined?(@ci_yaml_file)
-
- @ci_yaml_file =
- if auto_devops_source?
- implied_ci_yaml_file
- else
- ci_yaml_from_repo
- end
-
- if @ci_yaml_file
- @ci_yaml_file
- else
- self.yaml_errors = "Failed to load CI/CD config file for #{sha}"
- nil
- end
- end
-
def has_yaml_errors?
yaml_errors.present?
end
@@ -711,7 +646,7 @@ module Ci
def predefined_variables
Gitlab::Ci::Variables::Collection.new.tap do |variables|
variables.append(key: 'CI_PIPELINE_IID', value: iid.to_s)
- variables.append(key: 'CI_CONFIG_PATH', value: ci_yaml_file_path)
+ variables.append(key: 'CI_CONFIG_PATH', value: config_path)
variables.append(key: 'CI_PIPELINE_SOURCE', value: source.to_s)
variables.append(key: 'CI_COMMIT_MESSAGE', value: git_commit_message.to_s)
variables.append(key: 'CI_COMMIT_TITLE', value: git_commit_full_title.to_s)
@@ -906,24 +841,6 @@ module Ci
private
- def ci_yaml_from_repo
- return unless project
- return unless sha
- return unless ci_yaml_file_path
-
- project.repository.gitlab_ci_yml_for(sha, ci_yaml_file_path)
- rescue GRPC::NotFound, GRPC::Internal
- nil
- end
-
- def implied_ci_yaml_file
- return unless project
-
- if project.auto_devops_enabled?
- Gitlab::Template::GitlabCiYmlTemplate.find('Auto-DevOps').content
- end
- end
-
def pipeline_data
Gitlab::DataBuilder::Pipeline.build(self)
end
diff --git a/app/models/concerns/storage/legacy_namespace.rb b/app/models/concerns/storage/legacy_namespace.rb
index ce98a3ad3d6..9c2b0372d54 100644
--- a/app/models/concerns/storage/legacy_namespace.rb
+++ b/app/models/concerns/storage/legacy_namespace.rb
@@ -57,18 +57,20 @@ module Storage
# Move the namespace directory in all storages used by member projects
repository_storages(legacy_only: true).each do |repository_storage|
# Ensure old directory exists before moving it
- gitlab_shell.add_namespace(repository_storage, full_path_before_last_save)
+ Gitlab::GitalyClient::NamespaceService.allow do
+ gitlab_shell.add_namespace(repository_storage, full_path_before_last_save)
- # Ensure new directory exists before moving it (if there's a parent)
- gitlab_shell.add_namespace(repository_storage, parent.full_path) if parent
+ # Ensure new directory exists before moving it (if there's a parent)
+ gitlab_shell.add_namespace(repository_storage, parent.full_path) if parent
- unless gitlab_shell.mv_namespace(repository_storage, full_path_before_last_save, full_path)
+ unless gitlab_shell.mv_namespace(repository_storage, full_path_before_last_save, full_path)
- Rails.logger.error "Exception moving path #{repository_storage} from #{full_path_before_last_save} to #{full_path}" # rubocop:disable Gitlab/RailsLogger
+ Rails.logger.error "Exception moving path #{repository_storage} from #{full_path_before_last_save} to #{full_path}" # rubocop:disable Gitlab/RailsLogger
- # if we cannot move namespace directory we should rollback
- # db changes in order to prevent out of sync between db and fs
- raise Gitlab::UpdatePathError.new('namespace directory cannot be moved')
+ # if we cannot move namespace directory we should rollback
+ # db changes in order to prevent out of sync between db and fs
+ raise Gitlab::UpdatePathError.new('namespace directory cannot be moved')
+ end
end
end
end
@@ -95,13 +97,15 @@ module Storage
# We will remove it later async
new_path = "#{full_path}+#{id}+deleted"
- if gitlab_shell.mv_namespace(repository_storage, full_path, new_path)
- Gitlab::AppLogger.info %Q(Namespace directory "#{full_path}" moved to "#{new_path}")
+ Gitlab::GitalyClient::NamespaceService.allow do
+ if gitlab_shell.mv_namespace(repository_storage, full_path, new_path)
+ Gitlab::AppLogger.info %Q(Namespace directory "#{full_path}" moved to "#{new_path}")
- # Remove namespace directory async with delay so
- # GitLab has time to remove all projects first
- run_after_commit do
- GitlabShellWorker.perform_in(5.minutes, :rm_namespace, repository_storage, new_path)
+ # Remove namespace directory async with delay so
+ # GitLab has time to remove all projects first
+ run_after_commit do
+ GitlabShellWorker.perform_in(5.minutes, :rm_namespace, repository_storage, new_path)
+ end
end
end
end
diff --git a/app/models/project_ci_cd_setting.rb b/app/models/project_ci_cd_setting.rb
index a495d34c07c..d089a004d3d 100644
--- a/app/models/project_ci_cd_setting.rb
+++ b/app/models/project_ci_cd_setting.rb
@@ -1,6 +1,9 @@
# frozen_string_literal: true
class ProjectCiCdSetting < ApplicationRecord
+ # TODO: remove once GitLab 12.7 is released
+ # https://gitlab.com/gitlab-org/gitlab/issues/36651
+ self.ignored_columns += %i[merge_trains_enabled]
belongs_to :project, inverse_of: :ci_cd_settings
# The version of the schema that first introduced this model/table.
diff --git a/app/presenters/project_presenter.rb b/app/presenters/project_presenter.rb
index 6d370f6241c..81018398d5d 100644
--- a/app/presenters/project_presenter.rb
+++ b/app/presenters/project_presenter.rb
@@ -21,7 +21,6 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
def statistics_anchors(show_auto_devops_callout:)
[
- license_anchor_data,
commits_anchor_data,
branches_anchor_data,
tags_anchor_data,
@@ -32,6 +31,7 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
def statistics_buttons(show_auto_devops_callout:)
[
readme_anchor_data,
+ license_anchor_data,
changelog_anchor_data,
contribution_guide_anchor_data,
autodevops_anchor_data(show_auto_devops_callout: show_auto_devops_callout),
@@ -41,15 +41,14 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
end
def empty_repo_statistics_anchors
- [
- license_anchor_data
- ].compact.select { |item| item.is_link }
+ []
end
def empty_repo_statistics_buttons
[
new_file_anchor_data,
readme_anchor_data,
+ license_anchor_data,
changelog_anchor_data,
contribution_guide_anchor_data,
gitlab_ci_anchor_data
@@ -227,17 +226,18 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
icon = statistic_icon('scale')
if repository.license_blob.present?
- AnchorData.new(true,
- icon + content_tag(:strong, license_short_name, class: 'project-stat-value'),
- license_path)
+ AnchorData.new(false,
+ icon + content_tag(:span, license_short_name, class: 'project-stat-value'),
+ license_path,
+ 'default')
else
if current_user && can_current_user_push_to_default_branch?
- AnchorData.new(true,
- content_tag(:span, icon + _('Add license'), class: 'add-license-link d-flex'),
+ AnchorData.new(false,
+ content_tag(:span, statistic_icon + _('Add LICENSE'), class: 'add-license-link d-flex'),
add_license_path)
else
- AnchorData.new(true,
- icon + content_tag(:strong, _('No license. All rights reserved'), class: 'project-stat-value'),
+ AnchorData.new(false,
+ icon + content_tag(:span, _('No license. All rights reserved'), class: 'project-stat-value'),
nil)
end
end
diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb
index 74c188c39d5..5778a48bce6 100644
--- a/app/services/ci/create_pipeline_service.rb
+++ b/app/services/ci/create_pipeline_service.rb
@@ -7,12 +7,14 @@ module Ci
CreateError = Class.new(StandardError)
SEQUENCE = [Gitlab::Ci::Pipeline::Chain::Build,
- Gitlab::Ci::Pipeline::Chain::RemoveUnwantedChatJobs,
Gitlab::Ci::Pipeline::Chain::Validate::Abilities,
Gitlab::Ci::Pipeline::Chain::Validate::Repository,
- Gitlab::Ci::Pipeline::Chain::Validate::Config,
+ Gitlab::Ci::Pipeline::Chain::Config::Content,
+ Gitlab::Ci::Pipeline::Chain::Config::Process,
+ Gitlab::Ci::Pipeline::Chain::RemoveUnwantedChatJobs,
Gitlab::Ci::Pipeline::Chain::Skip,
Gitlab::Ci::Pipeline::Chain::EvaluateWorkflowRules,
+ Gitlab::Ci::Pipeline::Chain::Seed,
Gitlab::Ci::Pipeline::Chain::Limit::Size,
Gitlab::Ci::Pipeline::Chain::Populate,
Gitlab::Ci::Pipeline::Chain::Create,
diff --git a/app/views/projects/pipelines/_with_tabs.html.haml b/app/views/projects/pipelines/_with_tabs.html.haml
index f9408184cb6..4d8cba5168d 100644
--- a/app/views/projects/pipelines/_with_tabs.html.haml
+++ b/app/views/projects/pipelines/_with_tabs.html.haml
@@ -39,10 +39,6 @@
%th
= render partial: "projects/stage/stage", collection: pipeline.legacy_stages, as: :stage
- - elsif pipeline.project.builds_enabled? && !pipeline.ci_yaml_file
- .bs-callout.bs-callout-warning
- = _("%{gitlab_ci_yml} not found in this commit") % { gitlab_ci_yml: ".gitlab-ci.yml" }
-
- if @pipeline.failed_builds.present?
#js-tab-failures.build-failures.tab-pane.build-page
%table.table.responsive-table.ci-table.responsive-table-sm-rounded
diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml
index e6335284541..d341520e4a2 100644
--- a/app/views/shared/issuable/_search_bar.html.haml
+++ b/app/views/shared/issuable/_search_bar.html.haml
@@ -5,169 +5,170 @@
- user_can_admin_list = board && can?(current_user, :admin_list, board.resource_parent)
.issues-filters{ class: ("w-100" if type == :boards_modal) }
- .issues-details-filters.filtered-search-block.d-flex.flex-column.flex-md-row{ class: block_css_class, "v-pre" => type == :boards_modal }
- - if type == :boards
- = render "shared/boards/switcher", board: board
- = form_tag page_filter_path, method: :get, class: 'filter-form js-filter-form w-100' do
- - if params[:search].present?
- = hidden_field_tag :search, params[:search]
- - if @can_bulk_update
- .check-all-holder.d-none.d-sm-block.hidden
- = check_box_tag "check-all-issues", nil, false, class: "check-all-issues left"
- .issues-other-filters.filtered-search-wrapper.d-flex.flex-column.flex-md-row
- .filtered-search-box
- - if type != :boards_modal && type != :boards
- = dropdown_tag(_('Recent searches'),
- options: { wrapper_class: "filtered-search-history-dropdown-wrapper",
- toggle_class: "filtered-search-history-dropdown-toggle-button",
- dropdown_class: "filtered-search-history-dropdown",
- content_class: "filtered-search-history-dropdown-content" }) do
- .js-filtered-search-history-dropdown{ data: { full_path: search_history_storage_prefix } }
- .filtered-search-box-input-container.droplab-dropdown
- .scroll-container
- %ul.tokens-container.list-unstyled
- %li.input-token
- %input.form-control.filtered-search{ search_filter_input_options(type) }
- #js-dropdown-hint.filtered-search-input-dropdown-menu.dropdown-menu.hint-dropdown
- %ul{ data: { dropdown: true } }
- %li.filter-dropdown-item{ data: { action: 'submit' } }
- %button.btn.btn-link{ type: 'button' }
- = sprite_icon('search')
- %span
- = _('Press Enter or click to search')
- %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
- %li.filter-dropdown-item
- %button.btn.btn-link{ type: 'button' }
- -# Encapsulate static class name `{{icon}}` inside #{} to bypass
- -# haml lint's ClassAttributeWithStaticValue
- %svg
- %use{ 'xlink:href': "#{'{{icon}}'}" }
- %span.js-filter-hint
- {{hint}}
- %span.js-filter-tag.dropdown-light-content
- {{tag}}
- #js-dropdown-author.filtered-search-input-dropdown-menu.dropdown-menu
- - if current_user
+ .issues-details-filters.filtered-search-block.d-flex.flex-column.flex-lg-row{ class: block_css_class, "v-pre" => type == :boards_modal }
+ .d-flex.flex-column.flex-md-row.flex-grow-1.mb-lg-0.mb-md-2.mb-sm-0
+ - if type == :boards
+ = render "shared/boards/switcher", board: board
+ = form_tag page_filter_path, method: :get, class: 'filter-form js-filter-form w-100' do
+ - if params[:search].present?
+ = hidden_field_tag :search, params[:search]
+ - if @can_bulk_update
+ .check-all-holder.d-none.d-sm-block.hidden
+ = check_box_tag "check-all-issues", nil, false, class: "check-all-issues left"
+ .issues-other-filters.filtered-search-wrapper.d-flex.flex-column.flex-md-row
+ .filtered-search-box
+ - if type != :boards_modal && type != :boards
+ = dropdown_tag(_('Recent searches'),
+ options: { wrapper_class: "filtered-search-history-dropdown-wrapper",
+ toggle_class: "filtered-search-history-dropdown-toggle-button",
+ dropdown_class: "filtered-search-history-dropdown",
+ content_class: "filtered-search-history-dropdown-content" }) do
+ .js-filtered-search-history-dropdown{ data: { full_path: search_history_storage_prefix } }
+ .filtered-search-box-input-container.droplab-dropdown
+ .scroll-container
+ %ul.tokens-container.list-unstyled
+ %li.input-token
+ %input.form-control.filtered-search{ search_filter_input_options(type) }
+ #js-dropdown-hint.filtered-search-input-dropdown-menu.dropdown-menu.hint-dropdown
%ul{ data: { dropdown: true } }
- = render 'shared/issuable/user_dropdown_item',
- user: current_user
- %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
- = render 'shared/issuable/user_dropdown_item',
- user: User.new(username: '{{username}}', name: '{{name}}'),
- avatar: { lazy: true, url: '{{avatar_url}}' }
- #js-dropdown-assignee.filtered-search-input-dropdown-menu.dropdown-menu
- %ul{ data: { dropdown: true } }
- %li.filter-dropdown-item{ data: { value: 'None' } }
- %button.btn.btn-link{ type: 'button' }
- = _('None')
- %li.filter-dropdown-item{ data: { value: 'Any' } }
- %button.btn.btn-link{ type: 'button' }
- = _('Any')
- %li.divider.droplab-item-ignore
+ %li.filter-dropdown-item{ data: { action: 'submit' } }
+ %button.btn.btn-link{ type: 'button' }
+ = sprite_icon('search')
+ %span
+ = _('Press Enter or click to search')
+ %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
+ %li.filter-dropdown-item
+ %button.btn.btn-link{ type: 'button' }
+ -# Encapsulate static class name `{{icon}}` inside #{} to bypass
+ -# haml lint's ClassAttributeWithStaticValue
+ %svg
+ %use{ 'xlink:href': "#{'{{icon}}'}" }
+ %span.js-filter-hint
+ {{hint}}
+ %span.js-filter-tag.dropdown-light-content
+ {{tag}}
+ #js-dropdown-author.filtered-search-input-dropdown-menu.dropdown-menu
- if current_user
+ %ul{ data: { dropdown: true } }
+ = render 'shared/issuable/user_dropdown_item',
+ user: current_user
+ %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
+ = render 'shared/issuable/user_dropdown_item',
+ user: User.new(username: '{{username}}', name: '{{name}}'),
+ avatar: { lazy: true, url: '{{avatar_url}}' }
+ #js-dropdown-assignee.filtered-search-input-dropdown-menu.dropdown-menu
+ %ul{ data: { dropdown: true } }
+ %li.filter-dropdown-item{ data: { value: 'None' } }
+ %button.btn.btn-link{ type: 'button' }
+ = _('None')
+ %li.filter-dropdown-item{ data: { value: 'Any' } }
+ %button.btn.btn-link{ type: 'button' }
+ = _('Any')
+ %li.divider.droplab-item-ignore
+ - if current_user
+ = render 'shared/issuable/user_dropdown_item',
+ user: current_user
+ %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
= render 'shared/issuable/user_dropdown_item',
- user: current_user
- %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
- = render 'shared/issuable/user_dropdown_item',
- user: User.new(username: '{{username}}', name: '{{name}}'),
- avatar: { lazy: true, url: '{{avatar_url}}' }
- = render_if_exists 'shared/issuable/approver_dropdown'
- #js-dropdown-milestone.filtered-search-input-dropdown-menu.dropdown-menu
- %ul{ data: { dropdown: true } }
- %li.filter-dropdown-item{ data: { value: 'None' } }
- %button.btn.btn-link{ type: 'button' }
- = _('None')
- %li.filter-dropdown-item{ data: { value: 'Any' } }
- %button.btn.btn-link{ type: 'button' }
- = _('Any')
- %li.filter-dropdown-item{ data: { value: 'Upcoming' } }
- %button.btn.btn-link{ type: 'button' }
- = _('Upcoming')
- %li.filter-dropdown-item{ data: { value: 'Started' } }
- %button.btn.btn-link{ type: 'button' }
- = _('Started')
- %li.divider.droplab-item-ignore
- %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
- %li.filter-dropdown-item
- %button.btn.btn-link.js-data-value{ type: 'button' }
- {{title}}
- #js-dropdown-release.filtered-search-input-dropdown-menu.dropdown-menu
- %ul{ data: { dropdown: true } }
- %li.filter-dropdown-item{ data: { value: 'None' } }
- %button.btn.btn-link{ type: 'button' }
- = _('None')
- %li.filter-dropdown-item{ data: { value: 'Any' } }
- %button.btn.btn-link{ type: 'button' }
- = _('Any')
- %li.divider.droplab-item-ignore
- %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
- %li.filter-dropdown-item
- %button.btn.btn-link.js-data-value{ type: 'button' }
- {{title}}
- #js-dropdown-label.filtered-search-input-dropdown-menu.dropdown-menu
- %ul{ data: { dropdown: true } }
- %li.filter-dropdown-item{ data: { value: 'None' } }
- %button.btn.btn-link{ type: 'button' }
- = _('None')
- %li.filter-dropdown-item{ data: { value: 'Any' } }
- %button.btn.btn-link{ type: 'button' }
- = _('Any')
- %li.divider.droplab-item-ignore
- %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
- %li.filter-dropdown-item
- %button.btn.btn-link{ type: 'button' }
- %span.dropdown-label-box{ style: 'background: {{color}}' }
- %span.label-title.js-data-value
+ user: User.new(username: '{{username}}', name: '{{name}}'),
+ avatar: { lazy: true, url: '{{avatar_url}}' }
+ = render_if_exists 'shared/issuable/approver_dropdown'
+ #js-dropdown-milestone.filtered-search-input-dropdown-menu.dropdown-menu
+ %ul{ data: { dropdown: true } }
+ %li.filter-dropdown-item{ data: { value: 'None' } }
+ %button.btn.btn-link{ type: 'button' }
+ = _('None')
+ %li.filter-dropdown-item{ data: { value: 'Any' } }
+ %button.btn.btn-link{ type: 'button' }
+ = _('Any')
+ %li.filter-dropdown-item{ data: { value: 'Upcoming' } }
+ %button.btn.btn-link{ type: 'button' }
+ = _('Upcoming')
+ %li.filter-dropdown-item{ data: { value: 'Started' } }
+ %button.btn.btn-link{ type: 'button' }
+ = _('Started')
+ %li.divider.droplab-item-ignore
+ %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
+ %li.filter-dropdown-item
+ %button.btn.btn-link.js-data-value{ type: 'button' }
+ {{title}}
+ #js-dropdown-release.filtered-search-input-dropdown-menu.dropdown-menu
+ %ul{ data: { dropdown: true } }
+ %li.filter-dropdown-item{ data: { value: 'None' } }
+ %button.btn.btn-link{ type: 'button' }
+ = _('None')
+ %li.filter-dropdown-item{ data: { value: 'Any' } }
+ %button.btn.btn-link{ type: 'button' }
+ = _('Any')
+ %li.divider.droplab-item-ignore
+ %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
+ %li.filter-dropdown-item
+ %button.btn.btn-link.js-data-value{ type: 'button' }
+ {{title}}
+ #js-dropdown-label.filtered-search-input-dropdown-menu.dropdown-menu
+ %ul{ data: { dropdown: true } }
+ %li.filter-dropdown-item{ data: { value: 'None' } }
+ %button.btn.btn-link{ type: 'button' }
+ = _('None')
+ %li.filter-dropdown-item{ data: { value: 'Any' } }
+ %button.btn.btn-link{ type: 'button' }
+ = _('Any')
+ %li.divider.droplab-item-ignore
+ %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
+ %li.filter-dropdown-item
+ %button.btn.btn-link{ type: 'button' }
+ %span.dropdown-label-box{ style: 'background: {{color}}' }
+ %span.label-title.js-data-value
+ {{title}}
+ #js-dropdown-my-reaction.filtered-search-input-dropdown-menu.dropdown-menu
+ %ul{ data: { dropdown: true } }
+ %li.filter-dropdown-item{ data: { value: 'None' } }
+ %button.btn.btn-link{ type: 'button' }
+ = _('None')
+ %li.filter-dropdown-item{ data: { value: 'Any' } }
+ %button.btn.btn-link{ type: 'button' }
+ = _('Any')
+ %li.divider.droplab-item-ignore
+ %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
+ %li.filter-dropdown-item
+ %button.btn.btn-link{ type: 'button' }
+ %gl-emoji
+ %span.js-data-value.prepend-left-10
+ {{name}}
+ #js-dropdown-wip.filtered-search-input-dropdown-menu.dropdown-menu
+ %ul.filter-dropdown{ data: { dropdown: true } }
+ %li.filter-dropdown-item{ data: { value: 'yes', capitalize: true } }
+ %button.btn.btn-link{ type: 'button' }
+ = _('Yes')
+ %li.filter-dropdown-item{ data: { value: 'no', capitalize: true } }
+ %button.btn.btn-link{ type: 'button' }
+ = _('No')
+ #js-dropdown-confidential.filtered-search-input-dropdown-menu.dropdown-menu
+ %ul.filter-dropdown{ data: { dropdown: true } }
+ %li.filter-dropdown-item{ data: { value: 'yes', capitalize: true } }
+ %button.btn.btn-link{ type: 'button' }
+ = _('Yes')
+ %li.filter-dropdown-item{ data: { value: 'no', capitalize: true } }
+ %button.btn.btn-link{ type: 'button' }
+ = _('No')
+ #js-dropdown-target-branch.filtered-search-input-dropdown-menu.dropdown-menu
+ %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
+ %li.filter-dropdown-item
+ %button.btn.btn-link.js-data-value.monospace
{{title}}
- #js-dropdown-my-reaction.filtered-search-input-dropdown-menu.dropdown-menu
- %ul{ data: { dropdown: true } }
- %li.filter-dropdown-item{ data: { value: 'None' } }
- %button.btn.btn-link{ type: 'button' }
- = _('None')
- %li.filter-dropdown-item{ data: { value: 'Any' } }
- %button.btn.btn-link{ type: 'button' }
- = _('Any')
- %li.divider.droplab-item-ignore
- %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
- %li.filter-dropdown-item
- %button.btn.btn-link{ type: 'button' }
- %gl-emoji
- %span.js-data-value.prepend-left-10
- {{name}}
- #js-dropdown-wip.filtered-search-input-dropdown-menu.dropdown-menu
- %ul.filter-dropdown{ data: { dropdown: true } }
- %li.filter-dropdown-item{ data: { value: 'yes', capitalize: true } }
- %button.btn.btn-link{ type: 'button' }
- = _('Yes')
- %li.filter-dropdown-item{ data: { value: 'no', capitalize: true } }
- %button.btn.btn-link{ type: 'button' }
- = _('No')
- #js-dropdown-confidential.filtered-search-input-dropdown-menu.dropdown-menu
- %ul.filter-dropdown{ data: { dropdown: true } }
- %li.filter-dropdown-item{ data: { value: 'yes', capitalize: true } }
- %button.btn.btn-link{ type: 'button' }
- = _('Yes')
- %li.filter-dropdown-item{ data: { value: 'no', capitalize: true } }
- %button.btn.btn-link{ type: 'button' }
- = _('No')
- #js-dropdown-target-branch.filtered-search-input-dropdown-menu.dropdown-menu
- %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
- %li.filter-dropdown-item
- %button.btn.btn-link.js-data-value.monospace
- {{title}}
- = render_if_exists 'shared/issuable/filter_weight', type: type
+ = render_if_exists 'shared/issuable/filter_weight', type: type
- %button.clear-search.hidden{ type: 'button' }
- = icon('times')
- #js-board-labels-toggle
- .filter-dropdown-container.d-flex.flex-column.flex-md-row
- - if type == :boards
- .js-board-config{ data: { can_admin_list: user_can_admin_list, has_scope: board.scoped? } }
- - if user_can_admin_list
- = render 'shared/issuable/board_create_list_dropdown', board: board
- - if @project
- #js-add-issues-btn.prepend-left-10{ data: { can_admin_list: can?(current_user, :admin_list, @project) } }
- #js-toggle-focus-btn
- - elsif is_not_boards_modal_or_productivity_analytics
- = render 'shared/issuable/sort_dropdown'
+ %button.clear-search.hidden{ type: 'button' }
+ = icon('times')
+ .filter-dropdown-container.d-flex.flex-column.flex-md-row
+ #js-board-labels-toggle
+ - if type == :boards
+ .js-board-config{ data: { can_admin_list: user_can_admin_list, has_scope: board.scoped? } }
+ - if user_can_admin_list
+ = render 'shared/issuable/board_create_list_dropdown', board: board
+ - if @project
+ #js-add-issues-btn.prepend-left-10{ data: { can_admin_list: can?(current_user, :admin_list, @project) } }
+ #js-toggle-focus-btn
+ - elsif is_not_boards_modal_or_productivity_analytics
+ = render 'shared/issuable/sort_dropdown'
diff --git a/app/workers/gitlab_shell_worker.rb b/app/workers/gitlab_shell_worker.rb
index 5dcf901e041..57e64570c09 100644
--- a/app/workers/gitlab_shell_worker.rb
+++ b/app/workers/gitlab_shell_worker.rb
@@ -8,6 +8,8 @@ class GitlabShellWorker
latency_sensitive_worker!
def perform(action, *arg)
- gitlab_shell.__send__(action, *arg) # rubocop:disable GitlabSecurity/PublicSend
+ Gitlab::GitalyClient::NamespaceService.allow do
+ gitlab_shell.__send__(action, *arg) # rubocop:disable GitlabSecurity/PublicSend
+ end
end
end
diff --git a/changelogs/unreleased/28302-move-add-license-button.yml b/changelogs/unreleased/28302-move-add-license-button.yml
new file mode 100644
index 00000000000..d9a5c15990f
--- /dev/null
+++ b/changelogs/unreleased/28302-move-add-license-button.yml
@@ -0,0 +1,5 @@
+---
+title: Move add license button to project buttons
+merge_request: 19370
+author:
+type: changed
diff --git a/changelogs/unreleased/29713-graphql-add-issue-relative-position-sort-2.yml b/changelogs/unreleased/29713-graphql-add-issue-relative-position-sort-2.yml
new file mode 100644
index 00000000000..38a02a027de
--- /dev/null
+++ b/changelogs/unreleased/29713-graphql-add-issue-relative-position-sort-2.yml
@@ -0,0 +1,5 @@
+---
+title: 'Graphql query for issues can now be sorted by relative_position'
+merge_request: 19713
+author:
+type: added
diff --git a/changelogs/unreleased/Update-boards_selector-vue-to-use-boardsStore.yml b/changelogs/unreleased/Update-boards_selector-vue-to-use-boardsStore.yml
new file mode 100644
index 00000000000..95234bd9069
--- /dev/null
+++ b/changelogs/unreleased/Update-boards_selector-vue-to-use-boardsStore.yml
@@ -0,0 +1,5 @@
+---
+title: remove all references of BoardService in boards_selector.vue
+merge_request: 20147
+author: nuwe1
+type: other
diff --git a/changelogs/unreleased/remove_var_from_project_select_js.yml b/changelogs/unreleased/remove_var_from_project_select_js.yml
new file mode 100644
index 00000000000..736f8adc5da
--- /dev/null
+++ b/changelogs/unreleased/remove_var_from_project_select_js.yml
@@ -0,0 +1,5 @@
+---
+title: Remove var from project_select.js
+merge_request: 20091
+author: Lee Tickett
+type: other
diff --git a/changelogs/unreleased/sh-add-exception-backtrace-production-log.yml b/changelogs/unreleased/sh-add-exception-backtrace-production-log.yml
new file mode 100644
index 00000000000..05e17892611
--- /dev/null
+++ b/changelogs/unreleased/sh-add-exception-backtrace-production-log.yml
@@ -0,0 +1,5 @@
+---
+title: Add backtrace to production_json.log
+merge_request: 20122
+author:
+type: changed
diff --git a/changelogs/unreleased/sh-support-project-template-id-in-api.yml b/changelogs/unreleased/sh-support-project-template-id-in-api.yml
new file mode 100644
index 00000000000..5087c6c711a
--- /dev/null
+++ b/changelogs/unreleased/sh-support-project-template-id-in-api.yml
@@ -0,0 +1,5 @@
+---
+title: Support template_project_id parameter in project creation API
+merge_request: 20258
+author:
+type: added
diff --git a/config/initializers/lograge.rb b/config/initializers/lograge.rb
index d5d4c589884..769ef2af0e7 100644
--- a/config/initializers/lograge.rb
+++ b/config/initializers/lograge.rb
@@ -10,6 +10,11 @@ unless Sidekiq.server?
# unmaintained gem that monkey patches `Time`
config.lograge.formatter = Lograge::Formatters::Json.new
config.lograge.logger = ActiveSupport::Logger.new(filename)
+ config.lograge.before_format = lambda do |data, payload|
+ data.delete(:error)
+ data
+ end
+
# Add request parameters to log output
config.lograge.custom_options = lambda do |event|
params = event.payload[:params]
@@ -36,6 +41,20 @@ unless Sidekiq.server?
payload[:cpu_s] = cpu_s
end
+ # https://github.com/roidrage/lograge#logging-errors--exceptions
+ exception = event.payload[:exception_object]
+
+ if exception
+ payload[:exception] = {
+ class: exception.class.name,
+ message: exception.message
+ }
+
+ if exception.backtrace
+ payload[:exception][:backtrace] = Gitlab::Profiler.clean_backtrace(exception.backtrace)
+ end
+ end
+
payload
end
end
diff --git a/doc/administration/logs.md b/doc/administration/logs.md
index dae0dae8395..aa10cdd220c 100644
--- a/doc/administration/logs.md
+++ b/doc/administration/logs.md
@@ -42,6 +42,48 @@ User clone/fetch activity using http transport appears in this log as `action: g
In addition, the log contains the IP address from which the request originated
(`remote_ip`) as well as the user's ID (`user_id`), and username (`username`).
+NOTE: **Note:** Starting with GitLab 12.5, if an error occurs, an
+`exception` field is included with `class`, `message`, and
+`backtrace`. Previous versions included an `error` field instead of
+`exception.class` and `exception.message`. For example:
+
+```json
+{
+ "method": "GET",
+ "path": "/admin",
+ "format": "html",
+ "controller": "Admin::DashboardController",
+ "action": "index",
+ "status": 500,
+ "duration": 2584.11,
+ "view": 0,
+ "db": 9.21,
+ "time": "2019-11-14T13:12:46.156Z",
+ "params": [],
+ "remote_ip": "127.0.0.1",
+ "user_id": 1,
+ "username": "root",
+ "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:70.0) Gecko/20100101 Firefox/70.0",
+ "queue_duration": 274.35,
+ "correlation_id": "KjDVUhNvvV3",
+ "cpu_s": 2.837645135999999,
+ "exception": {
+ "class": "NameError",
+ "message": "undefined local variable or method `adsf' for #<Admin::DashboardController:0x00007ff3c9648588>",
+ "backtrace": [
+ "app/controllers/admin/dashboard_controller.rb:11:in `index'",
+ "ee/app/controllers/ee/admin/dashboard_controller.rb:14:in `index'",
+ "ee/lib/gitlab/ip_address_state.rb:10:in `with'",
+ "ee/app/controllers/ee/application_controller.rb:43:in `set_current_ip_address'",
+ "lib/gitlab/session.rb:11:in `with_session'",
+ "app/controllers/application_controller.rb:450:in `set_session_storage'",
+ "app/controllers/application_controller.rb:444:in `set_locale'",
+ "ee/lib/gitlab/jira/middleware.rb:19:in `call'"
+ ]
+ }
+}
+```
+
## `production.log`
This file lives in `/var/log/gitlab/gitlab-rails/production.log` for
diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql
index 8a4e8166dde..a357c93b020 100644
--- a/doc/api/graphql/reference/gitlab_schema.graphql
+++ b/doc/api/graphql/reference/gitlab_schema.graphql
@@ -76,6 +76,11 @@ type Blob implements Entry {
lfsOid: String
name: String!
path: String!
+
+ """
+ Last commit sha for entry
+ """
+ sha: String!
type: EntryType!
webUrl: String
}
@@ -122,6 +127,11 @@ type Commit {
author: User
"""
+ Commit authors name
+ """
+ authorName: String
+
+ """
Timestamp of when the commit was authored
"""
authoredDate: Time
@@ -1100,6 +1110,11 @@ interface Entry {
id: ID!
name: String!
path: String!
+
+ """
+ Last commit sha for entry
+ """
+ sha: String!
type: EntryType!
}
@@ -2518,6 +2533,11 @@ enum IssueSort {
DUE_DATE_DESC
"""
+ Relative position by ascending order
+ """
+ RELATIVE_POSITION_ASC
+
+ """
Created at ascending order
"""
created_asc
@@ -4767,6 +4787,11 @@ type Submodule implements Entry {
id: ID!
name: String!
path: String!
+
+ """
+ Last commit sha for entry
+ """
+ sha: String!
treeUrl: String
type: EntryType!
webUrl: String
@@ -5113,6 +5138,11 @@ type TreeEntry implements Entry {
id: ID!
name: String!
path: String!
+
+ """
+ Last commit sha for entry
+ """
+ sha: String!
type: EntryType!
webUrl: String
}
diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json
index bf0cb2ca6f2..fea67f28d69 100644
--- a/doc/api/graphql/reference/gitlab_schema.json
+++ b/doc/api/graphql/reference/gitlab_schema.json
@@ -10228,6 +10228,20 @@
"deprecationReason": null
},
{
+ "name": "authorName",
+ "description": "Commit authors name",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "authoredDate",
"description": "Timestamp of when the commit was authored",
"args": [
@@ -11333,6 +11347,24 @@
"deprecationReason": null
},
{
+ "name": "sha",
+ "description": "Last commit sha for entry",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "type",
"description": null,
"args": [
@@ -11454,6 +11486,24 @@
"deprecationReason": null
},
{
+ "name": "sha",
+ "description": "Last commit sha for entry",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "type",
"description": null,
"args": [
@@ -11712,6 +11762,24 @@
"deprecationReason": null
},
{
+ "name": "sha",
+ "description": "Last commit sha for entry",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "treeUrl",
"description": null,
"args": [
@@ -11973,6 +12041,24 @@
"deprecationReason": null
},
{
+ "name": "sha",
+ "description": "Last commit sha for entry",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "type",
"description": null,
"args": [
@@ -13680,6 +13766,12 @@
"description": "Due date by descending order",
"isDeprecated": false,
"deprecationReason": null
+ },
+ {
+ "name": "RELATIVE_POSITION_ASC",
+ "description": "Relative position by ascending order",
+ "isDeprecated": false,
+ "deprecationReason": null
}
],
"possibleTypes": null
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 8432e9a43c3..151e43f4cff 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -36,6 +36,7 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph
| Name | Type | Description |
| --- | ---- | ---------- |
| `id` | ID! | |
+| `sha` | String! | Last commit sha for entry |
| `name` | String! | |
| `type` | EntryType! | |
| `path` | String! | |
@@ -55,6 +56,7 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph
| `authoredDate` | Time | Timestamp of when the commit was authored |
| `webUrl` | String! | Web URL of the commit |
| `signatureHtml` | String | Rendered HTML of the commit signature |
+| `authorName` | String | Commit authors name |
| `author` | User | Author of the commit |
| `latestPipeline` | Pipeline | Latest pipeline of the commit |
@@ -738,6 +740,7 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph
| Name | Type | Description |
| --- | ---- | ---------- |
| `id` | ID! | |
+| `sha` | String! | Last commit sha for entry |
| `name` | String! | |
| `type` | EntryType! | |
| `path` | String! | |
@@ -794,6 +797,7 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph
| Name | Type | Description |
| --- | ---- | ---------- |
| `id` | ID! | |
+| `sha` | String! | Last commit sha for entry |
| `name` | String! | |
| `type` | EntryType! | |
| `path` | String! | |
diff --git a/doc/api/projects.md b/doc/api/projects.md
index 2ec412d0f56..222ab729810 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -948,6 +948,7 @@ POST /projects
| `mirror_trigger_builds` | boolean | no | **(STARTER)** Pull mirroring triggers builds |
| `initialize_with_readme` | boolean | no | `false` by default |
| `template_name` | string | no | When used without `use_custom_template`, name of a [built-in project template](../gitlab-basics/create-project.md#built-in-templates). When used with `use_custom_template`, name of a custom project template |
+| `template_project_id` | integer | no | **(PREMIUM)** When used with `use_custom_template`, project ID of a custom project template. This is preferable to using `template_name` since `template_name` may be ambiguous. |
| `use_custom_template` | boolean | no | **(PREMIUM)** Use either custom [instance](../user/admin_area/custom_project_templates.md) or [group](../user/group/custom_project_templates.md) (with `group_with_project_templates_id`) project template |
| `group_with_project_templates_id` | integer | no | **(PREMIUM)** For group-level custom templates, specifies ID of group from which all the custom project templates are sourced. Leave empty for instance-level templates. Requires `use_custom_template` to be true |
diff --git a/doc/development/fe_guide/graphql.md b/doc/development/fe_guide/graphql.md
index fe4f6d7bec8..894a613ec2d 100644
--- a/doc/development/fe_guide/graphql.md
+++ b/doc/development/fe_guide/graphql.md
@@ -19,6 +19,14 @@ To save duplicated clients getting created in different apps, we have a
[default client][default-client] that should be used. This setups the
Apollo client with the correct URL and also sets the CSRF headers.
+Default client accepts two parameters: `resolvers` and `config`.
+
+- `resolvers` parameter is created to accept an object of resolvers for [local state management](#local-state-with-apollo) queries and mutations
+- `config` parameter takes an object of configuration settings:
+ - `cacheConfig` field accepts an optional object of settings to [customize Apollo cache](https://github.com/apollographql/apollo-client/tree/master/packages/apollo-cache-inmemory#configuration)
+ - `baseUrl` allows us to pass a URL for GraphQL endpoint different from our main endpoint (i.e.`${gon.relative_url_root}/api/graphql`)
+ - `assumeImmutableResults` (set to `false` by default) - this setting, when set to `true`, will assume that every single operation on updating Apollo Cache is immutable. It also sets `freezeResults` to `true`, so any attempt on mutating Apollo Cache will throw a console warning in development environment. Please ensure you're following the immutability pattern on cache update operations before setting this option to `true`.
+
## GraphQL Queries
To save query compilation at runtime, webpack can directly import `.graphql`
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 3d10f41d2e0..669def2b63c 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -71,7 +71,8 @@ module API
optional :namespace_id, type: Integer, desc: 'Namespace ID for the new project. Default to the user namespace.'
optional :import_url, type: String, desc: 'URL from which the project is imported'
optional :template_name, type: String, desc: "Name of template from which to create project"
- mutually_exclusive :import_url, :template_name
+ optional :template_project_id, type: Integer, desc: "Project ID of template from which to create project"
+ mutually_exclusive :import_url, :template_name, :template_project_id
end
def load_projects
diff --git a/lib/gitlab/ci/pipeline/chain/base.rb b/lib/gitlab/ci/pipeline/chain/base.rb
index bab1c73e2f1..aabdf7ce47d 100644
--- a/lib/gitlab/ci/pipeline/chain/base.rb
+++ b/lib/gitlab/ci/pipeline/chain/base.rb
@@ -5,7 +5,7 @@ module Gitlab
module Pipeline
module Chain
class Base
- attr_reader :pipeline, :command
+ attr_reader :pipeline, :command, :config
delegate :project, :current_user, to: :command
diff --git a/lib/gitlab/ci/pipeline/chain/build.rb b/lib/gitlab/ci/pipeline/chain/build.rb
index 899df81ea5c..9662209f88e 100644
--- a/lib/gitlab/ci/pipeline/chain/build.rb
+++ b/lib/gitlab/ci/pipeline/chain/build.rb
@@ -22,8 +22,6 @@ module Gitlab
external_pull_request: @command.external_pull_request,
variables_attributes: Array(@command.variables_attributes)
)
-
- @pipeline.set_config_source
end
def break?
diff --git a/lib/gitlab/ci/pipeline/chain/command.rb b/lib/gitlab/ci/pipeline/chain/command.rb
index 58f89a6be5e..c2df419cca0 100644
--- a/lib/gitlab/ci/pipeline/chain/command.rb
+++ b/lib/gitlab/ci/pipeline/chain/command.rb
@@ -10,7 +10,9 @@ module Gitlab
:trigger_request, :schedule, :merge_request, :external_pull_request,
:ignore_skip_ci, :save_incompleted,
:seeds_block, :variables_attributes, :push_options,
- :chat_data, :allow_mirror_update
+ :chat_data, :allow_mirror_update,
+ # These attributes are set by Chains during processing:
+ :config_content, :config_processor, :stage_seeds
) do
include Gitlab::Utils::StrongMemoize
diff --git a/lib/gitlab/ci/pipeline/chain/config/content.rb b/lib/gitlab/ci/pipeline/chain/config/content.rb
new file mode 100644
index 00000000000..a8cd99b8e92
--- /dev/null
+++ b/lib/gitlab/ci/pipeline/chain/config/content.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module Pipeline
+ module Chain
+ module Config
+ class Content < Chain::Base
+ include Chain::Helpers
+
+ def perform!
+ return if @command.config_content
+
+ if content = content_from_repo
+ @command.config_content = content
+ @pipeline.config_source = :repository_source
+ # TODO: we should persist ci_config_path
+ # @pipeline.config_path = ci_config_path
+ elsif content = content_from_auto_devops
+ @command.config_content = content
+ @pipeline.config_source = :auto_devops_source
+ end
+
+ unless @command.config_content
+ return error("Missing #{ci_config_path} file")
+ end
+ end
+
+ def break?
+ @pipeline.errors.any? || @pipeline.persisted?
+ end
+
+ private
+
+ def content_from_repo
+ return unless project
+ return unless @pipeline.sha
+ return unless ci_config_path
+
+ project.repository.gitlab_ci_yml_for(@pipeline.sha, ci_config_path)
+ rescue GRPC::NotFound, GRPC::Internal
+ nil
+ end
+
+ def content_from_auto_devops
+ return unless project&.auto_devops_enabled?
+
+ Gitlab::Template::GitlabCiYmlTemplate.find('Auto-DevOps').content
+ end
+
+ def ci_config_path
+ project.ci_config_path.presence || '.gitlab-ci.yml'
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/pipeline/chain/config/process.rb b/lib/gitlab/ci/pipeline/chain/config/process.rb
new file mode 100644
index 00000000000..731b0fdb286
--- /dev/null
+++ b/lib/gitlab/ci/pipeline/chain/config/process.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module Pipeline
+ module Chain
+ module Config
+ class Process < Chain::Base
+ include Chain::Helpers
+
+ def perform!
+ raise ArgumentError, 'missing config content' unless @command.config_content
+
+ @command.config_processor = ::Gitlab::Ci::YamlProcessor.new(
+ @command.config_content, {
+ project: project,
+ sha: @pipeline.sha,
+ user: current_user
+ }
+ )
+ rescue Gitlab::Ci::YamlProcessor::ValidationError => ex
+ error(ex.message, config_error: true)
+ rescue => ex
+ Gitlab::Sentry.track_acceptable_exception(ex, extra: {
+ project_id: project.id,
+ sha: @pipeline.sha
+ })
+
+ error("Undefined error (#{Labkit::Correlation::CorrelationId.current_id})",
+ config_error: true)
+ end
+
+ def break?
+ @pipeline.errors.any? || @pipeline.persisted?
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/pipeline/chain/evaluate_workflow_rules.rb b/lib/gitlab/ci/pipeline/chain/evaluate_workflow_rules.rb
index 5b46a43b725..0ee9485eebc 100644
--- a/lib/gitlab/ci/pipeline/chain/evaluate_workflow_rules.rb
+++ b/lib/gitlab/ci/pipeline/chain/evaluate_workflow_rules.rb
@@ -41,7 +41,7 @@ module Gitlab
end
def workflow_config
- @pipeline.config_processor.workflow_attributes || {}
+ @command.config_processor.workflow_attributes || {}
end
end
end
diff --git a/lib/gitlab/ci/pipeline/chain/populate.rb b/lib/gitlab/ci/pipeline/chain/populate.rb
index 13eca5a9d28..3a40c7b167c 100644
--- a/lib/gitlab/ci/pipeline/chain/populate.rb
+++ b/lib/gitlab/ci/pipeline/chain/populate.rb
@@ -10,29 +10,12 @@ module Gitlab
PopulateError = Class.new(StandardError)
def perform!
- # Allocate next IID. This operation must be outside of transactions of pipeline creations.
- pipeline.ensure_project_iid!
-
- # Protect the pipeline. This is assigned in Populate instead of
- # Build to prevent erroring out on ambiguous refs.
- pipeline.protected = @command.protected_ref?
-
- ##
- # Populate pipeline with block argument of CreatePipelineService#execute.
- #
- @command.seeds_block&.call(pipeline)
-
- ##
- # Gather all runtime build/stage errors
- #
- if seeds_errors = pipeline.stage_seeds.flat_map(&:errors).compact.presence
- return error(seeds_errors.join("\n"), config_error: true)
- end
+ raise ArgumentError, 'missing stage seeds' unless @command.stage_seeds
##
# Populate pipeline with all stages, and stages with builds.
#
- pipeline.stages = pipeline.stage_seeds.map(&:to_resource)
+ pipeline.stages = @command.stage_seeds.map(&:to_resource)
if pipeline.stages.none?
return error('No stages / jobs for this pipeline.')
diff --git a/lib/gitlab/ci/pipeline/chain/remove_unwanted_chat_jobs.rb b/lib/gitlab/ci/pipeline/chain/remove_unwanted_chat_jobs.rb
index 1e09b417311..9267c72efa4 100644
--- a/lib/gitlab/ci/pipeline/chain/remove_unwanted_chat_jobs.rb
+++ b/lib/gitlab/ci/pipeline/chain/remove_unwanted_chat_jobs.rb
@@ -6,11 +6,13 @@ module Gitlab
module Chain
class RemoveUnwantedChatJobs < Chain::Base
def perform!
- return unless pipeline.config_processor && pipeline.chat?
+ raise ArgumentError, 'missing config processor' unless @command.config_processor
+
+ return unless pipeline.chat?
# When scheduling a chat pipeline we only want to run the build
# that matches the chat command.
- pipeline.config_processor.jobs.select! do |name, _|
+ @command.config_processor.jobs.select! do |name, _|
name.to_s == command.chat_data[:command].to_s
end
end
diff --git a/lib/gitlab/ci/pipeline/chain/seed.rb b/lib/gitlab/ci/pipeline/chain/seed.rb
new file mode 100644
index 00000000000..2e177cfec7e
--- /dev/null
+++ b/lib/gitlab/ci/pipeline/chain/seed.rb
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module Pipeline
+ module Chain
+ class Seed < Chain::Base
+ include Chain::Helpers
+ include Gitlab::Utils::StrongMemoize
+
+ def perform!
+ raise ArgumentError, 'missing config processor' unless @command.config_processor
+
+ # Allocate next IID. This operation must be outside of transactions of pipeline creations.
+ pipeline.ensure_project_iid!
+
+ # Protect the pipeline. This is assigned in Populate instead of
+ # Build to prevent erroring out on ambiguous refs.
+ pipeline.protected = @command.protected_ref?
+
+ ##
+ # Populate pipeline with block argument of CreatePipelineService#execute.
+ #
+ @command.seeds_block&.call(pipeline)
+
+ ##
+ # Gather all runtime build/stage errors
+ #
+ if stage_seeds_errors
+ return error(stage_seeds_errors.join("\n"), config_error: true)
+ end
+
+ @command.stage_seeds = stage_seeds
+ end
+
+ def break?
+ pipeline.errors.any?
+ end
+
+ private
+
+ def stage_seeds_errors
+ stage_seeds.flat_map(&:errors).compact.presence
+ end
+
+ def stage_seeds
+ strong_memoize(:stage_seeds) do
+ seeds = stages_attributes.inject([]) do |previous_stages, attributes|
+ seed = Gitlab::Ci::Pipeline::Seed::Stage.new(pipeline, attributes, previous_stages)
+ previous_stages + [seed]
+ end
+
+ seeds.select(&:included?)
+ end
+ end
+
+ def stages_attributes
+ @command.config_processor.stages_attributes
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/pipeline/chain/validate/config.rb b/lib/gitlab/ci/pipeline/chain/validate/config.rb
deleted file mode 100644
index 28c38cc3d18..00000000000
--- a/lib/gitlab/ci/pipeline/chain/validate/config.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Ci
- module Pipeline
- module Chain
- module Validate
- class Config < Chain::Base
- include Chain::Helpers
-
- def perform!
- unless @pipeline.config_processor
- unless @pipeline.ci_yaml_file
- return error("Missing #{@pipeline.ci_yaml_file_path} file")
- end
-
- if @command.save_incompleted && @pipeline.has_yaml_errors?
- @pipeline.drop!(:config_error)
- end
-
- error(@pipeline.yaml_errors)
- end
- end
-
- def break?
- @pipeline.errors.any? || @pipeline.persisted?
- end
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces.rb b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces.rb
index 3e8a9b89998..cea25967801 100644
--- a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces.rb
+++ b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces.rb
@@ -66,11 +66,13 @@ module Gitlab
def move_repositories(namespace, old_full_path, new_full_path)
repo_shards_for_namespace(namespace).each do |repository_storage|
# Ensure old directory exists before moving it
- gitlab_shell.add_namespace(repository_storage, old_full_path)
+ Gitlab::GitalyClient::NamespaceService.allow do
+ gitlab_shell.add_namespace(repository_storage, old_full_path)
- unless gitlab_shell.mv_namespace(repository_storage, old_full_path, new_full_path)
- message = "Exception moving on shard #{repository_storage} from #{old_full_path} to #{new_full_path}"
- Rails.logger.error message # rubocop:disable Gitlab/RailsLogger
+ unless gitlab_shell.mv_namespace(repository_storage, old_full_path, new_full_path)
+ message = "Exception moving on shard #{repository_storage} from #{old_full_path} to #{new_full_path}"
+ Rails.logger.error message # rubocop:disable Gitlab/RailsLogger
+ end
end
end
end
diff --git a/lib/gitlab/gitaly_client/namespace_service.rb b/lib/gitlab/gitaly_client/namespace_service.rb
index f95833eed01..dbcebec3aa2 100644
--- a/lib/gitlab/gitaly_client/namespace_service.rb
+++ b/lib/gitlab/gitaly_client/namespace_service.rb
@@ -3,7 +3,22 @@
module Gitlab
module GitalyClient
class NamespaceService
+ extend Gitlab::TemporarilyAllow
+
+ NamespaceServiceAccessError = Class.new(StandardError)
+ ALLOW_KEY = :allow_namespace
+
+ def self.allow
+ temporarily_allow(ALLOW_KEY) { yield }
+ end
+
+ def self.denied?
+ !temporarily_allowed?(ALLOW_KEY)
+ end
+
def initialize(storage)
+ raise NamespaceServiceAccessError if self.class.denied?
+
@storage = storage
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 82bd6053144..f1b14d78292 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -261,9 +261,6 @@ msgstr ""
msgid "%{from} to %{to}"
msgstr ""
-msgid "%{gitlab_ci_yml} not found in this commit"
-msgstr ""
-
msgid "%{group_docs_link_start}Groups%{group_docs_link_end} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects."
msgstr ""
@@ -908,6 +905,9 @@ msgstr ""
msgid "Add Kubernetes cluster"
msgstr ""
+msgid "Add LICENSE"
+msgstr ""
+
msgid "Add README"
msgstr ""
@@ -11947,6 +11947,9 @@ msgstr ""
msgid "Owner"
msgstr ""
+msgid "Package deleted successfully"
+msgstr ""
+
msgid "Package information"
msgstr ""
@@ -11965,10 +11968,10 @@ msgstr ""
msgid "PackageRegistry|Copy yarn setup command"
msgstr ""
-msgid "PackageRegistry|Delete Package"
+msgid "PackageRegistry|Delete Package Version"
msgstr ""
-msgid "PackageRegistry|Delete Package Version"
+msgid "PackageRegistry|Delete package"
msgstr ""
msgid "PackageRegistry|Installation"
diff --git a/qa/qa/vendor/github/page/login.rb b/qa/qa/vendor/github/page/login.rb
index 232c8743de7..e581edcb7c7 100644
--- a/qa/qa/vendor/github/page/login.rb
+++ b/qa/qa/vendor/github/page/login.rb
@@ -12,7 +12,7 @@ module QA
fill_in 'password', with: QA::Runtime::Env.github_password
click_on 'Sign in'
- Support::Retrier.retry_until(exit_on_failure: true) do
+ Support::Retrier.retry_until(exit_on_failure: true, sleep_interval: 35) do
otp = OnePassword::CLI.new.otp
fill_in 'otp', with: otp
diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb
index 94bcabd3ca4..f538df89fd3 100644
--- a/spec/features/commits_spec.rb
+++ b/spec/features/commits_spec.rb
@@ -157,39 +157,6 @@ describe 'Commits' do
end
end
end
-
- describe '.gitlab-ci.yml not found warning' do
- before do
- project.add_reporter(user)
- end
-
- context 'ci builds enabled' do
- it 'does not show warning' do
- visit pipeline_path(pipeline)
-
- expect(page).not_to have_content '.gitlab-ci.yml not found in this commit'
- end
-
- it 'shows warning' do
- stub_ci_pipeline_yaml_file(nil)
-
- visit pipeline_path(pipeline)
-
- expect(page).to have_content '.gitlab-ci.yml not found in this commit'
- end
- end
-
- context 'ci builds disabled' do
- it 'does not show warning' do
- stub_ci_builds_disabled
- stub_ci_pipeline_yaml_file(nil)
-
- visit pipeline_path(pipeline)
-
- expect(page).not_to have_content '.gitlab-ci.yml not found in this commit'
- end
- end
- end
end
context 'viewing commits for a branch' do
diff --git a/spec/features/issuables/sorting_list_spec.rb b/spec/features/issuables/sorting_list_spec.rb
index b4531f5da4e..b7813c8ba30 100644
--- a/spec/features/issuables/sorting_list_spec.rb
+++ b/spec/features/issuables/sorting_list_spec.rb
@@ -57,7 +57,7 @@ describe 'Sort Issuable List' do
it 'is "last updated"' do
visit_merge_requests_with_state(project, 'merged')
- expect(find('.issues-other-filters')).to have_content('Last updated')
+ expect(find('.filter-dropdown-container')).to have_content('Last updated')
expect(first_merge_request).to include(last_updated_issuable.title)
expect(last_merge_request).to include(first_updated_issuable.title)
end
@@ -69,7 +69,7 @@ describe 'Sort Issuable List' do
it 'is "last updated"' do
visit_merge_requests_with_state(project, 'closed')
- expect(find('.issues-other-filters')).to have_content('Last updated')
+ expect(find('.filter-dropdown-container')).to have_content('Last updated')
expect(first_merge_request).to include(last_updated_issuable.title)
expect(last_merge_request).to include(first_updated_issuable.title)
end
@@ -81,7 +81,7 @@ describe 'Sort Issuable List' do
it 'is "created date"' do
visit_merge_requests_with_state(project, 'all')
- expect(find('.issues-other-filters')).to have_content('Created date')
+ expect(find('.filter-dropdown-container')).to have_content('Created date')
expect(first_merge_request).to include(last_created_issuable.title)
expect(last_merge_request).to include(first_created_issuable.title)
end
@@ -94,7 +94,7 @@ describe 'Sort Issuable List' do
it 'supports sorting in asc and desc order' do
visit_merge_requests_with_state(project, 'open')
- page.within('.issues-other-filters') do
+ page.within('.filter-dropdown-container') do
click_button('Created date')
click_link('Last updated')
end
@@ -102,7 +102,7 @@ describe 'Sort Issuable List' do
expect(first_merge_request).to include(last_updated_issuable.title)
expect(last_merge_request).to include(first_updated_issuable.title)
- find('.issues-other-filters .filter-dropdown-container .rspec-reverse-sort').click
+ find('.filter-dropdown-container .rspec-reverse-sort').click
expect(first_merge_request).to include(first_updated_issuable.title)
expect(last_merge_request).to include(last_updated_issuable.title)
@@ -133,7 +133,7 @@ describe 'Sort Issuable List' do
it 'is "created date"' do
visit_issues project
- expect(find('.issues-other-filters')).to have_content('Created date')
+ expect(find('.filter-dropdown-container')).to have_content('Created date')
expect(first_issue).to include(last_created_issuable.title)
expect(last_issue).to include(first_created_issuable.title)
end
@@ -145,7 +145,7 @@ describe 'Sort Issuable List' do
it 'is "created date"' do
visit_issues_with_state(project, 'open')
- expect(find('.issues-other-filters')).to have_content('Created date')
+ expect(find('.filter-dropdown-container')).to have_content('Created date')
expect(first_issue).to include(last_created_issuable.title)
expect(last_issue).to include(first_created_issuable.title)
end
@@ -157,7 +157,7 @@ describe 'Sort Issuable List' do
it 'is "last updated"' do
visit_issues_with_state(project, 'closed')
- expect(find('.issues-other-filters')).to have_content('Last updated')
+ expect(find('.filter-dropdown-container')).to have_content('Last updated')
expect(first_issue).to include(last_updated_issuable.title)
expect(last_issue).to include(first_updated_issuable.title)
end
@@ -169,7 +169,7 @@ describe 'Sort Issuable List' do
it 'is "created date"' do
visit_issues_with_state(project, 'all')
- expect(find('.issues-other-filters')).to have_content('Created date')
+ expect(find('.filter-dropdown-container')).to have_content('Created date')
expect(first_issue).to include(last_created_issuable.title)
expect(last_issue).to include(first_created_issuable.title)
end
@@ -183,7 +183,7 @@ describe 'Sort Issuable List' do
end
it 'shows the sort order as created date' do
- expect(find('.issues-other-filters')).to have_content('Created date')
+ expect(find('.filter-dropdown-container')).to have_content('Created date')
expect(first_issue).to include(last_created_issuable.title)
expect(last_issue).to include(first_created_issuable.title)
end
@@ -196,7 +196,7 @@ describe 'Sort Issuable List' do
it 'supports sorting in asc and desc order' do
visit_issues_with_state(project, 'open')
- page.within('.issues-other-filters') do
+ page.within('.filter-dropdown-container') do
click_button('Created date')
click_link('Last updated')
end
@@ -204,7 +204,7 @@ describe 'Sort Issuable List' do
expect(first_issue).to include(last_updated_issuable.title)
expect(last_issue).to include(first_updated_issuable.title)
- find('.issues-other-filters .filter-dropdown-container .rspec-reverse-sort').click
+ find('.filter-dropdown-container .rspec-reverse-sort').click
expect(first_issue).to include(first_updated_issuable.title)
expect(last_issue).to include(last_updated_issuable.title)
diff --git a/spec/features/projects/commits/user_browses_commits_spec.rb b/spec/features/projects/commits/user_browses_commits_spec.rb
index 1cff7f2c385..b22715a44f0 100644
--- a/spec/features/projects/commits/user_browses_commits_spec.rb
+++ b/spec/features/projects/commits/user_browses_commits_spec.rb
@@ -56,10 +56,6 @@ describe 'User browses commits' do
project.enable_ci
create(:ci_build, pipeline: pipeline)
-
- allow_next_instance_of(Ci::Pipeline) do |instance|
- allow(instance).to receive(:ci_yaml_file).and_return('')
- end
end
it 'renders commit ci info' do
diff --git a/spec/features/projects/files/files_sort_submodules_with_folders_spec.rb b/spec/features/projects/files/files_sort_submodules_with_folders_spec.rb
index 0e43f2fd26b..622764487d8 100644
--- a/spec/features/projects/files/files_sort_submodules_with_folders_spec.rb
+++ b/spec/features/projects/files/files_sort_submodules_with_folders_spec.rb
@@ -7,13 +7,11 @@ describe 'Projects > Files > User views files page' do
let(:user) { project.owner }
before do
- stub_feature_flags(vue_file_list: false)
-
sign_in user
visit project_tree_path(project, project.repository.root_ref)
end
- it 'user sees folders and submodules sorted together, followed by files' do
+ it 'user sees folders and submodules sorted together, followed by files', :js do
rows = all('td.tree-item-file-name').map(&:text)
tree = project.repository.tree
diff --git a/spec/features/projects/files/project_owner_creates_license_file_spec.rb b/spec/features/projects/files/project_owner_creates_license_file_spec.rb
index 943c6e0e959..9fccb3441d6 100644
--- a/spec/features/projects/files/project_owner_creates_license_file_spec.rb
+++ b/spec/features/projects/files/project_owner_creates_license_file_spec.rb
@@ -7,7 +7,6 @@ describe 'Projects > Files > Project owner creates a license file', :js do
let(:project_maintainer) { project.owner }
before do
- stub_feature_flags(vue_file_list: false)
project.repository.delete_file(project_maintainer, 'LICENSE',
message: 'Remove LICENSE', branch_name: 'master')
sign_in(project_maintainer)
@@ -39,7 +38,7 @@ describe 'Projects > Files > Project owner creates a license file', :js do
end
it 'project maintainer creates a license file from the "Add license" link' do
- click_link 'Add license'
+ click_link 'Add LICENSE'
expect(page).to have_content('New file')
expect(current_path).to eq(
diff --git a/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb b/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb
index 9f63b312146..ad6c565c8f9 100644
--- a/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb
+++ b/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb
@@ -12,7 +12,7 @@ describe 'Projects > Files > Project owner sees a link to create a license file
it 'project maintainer creates a license file from a template' do
visit project_path(project)
- click_on 'Add license'
+ click_on 'Add LICENSE'
expect(page).to have_content('New file')
expect(current_path).to eq(
diff --git a/spec/features/projects/files/user_browses_files_spec.rb b/spec/features/projects/files/user_browses_files_spec.rb
index 0b3f905b5de..10672bbec68 100644
--- a/spec/features/projects/files/user_browses_files_spec.rb
+++ b/spec/features/projects/files/user_browses_files_spec.rb
@@ -13,23 +13,22 @@ describe "User browses files" do
let(:user) { project.owner }
before do
- stub_feature_flags(vue_file_list: false)
sign_in(user)
end
- it "shows last commit for current directory" do
+ it "shows last commit for current directory", :js do
visit(tree_path_root_ref)
click_link("files")
last_commit = project.repository.last_commit_for_path(project.default_branch, "files")
- page.within(".blob-commit-info") do
+ page.within(".commit-detail") do
expect(page).to have_content(last_commit.short_id).and have_content(last_commit.author_name)
end
end
- context "when browsing the master branch" do
+ context "when browsing the master branch", :js do
before do
visit(tree_path_root_ref)
end
@@ -124,8 +123,7 @@ describe "User browses files" do
expect(current_path).to eq(project_tree_path(project, "markdown/doc/raketasks"))
expect(page).to have_content("backup_restore.md").and have_content("maintenance.md")
- click_link("shop")
- click_link("Maintenance")
+ click_link("maintenance.md")
expect(current_path).to eq(project_blob_path(project, "markdown/doc/raketasks/maintenance.md"))
expect(page).to have_content("bundle exec rake gitlab:env:info RAILS_ENV=production")
@@ -144,7 +142,7 @@ describe "User browses files" do
# rubocop:disable Lint/Void
# Test the full URLs of links instead of relative paths by `have_link(text: "...", href: "...")`.
- find("a", text: /^empty$/)["href"] == project_tree_url(project, "markdown/d")
+ find("a", text: "..")["href"] == project_tree_url(project, "markdown/d")
# rubocop:enable Lint/Void
page.within(".tree-table") do
@@ -168,7 +166,7 @@ describe "User browses files" do
end
end
- context "when browsing a specific ref" do
+ context "when browsing a specific ref", :js do
let(:ref) { project_tree_path(project, "6d39438") }
before do
@@ -180,7 +178,7 @@ describe "User browses files" do
expect(page).to have_content(".gitignore").and have_content("LICENSE")
end
- it "shows files from a repository with apostroph in its name", :js do
+ it "shows files from a repository with apostroph in its name" do
first(".js-project-refs-dropdown").click
page.within(".project-refs-form") do
@@ -191,10 +189,10 @@ describe "User browses files" do
visit(project_tree_path(project, "'test'"))
- expect(page).to have_css(".tree-commit-link").and have_no_content("Loading commit data...")
+ expect(page).not_to have_selector(".tree-commit .animation-container")
end
- it "shows the code with a leading dot in the directory", :js do
+ it "shows the code with a leading dot in the directory" do
first(".js-project-refs-dropdown").click
page.within(".project-refs-form") do
@@ -203,7 +201,7 @@ describe "User browses files" do
visit(project_tree_path(project, "fix/.testdir"))
- expect(page).to have_css(".tree-commit-link").and have_no_content("Loading commit data...")
+ expect(page).not_to have_selector(".tree-commit .animation-container")
end
it "does not show the permalink link" do
@@ -221,7 +219,7 @@ describe "User browses files" do
click_link(".gitignore")
end
- it "shows a file content", :js do
+ it "shows a file content" do
expect(page).to have_content("*.rbc")
end
diff --git a/spec/features/projects/files/user_browses_lfs_files_spec.rb b/spec/features/projects/files/user_browses_lfs_files_spec.rb
index 08ebeed2cdd..618290416bd 100644
--- a/spec/features/projects/files/user_browses_lfs_files_spec.rb
+++ b/spec/features/projects/files/user_browses_lfs_files_spec.rb
@@ -7,8 +7,6 @@ describe 'Projects > Files > User browses LFS files' do
let(:user) { project.owner }
before do
- stub_feature_flags(vue_file_list: false)
-
sign_in(user)
end
diff --git a/spec/features/projects/files/user_creates_directory_spec.rb b/spec/features/projects/files/user_creates_directory_spec.rb
index f828ee63cd7..b8765066217 100644
--- a/spec/features/projects/files/user_creates_directory_spec.rb
+++ b/spec/features/projects/files/user_creates_directory_spec.rb
@@ -13,8 +13,6 @@ describe 'Projects > Files > User creates a directory', :js do
let(:user) { create(:user) }
before do
- stub_feature_flags(vue_file_list: false)
-
project.add_developer(user)
sign_in(user)
visit project_tree_path(project, 'master')
diff --git a/spec/features/projects/files/user_creates_files_spec.rb b/spec/features/projects/files/user_creates_files_spec.rb
index 23663aeaef0..eb9a4d8cb09 100644
--- a/spec/features/projects/files/user_creates_files_spec.rb
+++ b/spec/features/projects/files/user_creates_files_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Projects > Files > User creates files' do
+describe 'Projects > Files > User creates files', :js do
let(:fork_message) do
"You're not allowed to make changes to this project directly. "\
"A fork of this project has been created that you can make changes in, so you can submit a merge request."
@@ -14,7 +14,6 @@ describe 'Projects > Files > User creates files' do
let(:user) { create(:user) }
before do
- stub_feature_flags(vue_file_list: false)
stub_feature_flags(web_ide_default: false)
project.add_maintainer(user)
@@ -68,8 +67,7 @@ describe 'Projects > Files > User creates files' do
file_name = find('#file_name')
file_name.set options[:file_name] || 'README.md'
- file_content = find('#file-content', visible: false)
- file_content.set options[:file_content] || 'Some content'
+ find('.ace_text-input', visible: false).send_keys.native.send_keys options[:file_content] || 'Some content'
click_button 'Commit changes'
end
@@ -89,7 +87,7 @@ describe 'Projects > Files > User creates files' do
expect(page).to have_content 'Path cannot include directory traversal'
end
- it 'creates and commit a new file', :js do
+ it 'creates and commit a new file' do
find('#editor')
execute_script("ace.edit('editor').setValue('*.rbca')")
fill_in(:file_name, with: 'not_a_file.md')
@@ -105,7 +103,7 @@ describe 'Projects > Files > User creates files' do
expect(page).to have_content('*.rbca')
end
- it 'creates and commit a new file with new lines at the end of file', :js do
+ it 'creates and commit a new file with new lines at the end of file' do
find('#editor')
execute_script('ace.edit("editor").setValue("Sample\n\n\n")')
fill_in(:file_name, with: 'not_a_file.md')
@@ -122,7 +120,7 @@ describe 'Projects > Files > User creates files' do
expect(evaluate_script('ace.edit("editor").getValue()')).to eq("Sample\n\n\n")
end
- it 'creates and commit a new file with a directory name', :js do
+ it 'creates and commit a new file with a directory name' do
fill_in(:file_name, with: 'foo/bar/baz.txt')
expect(page).to have_selector('.file-editor')
@@ -139,7 +137,7 @@ describe 'Projects > Files > User creates files' do
expect(page).to have_content('*.rbca')
end
- it 'creates and commit a new file specifying a new branch', :js do
+ it 'creates and commit a new file specifying a new branch' do
expect(page).to have_selector('.file-editor')
find('#editor')
@@ -174,7 +172,7 @@ describe 'Projects > Files > User creates files' do
expect(page).to have_content(message)
end
- it 'creates and commit new file in forked project', :js do
+ it 'creates and commit new file in forked project' do
expect(page).to have_selector('.file-editor')
find('#editor')
diff --git a/spec/features/projects/files/user_deletes_files_spec.rb b/spec/features/projects/files/user_deletes_files_spec.rb
index 570813ce085..0f543e47631 100644
--- a/spec/features/projects/files/user_deletes_files_spec.rb
+++ b/spec/features/projects/files/user_deletes_files_spec.rb
@@ -14,8 +14,6 @@ describe 'Projects > Files > User deletes files', :js do
let(:user) { create(:user) }
before do
- stub_feature_flags(vue_file_list: false)
-
sign_in(user)
end
diff --git a/spec/features/projects/files/user_edits_files_spec.rb b/spec/features/projects/files/user_edits_files_spec.rb
index c0312f5bb62..374a7fb7936 100644
--- a/spec/features/projects/files/user_edits_files_spec.rb
+++ b/spec/features/projects/files/user_edits_files_spec.rb
@@ -12,7 +12,6 @@ describe 'Projects > Files > User edits files', :js do
before do
stub_feature_flags(web_ide_default: false)
- stub_feature_flags(vue_file_list: false)
sign_in(user)
end
diff --git a/spec/features/projects/files/user_replaces_files_spec.rb b/spec/features/projects/files/user_replaces_files_spec.rb
index bdef40690a2..4c54bbdcd67 100644
--- a/spec/features/projects/files/user_replaces_files_spec.rb
+++ b/spec/features/projects/files/user_replaces_files_spec.rb
@@ -16,8 +16,6 @@ describe 'Projects > Files > User replaces files', :js do
let(:user) { create(:user) }
before do
- stub_feature_flags(vue_file_list: false)
-
sign_in(user)
end
diff --git a/spec/features/projects/files/user_uploads_files_spec.rb b/spec/features/projects/files/user_uploads_files_spec.rb
index 284f891731c..35a3835ff12 100644
--- a/spec/features/projects/files/user_uploads_files_spec.rb
+++ b/spec/features/projects/files/user_uploads_files_spec.rb
@@ -16,8 +16,6 @@ describe 'Projects > Files > User uploads files' do
let(:project2_tree_path_root_ref) { project_tree_path(project2, project2.repository.root_ref) }
before do
- stub_feature_flags(vue_file_list: false)
-
project.add_maintainer(user)
sign_in(user)
end
diff --git a/spec/features/projects/show/user_sees_collaboration_links_spec.rb b/spec/features/projects/show/user_sees_collaboration_links_spec.rb
index bbb3a066ed5..ff133b58f89 100644
--- a/spec/features/projects/show/user_sees_collaboration_links_spec.rb
+++ b/spec/features/projects/show/user_sees_collaboration_links_spec.rb
@@ -2,12 +2,11 @@
require 'spec_helper'
-describe 'Projects > Show > Collaboration links' do
+describe 'Projects > Show > Collaboration links', :js do
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
before do
- stub_feature_flags(vue_file_list: false)
project.add_developer(user)
sign_in(user)
end
@@ -17,15 +16,21 @@ describe 'Projects > Show > Collaboration links' do
# The navigation bar
page.within('.header-new') do
+ find('.qa-new-menu-toggle').click
+
aggregate_failures 'dropdown links in the navigation bar' do
expect(page).to have_link('New issue')
expect(page).to have_link('New merge request')
expect(page).to have_link('New snippet', href: new_project_snippet_path(project))
end
+
+ find('.qa-new-menu-toggle').click
end
# The dropdown above the tree
page.within('.repo-breadcrumb') do
+ find('.qa-add-to-tree').click
+
aggregate_failures 'dropdown links above the repo tree' do
expect(page).to have_link('New file')
expect(page).to have_link('Upload file')
@@ -45,23 +50,19 @@ describe 'Projects > Show > Collaboration links' do
visit project_path(project)
page.within('.header-new') do
+ find('.qa-new-menu-toggle').click
+
aggregate_failures 'dropdown links' do
expect(page).not_to have_link('New issue')
expect(page).not_to have_link('New merge request')
expect(page).not_to have_link('New snippet', href: new_project_snippet_path(project))
end
- end
- page.within('.repo-breadcrumb') do
- aggregate_failures 'dropdown links' do
- expect(page).not_to have_link('New file')
- expect(page).not_to have_link('Upload file')
- expect(page).not_to have_link('New directory')
- expect(page).not_to have_link('New branch')
- expect(page).not_to have_link('New tag')
- end
+ find('.qa-new-menu-toggle').click
end
+ expect(page).not_to have_selector('.qa-add-to-tree')
+
expect(page).not_to have_link('Web IDE')
end
end
diff --git a/spec/features/projects/show/user_sees_last_commit_ci_status_spec.rb b/spec/features/projects/show/user_sees_last_commit_ci_status_spec.rb
index fdc238d55cf..cf1a679102c 100644
--- a/spec/features/projects/show/user_sees_last_commit_ci_status_spec.rb
+++ b/spec/features/projects/show/user_sees_last_commit_ci_status_spec.rb
@@ -5,10 +5,6 @@ require 'spec_helper'
describe 'Projects > Show > User sees last commit CI status' do
set(:project) { create(:project, :repository, :public) }
- before do
- stub_feature_flags(vue_file_list: false)
- end
-
it 'shows the project README', :js do
project.enable_ci
pipeline = create(:ci_pipeline, project: project, sha: project.commit.sha, ref: 'master')
@@ -16,9 +12,9 @@ describe 'Projects > Show > User sees last commit CI status' do
visit project_path(project)
- page.within '.blob-commit-info' do
+ page.within '.commit-detail' do
expect(page).to have_content(project.commit.sha[0..6])
- expect(page).to have_link('Pipeline: skipped')
+ expect(page).to have_selector('[aria-label="Commit: skipped"]')
end
end
end
diff --git a/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb b/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb
index c136d7607fd..41c3c6b5770 100644
--- a/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb
+++ b/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb
@@ -59,8 +59,8 @@ describe 'Projects > Show > User sees setup shortcut buttons' do
end
it '"Add license" button linked to new file populated for a license' do
- page.within('.project-stats') do
- expect(page).to have_link('Add license', href: presenter.add_license_path)
+ page.within('.project-buttons') do
+ expect(page).to have_link('Add LICENSE', href: presenter.add_license_path)
end
end
end
@@ -175,7 +175,7 @@ describe 'Projects > Show > User sees setup shortcut buttons' do
expect(project.repository.license_blob).not_to be_nil
page.within('.project-buttons') do
- expect(page).not_to have_link('Add license')
+ expect(page).not_to have_link('Add LICENSE')
end
end
diff --git a/spec/features/projects/tree/tree_show_spec.rb b/spec/features/projects/tree/tree_show_spec.rb
index ca616be341d..180ffac4d4d 100644
--- a/spec/features/projects/tree/tree_show_spec.rb
+++ b/spec/features/projects/tree/tree_show_spec.rb
@@ -10,7 +10,6 @@ describe 'Projects tree', :js do
let(:test_sha) { '7975be0116940bf2ad4321f79d02a55c5f7779aa' }
before do
- stub_feature_flags(vue_file_list: false)
project.add_maintainer(user)
sign_in(user)
end
diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb
index df71a4f3f70..90e48f3c230 100644
--- a/spec/features/projects_spec.rb
+++ b/spec/features/projects_spec.rb
@@ -6,10 +6,6 @@ describe 'Project' do
include ProjectForksHelper
include MobileHelpers
- before do
- stub_feature_flags(vue_file_list: false)
- end
-
describe 'creating from template' do
let(:user) { create(:user) }
let(:template) { Gitlab::ProjectTemplate.find(:rails) }
@@ -272,7 +268,7 @@ describe 'Project' do
end
end
- describe 'tree view (default view is set to Files)' do
+ describe 'tree view (default view is set to Files)', :js do
let(:user) { create(:user, project_view: 'files') }
let(:project) { create(:forked_project_with_submodules) }
@@ -285,19 +281,19 @@ describe 'Project' do
it 'has working links to files' do
click_link('PROCESS.md')
- expect(page.status_code).to eq(200)
+ expect(page).to have_selector('.file-holder')
end
it 'has working links to directories' do
click_link('encoding')
- expect(page.status_code).to eq(200)
+ expect(page).to have_selector('.breadcrumb-item', text: 'encoding')
end
it 'has working links to submodules' do
click_link('645f6c4c')
- expect(page.status_code).to eq(200)
+ expect(page).to have_selector('.qa-branches-select', text: '645f6c4c82fd3f5e06f67134450a570b795e55a6')
end
context 'for signed commit on default branch', :js do
diff --git a/spec/features/signed_commits_spec.rb b/spec/features/signed_commits_spec.rb
index 9e2634657a0..f56bd055224 100644
--- a/spec/features/signed_commits_spec.rb
+++ b/spec/features/signed_commits_spec.rb
@@ -173,13 +173,5 @@ describe 'GPG signed commits' do
context 'with vue tree view enabled' do
it_behaves_like 'a commit with a signature'
end
-
- context 'with vue tree view disabled' do
- before do
- stub_feature_flags(vue_file_list: false)
- end
-
- it_behaves_like 'a commit with a signature'
- end
end
end
diff --git a/spec/frontend/repository/components/table/index_spec.js b/spec/frontend/repository/components/table/index_spec.js
index 1e2f3501c8c..41450becabb 100644
--- a/spec/frontend/repository/components/table/index_spec.js
+++ b/spec/frontend/repository/components/table/index_spec.js
@@ -9,6 +9,7 @@ let $apollo;
const MOCK_BLOBS = [
{
id: '123abc',
+ sha: '123abc',
flatPath: 'blob',
name: 'blob.md',
type: 'blob',
@@ -16,6 +17,7 @@ const MOCK_BLOBS = [
},
{
id: '124abc',
+ sha: '124abc',
flatPath: 'blob2',
name: 'blob2.md',
type: 'blob',
diff --git a/spec/frontend/repository/components/table/row_spec.js b/spec/frontend/repository/components/table/row_spec.js
index 04a5e0778a1..aa0b9385f1a 100644
--- a/spec/frontend/repository/components/table/row_spec.js
+++ b/spec/frontend/repository/components/table/row_spec.js
@@ -41,6 +41,7 @@ describe('Repository table row component', () => {
it('renders table row', () => {
factory({
id: '1',
+ sha: '123',
path: 'test',
type: 'file',
currentPath: '/',
@@ -57,6 +58,7 @@ describe('Repository table row component', () => {
`('renders a $componentName for type $type', ({ type, component }) => {
factory({
id: '1',
+ sha: '123',
path: 'test',
type,
currentPath: '/',
@@ -73,6 +75,7 @@ describe('Repository table row component', () => {
`('pushes new router if type $type is tree', ({ type, pushes }) => {
factory({
id: '1',
+ sha: '123',
path: 'test',
type,
currentPath: '/',
@@ -95,6 +98,7 @@ describe('Repository table row component', () => {
`('calls visitUrl if $type is not tree', ({ type, pushes }) => {
factory({
id: '1',
+ sha: '123',
path: 'test',
type,
currentPath: '/',
@@ -112,6 +116,7 @@ describe('Repository table row component', () => {
it('renders commit ID for submodule', () => {
factory({
id: '1',
+ sha: '123',
path: 'test',
type: 'commit',
currentPath: '/',
@@ -123,6 +128,7 @@ describe('Repository table row component', () => {
it('renders link with href', () => {
factory({
id: '1',
+ sha: '123',
path: 'test',
type: 'blob',
url: 'https://test.com',
@@ -135,6 +141,7 @@ describe('Repository table row component', () => {
it('renders LFS badge', () => {
factory({
id: '1',
+ sha: '123',
path: 'test',
type: 'commit',
currentPath: '/',
@@ -147,6 +154,7 @@ describe('Repository table row component', () => {
it('renders commit and web links with href for submodule', () => {
factory({
id: '1',
+ sha: '123',
path: 'test',
type: 'commit',
url: 'https://test.com',
@@ -161,6 +169,7 @@ describe('Repository table row component', () => {
it('renders lock icon', () => {
factory({
id: '1',
+ sha: '123',
path: 'test',
type: 'tree',
currentPath: '/',
diff --git a/spec/frontend/repository/log_tree_spec.js b/spec/frontend/repository/log_tree_spec.js
index ad42f8b2ffc..9199c726680 100644
--- a/spec/frontend/repository/log_tree_spec.js
+++ b/spec/frontend/repository/log_tree_spec.js
@@ -41,7 +41,7 @@ describe('fetchLogsTree', () => {
jest.spyOn(axios, 'get');
- global.gon = { gitlab_url: 'https://test.com' };
+ global.gon = { relative_url_root: '' };
client = {
readQuery: () => ({
@@ -64,10 +64,9 @@ describe('fetchLogsTree', () => {
it('calls axios get', () =>
fetchLogsTree(client, '', '0', resolver).then(() => {
- expect(axios.get).toHaveBeenCalledWith(
- 'https://test.com/gitlab-org/gitlab-foss/refs/master/logs_tree/',
- { params: { format: 'json', offset: '0' } },
- );
+ expect(axios.get).toHaveBeenCalledWith('/gitlab-org/gitlab-foss/refs/master/logs_tree/', {
+ params: { format: 'json', offset: '0' },
+ });
}));
it('calls axios get once', () =>
diff --git a/spec/graphql/resolvers/issues_resolver_spec.rb b/spec/graphql/resolvers/issues_resolver_spec.rb
index 3ac698e0e72..bf9106643eb 100644
--- a/spec/graphql/resolvers/issues_resolver_spec.rb
+++ b/spec/graphql/resolvers/issues_resolver_spec.rb
@@ -99,6 +99,19 @@ describe Resolvers::IssuesResolver do
expect(resolve_issues(sort: :due_date_desc)).to eq [due_issue1, due_issue3, due_issue4, due_issue2]
end
end
+
+ context 'when sorting by relative position' do
+ let(:project) { create(:project) }
+
+ let!(:relative_issue1) { create(:issue, project: project, relative_position: 2000) }
+ let!(:relative_issue2) { create(:issue, project: project, relative_position: nil) }
+ let!(:relative_issue3) { create(:issue, project: project, relative_position: 1000) }
+ let!(:relative_issue4) { create(:issue, project: project, relative_position: nil) }
+
+ it 'sorts issues ascending' do
+ expect(resolve_issues(sort: :relative_position_asc)).to eq [relative_issue3, relative_issue1, relative_issue4, relative_issue2]
+ end
+ end
end
it 'returns issues user can see' do
diff --git a/spec/graphql/types/commit_type_spec.rb b/spec/graphql/types/commit_type_spec.rb
index ee9af886e60..1c3b46ecfde 100644
--- a/spec/graphql/types/commit_type_spec.rb
+++ b/spec/graphql/types/commit_type_spec.rb
@@ -10,7 +10,7 @@ describe GitlabSchema.types['Commit'] do
it 'contains attributes related to commit' do
expect(described_class).to have_graphql_fields(
:id, :sha, :title, :description, :message, :authored_date,
- :author, :web_url, :latest_pipeline, :pipelines, :signature_html
+ :author_name, :author, :web_url, :latest_pipeline, :pipelines, :signature_html
)
end
end
diff --git a/spec/graphql/types/issue_sort_enum_spec.rb b/spec/graphql/types/issue_sort_enum_spec.rb
index da7126f3d84..1b6aa6d6069 100644
--- a/spec/graphql/types/issue_sort_enum_spec.rb
+++ b/spec/graphql/types/issue_sort_enum_spec.rb
@@ -8,6 +8,6 @@ describe GitlabSchema.types['IssueSort'] do
it_behaves_like 'common sort values'
it 'exposes all the existing issue sort values' do
- expect(described_class.values.keys).to include(*%w[DUE_DATE_ASC DUE_DATE_DESC])
+ expect(described_class.values.keys).to include(*%w[DUE_DATE_ASC DUE_DATE_DESC RELATIVE_POSITION_ASC])
end
end
diff --git a/spec/graphql/types/tree/blob_type_spec.rb b/spec/graphql/types/tree/blob_type_spec.rb
index 22c11aff90a..516c862b9c6 100644
--- a/spec/graphql/types/tree/blob_type_spec.rb
+++ b/spec/graphql/types/tree/blob_type_spec.rb
@@ -5,5 +5,5 @@ require 'spec_helper'
describe Types::Tree::BlobType do
it { expect(described_class.graphql_name).to eq('Blob') }
- it { expect(described_class).to have_graphql_fields(:id, :name, :type, :path, :flat_path, :web_url, :lfs_oid) }
+ it { expect(described_class).to have_graphql_fields(:id, :sha, :name, :type, :path, :flat_path, :web_url, :lfs_oid) }
end
diff --git a/spec/graphql/types/tree/submodule_type_spec.rb b/spec/graphql/types/tree/submodule_type_spec.rb
index 768eccba68c..81f7ad825a1 100644
--- a/spec/graphql/types/tree/submodule_type_spec.rb
+++ b/spec/graphql/types/tree/submodule_type_spec.rb
@@ -5,5 +5,5 @@ require 'spec_helper'
describe Types::Tree::SubmoduleType do
it { expect(described_class.graphql_name).to eq('Submodule') }
- it { expect(described_class).to have_graphql_fields(:id, :name, :type, :path, :flat_path, :web_url, :tree_url) }
+ it { expect(described_class).to have_graphql_fields(:id, :sha, :name, :type, :path, :flat_path, :web_url, :tree_url) }
end
diff --git a/spec/graphql/types/tree/tree_entry_type_spec.rb b/spec/graphql/types/tree/tree_entry_type_spec.rb
index ea1b6426034..228a4be0949 100644
--- a/spec/graphql/types/tree/tree_entry_type_spec.rb
+++ b/spec/graphql/types/tree/tree_entry_type_spec.rb
@@ -5,5 +5,5 @@ require 'spec_helper'
describe Types::Tree::TreeEntryType do
it { expect(described_class.graphql_name).to eq('TreeEntry') }
- it { expect(described_class).to have_graphql_fields(:id, :name, :type, :path, :flat_path, :web_url) }
+ it { expect(described_class).to have_graphql_fields(:id, :sha, :name, :type, :path, :flat_path, :web_url) }
end
diff --git a/spec/initializers/lograge_spec.rb b/spec/initializers/lograge_spec.rb
index c2c1960eeab..9267231390d 100644
--- a/spec/initializers/lograge_spec.rb
+++ b/spec/initializers/lograge_spec.rb
@@ -68,4 +68,52 @@ describe 'lograge', type: :request do
subject
end
end
+
+ context 'with a log subscriber' do
+ let(:subscriber) { Lograge::RequestLogSubscriber.new }
+
+ let(:event) do
+ ActiveSupport::Notifications::Event.new(
+ 'process_action.action_controller',
+ Time.now,
+ Time.now,
+ 2,
+ status: 200,
+ controller: 'HomeController',
+ action: 'index',
+ format: 'application/json',
+ method: 'GET',
+ path: '/home?foo=bar',
+ params: {},
+ db_runtime: 0.02,
+ view_runtime: 0.01
+ )
+ end
+
+ let(:log_output) { StringIO.new }
+ let(:logger) do
+ Logger.new(log_output).tap { |logger| logger.formatter = ->(_, _, _, msg) { msg } }
+ end
+
+ describe 'with an exception' do
+ let(:exception) { RuntimeError.new('bad request') }
+ let(:backtrace) { caller }
+
+ before do
+ allow(exception).to receive(:backtrace).and_return(backtrace)
+ event.payload[:exception_object] = exception
+ Lograge.logger = logger
+ end
+
+ it 'adds exception data to log' do
+ subscriber.process_action(event)
+
+ log_data = JSON.parse(log_output.string)
+
+ expect(log_data['exception']['class']).to eq('RuntimeError')
+ expect(log_data['exception']['message']).to eq('bad request')
+ expect(log_data['exception']['backtrace']).to eq(Gitlab::Profiler.clean_backtrace(backtrace))
+ end
+ end
+ end
end
diff --git a/spec/javascripts/boards/components/boards_selector_spec.js b/spec/javascripts/boards/components/boards_selector_spec.js
index 473cc0612ea..d1f36a0a652 100644
--- a/spec/javascripts/boards/components/boards_selector_spec.js
+++ b/spec/javascripts/boards/components/boards_selector_spec.js
@@ -1,5 +1,4 @@
import Vue from 'vue';
-import BoardService from '~/boards/services/board_service';
import BoardsSelector from '~/boards/components/boards_selector.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { TEST_HOST } from 'spec/test_constants';
@@ -37,7 +36,6 @@ describe('BoardsSelector', () => {
bulkUpdatePath: '',
boardId: '',
});
- window.gl.boardService = new BoardService();
allBoardsResponse = Promise.resolve({
data: boards,
@@ -46,8 +44,8 @@ describe('BoardsSelector', () => {
data: recentBoards,
});
- spyOn(BoardService.prototype, 'allBoards').and.returnValue(allBoardsResponse);
- spyOn(BoardService.prototype, 'recentBoards').and.returnValue(recentBoardsResponse);
+ spyOn(boardsStore, 'allBoards').and.returnValue(allBoardsResponse);
+ spyOn(boardsStore, 'recentBoards').and.returnValue(recentBoardsResponse);
const Component = Vue.extend(BoardsSelector);
vm = mountComponent(
@@ -94,7 +92,6 @@ describe('BoardsSelector', () => {
afterEach(() => {
vm.$destroy();
- window.gl.boardService = undefined;
});
describe('filtering', () => {
diff --git a/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb
index ba4f841cf43..a631cd2777b 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb
@@ -11,6 +11,7 @@ describe Gitlab::Ci::Pipeline::Chain::Build do
[{ key: 'first', secret_value: 'world' },
{ key: 'second', secret_value: 'second_world' }]
end
+
let(:command) do
Gitlab::Ci::Pipeline::Chain::Command.new(
source: :push,
@@ -51,12 +52,6 @@ describe Gitlab::Ci::Pipeline::Chain::Build do
.to eq variables_attributes.map(&:with_indifferent_access)
end
- it 'sets a valid config source' do
- step.perform!
-
- expect(pipeline.repository_source?).to be true
- end
-
it 'returns a valid pipeline' do
step.perform!
diff --git a/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb
index 75160a93ba7..52e9432dc92 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb
@@ -18,19 +18,32 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do
seeds_block: nil)
end
+ let(:dependencies) do
+ [
+ Gitlab::Ci::Pipeline::Chain::Config::Content.new(pipeline, command),
+ Gitlab::Ci::Pipeline::Chain::Config::Process.new(pipeline, command),
+ Gitlab::Ci::Pipeline::Chain::Seed.new(pipeline, command)
+ ]
+ end
+
let(:step) { described_class.new(pipeline, command) }
let(:config) do
{ rspec: { script: 'rspec' } }
end
+ def run_chain
+ dependencies.map(&:perform!)
+ step.perform!
+ end
+
before do
stub_ci_pipeline_yaml_file(YAML.dump(config))
end
context 'when pipeline doesn not have seeds block' do
before do
- step.perform!
+ run_chain
end
it 'does not persist the pipeline' do
@@ -66,7 +79,7 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do
end
before do
- step.perform!
+ run_chain
end
it 'breaks the chain' do
@@ -84,16 +97,16 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do
end
describe 'pipeline protect' do
- subject { step.perform! }
-
context 'when ref is protected' do
before do
allow(project).to receive(:protected_for?).with('master').and_return(true)
allow(project).to receive(:protected_for?).with('refs/heads/master').and_return(true)
+
+ dependencies.map(&:perform!)
end
it 'does not protect the pipeline' do
- subject
+ run_chain
expect(pipeline.protected).to eq(true)
end
@@ -101,7 +114,7 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do
context 'when ref is not protected' do
it 'does not protect the pipeline' do
- subject
+ run_chain
expect(pipeline.protected).to eq(false)
end
@@ -114,7 +127,7 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do
end
before do
- step.perform!
+ run_chain
end
it 'breaks the chain' do
@@ -146,7 +159,7 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do
end
it 'populates pipeline with resources described in the seeds block' do
- step.perform!
+ run_chain
expect(pipeline).not_to be_persisted
expect(pipeline.variables).not_to be_empty
@@ -156,7 +169,7 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do
end
it 'has pipeline iid' do
- step.perform!
+ run_chain
expect(pipeline.iid).to be > 0
end
@@ -168,7 +181,7 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do
end
it 'wastes pipeline iid' do
- expect { step.perform! }.to raise_error(ActiveRecord::RecordNotSaved)
+ expect { run_chain }.to raise_error(ActiveRecord::RecordNotSaved)
last_iid = InternalId.ci_pipelines
.where(project_id: project.id)
@@ -183,14 +196,14 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do
let(:pipeline) { create(:ci_pipeline, project: project) }
it 'raises error' do
- expect { step.perform! }.to raise_error(described_class::PopulateError)
+ expect { run_chain }.to raise_error(described_class::PopulateError)
end
end
context 'when variables policy is specified' do
shared_examples_for 'a correct pipeline' do
it 'populates pipeline according to used policies' do
- step.perform!
+ run_chain
expect(pipeline.stages.size).to eq 1
expect(pipeline.stages.first.statuses.size).to eq 1
diff --git a/spec/lib/gitlab/ci/pipeline/chain/remove_unwanted_chat_jobs_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/remove_unwanted_chat_jobs_spec.rb
index af0f4d68150..92eadf5548c 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/remove_unwanted_chat_jobs_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/remove_unwanted_chat_jobs_spec.rb
@@ -2,36 +2,38 @@
require 'spec_helper'
-describe Gitlab::Ci::Pipeline::Chain::RemoveUnwantedChatJobs do
- let(:project) { create(:project, :repository) }
+describe ::Gitlab::Ci::Pipeline::Chain::RemoveUnwantedChatJobs do
+ let(:project) { create(:project) }
let(:pipeline) do
build(:ci_pipeline, project: project)
end
let(:command) do
- double(:command, project: project, chat_data: { command: 'echo' })
- end
-
- before do
- stub_ci_pipeline_yaml_file(YAML.dump(rspec: { script: 'rspec' }))
+ double(:command,
+ config_processor: double(:processor,
+ jobs: { echo: double(:job_echo), rspec: double(:job_rspec) }),
+ project: project,
+ chat_data: { command: 'echo' })
end
describe '#perform!' do
- it 'removes unwanted jobs for chat pipelines' do
- allow(pipeline).to receive(:chat?).and_return(true)
+ subject { described_class.new(pipeline, command).perform! }
- pipeline.config_processor.jobs[:echo] = double(:job)
+ it 'removes unwanted jobs for chat pipelines' do
+ expect(pipeline).to receive(:chat?).and_return(true)
- described_class.new(pipeline, command).perform!
+ subject
- expect(pipeline.config_processor.jobs.keys).to eq([:echo])
+ expect(command.config_processor.jobs.keys).to eq([:echo])
end
- end
- it 'does not remove any jobs for non-chat pipelines' do
- described_class.new(pipeline, command).perform!
+ it 'does not remove any jobs for non chat-pipelines' do
+ expect(pipeline).to receive(:chat?).and_return(false)
+
+ subject
- expect(pipeline.config_processor.jobs.keys).to eq([:rspec])
+ expect(command.config_processor.jobs.keys).to eq([:echo, :rspec])
+ end
end
end
diff --git a/spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb
new file mode 100644
index 00000000000..aa54f19b26c
--- /dev/null
+++ b/spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb
@@ -0,0 +1,161 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Ci::Pipeline::Chain::Seed do
+ let(:project) { create(:project, :repository) }
+ let(:user) { create(:user, developer_projects: [project]) }
+
+ let(:command) do
+ Gitlab::Ci::Pipeline::Chain::Command.new(
+ project: project,
+ current_user: user,
+ origin_ref: 'master',
+ seeds_block: nil)
+ end
+
+ def run_chain(pipeline, command)
+ [
+ Gitlab::Ci::Pipeline::Chain::Config::Content.new(pipeline, command),
+ Gitlab::Ci::Pipeline::Chain::Config::Process.new(pipeline, command)
+ ].map(&:perform!)
+
+ described_class.new(pipeline, command).perform!
+ end
+
+ let(:pipeline) { build(:ci_pipeline, project: project) }
+
+ describe '#perform!' do
+ before do
+ stub_ci_pipeline_yaml_file(YAML.dump(config))
+ run_chain(pipeline, command)
+ end
+
+ let(:config) do
+ { rspec: { script: 'rake' } }
+ end
+
+ it 'allocates next IID' do
+ expect(pipeline.iid).to be_present
+ end
+
+ it 'sets the seeds in the command object' do
+ expect(command.stage_seeds).to all(be_a Gitlab::Ci::Pipeline::Seed::Base)
+ expect(command.stage_seeds.count).to eq 1
+ end
+
+ context 'when no ref policy is specified' do
+ let(:config) do
+ {
+ production: { stage: 'deploy', script: 'cap prod' },
+ rspec: { stage: 'test', script: 'rspec' },
+ spinach: { stage: 'test', script: 'spinach' }
+ }
+ end
+
+ it 'correctly fabricates a stage seeds object' do
+ seeds = command.stage_seeds
+ expect(seeds.size).to eq 2
+ expect(seeds.first.attributes[:name]).to eq 'test'
+ expect(seeds.second.attributes[:name]).to eq 'deploy'
+ expect(seeds.dig(0, 0, :name)).to eq 'rspec'
+ expect(seeds.dig(0, 1, :name)).to eq 'spinach'
+ expect(seeds.dig(1, 0, :name)).to eq 'production'
+ end
+ end
+
+ context 'when refs policy is specified' do
+ let(:pipeline) do
+ build(:ci_pipeline, project: project, ref: 'feature', tag: true)
+ end
+
+ let(:config) do
+ {
+ production: { stage: 'deploy', script: 'cap prod', only: ['master'] },
+ spinach: { stage: 'test', script: 'spinach', only: ['tags'] }
+ }
+ end
+
+ it 'returns stage seeds only assigned to master' do
+ seeds = command.stage_seeds
+
+ expect(seeds.size).to eq 1
+ expect(seeds.first.attributes[:name]).to eq 'test'
+ expect(seeds.dig(0, 0, :name)).to eq 'spinach'
+ end
+ end
+
+ context 'when source policy is specified' do
+ let(:pipeline) { create(:ci_pipeline, source: :schedule) }
+
+ let(:config) do
+ {
+ production: { stage: 'deploy', script: 'cap prod', only: ['triggers'] },
+ spinach: { stage: 'test', script: 'spinach', only: ['schedules'] }
+ }
+ end
+
+ it 'returns stage seeds only assigned to schedules' do
+ seeds = command.stage_seeds
+
+ expect(seeds.size).to eq 1
+ expect(seeds.first.attributes[:name]).to eq 'test'
+ expect(seeds.dig(0, 0, :name)).to eq 'spinach'
+ end
+ end
+
+ context 'when kubernetes policy is specified' do
+ let(:config) do
+ {
+ spinach: { stage: 'test', script: 'spinach' },
+ production: {
+ stage: 'deploy',
+ script: 'cap',
+ only: { kubernetes: 'active' }
+ }
+ }
+ end
+
+ context 'when kubernetes is active' do
+ context 'when user configured kubernetes from CI/CD > Clusters' do
+ let!(:cluster) { create(:cluster, :project, :provided_by_gcp) }
+ let(:project) { cluster.project }
+ let(:pipeline) { build(:ci_pipeline, project: project) }
+
+ it 'returns seeds for kubernetes dependent job' do
+ seeds = command.stage_seeds
+
+ expect(seeds.size).to eq 2
+ expect(seeds.dig(0, 0, :name)).to eq 'spinach'
+ expect(seeds.dig(1, 0, :name)).to eq 'production'
+ end
+ end
+ end
+
+ context 'when kubernetes is not active' do
+ it 'does not return seeds for kubernetes dependent job' do
+ seeds = command.stage_seeds
+
+ expect(seeds.size).to eq 1
+ expect(seeds.dig(0, 0, :name)).to eq 'spinach'
+ end
+ end
+ end
+
+ context 'when variables policy is specified' do
+ let(:config) do
+ {
+ unit: { script: 'minitest', only: { variables: ['$CI_PIPELINE_SOURCE'] } },
+ feature: { script: 'spinach', only: { variables: ['$UNDEFINED'] } }
+ }
+ end
+
+ it 'returns stage seeds only when variables expression is truthy' do
+ seeds = command.stage_seeds
+
+ expect(seeds.size).to eq 1
+ expect(seeds.dig(0, 0, :name)).to eq 'unit'
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb
deleted file mode 100644
index ed3ce6760a3..00000000000
--- a/spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb
+++ /dev/null
@@ -1,165 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe Gitlab::Ci::Pipeline::Chain::Validate::Config do
- set(:project) { create(:project, :repository) }
- set(:user) { create(:user) }
-
- let(:command) do
- Gitlab::Ci::Pipeline::Chain::Command.new(
- project: project,
- current_user: user,
- save_incompleted: true)
- end
-
- let(:pipeline) do
- build(:ci_pipeline, project: project)
- end
-
- let!(:step) { described_class.new(pipeline, command) }
-
- subject { step.perform! }
-
- context 'when pipeline has no YAML configuration' do
- let(:pipeline) do
- build_stubbed(:ci_pipeline, project: project)
- end
-
- it 'appends errors about missing configuration' do
- subject
-
- expect(pipeline.errors.to_a)
- .to include 'Missing .gitlab-ci.yml file'
- end
-
- it 'breaks the chain' do
- subject
-
- expect(step.break?).to be true
- end
- end
-
- context 'when YAML configuration contains errors' do
- before do
- stub_ci_pipeline_yaml_file('invalid YAML')
- subject
- end
-
- it 'appends errors about YAML errors' do
- expect(pipeline.errors.to_a)
- .to include 'Invalid configuration format'
- end
-
- it 'breaks the chain' do
- expect(step.break?).to be true
- end
-
- context 'when saving incomplete pipeline is allowed' do
- let(:command) do
- double('command', project: project,
- current_user: user,
- save_incompleted: true)
- end
-
- it 'fails the pipeline' do
- subject
-
- expect(pipeline.reload).to be_failed
- end
-
- it 'sets a config error failure reason' do
- subject
-
- expect(pipeline.reload.config_error?).to eq true
- end
- end
-
- context 'when saving incomplete pipeline is not allowed' do
- let(:command) do
- double('command', project: project,
- current_user: user,
- save_incompleted: false)
- end
-
- it 'does not drop pipeline' do
- subject
-
- expect(pipeline).not_to be_failed
- expect(pipeline).not_to be_persisted
- end
- end
- end
-
- context 'when pipeline contains configuration validation errors' do
- before do
- stub_ci_pipeline_yaml_file(YAML.dump({
- rspec: {
- before_script: 10,
- script: 'ls -al'
- }
- }))
-
- subject
- end
-
- it 'appends configuration validation errors to pipeline errors' do
- expect(pipeline.errors.to_a)
- .to include "jobs:rspec:before_script config should be an array containing strings and arrays of strings"
- end
-
- it 'breaks the chain' do
- expect(step.break?).to be true
- end
- end
-
- context 'when pipeline is correct and complete' do
- before do
- stub_ci_pipeline_yaml_file(YAML.dump({
- rspec: {
- script: 'rspec'
- }
- }))
- subject
- end
-
- it 'does not invalidate the pipeline' do
- expect(pipeline).to be_valid
- end
-
- it 'does not break the chain' do
- expect(step.break?).to be false
- end
- end
-
- context 'when pipeline source is merge request' do
- before do
- stub_ci_pipeline_yaml_file(YAML.dump(config))
- subject
- end
-
- let(:pipeline) { build_stubbed(:ci_pipeline, project: project) }
-
- let(:merge_request_pipeline) do
- build(:ci_pipeline, source: :merge_request_event, project: project)
- end
-
- let(:chain) { described_class.new(merge_request_pipeline, command).tap(&:perform!) }
-
- context "when config contains 'merge_requests' keyword" do
- let(:config) { { rspec: { script: 'echo', only: ['merge_requests'] } } }
-
- it 'does not break the chain' do
- expect(chain).not_to be_break
- end
- end
-
- context "when config contains 'merge_request' keyword" do
- let(:config) { { rspec: { script: 'echo', only: ['merge_request'] } } }
-
- it 'does not break the chain' do
- expect(chain).not_to be_break
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb
index 79ad1c0b43f..eefc548a4d9 100644
--- a/spec/lib/gitlab/shell_spec.rb
+++ b/spec/lib/gitlab/shell_spec.rb
@@ -401,7 +401,7 @@ describe Gitlab::Shell do
describe '#add_namespace' do
it 'creates a namespace' do
- subject.add_namespace(storage, "mepmep")
+ Gitlab::GitalyClient::NamespaceService.allow { subject.add_namespace(storage, "mepmep") }
expect(TestEnv.storage_dir_exists?(storage, "mepmep")).to be(true)
end
@@ -425,8 +425,10 @@ describe Gitlab::Shell do
describe '#remove' do
it 'removes the namespace' do
- subject.add_namespace(storage, "mepmep")
- subject.rm_namespace(storage, "mepmep")
+ Gitlab::GitalyClient::NamespaceService.allow do
+ subject.add_namespace(storage, "mepmep")
+ subject.rm_namespace(storage, "mepmep")
+ end
expect(TestEnv.storage_dir_exists?(storage, "mepmep")).to be(false)
end
@@ -434,8 +436,10 @@ describe Gitlab::Shell do
describe '#mv_namespace' do
it 'renames the namespace' do
- subject.add_namespace(storage, "mepmep")
- subject.mv_namespace(storage, "mepmep", "2mep")
+ Gitlab::GitalyClient::NamespaceService.allow do
+ subject.add_namespace(storage, "mepmep")
+ subject.mv_namespace(storage, "mepmep", "2mep")
+ end
expect(TestEnv.storage_dir_exists?(storage, "mepmep")).to be(false)
expect(TestEnv.storage_dir_exists?(storage, "2mep")).to be(true)
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 04c8fb8c5f7..24fa3b9b1ea 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -2221,7 +2221,7 @@ describe Ci::Build do
{ key: 'CI_PAGES_URL', value: project.pages_url, public: true, masked: false },
{ key: 'CI_API_V4_URL', value: 'http://localhost/api/v4', public: true, masked: false },
{ key: 'CI_PIPELINE_IID', value: pipeline.iid.to_s, public: true, masked: false },
- { key: 'CI_CONFIG_PATH', value: pipeline.ci_yaml_file_path, public: true, masked: false },
+ { key: 'CI_CONFIG_PATH', value: pipeline.config_path, public: true, masked: false },
{ key: 'CI_PIPELINE_SOURCE', value: pipeline.source, public: true, masked: false },
{ key: 'CI_COMMIT_MESSAGE', value: pipeline.git_commit_message, public: true, masked: false },
{ key: 'CI_COMMIT_TITLE', value: pipeline.git_commit_title, public: true, masked: false },
@@ -2667,11 +2667,17 @@ describe Ci::Build do
it { is_expected.to include(deployment_variable) }
end
+ context 'when project has default CI config path' do
+ let(:ci_config_path) { { key: 'CI_CONFIG_PATH', value: '.gitlab-ci.yml', public: true, masked: false } }
+
+ it { is_expected.to include(ci_config_path) }
+ end
+
context 'when project has custom CI config path' do
let(:ci_config_path) { { key: 'CI_CONFIG_PATH', value: 'custom', public: true, masked: false } }
before do
- project.update(ci_config_path: 'custom')
+ expect_any_instance_of(Project).to receive(:ci_config_path) { 'custom' }
end
it { is_expected.to include(ci_config_path) }
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 1242cf05a59..d24cf3d2115 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -979,149 +979,6 @@ describe Ci::Pipeline, :mailer do
end
describe 'pipeline stages' do
- describe '#stage_seeds' do
- before do
- stub_ci_pipeline_yaml_file(YAML.dump(config))
- end
-
- let(:pipeline) { build(:ci_pipeline) }
- let(:config) { { rspec: { script: 'rake' } } }
-
- it 'returns preseeded stage seeds object' do
- expect(pipeline.stage_seeds)
- .to all(be_a Gitlab::Ci::Pipeline::Seed::Base)
- expect(pipeline.stage_seeds.count).to eq 1
- end
-
- context 'when no refs policy is specified' do
- let(:config) do
- { production: { stage: 'deploy', script: 'cap prod' },
- rspec: { stage: 'test', script: 'rspec' },
- spinach: { stage: 'test', script: 'spinach' } }
- end
-
- it 'correctly fabricates a stage seeds object' do
- seeds = pipeline.stage_seeds
-
- expect(seeds.size).to eq 2
- expect(seeds.first.attributes[:name]).to eq 'test'
- expect(seeds.second.attributes[:name]).to eq 'deploy'
- expect(seeds.dig(0, 0, :name)).to eq 'rspec'
- expect(seeds.dig(0, 1, :name)).to eq 'spinach'
- expect(seeds.dig(1, 0, :name)).to eq 'production'
- end
- end
-
- context 'when refs policy is specified' do
- let(:pipeline) do
- build(:ci_pipeline, ref: 'feature', tag: true)
- end
-
- let(:config) do
- { production: { stage: 'deploy', script: 'cap prod', only: ['master'] },
- spinach: { stage: 'test', script: 'spinach', only: ['tags'] } }
- end
-
- it 'returns stage seeds only assigned to master to master' do
- seeds = pipeline.stage_seeds
-
- expect(seeds.size).to eq 1
- expect(seeds.first.attributes[:name]).to eq 'test'
- expect(seeds.dig(0, 0, :name)).to eq 'spinach'
- end
- end
-
- context 'when source policy is specified' do
- let(:pipeline) { build(:ci_pipeline, source: :schedule) }
-
- let(:config) do
- { production: { stage: 'deploy', script: 'cap prod', only: ['triggers'] },
- spinach: { stage: 'test', script: 'spinach', only: ['schedules'] } }
- end
-
- it 'returns stage seeds only assigned to schedules' do
- seeds = pipeline.stage_seeds
-
- expect(seeds.size).to eq 1
- expect(seeds.first.attributes[:name]).to eq 'test'
- expect(seeds.dig(0, 0, :name)).to eq 'spinach'
- end
- end
-
- context 'when kubernetes policy is specified' do
- let(:config) do
- {
- spinach: { stage: 'test', script: 'spinach' },
- production: {
- stage: 'deploy',
- script: 'cap',
- only: { kubernetes: 'active' }
- }
- }
- end
-
- context 'when kubernetes is active' do
- context 'when user configured kubernetes from CI/CD > Clusters' do
- let!(:cluster) { create(:cluster, :project, :provided_by_gcp) }
- let(:project) { cluster.project }
- let(:pipeline) { build(:ci_pipeline, project: project) }
-
- it 'returns seeds for kubernetes dependent job' do
- seeds = pipeline.stage_seeds
-
- expect(seeds.size).to eq 2
- expect(seeds.dig(0, 0, :name)).to eq 'spinach'
- expect(seeds.dig(1, 0, :name)).to eq 'production'
- end
- end
- end
-
- context 'when kubernetes is not active' do
- it 'does not return seeds for kubernetes dependent job' do
- seeds = pipeline.stage_seeds
-
- expect(seeds.size).to eq 1
- expect(seeds.dig(0, 0, :name)).to eq 'spinach'
- end
- end
- end
-
- context 'when variables policy is specified' do
- let(:config) do
- { unit: { script: 'minitest', only: { variables: ['$CI_PIPELINE_SOURCE'] } },
- feature: { script: 'spinach', only: { variables: ['$UNDEFINED'] } } }
- end
-
- it 'returns stage seeds only when variables expression is truthy' do
- seeds = pipeline.stage_seeds
-
- expect(seeds.size).to eq 1
- expect(seeds.dig(0, 0, :name)).to eq 'unit'
- end
- end
- end
-
- describe '#seeds_size' do
- before do
- stub_ci_pipeline_yaml_file(YAML.dump(config))
- end
-
- context 'when refs policy is specified' do
- let(:config) do
- { production: { stage: 'deploy', script: 'cap prod', only: ['master'] },
- spinach: { stage: 'test', script: 'spinach', only: ['tags'] } }
- end
-
- let(:pipeline) do
- build(:ci_pipeline, ref: 'feature', tag: true)
- end
-
- it 'returns real seeds size' do
- expect(pipeline.seeds_size).to eq 1
- end
- end
- end
-
describe 'legacy stages' do
before do
create(:commit_status, pipeline: pipeline,
@@ -2194,161 +2051,6 @@ describe Ci::Pipeline, :mailer do
end
end
- describe '#ci_yaml_file_path' do
- subject { pipeline.ci_yaml_file_path }
-
- %i[unknown_source repository_source].each do |source|
- context source.to_s do
- before do
- pipeline.config_source = described_class.config_sources.fetch(source)
- end
-
- it 'returns the path from project' do
- allow(pipeline.project).to receive(:ci_config_path) { 'custom/path' }
-
- is_expected.to eq('custom/path')
- end
-
- it 'returns default when custom path is nil' do
- allow(pipeline.project).to receive(:ci_config_path) { nil }
-
- is_expected.to eq('.gitlab-ci.yml')
- end
-
- it 'returns default when custom path is empty' do
- allow(pipeline.project).to receive(:ci_config_path) { '' }
-
- is_expected.to eq('.gitlab-ci.yml')
- end
- end
- end
-
- context 'when pipeline is for auto-devops' do
- before do
- pipeline.config_source = 'auto_devops_source'
- end
-
- it 'does not return config file' do
- is_expected.to be_nil
- end
- end
- end
-
- describe '#set_config_source' do
- context 'when pipelines does not contain needed data and auto devops is disabled' do
- before do
- stub_application_setting(auto_devops_enabled: false)
- end
-
- it 'defines source to be unknown' do
- pipeline.set_config_source
-
- expect(pipeline).to be_unknown_source
- end
- end
-
- context 'when pipeline contains all needed data' do
- let(:pipeline) do
- create(:ci_pipeline, project: project,
- sha: '1234',
- ref: 'master',
- source: :push)
- end
-
- context 'when the repository has a config file' do
- before do
- allow(project.repository).to receive(:gitlab_ci_yml_for)
- .and_return('config')
- end
-
- it 'defines source to be from repository' do
- pipeline.set_config_source
-
- expect(pipeline).to be_repository_source
- end
-
- context 'when loading an object' do
- let(:new_pipeline) { Ci::Pipeline.find(pipeline.id) }
-
- it 'does not redefine the source' do
- # force to overwrite the source
- pipeline.unknown_source!
-
- expect(new_pipeline).to be_unknown_source
- end
- end
- end
-
- context 'when the repository does not have a config file' do
- let(:implied_yml) { Gitlab::Template::GitlabCiYmlTemplate.find('Auto-DevOps').content }
-
- context 'auto devops enabled' do
- before do
- allow(project).to receive(:ci_config_path) { 'custom' }
- end
-
- it 'defines source to be auto devops' do
- pipeline.set_config_source
-
- expect(pipeline).to be_auto_devops_source
- end
- end
- end
- end
- end
-
- describe '#ci_yaml_file' do
- let(:implied_yml) { Gitlab::Template::GitlabCiYmlTemplate.find('Auto-DevOps').content }
-
- context 'the source is unknown' do
- before do
- pipeline.unknown_source!
- end
-
- it 'returns the configuration if found' do
- allow(pipeline.project.repository).to receive(:gitlab_ci_yml_for)
- .and_return('config')
-
- expect(pipeline.ci_yaml_file).to be_a(String)
- expect(pipeline.ci_yaml_file).not_to eq(implied_yml)
- expect(pipeline.yaml_errors).to be_nil
- end
-
- it 'sets yaml errors if not found' do
- expect(pipeline.ci_yaml_file).to be_nil
- expect(pipeline.yaml_errors)
- .to start_with('Failed to load CI/CD config file')
- end
- end
-
- context 'the source is the repository' do
- before do
- pipeline.repository_source!
- end
-
- it 'returns the configuration if found' do
- allow(pipeline.project.repository).to receive(:gitlab_ci_yml_for)
- .and_return('config')
-
- expect(pipeline.ci_yaml_file).to be_a(String)
- expect(pipeline.ci_yaml_file).not_to eq(implied_yml)
- expect(pipeline.yaml_errors).to be_nil
- end
- end
-
- context 'when the source is auto_devops_source' do
- before do
- stub_application_setting(auto_devops_enabled: true)
- pipeline.auto_devops_source!
- end
-
- it 'finds the implied config' do
- expect(pipeline.ci_yaml_file).to eq(implied_yml)
- expect(pipeline.yaml_errors).to be_nil
- end
- end
- end
-
describe '#update_status' do
context 'when pipeline is empty' do
it 'updates does not change pipeline status' do
@@ -2894,49 +2596,19 @@ describe Ci::Pipeline, :mailer do
end
describe '#has_yaml_errors?' do
- before do
- stub_ci_pipeline_yaml_file(YAML.dump(config))
- end
-
- let(:pipeline) { create(:ci_pipeline) }
-
- context 'when pipeline has errors' do
- let(:config) { { rspec: nil } }
-
- it 'contains yaml errors' do
- pipeline.config_processor
-
- expect(pipeline).to have_yaml_errors
- expect(pipeline.yaml_errors).to include('contains unknown keys')
+ context 'when yaml_errors is set' do
+ before do
+ pipeline.yaml_errors = 'File not found'
end
- end
-
- context 'when pipeline has undefined error' do
- let(:config) { double(:config) }
-
- it 'contains yaml errors' do
- expect(::Gitlab::Ci::YamlProcessor).to receive(:new)
- .and_raise(RuntimeError, 'undefined failure')
-
- expect(Gitlab::Sentry).to receive(:track_acceptable_exception)
- .with(be_a(RuntimeError), anything)
- .and_call_original
-
- pipeline.config_processor
+ it 'returns true if yaml_errors is set' do
expect(pipeline).to have_yaml_errors
- expect(pipeline.yaml_errors).to include('Undefined error')
+ expect(pipeline.yaml_errors).to include('File not foun')
end
end
- context 'when pipeline does not have errors' do
- let(:config) do
- { rspec: { script: 'rake test' } }
- end
-
- it 'does not contain yaml errors' do
- expect(pipeline).not_to have_yaml_errors
- end
+ it 'returns false if yaml_errors is not set' do
+ expect(pipeline).not_to have_yaml_errors
end
end
diff --git a/spec/presenters/project_presenter_spec.rb b/spec/presenters/project_presenter_spec.rb
index 87345270100..ce095d2225f 100644
--- a/spec/presenters/project_presenter_spec.rb
+++ b/spec/presenters/project_presenter_spec.rb
@@ -312,8 +312,8 @@ describe ProjectPresenter do
project.add_developer(user)
allow(project.repository).to receive(:license_blob).and_return(nil)
- expect(presenter.license_anchor_data).to have_attributes(is_link: true,
- label: a_string_including('Add license'),
+ expect(presenter.license_anchor_data).to have_attributes(is_link: false,
+ label: a_string_including('Add LICENSE'),
link: presenter.add_license_path)
end
end
@@ -322,7 +322,7 @@ describe ProjectPresenter do
it 'returns anchor data' do
allow(project.repository).to receive(:license_blob).and_return(double(name: 'foo'))
- expect(presenter.license_anchor_data).to have_attributes(is_link: true,
+ expect(presenter.license_anchor_data).to have_attributes(is_link: false,
label: a_string_including(presenter.license_short_name),
link: presenter.license_path)
end
@@ -420,6 +420,7 @@ describe ProjectPresenter do
it 'orders the items correctly' do
allow(project.repository).to receive(:readme).and_return(double(name: 'readme'))
+ allow(project.repository).to receive(:license_blob).and_return(nil)
allow(project.repository).to receive(:changelog).and_return(nil)
allow(project.repository).to receive(:contribution_guide).and_return(double(name: 'foo'))
allow(presenter).to receive(:filename_path).and_return('fake/path')
@@ -433,25 +434,54 @@ describe ProjectPresenter do
end
end
- describe '#empty_repo_statistics_buttons' do
- let(:project) { create(:project, :repository) }
+ describe '#repo_statistics_buttons' do
let(:presenter) { described_class.new(project, current_user: user) }
-
subject(:empty_repo_statistics_buttons) { presenter.empty_repo_statistics_buttons }
before do
- project.add_developer(user)
allow(project).to receive(:auto_devops_enabled?).and_return(false)
end
- it 'orders the items correctly in an empty project' do
- expect(empty_repo_statistics_buttons.map(&:label)).to start_with(
- a_string_including('New'),
- a_string_including('README'),
- a_string_including('CHANGELOG'),
- a_string_including('CONTRIBUTING'),
- a_string_including('CI/CD')
- )
+ context 'empty repo' do
+ let(:project) { create(:project, :stubbed_repository)}
+
+ context 'for a guest user' do
+ it 'orders the items correctly' do
+ expect(empty_repo_statistics_buttons.map(&:label)).to start_with(
+ a_string_including('No license')
+ )
+ end
+ end
+
+ context 'for a developer' do
+ before do
+ project.add_developer(user)
+ end
+
+ it 'orders the items correctly' do
+ expect(empty_repo_statistics_buttons.map(&:label)).to start_with(
+ a_string_including('New'),
+ a_string_including('README'),
+ a_string_including('LICENSE'),
+ a_string_including('CHANGELOG'),
+ a_string_including('CONTRIBUTING'),
+ a_string_including('CI/CD')
+ )
+ end
+ end
+ end
+
+ context 'initialized repo' do
+ let(:project) { create(:project, :repository) }
+
+ it 'orders the items correctly' do
+ expect(empty_repo_statistics_buttons.map(&:label)).to start_with(
+ a_string_including('README'),
+ a_string_including('License'),
+ a_string_including('CHANGELOG'),
+ a_string_including('CONTRIBUTING')
+ )
+ end
end
end
end
diff --git a/spec/requests/api/graphql/project/issues_spec.rb b/spec/requests/api/graphql/project/issues_spec.rb
index c98f39ffee8..4ce7a3912a3 100644
--- a/spec/requests/api/graphql/project/issues_spec.rb
+++ b/spec/requests/api/graphql/project/issues_spec.rb
@@ -200,6 +200,52 @@ describe 'getting an issue list for a project' do
end
end
end
+
+ context 'when sorting by relative position' do
+ let(:sort_project) { create(:project, :public) }
+
+ let!(:relative_issue1) { create(:issue, project: sort_project, relative_position: 2000) }
+ let!(:relative_issue2) { create(:issue, project: sort_project, relative_position: nil) }
+ let!(:relative_issue3) { create(:issue, project: sort_project, relative_position: 1000) }
+ let!(:relative_issue4) { create(:issue, project: sort_project, relative_position: nil) }
+ let!(:relative_issue5) { create(:issue, project: sort_project, relative_position: 500) }
+
+ let(:params) { 'sort: RELATIVE_POSITION_ASC' }
+
+ def query(issue_params = params)
+ graphql_query_for(
+ 'project',
+ { 'fullPath' => sort_project.full_path },
+ "issues(#{issue_params}) { pageInfo { endCursor} edges { node { iid dueDate } } }"
+ )
+ end
+
+ before do
+ post_graphql(query, current_user: current_user)
+ end
+
+ it_behaves_like 'a working graphql query'
+
+ context 'when ascending' do
+ it 'sorts issues' do
+ expect(grab_iids).to eq [relative_issue5.iid, relative_issue3.iid, relative_issue1.iid, relative_issue4.iid, relative_issue2.iid]
+ end
+
+ context 'when paginating' do
+ let(:params) { 'sort: RELATIVE_POSITION_ASC, first: 2' }
+
+ it 'sorts issues' do
+ expect(grab_iids).to eq [relative_issue5.iid, relative_issue3.iid]
+
+ cursored_query = query("sort: RELATIVE_POSITION_ASC, after: \"#{end_cursor}\"")
+ post_graphql(cursored_query, current_user: current_user)
+ response_data = JSON.parse(response.body)['data']['project']['issues']['edges']
+
+ expect(grab_iids(response_data)).to eq [relative_issue1.iid, relative_issue4.iid, relative_issue2.iid]
+ end
+ end
+ end
+ end
end
def grab_iids(data = issues_data)
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index ddfe42129c0..c96c80b6998 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -1043,14 +1043,12 @@ describe API::MergeRequests do
describe 'POST /projects/:id/merge_requests/:merge_request_iid/pipelines' do
before do
- allow_any_instance_of(Ci::Pipeline)
- .to receive(:ci_yaml_file)
- .and_return(YAML.dump({
- rspec: {
- script: 'ls',
- only: ['merge_requests']
- }
- }))
+ stub_ci_pipeline_yaml_file(YAML.dump({
+ rspec: {
+ script: 'ls',
+ only: ['merge_requests']
+ }
+ }))
end
let(:project) do
diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb
index a2169a015ee..de0f4841215 100644
--- a/spec/services/ci/create_pipeline_service_spec.rb
+++ b/spec/services/ci/create_pipeline_service_spec.rb
@@ -65,6 +65,7 @@ describe Ci::CreatePipelineService do
expect(pipeline.iid).not_to be_nil
expect(pipeline.repository_source?).to be true
expect(pipeline.builds.first).to be_kind_of(Ci::Build)
+ expect(pipeline.yaml_errors).not_to be_present
end
it 'increments the prometheus counter' do
@@ -474,6 +475,66 @@ describe Ci::CreatePipelineService do
end
end
+ context 'config evaluation' do
+ context 'when config is in a file in repository' do
+ before do
+ content = YAML.dump(rspec: { script: 'echo' })
+ stub_ci_pipeline_yaml_file(content)
+ end
+
+ it 'pull it from the repository' do
+ pipeline = execute_service
+ expect(pipeline).to be_repository_source
+ expect(pipeline.builds.map(&:name)).to eq ['rspec']
+ end
+ end
+
+ context 'when config is from Auto-DevOps' do
+ before do
+ stub_ci_pipeline_yaml_file(nil)
+ allow_any_instance_of(Project).to receive(:auto_devops_enabled?).and_return(true)
+ end
+
+ it 'pull it from Auto-DevOps' do
+ pipeline = execute_service
+ expect(pipeline).to be_auto_devops_source
+ expect(pipeline.builds.map(&:name)).to eq %w[test code_quality build]
+ end
+ end
+
+ context 'when config is not found' do
+ before do
+ stub_ci_pipeline_yaml_file(nil)
+ end
+
+ it 'attaches errors to the pipeline' do
+ pipeline = execute_service
+
+ expect(pipeline.errors.full_messages).to eq ['Missing .gitlab-ci.yml file']
+ expect(pipeline).not_to be_persisted
+ end
+ end
+
+ context 'when an unexpected error is raised' do
+ before do
+ expect(Gitlab::Ci::YamlProcessor).to receive(:new)
+ .and_raise(RuntimeError, 'undefined failure')
+ end
+
+ it 'saves error in pipeline' do
+ pipeline = execute_service
+
+ expect(pipeline.yaml_errors).to include('Undefined error')
+ end
+
+ it 'logs error' do
+ expect(Gitlab::Sentry).to receive(:track_acceptable_exception).and_call_original
+
+ execute_service
+ end
+ end
+ end
+
context 'when yaml is invalid' do
let(:ci_yaml) { 'invalid: file: fiile' }
let(:message) { 'Message' }
@@ -539,6 +600,25 @@ describe Ci::CreatePipelineService do
end
end
+ context 'when an unexpected error is raised' do
+ before do
+ expect(Gitlab::Ci::YamlProcessor).to receive(:new)
+ .and_raise(RuntimeError, 'undefined failure')
+ end
+
+ it 'saves error in pipeline' do
+ pipeline = execute_service
+
+ expect(pipeline.yaml_errors).to include('Undefined error')
+ end
+
+ it 'logs error' do
+ expect(Gitlab::Sentry).to receive(:track_acceptable_exception).and_call_original
+
+ execute_service
+ end
+ end
+
context 'when commit contains a [ci skip] directive' do
let(:message) { "some message[ci skip]" }
diff --git a/spec/support/helpers/stub_gitlab_calls.rb b/spec/support/helpers/stub_gitlab_calls.rb
index e3dde888277..fe343da7838 100644
--- a/spec/support/helpers/stub_gitlab_calls.rb
+++ b/spec/support/helpers/stub_gitlab_calls.rb
@@ -18,8 +18,13 @@ module StubGitlabCalls
stub_ci_pipeline_yaml_file(gitlab_ci_yaml)
end
- def stub_ci_pipeline_yaml_file(ci_yaml)
- allow_any_instance_of(Ci::Pipeline).to receive(:ci_yaml_file) { ci_yaml }
+ def stub_ci_pipeline_yaml_file(ci_yaml_content)
+ allow_any_instance_of(Repository).to receive(:gitlab_ci_yml_for).and_return(ci_yaml_content)
+
+ # Ensure we don't hit auto-devops when config not found in repository
+ unless ci_yaml_content
+ allow_any_instance_of(Project).to receive(:auto_devops_enabled?).and_return(false)
+ end
end
def stub_pipeline_modified_paths(pipeline, modified_paths)
diff --git a/spec/views/projects/show.html.haml_spec.rb b/spec/views/projects/show.html.haml_spec.rb
index 820772b592f..4f5f0f0285c 100644
--- a/spec/views/projects/show.html.haml_spec.rb
+++ b/spec/views/projects/show.html.haml_spec.rb
@@ -18,6 +18,14 @@ describe 'projects/show' do
end
context 'commit signatures' do
+ context 'with vue tree view enabled' do
+ it 'are not rendered via js-signature-container' do
+ render
+
+ expect(rendered).not_to have_css('.js-signature-container')
+ end
+ end
+
context 'with vue tree view disabled' do
before do
stub_feature_flags(vue_file_list: false)
@@ -29,13 +37,5 @@ describe 'projects/show' do
expect(rendered).to have_css('.js-signature-container')
end
end
-
- context 'with vue tree view enabled' do
- it 'are not rendered via js-signature-container' do
- render
-
- expect(rendered).not_to have_css('.js-signature-container')
- end
- end
end
end
diff --git a/spec/views/projects/tree/show.html.haml_spec.rb b/spec/views/projects/tree/show.html.haml_spec.rb
index 4307d1b49c9..8c6b229247d 100644
--- a/spec/views/projects/tree/show.html.haml_spec.rb
+++ b/spec/views/projects/tree/show.html.haml_spec.rb
@@ -13,8 +13,6 @@ describe 'projects/tree/show' do
let(:tree) { repository.tree(commit.id, path) }
before do
- stub_feature_flags(vue_file_list: false)
-
assign(:project, project)
assign(:repository, repository)
assign(:lfs_blob_ids, [])
@@ -39,12 +37,15 @@ describe 'projects/tree/show' do
render
expect(rendered).to have_css('.js-project-refs-dropdown .dropdown-toggle-text', text: ref)
- expect(rendered).to have_css('.readme-holder')
end
end
context 'commit signatures' do
context 'with vue tree view disabled' do
+ before do
+ stub_feature_flags(vue_file_list: false)
+ end
+
it 'rendered via js-signature-container' do
render
@@ -53,10 +54,6 @@ describe 'projects/tree/show' do
end
context 'with vue tree view enabled' do
- before do
- stub_feature_flags(vue_file_list: true)
- end
-
it 'are not rendered via js-signature-container' do
render