Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2019-11-18 18:06:53 +00:00
parent 575ccb036e
commit 143f196f8b
98 changed files with 1251 additions and 1112 deletions

View File

@ -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)

View File

@ -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;

View File

@ -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);

View File

@ -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"

View File

@ -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"

View File

@ -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;

View File

@ -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 });

View File

@ -2,6 +2,7 @@
fragment TreeEntry on Entry {
id
sha
name
flatPath
type

View File

@ -8,6 +8,7 @@ query pathLastCommit($projectPath: ID!, $path: String, $ref: String!) {
description
webUrl
authoredDate
authorName
author {
name
avatarUrl

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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,

View File

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

View File

@ -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: 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}}' }
#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: 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'

View File

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

View File

@ -0,0 +1,5 @@
---
title: Move add license button to project buttons
merge_request: 19370
author:
type: changed

View File

@ -0,0 +1,5 @@
---
title: 'Graphql query for issues can now be sorted by relative_position'
merge_request: 19713
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: remove all references of BoardService in boards_selector.vue
merge_request: 20147
author: nuwe1
type: other

View File

@ -0,0 +1,5 @@
---
title: Remove var from project_select.js
merge_request: 20091
author: Lee Tickett
type: other

View File

@ -0,0 +1,5 @@
---
title: Add backtrace to production_json.log
merge_request: 20122
author:
type: changed

View File

@ -0,0 +1,5 @@
---
title: Support template_project_id parameter in project creation API
merge_request: 20258
author:
type: added

View File

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

View File

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

View File

@ -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
}
@ -121,6 +126,11 @@ type Commit {
"""
author: User
"""
Commit authors name
"""
authorName: String
"""
Timestamp of when the commit was authored
"""
@ -1100,6 +1110,11 @@ interface Entry {
id: ID!
name: String!
path: String!
"""
Last commit sha for entry
"""
sha: String!
type: EntryType!
}
@ -2517,6 +2532,11 @@ enum IssueSort {
"""
DUE_DATE_DESC
"""
Relative position by ascending order
"""
RELATIVE_POSITION_ASC
"""
Created at ascending order
"""
@ -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
}

View File

@ -10227,6 +10227,20 @@
"isDeprecated": false,
"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",
@ -11332,6 +11346,24 @@
"isDeprecated": false,
"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,
@ -11453,6 +11485,24 @@
"isDeprecated": false,
"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,
@ -11711,6 +11761,24 @@
"isDeprecated": false,
"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,
@ -11972,6 +12040,24 @@
"isDeprecated": false,
"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,
@ -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

View File

@ -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! | |

View File

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

View File

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

View File

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

View File

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

View File

@ -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?

View File

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

View File

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

View File

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

View File

@ -41,7 +41,7 @@ module Gitlab
end
def workflow_config
@pipeline.config_processor.workflow_attributes || {}
@command.config_processor.workflow_attributes || {}
end
end
end

View File

@ -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.')

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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"

View File

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

View File

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

View File

@ -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)

View File

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

View File

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

View File

@ -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(

View File

@ -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(

View File

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

View File

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

View File

@ -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')

View File

@ -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')

View File

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

View File

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

View File

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

View File

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

View File

@ -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,22 +50,18 @@ 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
find('.qa-new-menu-toggle').click
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
end
expect(page).not_to have_selector('.qa-add-to-tree')
expect(page).not_to have_link('Web IDE')
end

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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',

View File

@ -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: '/',

View File

@ -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', () =>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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', () => {

View File

@ -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!

View File

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

View File

@ -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
subject { described_class.new(pipeline, command).perform! }
it 'removes unwanted jobs for chat pipelines' do
allow(pipeline).to receive(:chat?).and_return(true)
expect(pipeline).to receive(:chat?).and_return(true)
pipeline.config_processor.jobs[:echo] = double(:job)
subject
described_class.new(pipeline, command).perform!
expect(command.config_processor.jobs.keys).to eq([:echo])
end
expect(pipeline.config_processor.jobs.keys).to eq([:echo])
it 'does not remove any jobs for non chat-pipelines' do
expect(pipeline).to receive(:chat?).and_return(false)
subject
expect(command.config_processor.jobs.keys).to eq([:echo, :rspec])
end
end
it 'does not remove any jobs for non-chat pipelines' do
described_class.new(pipeline, command).perform!
expect(pipeline.config_processor.jobs.keys).to eq([:rspec])
end
end

View File

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

View File

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

View File

@ -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)

View File

@ -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) }

View File

@ -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
context 'when yaml_errors is set' do
before do
pipeline.yaml_errors = 'File not found'
end
it 'returns true if yaml_errors is set' do
expect(pipeline).to have_yaml_errors
expect(pipeline.yaml_errors).to include('contains unknown keys')
expect(pipeline.yaml_errors).to include('File not foun')
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
expect(pipeline).to have_yaml_errors
expect(pipeline.yaml_errors).to include('Undefined error')
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

View File

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

View File

@ -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)

View File

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

View File

@ -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]" }

View File

@ -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)

View File

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

View File

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