diff options
177 files changed, 2366 insertions, 1090 deletions
diff --git a/.eslintrc.yml b/.eslintrc.yml index ecd9f57b075..b0794bb7434 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -33,3 +33,4 @@ rules: vue/no-unused-components: off vue/no-use-v-if-with-v-for: off vue/no-v-html: off + vue/use-v-on-exact: off diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4ae319d64d7..b26c2d16d77 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -554,8 +554,7 @@ docs lint: # Build HTML from Markdown - bundle exec nanoc # Check the internal links - # Disabled until https://gitlab.com/gitlab-com/gitlab-docs/issues/305 is resolved - # - bundle exec nanoc check internal_links + - bundle exec nanoc check internal_links downtime_check: <<: *rake-exec @@ -637,7 +636,7 @@ gitlab:setup-mysql: # Frontend-related jobs gitlab:assets:compile: <<: *dedicated-no-docs-and-no-qa-pull-cache-job - image: dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.5.3-git-2.18-chrome-69.0-node-8.x-yarn-1.2-graphicsmagick-1.3.29-docker-18.06.1 + image: dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.5.3-git-2.18-chrome-69.0-node-8.x-yarn-1.12-graphicsmagick-1.3.29-docker-18.06.1 dependencies: [] services: - docker:stable-dind diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 571df7534cb..3ab76965287 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -107,12 +107,6 @@ Lint/UriEscapeUnescape: Metrics/LineLength: Max: 1310 -# Offense count: 2 -Naming/ConstantName: - Exclude: - - 'lib/gitlab/import_sources.rb' - - 'lib/gitlab/ssh_public_key.rb' - # Offense count: 11 # Configuration parameters: EnforcedStyle. # SupportedStyles: lowercase, uppercase diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION index 93c8ddab9fe..ae9a76b9249 100644 --- a/GITLAB_WORKHORSE_VERSION +++ b/GITLAB_WORKHORSE_VERSION @@ -1 +1 @@ -7.6.0 +8.0.0 @@ -277,6 +277,7 @@ gem 'webpack-rails', '~> 0.9.10' gem 'rack-proxy', '~> 0.6.0' gem 'sass-rails', '~> 5.0.6' +gem 'sass', '~> 3.5' gem 'uglifier', '~> 2.7.2' gem 'addressable', '~> 2.5.2' diff --git a/Gemfile.lock b/Gemfile.lock index 430025c7bde..b9780d4c23f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1129,6 +1129,7 @@ DEPENDENCIES rufus-scheduler (~> 3.4) rugged (~> 0.27) sanitize (~> 4.6) + sass (~> 3.5) sass-rails (~> 5.0.6) scss_lint (~> 0.56.0) seed-fu (~> 2.3.7) diff --git a/Gemfile.rails4.lock b/Gemfile.rails4.lock index 9e7bae84299..3d81c570b89 100644 --- a/Gemfile.rails4.lock +++ b/Gemfile.rails4.lock @@ -1119,6 +1119,7 @@ DEPENDENCIES rufus-scheduler (~> 3.4) rugged (~> 0.27) sanitize (~> 4.6) + sass (~> 3.5) sass-rails (~> 5.0.6) scss_lint (~> 0.56.0) seed-fu (~> 2.3.7) diff --git a/app/assets/javascripts/boards/components/new_list_dropdown.js b/app/assets/javascripts/boards/components/new_list_dropdown.js index f7016561f93..10577da9305 100644 --- a/app/assets/javascripts/boards/components/new_list_dropdown.js +++ b/app/assets/javascripts/boards/components/new_list_dropdown.js @@ -37,7 +37,7 @@ export default function initNewListDropdown() { }); }, renderRow(label) { - const active = boardsStore.findList('title', label.title); + const active = boardsStore.findListByLabelId(label.id); const $li = $('<li />'); const $a = $('<a />', { class: active ? `is-active js-board-list-${active.id}` : '', @@ -63,7 +63,7 @@ export default function initNewListDropdown() { const label = options.selectedObj; e.preventDefault(); - if (!boardsStore.findList('title', label.title)) { + if (!boardsStore.findListByLabelId(label.id)) { boardsStore.new({ title: label.title, position: boardsStore.state.lists.length - 2, diff --git a/app/assets/javascripts/boards/models/issue.js b/app/assets/javascripts/boards/models/issue.js index 5e0f0b07247..dd92d3c8552 100644 --- a/app/assets/javascripts/boards/models/issue.js +++ b/app/assets/javascripts/boards/models/issue.js @@ -55,12 +55,12 @@ class ListIssue { } findLabel(findLabel) { - return this.labels.filter(label => label.title === findLabel.title)[0]; + return this.labels.find(label => label.id === findLabel.id); } removeLabel(removeLabel) { if (removeLabel) { - this.labels = this.labels.filter(label => removeLabel.title !== label.title); + this.labels = this.labels.filter(label => removeLabel.id !== label.id); } } @@ -75,7 +75,7 @@ class ListIssue { } findAssignee(findAssignee) { - return this.assignees.filter(assignee => assignee.id === findAssignee.id)[0]; + return this.assignees.find(assignee => assignee.id === findAssignee.id); } removeAssignee(removeAssignee) { diff --git a/app/assets/javascripts/boards/stores/boards_store.js b/app/assets/javascripts/boards/stores/boards_store.js index cf88a973d33..802796208c2 100644 --- a/app/assets/javascripts/boards/stores/boards_store.js +++ b/app/assets/javascripts/boards/stores/boards_store.js @@ -166,6 +166,9 @@ const boardsStore = { }); return filteredList[0]; }, + findListByLabelId(id) { + return this.state.lists.find(list => list.type === 'label' && list.label.id === id); + }, updateFiltersUrl() { window.history.pushState(null, null, `?${this.filter.path}`); }, diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue index bf9244df7f7..f0e82b1ed27 100644 --- a/app/assets/javascripts/diffs/components/app.vue +++ b/app/assets/javascripts/diffs/components/app.vue @@ -42,6 +42,11 @@ export default { type: Object, required: true, }, + changesEmptyStateIllustration: { + type: String, + required: false, + default: '', + }, }, data() { return { @@ -63,7 +68,7 @@ export default { plainDiffPath: state => state.diffs.plainDiffPath, emailPatchPath: state => state.diffs.emailPatchPath, }), - ...mapState('diffs', ['showTreeList', 'isLoading']), + ...mapState('diffs', ['showTreeList', 'isLoading', 'startVersion']), ...mapGetters('diffs', ['isParallelView']), ...mapGetters(['isNotesFetched', 'getNoteableData']), targetBranch() { @@ -79,6 +84,13 @@ export default { showCompareVersions() { return this.mergeRequestDiffs && this.mergeRequestDiff; }, + renderDiffFiles() { + return ( + this.diffFiles.length > 0 || + (this.startVersion && + this.startVersion.version_index === this.mergeRequestDiff.version_index) + ); + }, }, watch: { diffViewType() { @@ -191,7 +203,7 @@ export default { <div v-show="showTreeList" class="diff-tree-list"><tree-list /></div> <div class="diff-files-holder"> <commit-widget v-if="commit" :commit="commit" /> - <template v-if="diffFiles.length > 0"> + <template v-if="renderDiffFiles"> <diff-file v-for="file in diffFiles" :key="file.newPath" @@ -199,7 +211,7 @@ export default { :can-current-user-fork="canCurrentUserFork" /> </template> - <no-changes v-else /> + <no-changes v-else :changes-empty-state-illustration="changesEmptyStateIllustration" /> </div> </div> </div> diff --git a/app/assets/javascripts/diffs/components/no_changes.vue b/app/assets/javascripts/diffs/components/no_changes.vue index 25ec157ed25..47e9627a957 100644 --- a/app/assets/javascripts/diffs/components/no_changes.vue +++ b/app/assets/javascripts/diffs/components/no_changes.vue @@ -1,34 +1,51 @@ <script> -import { mapState } from 'vuex'; -import emptyImage from '~/../../views/shared/icons/_mr_widget_empty_state.svg'; +import { mapGetters } from 'vuex'; +import _ from 'underscore'; +import { GlButton } from '@gitlab/ui'; +import { __, sprintf } from '~/locale'; export default { - data() { - return { - emptyImage, - }; + components: { + GlButton, + }, + props: { + changesEmptyStateIllustration: { + type: String, + required: true, + }, }, computed: { - ...mapState({ - sourceBranch: state => state.notes.noteableData.source_branch, - targetBranch: state => state.notes.noteableData.target_branch, - newBlobPath: state => state.notes.noteableData.new_blob_path, - }), + ...mapGetters(['getNoteableData']), + emptyStateText() { + return sprintf( + __( + 'No changes between %{ref_start}%{source_branch}%{ref_end} and %{ref_start}%{target_branch}%{ref_end}', + ), + { + ref_start: '<span class="ref-name">', + ref_end: '</span>', + source_branch: _.escape(this.getNoteableData.source_branch), + target_branch: _.escape(this.getNoteableData.target_branch), + }, + false, + ); + }, }, }; </script> <template> - <div class="row empty-state nothing-here-block"> - <div class="col-xs-12"> - <div class="svg-content"><span v-html="emptyImage"></span></div> + <div class="row empty-state"> + <div class="col-12"> + <div class="svg-content svg-250"><img :src="changesEmptyStateIllustration" /></div> </div> - <div class="col-xs-12"> + <div class="col-12"> <div class="text-content text-center"> - No changes between <span class="ref-name">{{ sourceBranch }}</span> and - <span class="ref-name">{{ targetBranch }}</span> + <span v-html="emptyStateText"></span> <div class="text-center"> - <a :href="newBlobPath" class="btn btn-success"> {{ __('Create commit') }} </a> + <gl-button :href="getNoteableData.new_blob_path" variant="success">{{ + __('Create commit') + }}</gl-button> </div> </div> </div> diff --git a/app/assets/javascripts/diffs/index.js b/app/assets/javascripts/diffs/index.js index 06ef4207d85..915cacb374f 100644 --- a/app/assets/javascripts/diffs/index.js +++ b/app/assets/javascripts/diffs/index.js @@ -17,6 +17,7 @@ export default function initDiffsApp(store) { endpoint: dataset.endpoint, projectPath: dataset.projectPath, currentUser: JSON.parse(dataset.currentUserData) || {}, + changesEmptyStateIllustration: dataset.changesEmptyStateIllustration, }; }, computed: { @@ -31,6 +32,7 @@ export default function initDiffsApp(store) { currentUser: this.currentUser, projectPath: this.projectPath, shouldShow: this.activeTab === 'diffs', + changesEmptyStateIllustration: this.changesEmptyStateIllustration, }, }); }, diff --git a/app/assets/javascripts/diffs/store/mutations.js b/app/assets/javascripts/diffs/store/mutations.js index 61314db1dbd..2ea884d1293 100644 --- a/app/assets/javascripts/diffs/store/mutations.js +++ b/app/assets/javascripts/diffs/store/mutations.js @@ -123,22 +123,23 @@ export default { diffPosition: diffPositionByLineCode[line.line_code], latestDiff, }); + const mapDiscussions = (line, extraCheck = () => true) => ({ + ...line, + discussions: extraCheck() + ? line.discussions + .filter(() => !line.discussions.some(({ id }) => discussion.id === id)) + .concat(lineCheck(line) ? discussion : line.discussions) + : [], + }); state.diffFiles = state.diffFiles.map(diffFile => { if (diffFile.file_hash === fileHash) { const file = { ...diffFile }; if (file.highlighted_diff_lines) { - file.highlighted_diff_lines = file.highlighted_diff_lines.map(line => { - if (!line.discussions.some(({ id }) => discussion.id === id) && lineCheck(line)) { - return { - ...line, - discussions: line.discussions.concat(discussion), - }; - } - - return line; - }); + file.highlighted_diff_lines = file.highlighted_diff_lines.map(line => + mapDiscussions(line), + ); } if (file.parallel_diff_lines) { @@ -148,20 +149,8 @@ export default { if (left || right) { return { - left: { - ...line.left, - discussions: - left && !line.left.discussions.some(({ id }) => id === discussion.id) - ? line.left.discussions.concat(discussion) - : (line.left && line.left.discussions) || [], - }, - right: { - ...line.right, - discussions: - right && !left && !line.right.discussions.some(({ id }) => id === discussion.id) - ? line.right.discussions.concat(discussion) - : (line.right && line.right.discussions) || [], - }, + left: line.left ? mapDiscussions(line.left) : null, + right: line.right ? mapDiscussions(line.right, () => !left) : null, }; } @@ -180,7 +169,7 @@ export default { }); }, - [types.REMOVE_LINE_DISCUSSIONS_FOR_FILE](state, { fileHash, lineCode, id }) { + [types.REMOVE_LINE_DISCUSSIONS_FOR_FILE](state, { fileHash, lineCode }) { const selectedFile = state.diffFiles.find(f => f.file_hash === fileHash); if (selectedFile) { if (selectedFile.parallel_diff_lines) { @@ -193,7 +182,7 @@ export default { const side = targetLine.left && targetLine.left.line_code === lineCode ? 'left' : 'right'; Object.assign(targetLine[side], { - discussions: [], + discussions: targetLine[side].discussions.filter(discussion => discussion.notes.length), }); } } @@ -205,14 +194,14 @@ export default { if (targetInlineLine) { Object.assign(targetInlineLine, { - discussions: [], + discussions: targetInlineLine.discussions.filter(discussion => discussion.notes.length), }); } } if (selectedFile.discussions && selectedFile.discussions.length) { selectedFile.discussions = selectedFile.discussions.filter( - discussion => discussion.id !== id, + discussion => discussion.notes.length, ); } } diff --git a/app/assets/javascripts/filtered_search/dropdown_utils.js b/app/assets/javascripts/filtered_search/dropdown_utils.js index 1b79a3320c6..8d92af2cf7e 100644 --- a/app/assets/javascripts/filtered_search/dropdown_utils.js +++ b/app/assets/javascripts/filtered_search/dropdown_utils.js @@ -54,67 +54,6 @@ export default class DropdownUtils { return updatedItem; } - static mergeDuplicateLabels(dataMap, newLabel) { - const updatedMap = dataMap; - const key = newLabel.title; - - const hasKeyProperty = Object.prototype.hasOwnProperty.call(updatedMap, key); - - if (!hasKeyProperty) { - updatedMap[key] = newLabel; - } else { - const existing = updatedMap[key]; - - if (!existing.multipleColors) { - existing.multipleColors = [existing.color]; - } - - existing.multipleColors.push(newLabel.color); - } - - return updatedMap; - } - - static duplicateLabelColor(labelColors) { - const colors = labelColors; - const spacing = 100 / colors.length; - - // Reduce the colors to 4 - colors.length = Math.min(colors.length, 4); - - const color = colors - .map((c, i) => { - const percentFirst = Math.floor(spacing * i); - const percentSecond = Math.floor(spacing * (i + 1)); - return `${c} ${percentFirst}%, ${c} ${percentSecond}%`; - }) - .join(', '); - - return `linear-gradient(${color})`; - } - - static duplicateLabelPreprocessing(data) { - const results = []; - const dataMap = {}; - - data.forEach(DropdownUtils.mergeDuplicateLabels.bind(null, dataMap)); - - Object.keys(dataMap).forEach(key => { - const label = dataMap[key]; - - if (label.multipleColors) { - label.color = DropdownUtils.duplicateLabelColor(label.multipleColors); - label.text_color = '#000000'; - } - - results.push(label); - }); - - results.preprocessed = true; - - return results; - } - static filterHint(config, item) { const { input, allowedKeys } = config; const updatedItem = item; diff --git a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js index 89dcff74d0e..fba31f16d65 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js +++ b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js @@ -79,11 +79,7 @@ export default class FilteredSearchVisualTokens { static setTokenStyle(tokenContainer, backgroundColor, textColor) { const token = tokenContainer; - // Labels with linear gradient should not override default background color - if (backgroundColor.indexOf('linear-gradient') === -1) { - token.style.backgroundColor = backgroundColor; - } - + token.style.backgroundColor = backgroundColor; token.style.color = textColor; if (textColor === '#FFFFFF') { @@ -94,18 +90,6 @@ export default class FilteredSearchVisualTokens { return token; } - static preprocessLabel(labelsEndpoint, labels) { - let processed = labels; - - if (!labels.preprocessed) { - processed = DropdownUtils.duplicateLabelPreprocessing(labels); - AjaxCache.override(labelsEndpoint, processed); - processed.preprocessed = true; - } - - return processed; - } - static updateLabelTokenColor(tokenValueContainer, tokenValue) { const filteredSearchInput = FilteredSearchContainer.container.querySelector('.filtered-search'); const { baseEndpoint } = filteredSearchInput.dataset; @@ -115,7 +99,6 @@ export default class FilteredSearchVisualTokens { ); return AjaxCache.retrieve(labelsEndpoint) - .then(FilteredSearchVisualTokens.preprocessLabel.bind(null, labelsEndpoint)) .then(labels => { const matchingLabel = (labels || []).find( label => `~${DropdownUtils.getEscapedText(label.title)}` === tokenValue, diff --git a/app/assets/javascripts/issuable_suggestions/components/app.vue b/app/assets/javascripts/issuable_suggestions/components/app.vue index eea0701312b..575c860851c 100644 --- a/app/assets/javascripts/issuable_suggestions/components/app.vue +++ b/app/assets/javascripts/issuable_suggestions/components/app.vue @@ -27,7 +27,7 @@ export default { apollo: { issues: { query, - debounce: 250, + debounce: 1000, skip() { return this.isSearchEmpty; }, diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js index c0a76814102..f7a611fbca0 100644 --- a/app/assets/javascripts/labels_select.js +++ b/app/assets/javascripts/labels_select.js @@ -7,7 +7,6 @@ import _ from 'underscore'; import { sprintf, __ } from './locale'; import axios from './lib/utils/axios_utils'; import IssuableBulkUpdateActions from './issuable_bulk_update_actions'; -import DropdownUtils from './filtered_search/dropdown_utils'; import CreateLabelDropdown from './create_label'; import flash from './flash'; import ModalStore from './boards/stores/modal_store'; @@ -171,23 +170,7 @@ export default class LabelsSelect { axios .get(labelUrl) .then(res => { - let data = _.chain(res.data) - .groupBy(function(label) { - return label.title; - }) - .map(function(label) { - var color; - color = _.map(label, function(dup) { - return dup.color; - }); - return { - id: label[0].id, - title: label[0].title, - color: color, - duplicate: color.length > 1, - }; - }) - .value(); + let { data } = res; if ($dropdown.hasClass('js-extra-options')) { var extraData = []; if (showNo) { @@ -272,15 +255,9 @@ export default class LabelsSelect { selectedClass.push('dropdown-clear-active'); } } - if (label.duplicate) { - color = DropdownUtils.duplicateLabelColor(label.color); - } else { - if (label.color != null) { - [color] = label.color; - } - } - if (color) { - colorEl = "<span class='dropdown-label-box' style='background: " + color + "'></span>"; + if (label.color) { + colorEl = + "<span class='dropdown-label-box' style='background: " + label.color + "'></span>"; } else { colorEl = ''; } @@ -435,7 +412,7 @@ export default class LabelsSelect { new ListLabel({ id: label.id, title: label.title, - color: label.color[0], + color: label.color, textColor: '#fff', }), ); diff --git a/app/assets/javascripts/notes/components/notes_app.vue b/app/assets/javascripts/notes/components/notes_app.vue index 445d3267a3f..27f896cee35 100644 --- a/app/assets/javascripts/notes/components/notes_app.vue +++ b/app/assets/javascripts/notes/components/notes_app.vue @@ -102,7 +102,7 @@ export default { if (parentElement && parentElement.classList.contains('js-vue-notes-event')) { parentElement.addEventListener('toggleAward', event => { const { awardName, noteId } = event.detail; - this.actionToggleAward({ awardName, noteId }); + this.toggleAward({ awardName, noteId }); }); } }, diff --git a/app/assets/javascripts/pages/dashboard/projects/index.js b/app/assets/javascripts/pages/dashboard/projects/index.js index 0c585e162cb..8f98be79640 100644 --- a/app/assets/javascripts/pages/dashboard/projects/index.js +++ b/app/assets/javascripts/pages/dashboard/projects/index.js @@ -1,3 +1,7 @@ import ProjectsList from '~/projects_list'; +import Star from '../../../star'; -document.addEventListener('DOMContentLoaded', () => new ProjectsList()); +document.addEventListener('DOMContentLoaded', () => { + new ProjectsList(); // eslint-disable-line no-new + new Star('.project-row'); // eslint-disable-line no-new +}); diff --git a/app/assets/javascripts/pages/root/index.js b/app/assets/javascripts/pages/root/index.js new file mode 100644 index 00000000000..09f8185d3b5 --- /dev/null +++ b/app/assets/javascripts/pages/root/index.js @@ -0,0 +1,5 @@ +// if the "projects dashboard" is a user's default dashboard, when they visit the +// instance root index, the dashboard will be served by the root controller instead +// of a dashboard controller. The root index redirects for all other default dashboards. + +import '../dashboard/projects/index'; diff --git a/app/assets/javascripts/pages/users/user_tabs.js b/app/assets/javascripts/pages/users/user_tabs.js index aa537d4a43e..1c3fd58ca74 100644 --- a/app/assets/javascripts/pages/users/user_tabs.js +++ b/app/assets/javascripts/pages/users/user_tabs.js @@ -151,8 +151,10 @@ export default class UserTabs { loadTab(action, endpoint) { this.toggleLoading(true); + const params = action === 'projects' ? { skip_namespace: true } : {}; + return axios - .get(endpoint) + .get(endpoint, { params }) .then(({ data }) => { const tabSelector = `div#${action}`; this.$parentEl.find(tabSelector).html(data.html); @@ -188,7 +190,7 @@ export default class UserTabs { requestParams: { limit: 10 }, }); UserTabs.renderMostRecentBlocks('#js-overview .projects-block', { - requestParams: { limit: 10, skip_pagination: true }, + requestParams: { limit: 10, skip_pagination: true, skip_namespace: true, compact_mode: true }, }); this.loaded.overview = true; @@ -206,6 +208,8 @@ export default class UserTabs { loadActivityCalendar() { const $calendarWrap = this.$parentEl.find('.tab-pane.active .user-calendar'); + if (!$calendarWrap.length) return; + const calendarPath = $calendarWrap.data('calendarPath'); AjaxCache.retrieve(calendarPath) diff --git a/app/assets/javascripts/star.js b/app/assets/javascripts/star.js index 9af5d5b23cb..7404dfbf22a 100644 --- a/app/assets/javascripts/star.js +++ b/app/assets/javascripts/star.js @@ -5,11 +5,12 @@ import { spriteIcon } from './lib/utils/common_utils'; import axios from './lib/utils/axios_utils'; export default class Star { - constructor() { - $('.project-home-panel .toggle-star').on('click', function toggleStarClickCallback() { + constructor(container = '.project-home-panel') { + $(`${container} .toggle-star`).on('click', function toggleStarClickCallback() { const $this = $(this); const $starSpan = $this.find('span'); - const $startIcon = $this.find('svg'); + const $starIcon = $this.find('svg'); + const iconClasses = $starIcon.attr('class').split(' '); axios .post($this.data('endpoint')) @@ -22,12 +23,12 @@ export default class Star { if (isStarred) { $starSpan.removeClass('starred').text(s__('StarProject|Star')); - $startIcon.remove(); - $this.prepend(spriteIcon('star-o', 'icon')); + $starIcon.remove(); + $this.prepend(spriteIcon('star-o', iconClasses)); } else { $starSpan.addClass('starred').text(__('Unstar')); - $startIcon.remove(); - $this.prepend(spriteIcon('star', 'icon')); + $starIcon.remove(); + $this.prepend(spriteIcon('star', iconClasses)); } }) .catch(() => Flash('Star toggle failed. Try again later.')); diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_icon.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_icon.vue index e3adc7f7af5..4b57693e8f1 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_icon.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_icon.vue @@ -13,5 +13,7 @@ export default { </script> <template> - <div class="circle-icon-container append-right-default"><icon :name="name" /></div> + <div class="circle-icon-container append-right-default align-self-start align-self-lg-center"> + <icon :name="name" /> + </div> </template> diff --git a/app/assets/javascripts/vue_shared/components/markdown/field.vue b/app/assets/javascripts/vue_shared/components/markdown/field.vue index 21d6519191f..43def2673eb 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/field.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/field.vue @@ -1,6 +1,6 @@ <script> import $ from 'jquery'; -import { s__ } from '~/locale'; +import { __ } from '~/locale'; import Flash from '../../../flash'; import GLForm from '../../../gl_form'; import markdownHeader from './header.vue'; @@ -99,11 +99,12 @@ export default { if (text) { this.markdownPreviewLoading = true; + this.markdownPreview = __('Loading…'); this.$http .post(this.versionedPreviewPath(), { text }) .then(resp => resp.json()) .then(data => this.renderMarkdown(data)) - .catch(() => new Flash(s__('Error loading markdown preview'))); + .catch(() => new Flash(__('Error loading markdown preview'))); } else { this.renderMarkdown(); } @@ -162,10 +163,12 @@ export default { /> </div> </div> - <div v-show="previewMarkdown" class="md md-preview-holder md-preview js-vue-md-preview"> - <div ref="markdown-preview" v-html="markdownPreview"></div> - <span v-if="markdownPreviewLoading"> Loading... </span> - </div> + <div + v-show="previewMarkdown" + ref="markdown-preview" + class="md-preview js-vue-md-preview md md-preview-holder" + v-html="markdownPreview" + ></div> <template v-if="previewMarkdown && !markdownPreviewLoading"> <div v-if="referencedCommands" class="referenced-commands" v-html="referencedCommands"></div> <div v-if="shouldShowReferencedUsers" class="referenced-users"> diff --git a/app/assets/stylesheets/framework/avatar.scss b/app/assets/stylesheets/framework/avatar.scss index 054c75912ea..e132aa4c216 100644 --- a/app/assets/stylesheets/framework/avatar.scss +++ b/app/assets/stylesheets/framework/avatar.scss @@ -108,6 +108,7 @@ width: 100%; height: 100%; display: flex; + text-decoration: none; } .avatar { @@ -120,6 +121,7 @@ } &.s40 { min-width: 40px; min-height: 40px; } + &.s64 { min-width: 64px; min-height: 64px; } } .avatar-counter { diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index e36f99ac577..a4a9276c580 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -148,8 +148,8 @@ &.btn-xs { padding: 2px $gl-btn-padding; - font-size: $gl-btn-small-font-size; - line-height: $gl-btn-small-line-height; + font-size: $gl-btn-xs-font-size; + line-height: $gl-btn-xs-line-height; } &.btn-success, diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss index f2f3a45ca09..e037b02a30c 100644 --- a/app/assets/stylesheets/framework/common.scss +++ b/app/assets/stylesheets/framework/common.scss @@ -387,3 +387,17 @@ img.emoji { .mw-460 { max-width: 460px; } .ws-initial { white-space: initial; } .min-height-0 { min-height: 0; } + +.gl-pl-0 { padding-left: 0; } +.gl-pl-1 { padding-left: #{0.5 * $grid-size}; } +.gl-pl-2 { padding-left: $grid-size; } +.gl-pl-3 { padding-left: #{2 * $grid-size}; } +.gl-pl-4 { padding-left: #{3 * $grid-size}; } +.gl-pl-5 { padding-left: #{4 * $grid-size}; } + +.gl-pr-0 { padding-right: 0; } +.gl-pr-1 { padding-right: #{0.5 * $grid-size}; } +.gl-pr-2 { padding-right: $grid-size; } +.gl-pr-3 { padding-right: #{2 * $grid-size}; } +.gl-pr-4 { padding-right: #{3 * $grid-size}; } +.gl-pr-5 { padding-right: #{4 * $grid-size}; } diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index f273eb9533d..b47b1cb76dc 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -290,6 +290,10 @@ } } + .dropdown-item { + @include dropdown-link; + } + .divider { height: 1px; margin: #{$grid-size / 2} 0; diff --git a/app/assets/stylesheets/framework/gitlab_theme.scss b/app/assets/stylesheets/framework/gitlab_theme.scss index b8bb9e1e07b..0ef50e139f2 100644 --- a/app/assets/stylesheets/framework/gitlab_theme.scss +++ b/app/assets/stylesheets/framework/gitlab_theme.scss @@ -22,6 +22,10 @@ .container-fluid { .navbar-toggler { border-left: 1px solid lighten($border-and-box-shadow, 10%); + + svg { + fill: $search-and-nav-links; + } } } @@ -309,12 +313,14 @@ body { .navbar-nav { > li { > a:hover, - > a:focus { + > a:focus, + > button:hover { color: $theme-gray-900; } &.active > a, - &.active > a:hover { + &.active > a:hover, + &.active > button { color: $white-light; } } diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index 45a52d99302..7d283dcfb71 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -33,6 +33,7 @@ .close-icon { display: block; + margin: auto; } } @@ -168,12 +169,6 @@ color: currentColor; background-color: transparent; } - - .more-icon, - .close-icon { - fill: $white-light; - margin: auto; - } } .navbar-nav { diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 5310195d9c5..a92481b3ebb 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -198,6 +198,7 @@ $well-light-text-color: #5b6169; $gl-font-size: 14px; $gl-font-size-xs: 11px; $gl-font-size-small: 12px; +$gl-font-size-medium: 1.43rem; $gl-font-size-large: 16px; $gl-font-weight-normal: 400; $gl-font-weight-bold: 600; @@ -276,6 +277,7 @@ $project-title-row-height: 64px; $project-avatar-mobile-size: 24px; $gl-line-height: 16px; $gl-line-height-24: 24px; +$gl-line-height-14: 14px; /* * Common component specific colors @@ -369,7 +371,9 @@ $gl-btn-line-height: 16px; $gl-btn-vert-padding: 8px; $gl-btn-horz-padding: 12px; $gl-btn-small-font-size: 13px; -$gl-btn-small-line-height: 13px; +$gl-btn-small-line-height: 18px; +$gl-btn-xs-font-size: 13px; +$gl-btn-xs-line-height: 13px; /* * Badges diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index fa0ab1a3bae..67d7a8175ac 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -49,8 +49,8 @@ .login-box, .omniauth-container { box-shadow: 0 0 0 1px $border-color; - border-bottom-right-radius: 2px; - border-bottom-left-radius: 2px; + border-bottom-right-radius: $border-radius-small; + border-bottom-left-radius: $border-radius-small; padding: 15px; .login-heading h3 { @@ -95,6 +95,7 @@ } .omniauth-container { + border-radius: $border-radius-small; font-size: 13px; p { diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 278800aba95..0ce0db038a7 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -969,34 +969,73 @@ pre.light-well { @include basic-list-stats; display: flex; align-items: center; - } + color: $gl-text-color-secondary; + padding: $gl-padding 0; - h3 { - font-size: $gl-font-size; + @include media-breakpoint-up(lg) { + padding: $gl-padding-24 0; + } + + &.no-description { + @include media-breakpoint-up(sm) { + .avatar-container { + align-self: center; + } + + .metadata-info { + margin-bottom: 0; + } + } + } } - .avatar-container, - .controls { - flex: 0 0 auto; + h2 { + font-size: $gl-font-size-medium; + font-weight: $gl-font-weight-bold; + margin-bottom: 0; + + @include media-breakpoint-up(sm) { + .namespace-name { + font-weight: $gl-font-weight-normal; + } + } } .avatar-container { + flex: 0 0 auto; align-self: flex-start; } .project-details { min-width: 0; + line-height: $gl-line-height; + + .flex-wrapper { + min-width: 0; + margin-top: -$gl-padding-8; // negative margin required for flex-wrap + } p, .commit-row-message { @include str-truncated(100%); margin-bottom: 0; } - } - .controls { - margin-left: auto; - text-align: right; + .user-access-role { + margin: 0; + } + + @include media-breakpoint-up(md) { + .description { + color: $gl-text-color; + } + } + + @include media-breakpoint-down(md) { + .user-access-role { + line-height: $gl-line-height-14; + } + } } .ci-status-link { @@ -1008,6 +1047,149 @@ pre.light-well { text-decoration: none; } } + + .controls { + margin-top: $gl-padding; + + @include media-breakpoint-down(md) { + margin-top: 0; + } + + @include media-breakpoint-down(xs) { + margin-top: $gl-padding-8; + } + + .icon-wrapper { + color: inherit; + margin-right: $gl-padding; + + @include media-breakpoint-down(md) { + margin-right: 0; + margin-left: $gl-padding-8; + } + + @include media-breakpoint-down(xs) { + &:first-child { + margin-left: 0; + } + } + } + + .ci-status-link { + display: inline-flex; + } + } + + .star-button { + .icon { + top: 0; + } + } + + .icon-container { + @include media-breakpoint-down(xs) { + margin-right: $gl-padding-8; + } + } + + &.compact { + .project-row { + padding: $gl-padding 0; + } + + h2 { + font-size: $gl-font-size; + } + + .avatar-container { + @include avatar-size(40px, 10px); + min-height: 40px; + min-width: 40px; + + .identicon.s64 { + font-size: 16px; + } + } + + .controls { + @include media-breakpoint-up(sm) { + margin-top: 0; + } + } + + .updated-note { + @include media-breakpoint-up(sm) { + margin-top: $gl-padding-8; + } + } + + .icon-wrapper { + margin-left: $gl-padding-8; + margin-right: 0; + + @include media-breakpoint-down(xs) { + &:first-child { + margin-left: 0; + } + } + } + + .user-access-role { + line-height: $gl-line-height-14; + } + } + + @include media-breakpoint-down(md) { + h2 { + font-size: $gl-font-size; + } + + .avatar-container { + @include avatar-size(40px, 10px); + min-height: 40px; + min-width: 40px; + + .identicon.s64 { + font-size: 16px; + } + } + } + + @include media-breakpoint-down(md) { + .updated-note { + margin-top: $gl-padding-8; + text-align: right; + } + } + + .forks, + .pipeline-status, + .updated-note { + display: flex; + } + + @include media-breakpoint-down(md) { + &:not(.explore) { + .forks { + display: none; + + } + } + + &.explore { + .pipeline-status, + .updated-note { + display: none !important; + } + } + } + + @include media-breakpoint-down(xs) { + .updated-note { + margin-top: 0; + text-align: left; + } + } } .card .projects-list li { diff --git a/app/controllers/concerns/issuable_collections.rb b/app/controllers/concerns/issuable_collections.rb index a597996a362..789e0dc736e 100644 --- a/app/controllers/concerns/issuable_collections.rb +++ b/app/controllers/concerns/issuable_collections.rb @@ -126,6 +126,8 @@ module IssuableCollections sort_param = params[:sort] sort_param ||= user_preference[issuable_sorting_field] + return sort_param if Gitlab::Database.read_only? + if user_preference[issuable_sorting_field] != sort_param user_preference.update_attribute(issuable_sorting_field, sort_param) end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 8b040dc080e..072d62ddf38 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -58,11 +58,13 @@ class UsersController < ApplicationController load_projects skip_pagination = Gitlab::Utils.to_boolean(params[:skip_pagination]) + skip_namespace = Gitlab::Utils.to_boolean(params[:skip_namespace]) + compact_mode = Gitlab::Utils.to_boolean(params[:compact_mode]) respond_to do |format| format.html { render 'show' } format.json do - pager_json("shared/projects/_list", @projects.count, projects: @projects, skip_pagination: skip_pagination) + pager_json("shared/projects/_list", @projects.count, projects: @projects, skip_pagination: skip_pagination, skip_namespace: skip_namespace, compact_mode: compact_mode) end end end diff --git a/app/finders/remote_mirror_finder.rb b/app/finders/remote_mirror_finder.rb new file mode 100644 index 00000000000..420db0077aa --- /dev/null +++ b/app/finders/remote_mirror_finder.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class RemoteMirrorFinder + attr_accessor :params + + def initialize(params) + @params = params + end + + # rubocop: disable CodeReuse/ActiveRecord + def execute + RemoteMirror.find_by(id: params[:id]) + end + # rubocop: enable CodeReuse/ActiveRecord +end diff --git a/app/helpers/emails_helper.rb b/app/helpers/emails_helper.rb index 2d2e89a2a50..e4c46ceeaa2 100644 --- a/app/helpers/emails_helper.rb +++ b/app/helpers/emails_helper.rb @@ -98,4 +98,29 @@ module EmailsHelper "#{string} on #{Gitlab.config.gitlab.host}" end + + def create_list_id_string(project, list_id_max_length = 255) + project_path_as_domain = project.full_path.downcase + .split('/').reverse.join('/') + .gsub(%r{[^a-z0-9\/]}, '-') + .gsub(%r{\/+}, '.') + .gsub(/(\A\.+|\.+\z)/, '') + + max_domain_length = list_id_max_length - Gitlab.config.gitlab.host.length - project.id.to_s.length - 2 + + if max_domain_length < 3 + return project.id.to_s + "..." + Gitlab.config.gitlab.host + end + + if project_path_as_domain.length > max_domain_length + project_path_as_domain = project_path_as_domain.slice(0, max_domain_length) + + last_dot_index = project_path_as_domain[0..-2].rindex(".") + last_dot_index ||= max_domain_length - 2 + + project_path_as_domain = project_path_as_domain.slice(0, last_dot_index).concat("..") + end + + project.id.to_s + "." + project_path_as_domain + "." + Gitlab.config.gitlab.host + end end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 7c8557a1a8a..1186eb3ddcc 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -515,6 +515,20 @@ module ProjectsHelper end end + def explore_projects_tab? + current_page?(explore_projects_path) || + current_page?(trending_explore_projects_path) || + current_page?(starred_explore_projects_path) + end + + def show_merge_request_count?(merge_requests, compact_mode) + merge_requests && !compact_mode && Feature.enabled?(:project_list_show_mr_count, default_enabled: true) + end + + def show_issue_count?(issues, compact_mode) + issues && !compact_mode && Feature.enabled?(:project_list_show_issue_count, default_enabled: true) + end + def sidebar_projects_paths %w[ projects#show diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb index f51b96ba8ce..67c808b167a 100644 --- a/app/helpers/sorting_helper.rb +++ b/app/helpers/sorting_helper.rb @@ -164,7 +164,7 @@ module SortingHelper reverse_sort = issuable_reverse_sort_order_hash[sort_value] if reverse_sort - reverse_url = page_filter_path(sort: reverse_sort) + reverse_url = page_filter_path(sort: reverse_sort, label: true) else reverse_url = '#' link_class += ' disabled' diff --git a/app/mailers/emails/remote_mirrors.rb b/app/mailers/emails/remote_mirrors.rb new file mode 100644 index 00000000000..2018eb7260b --- /dev/null +++ b/app/mailers/emails/remote_mirrors.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module Emails + module RemoteMirrors + def remote_mirror_update_failed_email(remote_mirror_id, recipient_id) + @remote_mirror = RemoteMirrorFinder.new(id: remote_mirror_id).execute + @project = @remote_mirror.project + + mail(to: recipient(recipient_id), subject: subject('Remote mirror update failed')) + end + end +end diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb index 88ad4c3e893..15710bee4d4 100644 --- a/app/mailers/notify.rb +++ b/app/mailers/notify.rb @@ -3,6 +3,7 @@ class Notify < BaseMailer include ActionDispatch::Routing::PolymorphicRoutes include GitlabRoutingHelper + include EmailsHelper include Emails::Issues include Emails::MergeRequests @@ -13,6 +14,7 @@ class Notify < BaseMailer include Emails::Pipelines include Emails::Members include Emails::AutoDevops + include Emails::RemoteMirrors helper MergeRequestsHelper helper DiffHelper @@ -128,7 +130,7 @@ class Notify < BaseMailer address.display_name = reply_display_name(model) end - fallback_reply_message_id = "<reply-#{reply_key}@#{Gitlab.config.gitlab.host}>".freeze + fallback_reply_message_id = "<reply-#{reply_key}@#{Gitlab.config.gitlab.host}>" headers['References'] ||= [] headers['References'].unshift(fallback_reply_message_id) @@ -178,7 +180,7 @@ class Notify < BaseMailer headers['X-GitLab-Discussion-ID'] = note.discussion.id if note.part_of_discussion? - headers[:subject]&.prepend('Re: ') + headers[:subject] = "Re: #{headers[:subject]}" if headers[:subject] mail_thread(model, headers) end @@ -193,6 +195,7 @@ class Notify < BaseMailer headers['X-GitLab-Project'] = @project.name headers['X-GitLab-Project-Id'] = @project.id headers['X-GitLab-Project-Path'] = @project.full_path + headers['List-Id'] = "#{@project.full_path} <#{create_list_id_string(@project)}>" end def add_unsubscription_headers_and_links diff --git a/app/mailers/previews/notify_preview.rb b/app/mailers/previews/notify_preview.rb index e7e8d96eca4..2ac4610967d 100644 --- a/app/mailers/previews/notify_preview.rb +++ b/app/mailers/previews/notify_preview.rb @@ -145,6 +145,10 @@ class NotifyPreview < ActionMailer::Preview Notify.autodevops_disabled_email(pipeline, user.email).message end + def remote_mirror_update_failed_email + Notify.remote_mirror_update_failed_email(remote_mirror.id, user.id).message + end + private def project @@ -167,6 +171,10 @@ class NotifyPreview < ActionMailer::Preview @pipeline = Ci::Pipeline.last end + def remote_mirror + @remote_mirror ||= RemoteMirror.last + end + def user @user ||= User.last end diff --git a/app/models/ci/bridge.rb b/app/models/ci/bridge.rb new file mode 100644 index 00000000000..29aa00a66d9 --- /dev/null +++ b/app/models/ci/bridge.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +module Ci + class Bridge < CommitStatus + include Importable + include AfterCommitQueue + include Gitlab::Utils::StrongMemoize + + belongs_to :project + validates :ref, presence: true + + def self.retry(bridge, current_user) + raise NotImplementedError + end + + def tags + [:bridge] + end + + def detailed_status(current_user) + Gitlab::Ci::Status::Bridge::Factory + .new(self, current_user) + .fabricate! + end + + def predefined_variables + raise NotImplementedError + end + + def execute_hooks + raise NotImplementedError + end + end +end diff --git a/app/models/pool_repository.rb b/app/models/pool_repository.rb index dbde00b5584..47da0209c2f 100644 --- a/app/models/pool_repository.rb +++ b/app/models/pool_repository.rb @@ -84,6 +84,10 @@ class PoolRepository < ActiveRecord::Base source_project.repository.raw) end + def inspect + "#<#{self.class.name} id:#{id} state:#{state} disk_path:#{disk_path} source_project: #{source_project.full_path}>" + end + private def correct_disk_path diff --git a/app/models/project.rb b/app/models/project.rb index 075c07f5c8e..67262ecce85 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -749,15 +749,9 @@ class Project < ActiveRecord::Base return if data.nil? && credentials.nil? project_import_data = import_data || build_import_data - if data - project_import_data.data ||= {} - project_import_data.data = project_import_data.data.merge(data) - end - if credentials - project_import_data.credentials ||= {} - project_import_data.credentials = project_import_data.credentials.merge(credentials) - end + project_import_data.merge_data(data.to_h) + project_import_data.merge_credentials(credentials.to_h) project_import_data end @@ -2014,12 +2008,12 @@ class Project < ActiveRecord::Base def create_new_pool_repository pool = begin - create_or_find_pool_repository!(shard: Shard.by_name(repository_storage), source_project: self) + create_pool_repository!(shard: Shard.by_name(repository_storage), source_project: self) rescue ActiveRecord::RecordNotUnique - retry + pool_repository(true) end - pool.schedule + pool.schedule unless pool.scheduled? pool end diff --git a/app/models/project_import_data.rb b/app/models/project_import_data.rb index 2c3080c6d8d..525725034a5 100644 --- a/app/models/project_import_data.rb +++ b/app/models/project_import_data.rb @@ -22,4 +22,12 @@ class ProjectImportData < ActiveRecord::Base # bang doesn't work here - attr_encrypted makes it not to work self.credentials = self.credentials.deep_symbolize_keys unless self.credentials.blank? end + + def merge_data(hash) + self.data = data.to_h.merge(hash) unless hash.empty? + end + + def merge_credentials(hash) + self.credentials = credentials.to_h.merge(hash) unless hash.empty? + end end diff --git a/app/models/remote_mirror.rb b/app/models/remote_mirror.rb index b7b4d0f1be9..5a6895aefab 100644 --- a/app/models/remote_mirror.rb +++ b/app/models/remote_mirror.rb @@ -65,10 +65,14 @@ class RemoteMirror < ActiveRecord::Base ) end - after_transition started: :failed do |remote_mirror, _| + after_transition started: :failed do |remote_mirror| Gitlab::Metrics.add_event(:remote_mirrors_failed) remote_mirror.update(last_update_at: Time.now) + + remote_mirror.run_after_commit do + RemoteMirrorNotificationWorker.perform_async(remote_mirror.id) + end end end @@ -135,8 +139,8 @@ class RemoteMirror < ActiveRecord::Base end def mark_as_failed(error_message) - update_fail update_column(:last_error, Gitlab::UrlSanitizer.sanitize(error_message)) + update_fail end def url=(value) diff --git a/app/services/notification_recipient_service.rb b/app/services/notification_recipient_service.rb index 9c236d7f41d..68cdc69023a 100644 --- a/app/services/notification_recipient_service.rb +++ b/app/services/notification_recipient_service.rb @@ -24,6 +24,10 @@ module NotificationRecipientService Builder::MergeRequestUnmergeable.new(*args).notification_recipients end + def self.build_project_maintainers_recipients(*args) + Builder::ProjectMaintainers.new(*args).notification_recipients + end + module Builder class Base def initialize(*) @@ -380,5 +384,24 @@ module NotificationRecipientService nil end end + + class ProjectMaintainers < Base + attr_reader :target + + def initialize(target, action:) + @target = target + @action = action + end + + def build! + return [] unless project + + add_recipients(project.team.maintainers, :watch, nil) + end + + def acting_user + nil + end + end end end diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index e24ef7f9c87..ff035fea216 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -429,26 +429,26 @@ class NotificationService end def pages_domain_verification_succeeded(domain) - recipients_for_pages_domain(domain).each do |user| - mailer.pages_domain_verification_succeeded_email(domain, user).deliver_later + project_maintainers_recipients(domain, action: 'succeeded').each do |recipient| + mailer.pages_domain_verification_succeeded_email(domain, recipient.user).deliver_later end end def pages_domain_verification_failed(domain) - recipients_for_pages_domain(domain).each do |user| - mailer.pages_domain_verification_failed_email(domain, user).deliver_later + project_maintainers_recipients(domain, action: 'failed').each do |recipient| + mailer.pages_domain_verification_failed_email(domain, recipient.user).deliver_later end end def pages_domain_enabled(domain) - recipients_for_pages_domain(domain).each do |user| - mailer.pages_domain_enabled_email(domain, user).deliver_later + project_maintainers_recipients(domain, action: 'enabled').each do |recipient| + mailer.pages_domain_enabled_email(domain, recipient.user).deliver_later end end def pages_domain_disabled(domain) - recipients_for_pages_domain(domain).each do |user| - mailer.pages_domain_disabled_email(domain, user).deliver_later + project_maintainers_recipients(domain, action: 'disabled').each do |recipient| + mailer.pages_domain_disabled_email(domain, recipient.user).deliver_later end end @@ -474,6 +474,14 @@ class NotificationService mailer.send(:repository_cleanup_failure_email, project, user, error).deliver_later end + def remote_mirror_update_failed(remote_mirror) + recipients = project_maintainers_recipients(remote_mirror, action: 'update_failed') + + recipients.each do |recipient| + mailer.remote_mirror_update_failed_email(remote_mirror.id, recipient.user.id).deliver_later + end + end + protected def new_resource_email(target, method) @@ -569,12 +577,8 @@ class NotificationService private - def recipients_for_pages_domain(domain) - project = domain.project - - return [] unless project - - notifiable_users(project.team.maintainers, :watch, target: project) + def project_maintainers_recipients(target, action:) + NotificationRecipientService.build_project_maintainers_recipients(target, action: action) end def notifiable?(*args) diff --git a/app/views/layouts/devise.html.haml b/app/views/layouts/devise.html.haml index 43bd07679ba..4f8db74382f 100644 --- a/app/views/layouts/devise.html.haml +++ b/app/views/layouts/devise.html.haml @@ -9,7 +9,7 @@ .container.navless-container .content = render "layouts/flash" - .row + .row.append-bottom-15 .col-sm-7.brand-holder %h1 = brand_title diff --git a/app/views/notify/remote_mirror_update_failed_email.html.haml b/app/views/notify/remote_mirror_update_failed_email.html.haml new file mode 100644 index 00000000000..4fb0a4c5a8a --- /dev/null +++ b/app/views/notify/remote_mirror_update_failed_email.html.haml @@ -0,0 +1,46 @@ +%tr.alert{ style: "font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif;" } + %td{ style: "padding:10px;border-radius:3px;font-size:14px;line-height:1.3;text-align:center;overflow:hidden;background-color:#d22f57;color:#ffffff;" } + %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;margin:0 auto;" } + %tbody + %tr + %td{ style: "vertical-align:middle;color:#ffffff;text-align:center;padding-right:5px;line-height:1;" } + %img{ alt: "✖", height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-x-red-inverted.gif'), style: "display:block;", width: "13" }/ + %td{ style: "vertical-align:middle;color:#ffffff;text-align:center;" } + A remote mirror update has failed. +%tr.spacer{ style: "font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif;" } + %td{ style: "height:18px;font-size:18px;line-height:18px;" } + +%tr.section{ style: "font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif;" } + %td{ style: "padding:0 15px;border:1px solid #ededed;border-radius:3px;overflow:hidden;" } + %table.table-info{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:100%;" } + %tbody{ style: "font-size:15px;line-height:1.4;color:#8c8c8c;" } + %tr + %td{ style: "font-weight:300;padding:14px 0;margin:0;" } Project + %td{ style: "font-weight:500;padding:14px 0;margin:0;color:#333333;width:75%;padding-left:5px;" } + - namespace_url = @project.group ? group_url(@project.group) : user_url(@project.namespace.owner) + %a.muted{ href: namespace_url, style: "color:#333333;text-decoration:none;" } + = @project.owner_name + \/ + %a.muted{ href: project_url(@project), style: "color:#333333;text-decoration:none;" } + = @project.name + %tr + %td{ style: "font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" } Remote mirror + %td{ style: "font-weight:500;padding:14px 0;margin:0;color:#333333;width:75%;padding-left:5px;border-top:1px solid #ededed;" } + = @remote_mirror.safe_url + %tr + %td{ style: "font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" } Last update at + %td{ style: "font-weight:500;padding:14px 0;margin:0;color:#333333;width:75%;padding-left:5px;border-top:1px solid #ededed;" } + = @remote_mirror.last_update_at + +%tr.table-warning{ style: "font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif;" } + %td{ style: "border: 1px solid #ededed; border-bottom: 0; border-radius: 4px 4px 0 0; overflow: hidden; background-color: #fdf4f6; color: #d22852; font-size: 14px; line-height: 1.4; text-align: center; padding: 8px 16px;" } + Logs may contain sensitive data. Please consider before forwarding this email. +%tr.section{ style: "font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif;" } + %td{ style: "padding: 0 16px; border: 1px solid #ededed; border-radius: 4px; overflow: hidden; border-top: 0; border-radius: 0 0 4px 4px;" } + %table.builds{ border: "0", cellpadding: "0", cellspacing: "0", style: "width: 100%; border-collapse: collapse;" } + %tbody + %tr.build-log + %td{ colspan: "2", style: "padding: 0 0 16px;" } + %pre{ style: "font-family: Monaco,'Lucida Console','Courier New',Courier,monospace; background-color: #fafafa; border-radius: 4px; overflow: hidden; white-space: pre-wrap; word-break: break-all; font-size:13px; line-height: 1.4; padding: 16px 8px; color: #333333; margin: 0;" } + = @remote_mirror.last_error + diff --git a/app/views/notify/remote_mirror_update_failed_email.text.erb b/app/views/notify/remote_mirror_update_failed_email.text.erb new file mode 100644 index 00000000000..c6f29f0ad1c --- /dev/null +++ b/app/views/notify/remote_mirror_update_failed_email.text.erb @@ -0,0 +1,7 @@ +A remote mirror update has failed. + +Project: <%= @project.human_name %> ( <%= project_url(@project) %> ) +Remote mirror: <%= @remote_mirror.safe_url %> +Last update at: <%= @remote_mirror.last_update_at %> +Last error: +<%= @remote_mirror.last_error %> diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml index aab5712d197..2a919a767c0 100644 --- a/app/views/projects/commit/_commit_box.html.haml +++ b/app/views/projects/commit/_commit_box.html.haml @@ -28,7 +28,7 @@ = link_to project_tree_path(@project, @commit), class: "btn btn-default append-right-10 d-none d-sm-none d-md-inline" do #{ _('Browse files') } .dropdown.inline - %a.btn.btn-default.dropdown-toggle{ data: { toggle: "dropdown" } } + %a.btn.btn-default.dropdown-toggle.qa-options-button{ data: { toggle: "dropdown" } } %span= _('Options') = icon('caret-down') %ul.dropdown-menu.dropdown-menu-right @@ -48,8 +48,8 @@ %li.dropdown-header #{ _('Download') } - unless @commit.parents.length > 1 - %li= link_to s_("DownloadCommit|Email Patches"), project_commit_path(@project, @commit, format: :patch) - %li= link_to s_("DownloadCommit|Plain Diff"), project_commit_path(@project, @commit, format: :diff) + %li= link_to s_("DownloadCommit|Email Patches"), project_commit_path(@project, @commit, format: :patch), class: "qa-email-patches" + %li= link_to s_("DownloadCommit|Plain Diff"), project_commit_path(@project, @commit, format: :diff), class: "qa-plain-diff" .commit-box{ data: { project_path: project_path(@project) } } %h3.commit-title diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml index 6c0ad34c486..d66de7ab698 100644 --- a/app/views/projects/environments/index.html.haml +++ b/app/views/projects/environments/index.html.haml @@ -1,6 +1,5 @@ - @no_container = true - page_title _("Environments") -- add_to_breadcrumbs(_("Pipelines"), project_pipelines_path(@project)) #environments-list-view{ data: { environments_data: environments_list_data, "can-create-deployment" => can?(current_user, :create_deployment, @project).to_s, diff --git a/app/views/projects/merge_requests/show.html.haml b/app/views/projects/merge_requests/show.html.haml index 4ebb029e48b..c178206dda4 100644 --- a/app/views/projects/merge_requests/show.html.haml +++ b/app/views/projects/merge_requests/show.html.haml @@ -77,7 +77,8 @@ #js-diffs-app.diffs.tab-pane{ data: { "is-locked" => @merge_request.discussion_locked?, endpoint: diffs_project_merge_request_path(@project, @merge_request, 'json', request.query_parameters), current_user_data: UserSerializer.new(project: @project).represent(current_user, {}, MergeRequestUserEntity).to_json, - project_path: project_path(@merge_request.project)} } + project_path: project_path(@merge_request.project), + changes_empty_state_illustration: image_path('illustrations/merge_request_changes_empty.svg') } } .mr-loading-status = spinner diff --git a/app/views/shared/projects/_list.html.haml b/app/views/shared/projects/_list.html.haml index 06eb3d03e31..15c29e14cc0 100644 --- a/app/views/shared/projects/_list.html.haml +++ b/app/views/shared/projects/_list.html.haml @@ -2,24 +2,29 @@ - avatar = true unless local_assigns[:avatar] == false - use_creator_avatar = false unless local_assigns[:use_creator_avatar] == true - stars = true unless local_assigns[:stars] == false -- forks = false unless local_assigns[:forks] == true +- forks = true unless local_assigns[:forks] == false +- merge_requests = true unless local_assigns[:merge_requests] == false +- issues = true unless local_assigns[:issues] == false +- pipeline_status = true unless local_assigns[:pipeline_status] == false - ci = false unless local_assigns[:ci] == true - skip_namespace = false unless local_assigns[:skip_namespace] == true - user = local_assigns[:user] - show_last_commit_as_description = false unless local_assigns[:show_last_commit_as_description] == true - remote = false unless local_assigns[:remote] == true - skip_pagination = false unless local_assigns[:skip_pagination] == true +- compact_mode = false unless local_assigns[:compact_mode] == true +- css_classes = "#{'compact' if compact_mode} #{'explore' if explore_projects_tab?}" .js-projects-list-holder - if any_projects?(projects) - load_pipeline_status(projects) - - %ul.projects-list + %ul.projects-list{ class: css_classes } - projects.each_with_index do |project, i| - css_class = (i >= projects_limit) || project.pending_delete? ? 'hide' : nil = render "shared/projects/project", project: project, skip_namespace: skip_namespace, avatar: avatar, stars: stars, css_class: css_class, ci: ci, use_creator_avatar: use_creator_avatar, - forks: forks, show_last_commit_as_description: show_last_commit_as_description, user: user + forks: forks, show_last_commit_as_description: show_last_commit_as_description, user: user, merge_requests: merge_requests, + issues: issues, pipeline_status: pipeline_status, compact_mode: compact_mode - if @private_forks_count && @private_forks_count > 0 %li.project-row.private-forks-notice diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml index aba790e1217..9dde77fccef 100644 --- a/app/views/shared/projects/_project.html.haml +++ b/app/views/shared/projects/_project.html.haml @@ -1,62 +1,107 @@ - avatar = true unless local_assigns[:avatar] == false - stars = true unless local_assigns[:stars] == false -- forks = false unless local_assigns[:forks] == true +- forks = true unless local_assigns[:forks] == false +- merge_requests = true unless local_assigns[:merge_requests] == false +- issues = true unless local_assigns[:issues] == false +- pipeline_status = true unless local_assigns[:pipeline_status] == false - skip_namespace = false unless local_assigns[:skip_namespace] == true - access = max_project_member_access(project) -- css_class = '' unless local_assigns[:css_class] +- compact_mode = false unless local_assigns[:compact_mode] == true - show_last_commit_as_description = false unless local_assigns[:show_last_commit_as_description] == true && can_show_last_commit_in_list?(project) +- css_class = '' unless local_assigns[:css_class] - css_class += " no-description" if project.description.blank? && !show_last_commit_as_description - cache_key = project_list_cache_key(project) - updated_tooltip = time_ago_with_tooltip(project.last_activity_date) +- css_details_class = compact_mode ? "d-flex flex-column flex-sm-row flex-md-row align-items-sm-center" : "align-items-center flex-md-fill flex-lg-column d-sm-flex d-lg-block" +- css_controls_class = compact_mode ? "" : "align-items-md-end align-items-lg-center flex-lg-row" -%li.project-row{ class: css_class } +%li.project-row.d-flex{ class: css_class } = cache(cache_key) do - if avatar - .avatar-container.s40 + .avatar-container.s64.flex-grow-0.flex-shrink-0 = link_to project_path(project), class: dom_class(project) do - if project.creator && use_creator_avatar - = image_tag avatar_icon_for_user(project.creator, 40), class: "avatar s40", alt:'' + = image_tag avatar_icon_for_user(project.creator, 64), class: "avatar s65", alt:'' - else - = project_icon(project, alt: '', class: 'avatar project-avatar s40', width: 40, height: 40) - .project-details - %h3.prepend-top-0.append-bottom-0 - = link_to project_path(project), class: 'text-plain' do - %span.project-full-name>< - %span.namespace-name - - if project.namespace && !skip_namespace - = project.namespace.human_name - \/ - %span.project-name< - = project.name - - - if access&.nonzero? - -# haml-lint:disable UnnecessaryStringOutput - = ' ' # prevent haml from eating the space between elements - %span.user-access-role= Gitlab::Access.human_access(access) - - - if show_last_commit_as_description - .description.prepend-top-5 - = link_to_markdown(project.commit.title, project_commit_path(project, project.commit), class: "commit-row-message") - - elsif project.description.present? - .description.prepend-top-5 - = markdown_field(project, :description) - - .controls - .prepend-top-0 - - if project.archived - %span.prepend-left-10.badge.badge-warning archived - - if can?(current_user, :read_cross_project) && project.pipeline_status.has_status? - %span.prepend-left-10 - = render_project_pipeline_status(project.pipeline_status) - - if forks - %span.prepend-left-10 - = sprite_icon('fork', size: 12) - = number_with_delimiter(project.forks_count) - - if stars - %span.prepend-left-10 - = icon('star') - = number_with_delimiter(project.star_count) - %span.prepend-left-10.visibility-icon.has-tooltip{ data: { container: 'body', placement: 'left' }, title: visibility_icon_description(project) } - = visibility_level_icon(project.visibility_level, fw: true) - .prepend-top-0 - updated #{updated_tooltip} + = project_icon(project, alt: '', class: 'avatar project-avatar s64', width: 64, height: 64) + .project-details.flex-sm-fill{ class: css_details_class } + .flex-wrapper.flex-fill + .d-flex.align-items-center.flex-wrap + %h2.d-flex.prepend-top-8 + = link_to project_path(project), class: 'text-plain' do + %span.project-full-name.append-right-8>< + %span.namespace-name + - if project.namespace && !skip_namespace + = project.namespace.human_name + \/ + %span.project-name< + = project.name + + %span.metadata-info.visibility-icon.append-right-10.prepend-top-8.has-tooltip{ data: { container: 'body', placement: 'top' }, title: visibility_icon_description(project) } + = visibility_level_icon(project.visibility_level, fw: true) + + - if explore_projects_tab? && project.repository.license + %span.metadata-info.d-inline-flex.align-items-center.append-right-10.prepend-top-8 + = sprite_icon('scale', size: 14, css_class: 'append-right-4') + = project.repository.license.name + + - if !explore_projects_tab? && access&.nonzero? + -# haml-lint:disable UnnecessaryStringOutput + = ' ' # prevent haml from eating the space between elements + .metadata-info.prepend-top-8 + %span.user-access-role.d-block= Gitlab::Access.human_access(access) + + - if show_last_commit_as_description + .description.d-none.d-sm-block.prepend-top-8.append-right-default + = link_to_markdown(project.commit.title, project_commit_path(project, project.commit), class: "commit-row-message") + - elsif project.description.present? + .description.d-none.d-sm-block.prepend-top-8.append-right-default + = markdown_field(project, :description) + + .controls.d-flex.flex-row.flex-sm-column.flex-md-column.align-items-center.align-items-sm-end.flex-wrap.flex-shrink-0{ class: css_controls_class } + .icon-container.d-flex.align-items-center + - if project.archived + %span.d-flex.icon-wrapper.badge.badge-warning archived + - if stars + %span.d-flex.align-items-center.icon-wrapper.stars.has-tooltip{ data: { container: 'body', placement: 'top' }, title: _('Stars') } + = sprite_icon('star', size: 14, css_class: 'append-right-4') + = number_with_delimiter(project.star_count) + - if forks + = link_to project_forks_path(project), + class: "align-items-center icon-wrapper forks has-tooltip", + title: _('Forks'), data: { container: 'body', placement: 'top' } do + = sprite_icon('fork', size: 14, css_class: 'append-right-4') + = number_with_delimiter(project.forks_count) + - if show_merge_request_count?(merge_requests, compact_mode) + = link_to project_merge_requests_path(project), + class: "d-none d-lg-flex align-items-center icon-wrapper merge-requests has-tooltip", + title: _('Merge Requests'), data: { container: 'body', placement: 'top' } do + = sprite_icon('git-merge', size: 14, css_class: 'append-right-4') + = number_with_delimiter(project.open_merge_requests_count) + - if show_issue_count?(issues, compact_mode) + = link_to project_issues_path(project), + class: "d-none d-lg-flex align-items-center icon-wrapper issues has-tooltip", + title: _('Issues'), data: { container: 'body', placement: 'top' } do + = sprite_icon('issues', size: 14, css_class: 'append-right-4') + = number_with_delimiter(project.open_issues_count) + - if pipeline_status && can?(current_user, :read_cross_project) && project.pipeline_status.has_status? + %span.icon-wrapper.pipeline-status + = render_project_pipeline_status(project.pipeline_status, tooltip_placement: 'top') + .updated-note + %span Updated #{updated_tooltip} + + .d-none.d-lg-flex.align-item-stretch + - unless compact_mode + - if current_user + %button.star-button.btn.btn-default.d-flex.align-items-center.star-btn.toggle-star{ type: "button", data: { endpoint: toggle_star_project_path(project, :json) } } + - if current_user.starred?(project) + = sprite_icon('star', { css_class: 'icon' }) + %span.starred= s_('ProjectOverview|Unstar') + - else + = sprite_icon('star-o', { css_class: 'icon' }) + %span= s_('ProjectOverview|Star') + + - else + = link_to new_user_session_path, class: 'btn btn-default has-tooltip count-badge-button d-flex align-items-center star-btn', title: s_('ProjectOverview|You must sign in to star a project') do + = sprite_icon('star-o', { css_class: 'icon' }) + %span= s_('ProjectOverview|Star') diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml index dfce00a10a1..bc26b3f8ef2 100644 --- a/app/workers/all_queues.yml +++ b/app/workers/all_queues.yml @@ -124,6 +124,7 @@ - propagate_service_template - reactive_caching - rebase +- remote_mirror_notification - repository_fork - repository_import - repository_remove_remote diff --git a/app/workers/remote_mirror_notification_worker.rb b/app/workers/remote_mirror_notification_worker.rb new file mode 100644 index 00000000000..70c2e857d09 --- /dev/null +++ b/app/workers/remote_mirror_notification_worker.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class RemoteMirrorNotificationWorker + include ApplicationWorker + + def perform(remote_mirror_id) + remote_mirror = RemoteMirrorFinder.new(id: remote_mirror_id).execute + + # We check again if there's an error because a newer run since this job was + # fired could've completed successfully. + return unless remote_mirror && remote_mirror.last_error.present? + + NotificationService.new.remote_mirror_update_failed(remote_mirror) + end +end diff --git a/app/workers/repository_update_remote_mirror_worker.rb b/app/workers/repository_update_remote_mirror_worker.rb index bd429d526bf..c0bae08ba85 100644 --- a/app/workers/repository_update_remote_mirror_worker.rb +++ b/app/workers/repository_update_remote_mirror_worker.rb @@ -15,7 +15,7 @@ class RepositoryUpdateRemoteMirrorWorker end def perform(remote_mirror_id, scheduled_time) - remote_mirror = RemoteMirror.find(remote_mirror_id) + remote_mirror = RemoteMirrorFinder.new(id: remote_mirror_id).execute return if remote_mirror.updated_since?(scheduled_time) raise UpdateAlreadyInProgressError if remote_mirror.update_in_progress? diff --git a/changelogs/unreleased/51944-redesign-project-lists-ui.yml b/changelogs/unreleased/51944-redesign-project-lists-ui.yml new file mode 100644 index 00000000000..56f9a86a686 --- /dev/null +++ b/changelogs/unreleased/51944-redesign-project-lists-ui.yml @@ -0,0 +1,5 @@ +--- +title: Redesign project lists UI +merge_request: 22682 +author: +type: other diff --git a/changelogs/unreleased/51994-disable-merging-labels-in-dropdowns.yml b/changelogs/unreleased/51994-disable-merging-labels-in-dropdowns.yml new file mode 100644 index 00000000000..2d54cf814b7 --- /dev/null +++ b/changelogs/unreleased/51994-disable-merging-labels-in-dropdowns.yml @@ -0,0 +1,5 @@ +--- +title: Disable merging of labels with same names +merge_request: 23265 +author: +type: changed diff --git a/changelogs/unreleased/52774-fix-svgs-in-ie-11.yml b/changelogs/unreleased/52774-fix-svgs-in-ie-11.yml new file mode 100644 index 00000000000..656a915a281 --- /dev/null +++ b/changelogs/unreleased/52774-fix-svgs-in-ie-11.yml @@ -0,0 +1,5 @@ +--- +title: Ensure that SVG sprite icons are properly rendered in IE11 +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/53493-list-id-email-header.yml b/changelogs/unreleased/53493-list-id-email-header.yml new file mode 100644 index 00000000000..09a0639f6f5 --- /dev/null +++ b/changelogs/unreleased/53493-list-id-email-header.yml @@ -0,0 +1,5 @@ +--- +title: Add project identifier as List-Id email Header to ease filtering +merge_request: 22817 +author: Olivier Crête +type: added diff --git a/changelogs/unreleased/54736-sign-in-bottom-margin.yml b/changelogs/unreleased/54736-sign-in-bottom-margin.yml new file mode 100644 index 00000000000..32b5b44fe35 --- /dev/null +++ b/changelogs/unreleased/54736-sign-in-bottom-margin.yml @@ -0,0 +1,5 @@ +--- +title: Fix login box bottom margins on signin page +merge_request: 23739 +author: '@gear54' +type: fixed diff --git a/changelogs/unreleased/55183-frozenerror-can-t-modify-frozen-string-in-app-mailers-notify-rb.yml b/changelogs/unreleased/55183-frozenerror-can-t-modify-frozen-string-in-app-mailers-notify-rb.yml new file mode 100644 index 00000000000..685a8309c72 --- /dev/null +++ b/changelogs/unreleased/55183-frozenerror-can-t-modify-frozen-string-in-app-mailers-notify-rb.yml @@ -0,0 +1,5 @@ +--- +title: Fix a potential frozen string error in app/mailers/notify.rb +merge_request: 23728 +author: +type: fixed diff --git a/changelogs/unreleased/55191-update-workhorse.yml b/changelogs/unreleased/55191-update-workhorse.yml new file mode 100644 index 00000000000..d16518e673a --- /dev/null +++ b/changelogs/unreleased/55191-update-workhorse.yml @@ -0,0 +1,5 @@ +--- +title: Update GitLab Workhorse to v8.0.0 +merge_request: 23740 +author: +type: other diff --git a/changelogs/unreleased/define-default-value-for-only-except-keys.yml b/changelogs/unreleased/define-default-value-for-only-except-keys.yml index 3e5ecdcf51e..ed0e982f0fc 100644 --- a/changelogs/unreleased/define-default-value-for-only-except-keys.yml +++ b/changelogs/unreleased/define-default-value-for-only-except-keys.yml @@ -1,5 +1,5 @@ --- title: Define the default value for only/except policies -merge_request: 23531 +merge_request: 23765 author: type: changed diff --git a/changelogs/unreleased/diff-empty-state-fixes.yml b/changelogs/unreleased/diff-empty-state-fixes.yml new file mode 100644 index 00000000000..0d347dd17e4 --- /dev/null +++ b/changelogs/unreleased/diff-empty-state-fixes.yml @@ -0,0 +1,5 @@ +--- +title: Fixed merge request diffs empty states +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/fix-calendar-events-fetching-error.yml b/changelogs/unreleased/fix-calendar-events-fetching-error.yml new file mode 100644 index 00000000000..ad4a40cd9a0 --- /dev/null +++ b/changelogs/unreleased/fix-calendar-events-fetching-error.yml @@ -0,0 +1,5 @@ +--- +title: Fix calendar events fetching error on private profile page +merge_request: 23718 +author: Harry Kiselev +type: other diff --git a/changelogs/unreleased/gt-update-environment-breadcrumb.yml b/changelogs/unreleased/gt-update-environment-breadcrumb.yml new file mode 100644 index 00000000000..53b9673a96c --- /dev/null +++ b/changelogs/unreleased/gt-update-environment-breadcrumb.yml @@ -0,0 +1,5 @@ +--- +title: Update environments breadcrumb +merge_request: 23751 +author: George Tsiolis +type: changed diff --git a/changelogs/unreleased/gt-update-navigation-theme-colors.yml b/changelogs/unreleased/gt-update-navigation-theme-colors.yml new file mode 100644 index 00000000000..749587a6343 --- /dev/null +++ b/changelogs/unreleased/gt-update-navigation-theme-colors.yml @@ -0,0 +1,5 @@ +--- +title: Update header navigation theme colors +merge_request: 23734 +author: George Tsiolis +type: fixed diff --git a/changelogs/unreleased/remote-mirror-update-failed-notification.yml b/changelogs/unreleased/remote-mirror-update-failed-notification.yml new file mode 100644 index 00000000000..50ec8624ae5 --- /dev/null +++ b/changelogs/unreleased/remote-mirror-update-failed-notification.yml @@ -0,0 +1,5 @@ +--- +title: Send a notification email to project maintainers when a mirror update fails +merge_request: 23595 +author: +type: added diff --git a/changelogs/unreleased/winh-markdown-preview-lists.yml b/changelogs/unreleased/winh-markdown-preview-lists.yml new file mode 100644 index 00000000000..6e47726283d --- /dev/null +++ b/changelogs/unreleased/winh-markdown-preview-lists.yml @@ -0,0 +1,5 @@ +--- +title: Remove unnecessary div from MarkdownField to apply list styles correctly +merge_request: 23733 +author: +type: fixed diff --git a/changelogs/unreleased/zj-backup-restore-object-pools.yml b/changelogs/unreleased/zj-backup-restore-object-pools.yml new file mode 100644 index 00000000000..26e1d49aa04 --- /dev/null +++ b/changelogs/unreleased/zj-backup-restore-object-pools.yml @@ -0,0 +1,5 @@ +--- +title: Restore Object Pools when restoring an object pool +merge_request: 23682 +author: +type: added diff --git a/config/initializers/postgresql_opclasses_support.rb b/config/initializers/postgresql_opclasses_support.rb index 07b06629dea..5d643b75dd8 100644 --- a/config/initializers/postgresql_opclasses_support.rb +++ b/config/initializers/postgresql_opclasses_support.rb @@ -81,7 +81,7 @@ module ActiveRecord if index_name.length > max_index_length raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' is too long; the limit is #{max_index_length} characters" end - if table_exists?(table_name) && index_name_exists?(table_name, index_name, false) + if data_source_exists?(table_name) && index_name_exists?(table_name, index_name, false) raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists" end index_columns = quoted_columns_for_index(column_names, options).join(", ") diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml index 5985569bef4..3ee32678f34 100644 --- a/config/sidekiq_queues.yml +++ b/config/sidekiq_queues.yml @@ -84,3 +84,4 @@ - [object_pool, 1] - [repository_cleanup, 1] - [delete_stored_files, 1] + - [remote_mirror_notification, 2] diff --git a/db/migrate/20181101191341_create_clusters_applications_cert_manager.rb b/db/migrate/20181101191341_create_clusters_applications_cert_manager.rb index 4966b89964a..0b6155356d9 100644 --- a/db/migrate/20181101191341_create_clusters_applications_cert_manager.rb +++ b/db/migrate/20181101191341_create_clusters_applications_cert_manager.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class CreateClustersApplicationsCertManager < ActiveRecord::Migration +class CreateClustersApplicationsCertManager < ActiveRecord::Migration[4.2] include Gitlab::Database::MigrationHelpers DOWNTIME = false diff --git a/db/migrate/20181108091549_cleanup_environments_external_url.rb b/db/migrate/20181108091549_cleanup_environments_external_url.rb index 8d6c20a4b15..8439f6e55e6 100644 --- a/db/migrate/20181108091549_cleanup_environments_external_url.rb +++ b/db/migrate/20181108091549_cleanup_environments_external_url.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class CleanupEnvironmentsExternalUrl < ActiveRecord::Migration +class CleanupEnvironmentsExternalUrl < ActiveRecord::Migration[4.2] include Gitlab::Database::MigrationHelpers DOWNTIME = false diff --git a/db/migrate/20181115140140_add_encrypted_runners_token_to_settings.rb b/db/migrate/20181115140140_add_encrypted_runners_token_to_settings.rb index 36d9ad45b19..5b2bb4f6b08 100644 --- a/db/migrate/20181115140140_add_encrypted_runners_token_to_settings.rb +++ b/db/migrate/20181115140140_add_encrypted_runners_token_to_settings.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class AddEncryptedRunnersTokenToSettings < ActiveRecord::Migration +class AddEncryptedRunnersTokenToSettings < ActiveRecord::Migration[4.2] include Gitlab::Database::MigrationHelpers DOWNTIME = false diff --git a/db/migrate/20181116050532_knative_external_ip.rb b/db/migrate/20181116050532_knative_external_ip.rb index f1f903fb692..5645b040a23 100644 --- a/db/migrate/20181116050532_knative_external_ip.rb +++ b/db/migrate/20181116050532_knative_external_ip.rb @@ -3,7 +3,7 @@ # See http://doc.gitlab.com/ce/development/migration_style_guide.html # for more information on how to write migrations for GitLab. -class KnativeExternalIp < ActiveRecord::Migration +class KnativeExternalIp < ActiveRecord::Migration[4.2] include Gitlab::Database::MigrationHelpers DOWNTIME = false diff --git a/db/migrate/20181116141415_add_encrypted_runners_token_to_namespaces.rb b/db/migrate/20181116141415_add_encrypted_runners_token_to_namespaces.rb index b92b1b50218..dcf565cd6c0 100644 --- a/db/migrate/20181116141415_add_encrypted_runners_token_to_namespaces.rb +++ b/db/migrate/20181116141415_add_encrypted_runners_token_to_namespaces.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class AddEncryptedRunnersTokenToNamespaces < ActiveRecord::Migration +class AddEncryptedRunnersTokenToNamespaces < ActiveRecord::Migration[4.2] include Gitlab::Database::MigrationHelpers DOWNTIME = false diff --git a/db/migrate/20181116141504_add_encrypted_runners_token_to_projects.rb b/db/migrate/20181116141504_add_encrypted_runners_token_to_projects.rb index 53e475bd180..13cd80e5c8b 100644 --- a/db/migrate/20181116141504_add_encrypted_runners_token_to_projects.rb +++ b/db/migrate/20181116141504_add_encrypted_runners_token_to_projects.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class AddEncryptedRunnersTokenToProjects < ActiveRecord::Migration +class AddEncryptedRunnersTokenToProjects < ActiveRecord::Migration[4.2] include Gitlab::Database::MigrationHelpers DOWNTIME = false diff --git a/db/migrate/20181119081539_add_merge_request_id_to_ci_pipelines.rb b/db/migrate/20181119081539_add_merge_request_id_to_ci_pipelines.rb index 65476109c61..f96d80787f9 100644 --- a/db/migrate/20181119081539_add_merge_request_id_to_ci_pipelines.rb +++ b/db/migrate/20181119081539_add_merge_request_id_to_ci_pipelines.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class AddMergeRequestIdToCiPipelines < ActiveRecord::Migration +class AddMergeRequestIdToCiPipelines < ActiveRecord::Migration[4.2] DOWNTIME = false def up diff --git a/db/migrate/20181120091639_add_foreign_key_to_ci_pipelines_merge_requests.rb b/db/migrate/20181120091639_add_foreign_key_to_ci_pipelines_merge_requests.rb index 03f677a4678..f8b46395941 100644 --- a/db/migrate/20181120091639_add_foreign_key_to_ci_pipelines_merge_requests.rb +++ b/db/migrate/20181120091639_add_foreign_key_to_ci_pipelines_merge_requests.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class AddForeignKeyToCiPipelinesMergeRequests < ActiveRecord::Migration +class AddForeignKeyToCiPipelinesMergeRequests < ActiveRecord::Migration[4.2] include Gitlab::Database::MigrationHelpers DOWNTIME = false diff --git a/db/migrate/20181120151656_add_token_encrypted_to_ci_runners.rb b/db/migrate/20181120151656_add_token_encrypted_to_ci_runners.rb index 40db6b399ab..8b990451adc 100644 --- a/db/migrate/20181120151656_add_token_encrypted_to_ci_runners.rb +++ b/db/migrate/20181120151656_add_token_encrypted_to_ci_runners.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class AddTokenEncryptedToCiRunners < ActiveRecord::Migration +class AddTokenEncryptedToCiRunners < ActiveRecord::Migration[4.2] include Gitlab::Database::MigrationHelpers DOWNTIME = false diff --git a/db/migrate/20181121101842_add_ci_builds_partial_index_on_project_id_and_status.rb b/db/migrate/20181121101842_add_ci_builds_partial_index_on_project_id_and_status.rb index 5b47a279438..a524709faf8 100644 --- a/db/migrate/20181121101842_add_ci_builds_partial_index_on_project_id_and_status.rb +++ b/db/migrate/20181121101842_add_ci_builds_partial_index_on_project_id_and_status.rb @@ -3,7 +3,7 @@ # See http://doc.gitlab.com/ce/development/migration_style_guide.html # for more information on how to write migrations for GitLab. -class AddCiBuildsPartialIndexOnProjectIdAndStatus < ActiveRecord::Migration +class AddCiBuildsPartialIndexOnProjectIdAndStatus < ActiveRecord::Migration[4.2] include Gitlab::Database::MigrationHelpers DOWNTIME = false diff --git a/db/migrate/20181121101843_remove_redundant_ci_builds_partial_index.rb b/db/migrate/20181121101843_remove_redundant_ci_builds_partial_index.rb index a0a02e81323..e4fb703e887 100644 --- a/db/migrate/20181121101843_remove_redundant_ci_builds_partial_index.rb +++ b/db/migrate/20181121101843_remove_redundant_ci_builds_partial_index.rb @@ -3,7 +3,7 @@ # See http://doc.gitlab.com/ce/development/migration_style_guide.html # for more information on how to write migrations for GitLab. -class RemoveRedundantCiBuildsPartialIndex < ActiveRecord::Migration +class RemoveRedundantCiBuildsPartialIndex < ActiveRecord::Migration[4.2] include Gitlab::Database::MigrationHelpers DOWNTIME = false diff --git a/db/post_migrate/20181010133639_backfill_store_project_full_path_in_repo.rb b/db/post_migrate/20181010133639_backfill_store_project_full_path_in_repo.rb index e9ab45ae9a1..247f5980f7e 100644 --- a/db/post_migrate/20181010133639_backfill_store_project_full_path_in_repo.rb +++ b/db/post_migrate/20181010133639_backfill_store_project_full_path_in_repo.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class BackfillStoreProjectFullPathInRepo < ActiveRecord::Migration +class BackfillStoreProjectFullPathInRepo < ActiveRecord::Migration[4.2] include Gitlab::Database::MigrationHelpers DOWNTIME = false diff --git a/db/post_migrate/20181026091631_migrate_forbidden_redirect_uris.rb b/db/post_migrate/20181026091631_migrate_forbidden_redirect_uris.rb index ff5510e8eb7..7c2df832882 100644 --- a/db/post_migrate/20181026091631_migrate_forbidden_redirect_uris.rb +++ b/db/post_migrate/20181026091631_migrate_forbidden_redirect_uris.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class MigrateForbiddenRedirectUris < ActiveRecord::Migration +class MigrateForbiddenRedirectUris < ActiveRecord::Migration[4.2] include Gitlab::Database::MigrationHelpers DOWNTIME = false diff --git a/db/post_migrate/20181121111200_schedule_runners_token_encryption.rb b/db/post_migrate/20181121111200_schedule_runners_token_encryption.rb index 753e052f7a7..ba82072fc98 100644 --- a/db/post_migrate/20181121111200_schedule_runners_token_encryption.rb +++ b/db/post_migrate/20181121111200_schedule_runners_token_encryption.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class ScheduleRunnersTokenEncryption < ActiveRecord::Migration +class ScheduleRunnersTokenEncryption < ActiveRecord::Migration[4.2] include Gitlab::Database::MigrationHelpers DOWNTIME = false diff --git a/doc/ci/interactive_web_terminal/index.md b/doc/ci/interactive_web_terminal/index.md index 2c799e83a5f..d8136b80c11 100644 --- a/doc/ci/interactive_web_terminal/index.md +++ b/doc/ci/interactive_web_terminal/index.md @@ -1,4 +1,4 @@ -# Getting started with interactive web terminals **[CORE ONLY]** +# Interactive Web Terminals **[CORE ONLY]** > [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/50144) in GitLab 11.3. @@ -15,7 +15,7 @@ progress. Two things need to be configured for the interactive web terminal to work: - The Runner needs to have [`[session_server]` configured - properly][session-server] + properly](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-session_server-section) - If you are using a reverse proxy with your GitLab instance, web terminals need to be [enabled](../../administration/integration/terminal.md#enabling-and-disabling-terminal-support) @@ -45,9 +45,7 @@ the terminal and type commands like a normal shell. If you have the terminal open and the job has finished with its tasks, the terminal will block the job from finishing for the duration configured in -[`[session_server].terminal_max_retention_time`][session-server] until you +[`[session_server].terminal_max_retention_time`](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-session_server-section) until you close the terminal window.  - -[session-server]: https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-session_server-section diff --git a/doc/ci/triggers/README.md b/doc/ci/triggers/README.md index 8ed04e04e53..c9a60feb73f 100644 --- a/doc/ci/triggers/README.md +++ b/doc/ci/triggers/README.md @@ -172,6 +172,7 @@ stages: - package run_tests: + stage: test script: - make test diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 33cda5942b1..4dafde0462a 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -55,27 +55,27 @@ A job is defined by a list of parameters that define the job behavior. | Keyword | Required | Description | |---------------|----------|-------------| -| script | yes | Defines a shell script which is executed by Runner | -| extends | no | Defines a configuration entry that this job is going to inherit from | -| image | no | Use docker image, covered in [Using Docker Images](../docker/using_docker_images.md#define-image-and-services-from-gitlab-ciyml) | -| services | no | Use docker services, covered in [Using Docker Images](../docker/using_docker_images.md#define-image-and-services-from-gitlab-ciyml) | -| stage | no | Defines a job stage (default: `test`) | -| type | no | Alias for `stage` | -| variables | no | Define job variables on a job level | -| only | no | Defines a list of git refs for which job is created | -| except | no | Defines a list of git refs for which job is not created | -| tags | no | Defines a list of tags which are used to select Runner | -| allow_failure | no | Allow job to fail. Failed job doesn't contribute to commit status | -| when | no | Define when to run job. Can be `on_success`, `on_failure`, `always` or `manual` | -| dependencies | no | Define other jobs that a job depends on so that you can pass artifacts between them| -| artifacts | no | Define list of [job artifacts](#artifacts) | -| cache | no | Define list of files that should be cached between subsequent runs | -| before_script | no | Override a set of commands that are executed before job | -| after_script | no | Override a set of commands that are executed after job | -| environment | no | Defines a name of environment to which deployment is done by this job | -| coverage | no | Define code coverage settings for a given job | -| retry | no | Define when and how many times a job can be auto-retried in case of a failure | -| parallel | no | Defines how many instances of a job should be run in parallel | +| [script](#script) | yes | Defines a shell script which is executed by Runner | +| [extends](#extends) | no | Defines a configuration entry that this job is going to inherit from | +| [image](#image-and-services) | no | Use docker image, covered in [Using Docker Images](../docker/using_docker_images.md#define-image-and-services-from-gitlab-ciyml) | +| [services](#image-and-services) | no | Use docker services, covered in [Using Docker Images](../docker/using_docker_images.md#define-image-and-services-from-gitlab-ciyml) | +| [stage](#stage) | no | Defines a job stage (default: `test`) | +| type | no | Alias for `stage` | +| [variables](#variables) | no | Define job variables on a job level | +| [only](#only-and-except-simplified) | no | Defines a list of git refs for which job is created | +| [except](#only-and-except-simplified) | no | Defines a list of git refs for which job is not created | +| [tags](#tags) | no | Defines a list of tags which are used to select Runner | +| [allow_failure](#allow_failure) | no | Allow job to fail. Failed job doesn't contribute to commit status | +| [when](#when) | no | Define when to run job. Can be `on_success`, `on_failure`, `always` or `manual` | +| [dependencies](#dependencies) | no | Define other jobs that a job depends on so that you can pass artifacts between them| +| [artifacts](#artifacts) | no | Define list of [job artifacts](#artifacts) | +| [cache](#cache) | no | Define list of files that should be cached between subsequent runs | +| [before_script](#before_script-and-after_script) | no | Override a set of commands that are executed before job | +| [after_script](#before_script-and-after_script) | no | Override a set of commands that are executed after job | +| [environment](#environment) | no | Defines a name of environment to which deployment is done by this job | +| [coverage](#coverage) | no | Define code coverage settings for a given job | +| [retry](#retry) | no | Define when and how many times a job can be auto-retried in case of a failure | +| [parallel](#parallel) | no | Defines how many instances of a job should be run in parallel | ### `extends` diff --git a/doc/development/automatic_ce_ee_merge.md b/doc/development/automatic_ce_ee_merge.md index e4eb26b3aca..0cc083cefc0 100644 --- a/doc/development/automatic_ce_ee_merge.md +++ b/doc/development/automatic_ce_ee_merge.md @@ -1,33 +1,28 @@ # Automatic CE->EE merge -Whenever a commit is pushed to the CE `master` branch, it is automatically -merged into the EE `master` branch. If the commit produces any conflicts, it is -instead reverted from CE `master`. When this happens, a merge request will be -set up automatically that can be used to reinstate the changes. This merge -request will be assigned to the author of the conflicting commit, or the merge -request author if the commit author could not be associated with a GitLab user. -If no author could be found, the merge request is assigned to a random member of -the Delivery team. It is then up to this team member to figure out who to assign -the merge request to. - -Because some commits can not be reverted if new commits depend on them, we also -run a job periodically that processes a range of commits and tries to merge or -revert them. This should ensure that all commits are either merged into EE -`master`, or reverted, instead of just being left behind in CE. +Commits pushed to CE `master` are automatically merged into EE `master` roughly +every 5 minutes. Changes are merged using the `recursive=ours` merge strategy in +the context of EE. This means that any merge conflicts are resolved by taking +the EE changes and discarding the CE changes. This removes the need for +resolving conflicts or reverting changes, at the cost of **absolutely +requiring** EE merge requests to be created whenever a CE merge request causes +merge conflicts. Failing to do so can result in changes not making their way +into EE. + +## Always create an EE merge request if there are conflicts + +In CI there is a job called `ee_compat_check`, which checks if a CE MR causes +merge conflicts with EE. If this job reports conflicts, you **must** create an +EE merge request. If you are an external contributor you can ask the reviewer to +do this for you. ## Always merge EE merge requests before their CE counterparts **In order to avoid conflicts in the CE->EE merge, you should always merge the EE version of your CE merge request first, if present.** -The rationale for this is that as CE->EE merges are done automatically, it can -happen that: - -1. A CE merge request that needs EE-specific changes is merged. -1. The automatic CE->EE merge happens. -1. Conflicts due to the CE merge request occur since its EE merge request isn't - merged yet. -1. The CE changes are reverted. +Failing to do so will lead to CE changes being discarded when merging into EE, +if they cause merge conflicts. ## Avoiding CE->EE merge conflicts beforehand @@ -45,76 +40,181 @@ detect if the current branch's changes will conflict during the CE->EE merge. The job reports what files are conflicting and how to set up a merge request against EE. -## How to reinstate changes - -When a commit is reverted, the corresponding merge request to reinstate the -changes will include all the details necessary to ensure the changes make it -back into CE and EE. However, you still need to manually set up an EE merge -request that resolves the conflicts. - -Each merge request used to reinstate changes will have the "reverted" label -applied. Please do not remove this label, as it will be used to determine how -many times commits are reverted and how long it takes to reinstate the changes. +#### How the job works + +1. Generates the diff between your branch and current CE `master` +1. Tries to apply it to current EE `master` +1. If it applies cleanly, the job succeeds, otherwise... +1. Detects a branch with the `ee-` prefix or `-ee` suffix in EE +1. If it exists, generate the diff between this branch and current EE `master` +1. Tries to apply it to current EE `master` +1. If it applies cleanly, the job succeeds + +In the case where the job fails, it means you should create an `ee-<ce_branch>` +or `<ce_branch>-ee` branch, push it to EE and open a merge request against EE +`master`. +At this point if you retry the failing job in your CE merge request, it should +now pass. + +Notes: + +- This task is not a silver-bullet, its current goal is to bring awareness to + developers that their work needs to be ported to EE. +- Community contributors shouldn't be required to submit merge requests against + EE, but reviewers should take actions by either creating such EE merge request + or asking a GitLab developer to do it **before the merge request is merged**. +- If you branch is too far behind `master`, the job will fail. In that case you + should rebase your branch upon latest `master`. +- Code reviews for merge requests often consist of multiple iterations of + feedback and fixes. There is no need to update your EE MR after each + iteration. Instead, create an EE MR as soon as you see the + `ee_compat_check` job failing. After you receive the final approval + from a Maintainer (but **before the CE MR is merged**) update the EE MR. + This helps to identify significant conflicts sooner, but also reduces the + number of times you have to resolve conflicts. +- Please remember to + [always have your EE merge request merged before the CE version](#always-merge-ee-merge-requests-before-their-ce-counterparts). +- You can use [`git rerere`](https://git-scm.com/docs/git-rerere) + to avoid resolving the same conflicts multiple times. + +### Cherry-picking from CE to EE + +For avoiding merge conflicts, we use a method of creating equivalent branches +for CE and EE. If the `ee-compat-check` job fails, this process is required. + +This method only requires that you have cloned both CE and EE into your computer. +If you don't have them yet, please go ahead and clone them: + +- Clone CE repo: `git clone git@gitlab.com:gitlab-org/gitlab-ce.git` +- Clone EE repo: `git clone git@gitlab.com:gitlab-org/gitlab-ee.git` + +And the only additional setup we need is to add CE as remote of EE and vice-versa: + +- Open two terminal windows, one in CE, and another one in EE: + - In EE: `git remote add ce git@gitlab.com:gitlab-org/gitlab-ce.git` + - In CE: `git remote add ee git@gitlab.com:gitlab-org/gitlab-ee.git` + +That's all setup we need, so that we can cherry-pick a commit from CE to EE, and +from EE to CE. + +Now, every time you create an MR for CE and EE: + +1. Open two terminal windows, one in CE, and another one in EE +1. In the CE terminal: + 1. Create the CE branch, e.g., `branch-example` + 1. Make your changes and push a commit (commit A) + 1. Create the CE merge request in GitLab +1. In the EE terminal: + 1. Create the EE-equivalent branch ending with `-ee`, e.g., + `git checkout -b branch-example-ee` + 1. Fetch the CE branch: `git fetch ce branch-example` + 1. Cherry-pick the commit A: `git cherry-pick commit-A-SHA` + 1. If Git prompts you to fix the conflicts, do a `git status` + to check which files contain conflicts, fix them, save the files + 1. Add the changes with `git add .` but **DO NOT commit** them + 1. Continue cherry-picking: `git cherry-pick --continue` + 1. Push to EE: `git push origin branch-example-ee` +1. Create the EE-equivalent MR and link to the CE MR from the +description "Ports [CE-MR-LINK] to EE" +1. Once all the jobs are passing in both CE and EE, you've addressed the +feedback from your own team, and got them approved, the merge requests can be merged. +1. When both MRs are ready, the EE merge request will be merged first, and the +CE-equivalent will be merged next. + +**Important notes:** + +- The commit SHA can be easily found from the GitLab UI. From a merge request, +open the tab **Commits** and click the copy icon to copy the commit SHA. +- To cherry-pick a **commit range**, such as [A > B > C > D] use: + + ```shell + git cherry-pick "oldest-commit-SHA^..newest-commit-SHA" + ``` + + For example, suppose the commit A is the oldest, and its SHA is `4f5e4018c09ed797fdf446b3752f82e46f5af502`, + and the commit D is the newest, and its SHA is `80e1c9e56783bd57bd7129828ec20b252ebc0538`. + The cherry-pick command will be: + + ```shell + git cherry-pick "4f5e4018c09ed797fdf446b3752f82e46f5af502^..80e1c9e56783bd57bd7129828ec20b252ebc0538" + ``` + +- To cherry-pick a **merge commit**, use the flag `-m 1`. For example, suppose that the +merge commit SHA is `138f5e2f20289bb376caffa0303adb0cac859ce1`: + + ```shell + git cherry-pick -m 1 138f5e2f20289bb376caffa0303adb0cac859ce1 + ``` +- To cherry-pick multiple commits, such as B and D in a range [A > B > C > D], use: + + ```shell + git cherry-pick commmit-B-SHA commit-D-SHA + ``` + + For example, suppose commit B SHA = `4f5e4018c09ed797fdf446b3752f82e46f5af502`, + and the commit D SHA = `80e1c9e56783bd57bd7129828ec20b252ebc0538`. + The cherry-pick command will be: + + ```shell + git cherry-pick 4f5e4018c09ed797fdf446b3752f82e46f5af502 80e1c9e56783bd57bd7129828ec20b252ebc0538 + ``` + + This case is particularly useful when you have a merge commit in a sequence of + commits and you want to cherry-pick all but the merge commit. + +- If you push more commits to the CE branch, you can safely repeat the procedure +to cherry-pick them to the EE-equivalent branch. You can do that as many times as +necessary, using the same CE and EE branches. +- If you submitted the merge request to the CE repo and the `ee-compat-check` job passed, +you are not required to submit the EE-equivalent MR, but it's still recommended. If the +job failed, you are required to submit the EE MR so that you can fix the conflicts in EE +before merging your changes into CE. -An example merge request can be found in [CE merge request -23280](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/23280). +## FAQ -## How it works +### How does automatic merging work? The automatic merging is performed using a project called [Merge -Train](https://gitlab.com/gitlab-org/merge-train/). For every commit to merge or -revert, we generate patches using `git format-patch` which we then try to apply -using `git am --3way`. If this succeeds we push the changes to EE, if this fails -we decide what to do based on the failure reason: - -1. If the patch could not be applied because it was already applied, we just - skip it. -1. If the patch caused conflicts, we revert the source commits. - -Commits are reverted in reverse order, ensuring that if commit B depends on A, -and both conflict, we first revert B followed by reverting A. +Train](https://gitlab.com/gitlab-org/merge-train/). This project will clone CE +and EE master, and merge CE master into EE master using `git merge +--strategy=recursive --strategy-option=ours`. This process runs multiple times +per hour. -## FAQ - -### Why? +For more information on the exact implementation you can refer to the source +code. -We want to work towards being able to deploy continuously, but this requires -that `master` is always stable and has all the changes we need. If CE `master` -can not be merged into EE `master` due to merge conflicts, this prevents _any_ -change from CE making its way into EE. Since GitLab.com runs on EE, this -effectively prevents us from deploying changes. +### Why merge automatically? -Past experiences and data have shown that periodic CE to EE merge requests do -not scale, and often take a very long time to complete. For example, [in this +As we work towards continuous deployments and a single repository for both CE +and EE, we need to first make sure that all CE changes make their way into CE as +fast as possible. Past experiences and data have shown that periodic CE to EE +merge requests do not scale, and often take a very long time to complete. For +example, [in this comment](https://gitlab.com/gitlab-org/release/framework/issues/49#note_114614619) we determined that the average time to close an upstream merge request is around 5 hours, with peaks up to several days. Periodic merge requests are also frustrating to work with, because they often include many changes unrelated to your own changes. -Automatically merging or reverting commits allows us to keep merging changes -from CE into EE, as we never have to wait hours for somebody to resolve a set of -merge conflicts. - -### Does the CE to EE merge take into account merge commits? - -No. When merging CE changes into EE, merge commits are ignored. - -### My changes are reverted, but I set up an EE MR to resolve conflicts - -Most likely the automatic merge job ran before the EE merge request was merged. -If this keeps happening, consider reporting a bug in the [Merge Train issue -tracker](https://gitlab.com/gitlab-org/merge-train/issues). +To resolve these problems, we now merge changes using the `ours` strategy to +automatically resolve merge conflicts. This removes the need for resolving +conflicts in a periodic merge request, and allows us to merge changes from CE +into EE much faster. -### My changes keep getting reverted, and this is really annoying! +### My CE merge request caused conflicts after it was merged. What do I do? -This is understandable, but the solution to this is fairly straightforward: -simply set up an EE merge request for every CE merge request, and resolve your -conflicts before the changes are reverted. +If you notice this, you should set up an EE merge request that resolves these +conflicts as **soon as possible**. Failing to do so can lead to your changes not +being available in EE, which may break tests. This in turn would prevent us from +being able to deploy. -### Will we allow certain people to still merge changes, even if they conflict? +### Won't this setup be risky? -No. +No, not if there is an EE merge request for every CE merge request that causes +conflicts _and_ that EE merge request is merged first. In the past we may have +been a bit more relaxed when it comes to enforcing EE merge requests, but to +enable automatic merging have to start requiring such merge requests even for +the smallest conflicts. ### Some files I work with often conflict, how can I best deal with this? @@ -124,10 +224,6 @@ you can do this by moving the EE code to a separate module, which can then be injected into the appropriate classes or modules. See [Guidelines for implementing Enterprise Edition features](ee_features.md) for more information. -### Will changelog entries be reverted automatically? +--- -Only if the changelog was added in the commit that was reverted. If a changelog -entry was added in a separate commit, it is possible for it to be left behind. -Since changelog entries are related to the changes in question, there is no real -reason to commit the changelog separately, and as such this should not be a big -problem. +[Return to Development documentation](README.md) diff --git a/doc/development/background_migrations.md b/doc/development/background_migrations.md index bb9a296ef12..dd4a9e058d7 100644 --- a/doc/development/background_migrations.md +++ b/doc/development/background_migrations.md @@ -211,7 +211,7 @@ existing data. Since we're dealing with a lot of rows we'll schedule jobs in batches instead of doing this one by one: ```ruby -class ScheduleExtractServicesUrl < ActiveRecord::Migration +class ScheduleExtractServicesUrl < ActiveRecord::Migration[4.2] disable_ddl_transaction! class Service < ActiveRecord::Base @@ -242,7 +242,7 @@ jobs and manually run on any un-migrated rows. Such a migration would look like this: ```ruby -class ConsumeRemainingExtractServicesUrlJobs < ActiveRecord::Migration +class ConsumeRemainingExtractServicesUrlJobs < ActiveRecord::Migration[4.2] disable_ddl_transaction! class Service < ActiveRecord::Base diff --git a/doc/development/code_review.md b/doc/development/code_review.md index 7788d155154..25ea2211b64 100644 --- a/doc/development/code_review.md +++ b/doc/development/code_review.md @@ -10,7 +10,7 @@ code is effective, understandable, maintainable, and secure. ## Getting your merge request reviewed, approved, and merged You are strongly encouraged to get your code **reviewed** by a -[reviewer](https://about.gitlab.com/handbook/engineering/#reviewer) as soon as +[reviewer](https://about.gitlab.com/handbook/engineering/workflow/code-review/#reviewer) as soon as there is any code to review, to get a second opinion on the chosen solution and implementation, and an extra pair of eyes looking for bugs, logic problems, or uncovered edge cases. The reviewer can be from a different team, but it is @@ -24,7 +24,7 @@ If you need assistance with security scans or comments, feel free to include the Security Team (`@gitlab-com/gl-security`) in the review. Depending on the areas your merge request touches, it must be **approved** by one -or more [maintainers](https://about.gitlab.com/handbook/engineering/#maintainer): +or more [maintainers](https://about.gitlab.com/handbook/engineering/workflow/code-review/#maintainer): For approvals, we use the approval functionality found in the merge request widget. Reviewers can add their approval by [approving additionally](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html#adding-or-removing-an-approval). diff --git a/doc/development/fe_guide/vuex.md b/doc/development/fe_guide/vuex.md index 0f57835fb87..65963b959f7 100644 --- a/doc/development/fe_guide/vuex.md +++ b/doc/development/fe_guide/vuex.md @@ -137,6 +137,7 @@ By following this pattern we guarantee: #### Dispatching actions To dispatch an action from a component, use the `mapActions` helper: + ```javascript import { mapActions } from 'vuex'; @@ -204,6 +205,7 @@ export const getUsersWithPets = (state, getters) => { ``` To access a getter from a component, use the `mapGetters` helper: + ```javascript import { mapGetters } from 'vuex'; @@ -226,6 +228,7 @@ export const ADD_USER = 'ADD_USER'; ### How to include the store in your application The store should be included in the main component of your application: + ```javascript // app.vue import store from 'store'; // it will include the index.js file @@ -364,7 +367,8 @@ Because we're currently using [`babel-plugin-rewire`](https://github.com/speedsk `[vuex] actions should be function or object with "handler" function` To prevent this error from happening, you need to export an empty function as `default`: -``` + +```javascript // getters.js or actions.js // prevent babel-plugin-rewire from generating an invalid default during karma tests diff --git a/doc/development/migration_style_guide.md b/doc/development/migration_style_guide.md index a99267bfbba..d0a054c3290 100644 --- a/doc/development/migration_style_guide.md +++ b/doc/development/migration_style_guide.md @@ -67,7 +67,7 @@ body: For example: ```ruby -class MyMigration < ActiveRecord::Migration +class MyMigration < ActiveRecord::Migration[4.2] DOWNTIME = true DOWNTIME_REASON = 'This migration requires downtime because ...' @@ -95,7 +95,7 @@ migration. For this to work your migration needs to include the module `Gitlab::Database::MultiThreadedMigration`: ```ruby -class MyMigration < ActiveRecord::Migration +class MyMigration < ActiveRecord::Migration[4.2] include Gitlab::Database::MigrationHelpers include Gitlab::Database::MultiThreadedMigration end @@ -105,7 +105,7 @@ You can then use the method `with_multiple_threads` to perform work in separate threads. For example: ```ruby -class MyMigration < ActiveRecord::Migration +class MyMigration < ActiveRecord::Migration[4.2] include Gitlab::Database::MigrationHelpers include Gitlab::Database::MultiThreadedMigration @@ -139,7 +139,7 @@ by calling the method `disable_ddl_transaction!` in the body of your migration class like so: ```ruby -class MyMigration < ActiveRecord::Migration +class MyMigration < ActiveRecord::Migration[4.2] include Gitlab::Database::MigrationHelpers disable_ddl_transaction! @@ -167,7 +167,7 @@ the method `disable_ddl_transaction!` in the body of your migration class like so: ```ruby -class MyMigration < ActiveRecord::Migration +class MyMigration < ActiveRecord::Migration[4.2] include Gitlab::Database::MigrationHelpers disable_ddl_transaction! @@ -193,7 +193,7 @@ Here's an example where we add a new column with a foreign key constraint. Note it includes `index: true` to create an index for it. ```ruby -class Migration < ActiveRecord::Migration +class Migration < ActiveRecord::Migration[4.2] def change add_reference :model, :other_model, index: true, foreign_key: { on_delete: :cascade } @@ -216,7 +216,7 @@ For example, to add the column `foo` to the `projects` table with a default value of `10` you'd write the following: ```ruby -class MyMigration < ActiveRecord::Migration +class MyMigration < ActiveRecord::Migration[4.2] include Gitlab::Database::MigrationHelpers disable_ddl_transaction! @@ -365,7 +365,7 @@ If you need more complex logic you can define and use models local to a migration. For example: ```ruby -class MyMigration < ActiveRecord::Migration +class MyMigration < ActiveRecord::Migration[4.2] class Project < ActiveRecord::Base self.table_name = 'projects' end diff --git a/doc/development/prometheus_metrics.md b/doc/development/prometheus_metrics.md index b6b6d9665ea..0511e735843 100644 --- a/doc/development/prometheus_metrics.md +++ b/doc/development/prometheus_metrics.md @@ -30,7 +30,7 @@ You might want to add additional database migration that makes a decision what t For example: you might be interested in migrating all dependent data to a different metric. ```ruby -class ImportCommonMetrics < ActiveRecord::Migration +class ImportCommonMetrics < ActiveRecord::Migration[4.2] include Gitlab::Database::MigrationHelpers require Rails.root.join('db/importers/common_metrics_importer.rb') diff --git a/doc/development/sql.md b/doc/development/sql.md index e1e1d31a85f..06005a0a6f8 100644 --- a/doc/development/sql.md +++ b/doc/development/sql.md @@ -106,7 +106,7 @@ transaction. Transactions for migrations can be disabled using the following pattern: ```ruby -class MigrationName < ActiveRecord::Migration +class MigrationName < ActiveRecord::Migration[4.2] disable_ddl_transaction! end ``` @@ -114,7 +114,7 @@ end For example: ```ruby -class AddUsersLowerUsernameEmailIndexes < ActiveRecord::Migration +class AddUsersLowerUsernameEmailIndexes < ActiveRecord::Migration[4.2] disable_ddl_transaction! def up diff --git a/doc/development/testing_guide/best_practices.md b/doc/development/testing_guide/best_practices.md index 72abda26e3d..24f4d457d45 100644 --- a/doc/development/testing_guide/best_practices.md +++ b/doc/development/testing_guide/best_practices.md @@ -19,6 +19,16 @@ Here are some things to keep in mind regarding test performance: ## RSpec +To run rspec tests: + +```sh +# run all tests +bundle exec rspec + +# run test for path +bundle exec rspec spec/[path]/[to]/[spec].rb +``` + ### General guidelines - Use a single, top-level `describe ClassName` block. diff --git a/doc/development/what_requires_downtime.md b/doc/development/what_requires_downtime.md index 3630a28fae9..24edd05da2f 100644 --- a/doc/development/what_requires_downtime.md +++ b/doc/development/what_requires_downtime.md @@ -88,7 +88,7 @@ renaming. For example ```ruby # A regular migration in db/migrate -class RenameUsersUpdatedAtToUpdatedAtTimestamp < ActiveRecord::Migration +class RenameUsersUpdatedAtToUpdatedAtTimestamp < ActiveRecord::Migration[4.2] include Gitlab::Database::MigrationHelpers disable_ddl_transaction! @@ -118,7 +118,7 @@ We can perform this cleanup using ```ruby # A post-deployment migration in db/post_migrate -class CleanupUsersUpdatedAtRename < ActiveRecord::Migration +class CleanupUsersUpdatedAtRename < ActiveRecord::Migration[4.2] include Gitlab::Database::MigrationHelpers disable_ddl_transaction! @@ -157,7 +157,7 @@ as follows: ```ruby # A regular migration in db/migrate -class ChangeUsersUsernameStringToText < ActiveRecord::Migration +class ChangeUsersUsernameStringToText < ActiveRecord::Migration[4.2] include Gitlab::Database::MigrationHelpers disable_ddl_transaction! @@ -178,7 +178,7 @@ Next we need to clean up our changes using a post-deployment migration: ```ruby # A post-deployment migration in db/post_migrate -class ChangeUsersUsernameStringToTextCleanup < ActiveRecord::Migration +class ChangeUsersUsernameStringToTextCleanup < ActiveRecord::Migration[4.2] include Gitlab::Database::MigrationHelpers disable_ddl_transaction! @@ -213,7 +213,7 @@ the work / load over a longer time period, without slowing down deployments. For example, to change the column type using a background migration: ```ruby -class ExampleMigration < ActiveRecord::Migration +class ExampleMigration < ActiveRecord::Migration[4.2] include Gitlab::Database::MigrationHelpers disable_ddl_transaction! @@ -257,7 +257,7 @@ release) by a cleanup migration, which should steal from the queue and handle any remaining rows. For example: ```ruby -class MigrateRemainingIssuesClosedAt < ActiveRecord::Migration +class MigrateRemainingIssuesClosedAt < ActiveRecord::Migration[4.2] include Gitlab::Database::MigrationHelpers DOWNTIME = false @@ -322,7 +322,7 @@ Migrations can take advantage of this by using the method `add_concurrent_index`. For example: ```ruby -class MyMigration < ActiveRecord::Migration +class MyMigration < ActiveRecord::Migration[4.2] def up add_concurrent_index :projects, :column_name end diff --git a/doc/install/README.md b/doc/install/README.md index 92116305775..ae48306e65e 100644 --- a/doc/install/README.md +++ b/doc/install/README.md @@ -5,8 +5,25 @@ description: Read through the GitLab installation methods. # Installation -GitLab can be installed via various ways. Check the [installation methods][methods] -for an overview. +GitLab can be installed in most GNU/Linux distributions and in a number +of cloud providers. To get the best experience from GitLab you need to balance +performance, reliability, ease of administration (backups, upgrades and troubleshooting), +and cost of hosting. + +There are many ways you can install GitLab depending on your platform: + +1. **Omnibus Gitlab**: The official deb/rpm packages that contain a bundle of GitLab + and the various components it depends on like PostgreSQL, Redis, Sidekiq, etc. +1. **GitLab Helm chart**: The cloud native Helm chart for installing GitLab and all + its components on Kubernetes. +1. **Docker**: The Omnibus GitLab packages dockerized. +1. **Source**: Install GitLab and all its components from scratch. + +TIP: **If in doubt, choose Omnibus:** +The Omnibus GitLab packages are mature, scalable, support +[high availability](../administration/high_availability/README.md) and are used +today on GitLab.com. The Helm charts are recommended for those who are familiar +with Kubernetes. ## Requirements @@ -14,36 +31,58 @@ Before installing GitLab, make sure to check the [requirements documentation](re which includes useful information on the supported Operating Systems as well as the hardware requirements. -## Installation methods - -- [Installation using the Omnibus packages](https://about.gitlab.com/downloads/) - - Install GitLab using our official deb/rpm repositories. This is the - recommended way. -- [Installation from source](installation.md) - Install GitLab from source. - Useful for unsupported systems like *BSD. For an overview of the directory - structure, read the [structure documentation](structure.md). -- [Docker](docker.md) - Install GitLab using Docker. - -## Install GitLab on cloud providers - -- [Installing in Kubernetes](kubernetes/index.md): Install GitLab into a Kubernetes - Cluster using our official Helm Chart Repository. -- [Install GitLab on OpenShift](openshift_and_gitlab/index.md) -- [Install GitLab on DC/OS](https://mesosphere.com/blog/gitlab-dcos/) via [GitLab-Mesosphere integration](https://about.gitlab.com/2016/09/16/announcing-gitlab-and-mesosphere/) -- [Install GitLab on Azure](azure/index.md) -- [Install GitLab on Google Cloud Platform](google_cloud_platform/index.md) -- [Install GitLab on Google Kubernetes Engine (GKE)](https://about.gitlab.com/2017/01/23/video-tutorial-idea-to-production-on-google-container-engine-gke/): video tutorial on -the full process of installing GitLab on Google Kubernetes Engine (GKE), pushing an application to GitLab, building the app with GitLab CI/CD, and deploying to production. -- [Install on AWS](aws/index.md): Install GitLab on AWS using the community AMIs that GitLab provides. -- [Getting started with GitLab and DigitalOcean](https://about.gitlab.com/2016/04/27/getting-started-with-gitlab-and-digitalocean/): requirements, installation process, updates. -- [Demo: Cloud Native Development with GitLab](https://about.gitlab.com/2017/04/18/cloud-native-demo/): video demonstration on how to install GitLab on Kubernetes, build a project, create Review Apps, store Docker images in Container Registry, deploy to production on Kubernetes, and monitor with Prometheus. -- _Testing only!_ [DigitalOcean and Docker Machine](digitaloceandocker.md) - - Quickly test any version of GitLab on DigitalOcean using Docker Machine. +## Installing GitLab using the Omnibus GitLab package (recommended) + +The Omnibus GitLab package uses our official deb/rpm repositories. This is +recommended for most users. + +If you need additional flexibility and resilience, we recommend deploying +GitLab as described in our [High Availability documentation](../administration/high_availability/README.md). + +[**> Install GitLab using the Omnibus GitLab package.**](https://about.gitlab.com/install/) + +## Installing GitLab on Kubernetes via the GitLab Helm charts + +NOTE: **Kubernetes experience required:** +We recommend being familiar with Kubernetes before using it to deploy GitLab in +production. The methods for management, observability, and some concepts are +different than traditional deployments. + +When installing GitLab on Kubernetes, there are some trade-offs that you +need to be aware of: -## Database +- Administration and troubleshooting requires Kubernetes knowledge. +- It can be more expensive for smaller installations. The default installation + requires more resources than a single node Omnibus deployment, as most services + are deployed in a redundant fashion. +- There are some feature [limitations to be aware of](kubernetes/gitlab_chart.md#limitations). -While the recommended database is PostgreSQL, we provide information to install -GitLab using MySQL. Check the [MySQL documentation](database_mysql.md) for more -information. +[**> Install GitLab on Kubernetes using the GitLab Helm charts.**](kubernetes/index.md) -[methods]: https://about.gitlab.com/installation/ +## Installing GitLab with Docker + +GitLab maintains a set of official Docker images based on the Omnibus GitLab package. + +[**> Install GitLab using the official GitLab Docker images.**](docker.md) + +## Installing GitLab from source + +If the GitLab Omnibus package is not available in your distribution, you can +install GitLab from source: Useful for unsupported systems like *BSD. For an +overview of the directory structure, read the [structure documentation](structure.md). + +[**> Install GitLab from source.**](installation.md) + +## Installing GitLab on cloud providers + +GitLab can be installed on a variety of cloud providers by using any of +the above methods, provided the cloud provider supports it. + +- [Install on AWS](aws/index.md): Install Omnibus GitLab on AWS using the community AMIs that GitLab provides. +- [Install GitLab on Google Cloud Platform](google_cloud_platform/index.md): Install Omnibus GitLab on a VM in GCP. +- [Install GitLab on Azure](azure/index.md): Install Omnibus GitLab from Azure Marketplace. +- [Install GitLab on OpenShift](openshift_and_gitlab/index.md): Install GitLab using the Docker image on OpenShift. +- [Install GitLab on DC/OS](https://mesosphere.com/blog/gitlab-dcos/): Install GitLab on Mesosphere DC/OS via the [GitLab-Mesosphere integration](https://about.gitlab.com/2016/09/16/announcing-gitlab-and-mesosphere/). +- [Install GitLab on DigitalOcean](https://about.gitlab.com/2016/04/27/getting-started-with-gitlab-and-digitalocean/): Install Omnibus GitLab on DigitalOcean. +- _Testing only!_ [DigitalOcean and Docker Machine](digitaloceandocker.md): + Quickly test any version of GitLab on DigitalOcean using Docker Machine. diff --git a/doc/install/docker.md b/doc/install/docker.md index e90f6645b0c..d0129f0f5c4 100644 --- a/doc/install/docker.md +++ b/doc/install/docker.md @@ -8,9 +8,9 @@ GitLab provides official Docker images to allowing you to easily take advantage GitLab maintains a set of [official Docker images](https://hub.docker.com/r/gitlab) based on our [Omnibus GitLab package](https://docs.gitlab.com/omnibus/README.html). These images include: -- [GitLab Community Edition](https://hub.docker.com/r/gitlab/gitlab-ce/). -- [GitLab Enterprise Edition](https://hub.docker.com/r/gitlab/gitlab-ee/). -- [GitLab Runner](https://hub.docker.com/r/gitlab/gitlab-runner/). +- [GitLab Community Edition](https://hub.docker.com/r/gitlab/gitlab-ce/) +- [GitLab Enterprise Edition](https://hub.docker.com/r/gitlab/gitlab-ee/) +- [GitLab Runner](https://hub.docker.com/r/gitlab/gitlab-runner/) A [complete usage guide](https://docs.gitlab.com/omnibus/docker/) to these images is available, as well as the [Dockerfile used for building the images](https://gitlab.com/gitlab-org/omnibus-gitlab/tree/master/docker). diff --git a/doc/install/kubernetes/gitlab_chart.md b/doc/install/kubernetes/gitlab_chart.md index 5749eb0a9ec..74e2598f860 100644 --- a/doc/install/kubernetes/gitlab_chart.md +++ b/doc/install/kubernetes/gitlab_chart.md @@ -1,7 +1,14 @@ # GitLab Helm Chart -This is the official and recommended way to install GitLab on a cloud native environment. -For more information on other available GitLab Helm Charts, see the [charts overview](index.md#chart-overview). +This is the official way to install GitLab on a cloud native environment. + +NOTE: **Kubernetes experience required:** +Our Helm charts are recommended for those who are familiar with Kubernetes. +If you're not sure if Kubernetes is for you, our +[Omnibus GitLab packages](../README.md#install-gitlab-using-the-omnibus-gitlab-package-recommended) +are mature, scalable, support [high availability](../../administration/high_availability/README.md) +and are used today on GitLab.com. +It is not necessary to have GitLab installed on Kubernetes in order to use [GitLab Kubernetes integration](https://docs.gitlab.com/ee/user/project/clusters/index.html). ## Introduction diff --git a/doc/install/kubernetes/index.md b/doc/install/kubernetes/index.md index 69171fbb341..281630174e7 100644 --- a/doc/install/kubernetes/index.md +++ b/doc/install/kubernetes/index.md @@ -4,11 +4,19 @@ description: 'Read through the different methods to deploy GitLab on Kubernetes. # Installing GitLab on Kubernetes +NOTE: **Kubernetes experience required:** +Our Helm charts are recommended for those who are familiar with Kubernetes. +If you're not sure if Kubernetes is for you, our +[Omnibus GitLab packages](../README.md#install-gitlab-using-the-omnibus-gitlab-package-recommended) +are mature, scalable, support [high availability](../../administration/high_availability/README.md) +and are used today on GitLab.com. +It is not necessary to have GitLab installed on Kubernetes in order to use [GitLab Kubernetes integration](https://docs.gitlab.com/ee/user/project/clusters/index.html). + The easiest method to deploy GitLab on [Kubernetes](https://kubernetes.io/) is -to take advantage of GitLab's Helm charts. [Helm] is a package -management tool for Kubernetes, allowing apps to be easily managed via their -Charts. A [Chart] is a detailed description of the application including how it -should be deployed, upgraded, and configured. +to take advantage of GitLab's Helm charts. [Helm](https://github.com/kubernetes/helm/blob/master/README.md) +is a package management tool for Kubernetes, allowing apps to be easily managed via their +Charts. A [Chart](https://github.com/kubernetes/charts) is a detailed description +of the application including how it should be deployed, upgraded, and configured. ## GitLab Chart @@ -32,29 +40,3 @@ and you'd like to leverage the Runner's it can be deployed with the GitLab Runner chart. Learn more about [gitlab-runner chart](gitlab_runner_chart.md). - -## Deprecated Charts - -CAUTION: **Deprecated:** -These charts are **deprecated**. We recommend using the [GitLab Chart](gitlab_chart.md) -instead. - -### GitLab-Omnibus Chart - -This chart is based on the [GitLab Omnibus Docker images](https://docs.gitlab.com/omnibus/docker/). -It deploys and configures nearly all features of GitLab, including: - -- a [GitLab Runner](https://docs.gitlab.com/runner/) -- [Container Registry](../../user/project/container_registry.html#gitlab-container-registry) -- [Mattermost](https://docs.gitlab.com/omnibus/gitlab-mattermost/) -- [automatic SSL](https://github.com/kubernetes/charts/tree/master/stable/kube-lego) -- and an [NGINX load balancer](https://github.com/kubernetes/ingress/tree/master/controllers/nginx). - -Learn more about the [gitlab-omnibus chart](gitlab_omnibus.md). - -### Community Contributed Charts - -The community has also contributed GitLab [CE](https://github.com/kubernetes/charts/tree/master/stable/gitlab-ce) and [EE](https://github.com/kubernetes/charts/tree/master/stable/gitlab-ee) charts to the [Helm Stable Repository](https://github.com/kubernetes/charts#repository-structure). These charts are [deprecated](https://github.com/kubernetes/charts/issues/1138) in favor of the [official Chart](gitlab_chart.md). - -[chart]: https://github.com/kubernetes/charts -[helm]: https://github.com/kubernetes/helm/blob/master/README.md diff --git a/doc/integration/recaptcha.md b/doc/integration/recaptcha.md index 8fdadb008ec..825c3654492 100644 --- a/doc/integration/recaptcha.md +++ b/doc/integration/recaptcha.md @@ -9,9 +9,9 @@ to confirm that a real user, not a bot, is attempting to create an account. To use reCAPTCHA, first you must create a site and private key. 1. Go to the URL: <https://www.google.com/recaptcha/admin>. -1. Fill out the form necessary to obtain reCAPTCHA keys. -1. Login to your GitLab server, with administrator credentials. -1. Go to Applications Settings on Admin Area (`admin/application_settings`). +1. Fill out the form necessary to obtain reCAPTCHA v2 keys. +1. Log in to your GitLab server, with administrator credentials. +1. Go to Reporting Applications Settings in the Admin Area (`admin/application_settings/reporting`). 1. Fill all recaptcha fields with keys from previous steps. 1. Check the `Enable reCAPTCHA` checkbox. 1. Save the configuration. diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md index a63656fafef..57bc71d2903 100644 --- a/doc/raketasks/backup_restore.md +++ b/doc/raketasks/backup_restore.md @@ -657,6 +657,7 @@ Restoring database tables: - Loading fixture wikis...[SKIPPING] Restoring repositories: - Restoring repository abcd... [DONE] +- Object pool 1 ... Deleting tmp directories...[DONE] ``` diff --git a/doc/security/rack_attack.md b/doc/security/rack_attack.md index dcdc9f42c22..ad83dc05a93 100644 --- a/doc/security/rack_attack.md +++ b/doc/security/rack_attack.md @@ -10,8 +10,7 @@ Rack Attack offers IP whitelisting, blacklisting, Fail2ban style filtering and tracking. **Note:** Starting with 11.2, Rack Attack is disabled by default. To continue -using this feature, please enable it in your `gitlab.rb` by setting -`gitlab_rails['rack_attack_git_basic_auth'] = true`. +using this feature, please enable it by [configuring `gitlab.rb` as described in Settings](#settings). By default, user sign-in, user sign-up (if enabled), and user password reset is limited to 6 requests per minute. After trying for 6 times, the client will diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md index e40525d2577..95bd5d1cf4e 100644 --- a/doc/user/project/clusters/index.md +++ b/doc/user/project/clusters/index.md @@ -270,7 +270,7 @@ deployments. | [Cert Manager](http://docs.cert-manager.io/en/latest/) | 11.6+ | Cert Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert Manager on your cluster will issue a certificate by [Let's Encrypt](https://letsencrypt.org/) and ensure that certificates are valid and up-to-date. | [stable/cert-manager](https://github.com/helm/charts/tree/master/stable/cert-manager) | | [Prometheus](https://prometheus.io/docs/introduction/overview/) | 10.4+ | Prometheus is an open-source monitoring and alerting system useful to supervise your deployed applications. | [stable/prometheus](https://github.com/helm/charts/tree/master/stable/prometheus) | | [GitLab Runner](https://docs.gitlab.com/runner/) | 10.6+ | GitLab Runner is the open source project that is used to run your jobs and send the results back to GitLab. It is used in conjunction with [GitLab CI/CD](https://about.gitlab.com/features/gitlab-ci-cd/), the open-source continuous integration service included with GitLab that coordinates the jobs. When installing the GitLab Runner via the applications, it will run in **privileged mode** by default. Make sure you read the [security implications](#security-implications) before doing so. | [runner/gitlab-runner](https://gitlab.com/charts/gitlab-runner) | -| [JupyterHub](http://jupyter.org/) | 11.0+ | [JupyterHub](https://jupyterhub.readthedocs.io/en/stable/) is a multi-user service for managing notebooks across a team. [Jupyter Notebooks](https://jupyter-notebook.readthedocs.io/en/latest/) provide a web-based interactive programming environment used for data analysis, visualization, and machine learning. We use [this](https://gitlab.com/gitlab-org/jupyterhub-user-image/blob/master/Dockerfile) custom Jupyter image that installs additional useful packages on top of the base Jupyter. You will also see ready-to-use DevOps Runbooks built with Nurtch's [Rubix library](https://github.com/amit1rrr/rubix). More information on creating executable runbooks can be found at [Nurtch Documentation](http://docs.nurtch.com/en/latest). **Note**: Authentication will be enabled for any user of the GitLab server via OAuth2. HTTPS will be supported in a future release. | [jupyter/jupyterhub](https://jupyterhub.github.io/helm-chart/) | +| [JupyterHub](http://jupyter.org/) | 11.0+ | [JupyterHub](https://jupyterhub.readthedocs.io/en/stable/) is a multi-user service for managing notebooks across a team. [Jupyter Notebooks](https://jupyter-notebook.readthedocs.io/en/latest/) provide a web-based interactive programming environment used for data analysis, visualization, and machine learning. We use a [custom Jupyter image](https://gitlab.com/gitlab-org/jupyterhub-user-image/blob/master/Dockerfile) that installs additional useful packages on top of the base Jupyter. You will also see ready-to-use DevOps Runbooks built with Nurtch's [Rubix library](https://github.com/amit1rrr/rubix). More information on creating executable runbooks can be found in [our Nurtch documentation](runbooks/index.md#nurtch-executable-runbooks). **Note**: Authentication will be enabled for any user of the GitLab server via OAuth2. HTTPS will be supported in a future release. | [jupyter/jupyterhub](https://jupyterhub.github.io/helm-chart/) | | [Knative](https://cloud.google.com/knative) | 11.5+ | Knative provides a platform to create, deploy, and manage serverless workloads from a Kubernetes cluster. It is used in conjunction with, and includes [Istio](https://istio.io) to provide an external IP address for all programs hosted by Knative. You will be prompted to enter a wildcard domain where your applications will be exposed. Configure your DNS server to use the external IP address for that domain. For any application created and installed, they will be accessible as `<program_name>.<kubernetes_namespace>.<domain_name>`. This will require your kubernetes cluster to have [RBAC enabled](#role-based-access-control-rbac). | [knative/knative](https://storage.googleapis.com/triggermesh-charts) NOTE: **Note:** diff --git a/doc/user/project/settings/import_export.md b/doc/user/project/settings/import_export.md index f94671fcf87..cb68c9318bc 100644 --- a/doc/user/project/settings/import_export.md +++ b/doc/user/project/settings/import_export.md @@ -24,6 +24,10 @@ > Otherwise, a supplementary comment is left to mention the original author and > the MRs, notes or issues will be owned by the importer. > - Control project Import/Export with the [API](../../../api/project_import_export.md). +> - If an imported project contains merge requests originated from forks, +> then new branches associated with such merge requests will be created +> within a project during the import/export. Thus, the number of branches +> in the exported project could be bigger than in the original project. Existing projects running on any GitLab instance or GitLab.com can be exported with all their related data and be moved into a new GitLab instance. diff --git a/doc/user/project/web_ide/index.md b/doc/user/project/web_ide/index.md index e6b1f6b6aae..55e53b865af 100644 --- a/doc/user/project/web_ide/index.md +++ b/doc/user/project/web_ide/index.md @@ -1,6 +1,6 @@ # Web IDE -> [Introduced in](https://gitlab.com/gitlab-org/gitlab-ee/issues/4539) [GitLab Ultimate][ee] 10.4. +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/4539) in [GitLab Ultimate][ee] 10.4. > [Brought to GitLab Core](https://gitlab.com/gitlab-org/gitlab-ce/issues/44157) in 10.7. The Web IDE makes it faster and easier to contribute changes to your projects @@ -15,7 +15,7 @@ and from merge requests. ## File finder -> [Introduced in](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/18323) [GitLab Core][ce] 10.8. +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/18323) in [GitLab Core][ce] 10.8. The file finder allows you to quickly open files in the current branch by searching. The file finder is launched using the keyboard shortcut `Command-p`, @@ -65,7 +65,7 @@ shows you a preview of the merge request diff if you commit your changes. ## View CI job logs -> [Introduced in](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/19279) [GitLab Core][ce] 11.0. +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/19279) in [GitLab Core][ce] 11.0. The Web IDE can be used to quickly fix failing tests by opening the branch or merge request in the Web IDE and opening the logs of the failed job. The status @@ -77,7 +77,7 @@ left. ## Switching merge requests -> [Introduced in](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/19318) [GitLab Core][ce] 11.0. +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/19318) in [GitLab Core][ce] 11.0. Switching between your authored and assigned merge requests can be done without leaving the Web IDE. Click the dropdown in the top of the sidebar to open a list @@ -86,7 +86,7 @@ switching to a different merge request. ## Switching branches -> [Introduced in](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/20850) [GitLab Core][ce] 11.2. +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/20850) in [GitLab Core][ce] 11.2. Switching between branches of the current project repository can be done without leaving the Web IDE. Click the dropdown in the top of the sidebar to open a list @@ -95,7 +95,7 @@ switching to a different branch. ## Client Side Evaluation -> [Introduced in](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/19764) [GitLab Core][ce] 11.2. +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/19764) in [GitLab Core][ce] 11.2. The Web IDE can be used to preview JavaScript projects right in the browser. This feature uses CodeSandbox to compile and bundle the JavaScript used to diff --git a/doc/workflow/notifications.md b/doc/workflow/notifications.md index 020aa73f809..6ce789998a4 100644 --- a/doc/workflow/notifications.md +++ b/doc/workflow/notifications.md @@ -135,6 +135,7 @@ Notification emails include headers that provide extra content about the notific | X-GitLab-Pipeline-Id | Only in pipeline emails, the ID of the pipeline the notification is for | | X-GitLab-Reply-Key | A unique token to support reply by email | | X-GitLab-NotificationReason | The reason for being notified. "mentioned", "assigned", etc | +| List-Id | The path of the project in a RFC 2919 mailing list identifier useful for email organization, for example, with GMail filters | #### X-GitLab-NotificationReason diff --git a/jest.config.js b/jest.config.js index 23554a117f6..4dab7c2891a 100644 --- a/jest.config.js +++ b/jest.config.js @@ -14,6 +14,7 @@ if (process.env.CI) { // eslint-disable-next-line import/no-commonjs module.exports = { testMatch: ['<rootDir>/spec/frontend/**/*_spec.js'], + moduleFileExtensions: ['js', 'json', 'vue'], moduleNameMapper: { '^~(.*)$': '<rootDir>/app/assets/javascripts$1', '^helpers(.*)$': '<rootDir>/spec/frontend/helpers$1', @@ -26,4 +27,8 @@ module.exports = { reporters, setupTestFrameworkScriptFile: '<rootDir>/spec/frontend/test_setup.js', restoreMocks: true, + transform: { + '^.+\\.js$': 'babel-jest', + '^.+\\.vue$': 'vue-jest', + }, }; diff --git a/lib/backup/repository.rb b/lib/backup/repository.rb index c8a5377bfa0..184c7418e75 100644 --- a/lib/backup/repository.rb +++ b/lib/backup/repository.rb @@ -4,6 +4,7 @@ require 'yaml' module Backup class Repository + include Gitlab::ShellAdapter attr_reader :progress def initialize(progress) @@ -75,7 +76,6 @@ module Backup def restore prepare_directories - gitlab_shell = Gitlab::Shell.new Project.find_each(batch_size: 1000) do |project| progress.print " * #{project.full_path} ... " @@ -118,6 +118,8 @@ module Backup end end end + + restore_object_pools end protected @@ -159,5 +161,17 @@ module Backup def display_repo_path(project) project.hashed_storage?(:repository) ? "#{project.full_path} (#{project.disk_path})" : project.full_path end + + def restore_object_pools + PoolRepository.includes(:source_project).find_each do |pool| + progress.puts " - Object pool #{pool.disk_path}..." + + pool.source_project ||= pool.member_projects.first.root_of_fork_network + pool.state = 'none' + pool.save + + pool.schedule + end + end end end diff --git a/lib/gitlab/checks/diff_check.rb b/lib/gitlab/checks/diff_check.rb index 49d361fcef7..8ee345ab45a 100644 --- a/lib/gitlab/checks/diff_check.rb +++ b/lib/gitlab/checks/diff_check.rb @@ -11,6 +11,7 @@ module Gitlab }.freeze def validate! + return if deletion? || newrev.nil? return unless should_run_diff_validations? return if commits.empty? return unless uses_raw_delta_validations? @@ -28,7 +29,7 @@ module Gitlab private def should_run_diff_validations? - newrev && oldrev && !deletion? && validate_lfs_file_locks? + validate_lfs_file_locks? end def validate_lfs_file_locks? diff --git a/lib/gitlab/ci/config/entry/except_policy.rb b/lib/gitlab/ci/config/entry/except_policy.rb deleted file mode 100644 index 46ded35325d..00000000000 --- a/lib/gitlab/ci/config/entry/except_policy.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module Ci - class Config - module Entry - ## - # Entry that represents an only/except trigger policy for the job. - # - class ExceptPolicy < Policy - def self.default - end - end - end - end - end -end diff --git a/lib/gitlab/ci/config/entry/job.rb b/lib/gitlab/ci/config/entry/job.rb index 085be5da08d..50942fbdb40 100644 --- a/lib/gitlab/ci/config/entry/job.rb +++ b/lib/gitlab/ci/config/entry/job.rb @@ -16,6 +16,13 @@ module Gitlab dependencies before_script after_script variables environment coverage retry parallel extends].freeze + DEFAULT_ONLY_POLICY = { + refs: %w(branches tags) + }.freeze + + DEFAULT_EXCEPT_POLICY = { + }.freeze + validations do validates :config, allowed_keys: ALLOWED_KEYS validates :config, presence: true @@ -65,10 +72,10 @@ module Gitlab entry :services, Entry::Services, description: 'Services that will be used to execute this job.' - entry :only, Entry::OnlyPolicy, + entry :only, Entry::Policy, description: 'Refs policy this job will be executed for.' - entry :except, Entry::ExceptPolicy, + entry :except, Entry::Policy, description: 'Refs policy this job will be executed for.' entry :variables, Entry::Variables, @@ -154,8 +161,8 @@ module Gitlab services: services_value, stage: stage_value, cache: cache_value, - only: only_value, - except: except_value, + only: DEFAULT_ONLY_POLICY.deep_merge(only_value.to_h), + except: DEFAULT_EXCEPT_POLICY.deep_merge(except_value.to_h), variables: variables_defined? ? variables_value : nil, environment: environment_defined? ? environment_value : nil, environment_name: environment_defined? ? environment_value[:name] : nil, diff --git a/lib/gitlab/ci/config/entry/only_policy.rb b/lib/gitlab/ci/config/entry/only_policy.rb deleted file mode 100644 index 9a581b8e97e..00000000000 --- a/lib/gitlab/ci/config/entry/only_policy.rb +++ /dev/null @@ -1,18 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module Ci - class Config - module Entry - ## - # Entry that represents an only/except trigger policy for the job. - # - class OnlyPolicy < Policy - def self.default - { refs: %w[branches tags] } - end - end - end - end - end -end diff --git a/lib/gitlab/ci/config/entry/policy.rb b/lib/gitlab/ci/config/entry/policy.rb index 81e74a639fc..998da1f6837 100644 --- a/lib/gitlab/ci/config/entry/policy.rb +++ b/lib/gitlab/ci/config/entry/policy.rb @@ -5,9 +5,12 @@ module Gitlab class Config module Entry ## - # Base class for OnlyPolicy and ExceptPolicy + # Entry that represents an only/except trigger policy for the job. # class Policy < ::Gitlab::Config::Entry::Simplifiable + strategy :RefsPolicy, if: -> (config) { config.is_a?(Array) } + strategy :ComplexPolicy, if: -> (config) { config.is_a?(Hash) } + class RefsPolicy < ::Gitlab::Config::Entry::Node include ::Gitlab::Config::Entry::Validatable @@ -63,16 +66,6 @@ module Gitlab def self.default end - - ## - # Class-level execution won't be inherited by subclasses by default. - # Therefore, we need to explicitly execute that for OnlyPolicy and ExceptPolicy - def self.inherited(klass) - super - - klass.strategy :RefsPolicy, if: -> (config) { config.is_a?(Array) } - klass.strategy :ComplexPolicy, if: -> (config) { config.is_a?(Hash) } - end end end end diff --git a/lib/gitlab/ci/status/bridge/common.rb b/lib/gitlab/ci/status/bridge/common.rb new file mode 100644 index 00000000000..c6cb620f7a0 --- /dev/null +++ b/lib/gitlab/ci/status/bridge/common.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module Gitlab + module Ci + module Status + module Bridge + module Common + def label + subject.description + end + + def has_details? + false + end + + def has_action? + false + end + + def details_path + raise NotImplementedError + end + end + end + end + end +end diff --git a/lib/gitlab/ci/status/bridge/factory.rb b/lib/gitlab/ci/status/bridge/factory.rb new file mode 100644 index 00000000000..910de865483 --- /dev/null +++ b/lib/gitlab/ci/status/bridge/factory.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Gitlab + module Ci + module Status + module Bridge + class Factory < Status::Factory + def self.common_helpers + Status::Bridge::Common + end + end + end + end + end +end diff --git a/lib/gitlab/import_sources.rb b/lib/gitlab/import_sources.rb index f46bb837cf7..67952ca0f2d 100644 --- a/lib/gitlab/import_sources.rb +++ b/lib/gitlab/import_sources.rb @@ -10,7 +10,7 @@ module Gitlab ImportSource = Struct.new(:name, :title, :importer) # We exclude `bare_repository` here as it has no import class associated - ImportTable = [ + IMPORT_TABLE = [ ImportSource.new('github', 'GitHub', Gitlab::GithubImport::ParallelImporter), ImportSource.new('bitbucket', 'Bitbucket Cloud', Gitlab::BitbucketImport::Importer), ImportSource.new('bitbucket_server', 'Bitbucket Server', Gitlab::BitbucketServerImport::Importer), @@ -45,7 +45,7 @@ module Gitlab end def import_table - ImportTable + IMPORT_TABLE end end end diff --git a/lib/gitlab/ssh_public_key.rb b/lib/gitlab/ssh_public_key.rb index 47571239b5c..6df54852d02 100644 --- a/lib/gitlab/ssh_public_key.rb +++ b/lib/gitlab/ssh_public_key.rb @@ -4,7 +4,7 @@ module Gitlab class SSHPublicKey Technology = Struct.new(:name, :key_class, :supported_sizes) - Technologies = [ + TECHNOLOGIES = [ Technology.new(:rsa, OpenSSL::PKey::RSA, [1024, 2048, 3072, 4096]), Technology.new(:dsa, OpenSSL::PKey::DSA, [1024, 2048, 3072]), Technology.new(:ecdsa, OpenSSL::PKey::EC, [256, 384, 521]), @@ -12,11 +12,11 @@ module Gitlab ].freeze def self.technology(name) - Technologies.find { |tech| tech.name.to_s == name.to_s } + TECHNOLOGIES.find { |tech| tech.name.to_s == name.to_s } end def self.technology_for_key(key) - Technologies.find { |tech| key.is_a?(tech.key_class) } + TECHNOLOGIES.find { |tech| key.is_a?(tech.key_class) } end def self.supported_sizes(name) diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb index da22ea9cf5c..265f6213a99 100644 --- a/lib/gitlab/workhorse.rb +++ b/lib/gitlab/workhorse.rb @@ -31,7 +31,6 @@ module Gitlab GL_USERNAME: user&.username, ShowAllRefs: show_all_refs, Repository: repository.gitaly_repository.to_h, - RepoPath: 'ignored but not allowed to be empty in gitlab-workhorse', GitConfigOptions: [], GitalyServer: { address: Gitlab::GitalyClient.address(project.repository_storage), diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 032498babec..abd1ce4a13a 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -3094,6 +3094,9 @@ msgstr "" msgid "Forking in progress" msgstr "" +msgid "Forks" +msgstr "" + msgid "Format" msgstr "" @@ -3919,6 +3922,9 @@ msgstr "" msgid "Loading..." msgstr "" +msgid "Loading…" +msgstr "" + msgid "Lock" msgstr "" @@ -4365,6 +4371,9 @@ msgstr "" msgid "No changes" msgstr "" +msgid "No changes between %{ref_start}%{source_branch}%{ref_end} and %{ref_start}%{target_branch}%{ref_end}" +msgstr "" + msgid "No connection could be made to a Gitaly Server, please check your logs!" msgstr "" @@ -6260,6 +6269,9 @@ msgstr "" msgid "Starred projects" msgstr "" +msgid "Stars" +msgstr "" + msgid "Start a %{new_merge_request} with these changes" msgstr "" diff --git a/package.json b/package.json index 52c211f7afb..cf7e43f14dd 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "@babel/plugin-syntax-import-meta": "^7.0.0", "@babel/preset-env": "^7.1.0", "@gitlab/csslab": "^1.8.0", - "@gitlab/svgs": "^1.40.0", + "@gitlab/svgs": "^1.42.0", "@gitlab/ui": "^1.15.0", "apollo-boost": "^0.1.20", "apollo-client": "^2.4.5", @@ -118,7 +118,7 @@ "xterm": "^3.5.0" }, "devDependencies": { - "@gitlab/eslint-config": "^1.2.0", + "@gitlab/eslint-config": "^1.4.0", "@vue/test-utils": "^1.0.0-beta.25", "axios-mock-adapter": "^1.15.0", "babel-core": "^7.0.0-bridge", @@ -131,10 +131,10 @@ "babel-types": "^6.26.0", "chalk": "^2.4.1", "commander": "^2.18.0", - "eslint": "~5.6.0", + "eslint": "~5.9.0", "eslint-import-resolver-jest": "^2.1.1", "eslint-import-resolver-webpack": "^0.10.1", - "eslint-plugin-html": "4.0.5", + "eslint-plugin-html": "5.0.0", "eslint-plugin-import": "^2.14.0", "eslint-plugin-jasmine": "^2.10.1", "eslint-plugin-jest": "^22.1.0", @@ -157,6 +157,10 @@ "karma-webpack": "^4.0.0-beta.0", "nodemon": "^1.18.4", "prettier": "1.15.2", + "vue-jest": "^3.0.1", "webpack-dev-server": "^3.1.10" + }, + "engines": { + "yarn": "^1.10.0" } } @@ -158,6 +158,10 @@ module QA autoload :Activity, 'qa/page/project/activity' autoload :Menu, 'qa/page/project/menu' + module Commit + autoload :Show, 'qa/page/project/commit/show' + end + module Import autoload :Github, 'qa/page/project/import/github' end diff --git a/qa/qa/page/project/commit/show.rb b/qa/qa/page/project/commit/show.rb new file mode 100644 index 00000000000..9770b8a657c --- /dev/null +++ b/qa/qa/page/project/commit/show.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module QA + module Page + module Project + module Commit + class Show < Page::Base + view 'app/views/projects/commit/_commit_box.html.haml' do + element :options_button + element :email_patches + element :plain_diff + end + + def select_email_patches + click_element :options_button + click_element :email_patches + end + + def select_plain_diff + click_element :options_button + click_element :plain_diff + end + end + end + end + end +end diff --git a/qa/qa/page/project/show.rb b/qa/qa/page/project/show.rb index 99d849db439..945b244df15 100644 --- a/qa/qa/page/project/show.rb +++ b/qa/qa/page/project/show.rb @@ -72,9 +72,14 @@ module QA end end + def go_to_commit(commit_msg) + within_element(:file_tree) do + click_on commit_msg + end + end + def go_to_new_issue click_element :new_menu_toggle - click_link 'New issue' end diff --git a/qa/qa/resource/merge_request.rb b/qa/qa/resource/merge_request.rb index 45cb317e0eb..7150098a00a 100644 --- a/qa/qa/resource/merge_request.rb +++ b/qa/qa/resource/merge_request.rb @@ -58,7 +58,10 @@ module QA populate(:target, :source) project.visit! - Page::Project::Show.perform(&:new_merge_request) + Page::Project::Show.perform do |project| + project.wait_for_push + project.new_merge_request + end Page::MergeRequest::New.perform do |page| page.fill_title(@title) page.fill_description(@description) diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/user_views_raw_diff_patch_requests_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/user_views_raw_diff_patch_requests_spec.rb new file mode 100644 index 00000000000..75ad18a4111 --- /dev/null +++ b/qa/qa/specs/features/browser_ui/3_create/repository/user_views_raw_diff_patch_requests_spec.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +module QA + context 'Create' do + describe 'Commit data' do + before(:context) do + Runtime::Browser.visit(:gitlab, Page::Main::Login) + Page::Main::Login.perform(&:sign_in_using_credentials) + + @project = Resource::Repository::ProjectPush.fabricate! do |push| + push.file_name = 'README.md' + push.file_content = '# This is a test project' + push.commit_message = 'Add README.md' + end + + # first file added has no parent commit, thus no diff data + # add second file to repo to enable diff from initial commit + @commit_message = 'Add second file' + + @project.visit! + Page::Project::Show.perform(&:create_new_file!) + Page::File::Form.perform do |f| + f.add_name('second') + f.add_content('second file content') + f.add_commit_message(@commit_message) + f.commit_changes + end + end + + def view_commit + @project.visit! + Page::Project::Show.perform do |page| + page.go_to_commit(@commit_message) + end + end + + def raw_content + find('pre').text + end + + it 'user views raw email patch' do + view_commit + + Page::Project::Commit::Show.perform(&:select_email_patches) + + expect(page).to have_content('From: Administrator <admin@example.com>') + expect(page).to have_content('Subject: [PATCH] Add second file') + expect(page).to have_content('diff --git a/second b/second') + end + + it 'user views raw commit diff' do + view_commit + + Page::Project::Commit::Show.perform(&:select_plain_diff) + + expect(raw_content).to start_with('diff --git a/second b/second') + expect(page).to have_content('+second file content') + end + end + end +end diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb index 02930edbf72..6240ab6d867 100644 --- a/spec/controllers/projects/issues_controller_spec.rb +++ b/spec/controllers/projects/issues_controller_spec.rb @@ -42,6 +42,8 @@ describe Projects::IssuesController do it_behaves_like "issuables list meta-data", :issue + it_behaves_like 'set sort order from user preference' + it "returns index" do get :index, namespace_id: project.namespace, project_id: project diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 7f15da859e5..a37a831ddbb 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -160,6 +160,8 @@ describe Projects::MergeRequestsController do it_behaves_like "issuables list meta-data", :merge_request + it_behaves_like 'set sort order from user preference' + context 'when page param' do let(:last_page) { project.merge_requests.page().total_pages } let!(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) } diff --git a/spec/factories/ci/bridge.rb b/spec/factories/ci/bridge.rb new file mode 100644 index 00000000000..5f83b80ad7b --- /dev/null +++ b/spec/factories/ci/bridge.rb @@ -0,0 +1,17 @@ +FactoryBot.define do + factory :ci_bridge, class: Ci::Bridge do + name ' bridge' + stage 'test' + stage_idx 0 + ref 'master' + tag false + created_at 'Di 29. Okt 09:50:00 CET 2013' + status :success + + pipeline factory: :ci_pipeline + + after(:build) do |bridge, evaluator| + bridge.project ||= bridge.pipeline.project + end + end +end diff --git a/spec/features/merge_request/user_awards_emoji_spec.rb b/spec/features/merge_request/user_awards_emoji_spec.rb index 859a4c65562..93376bc8ce0 100644 --- a/spec/features/merge_request/user_awards_emoji_spec.rb +++ b/spec/features/merge_request/user_awards_emoji_spec.rb @@ -4,11 +4,14 @@ describe 'Merge request > User awards emoji', :js do let(:project) { create(:project, :public, :repository) } let(:user) { project.creator } let(:merge_request) { create(:merge_request, source_project: project, author: create(:user)) } + let!(:note) { create(:note, noteable: merge_request, project: merge_request.project) } describe 'logged in' do before do sign_in(user) visit project_merge_request_path(project, merge_request) + + wait_for_requests end it 'adds award to merge request' do @@ -36,6 +39,15 @@ describe 'Merge request > User awards emoji', :js do expect(page).to have_selector('.emoji-menu', count: 1) end + it 'adds awards to note' do + first('.js-note-emoji').click + first('.emoji-menu .js-emoji-btn').click + + wait_for_requests + + expect(page).to have_selector('.js-awards-block') + end + describe 'the project is archived' do let(:project) { create(:project, :public, :repository, :archived) } diff --git a/spec/frontend/dummy_spec.js b/spec/frontend/dummy_spec.js deleted file mode 100644 index 2bfef25e9c6..00000000000 --- a/spec/frontend/dummy_spec.js +++ /dev/null @@ -1 +0,0 @@ -it('does nothing', () => {}); diff --git a/spec/javascripts/vue_shared/components/notes/timeline_entry_item_spec.js b/spec/frontend/vue_shared/components/notes/timeline_entry_item_spec.js index c15635f2105..c15635f2105 100644 --- a/spec/javascripts/vue_shared/components/notes/timeline_entry_item_spec.js +++ b/spec/frontend/vue_shared/components/notes/timeline_entry_item_spec.js diff --git a/spec/helpers/emails_helper_spec.rb b/spec/helpers/emails_helper_spec.rb index 139387e0b24..3820cf5cb9d 100644 --- a/spec/helpers/emails_helper_spec.rb +++ b/spec/helpers/emails_helper_spec.rb @@ -73,4 +73,59 @@ describe EmailsHelper do end end end + + describe '#create_list_id_string' do + using RSpec::Parameterized::TableSyntax + + where(:full_path, :list_id_path) do + "01234" | "01234" + "5/0123" | "012.." + "45/012" | "012.." + "012" | "012" + "23/01" | "01.23" + "2/01" | "01.2" + "234/01" | "01.." + "4/2/0" | "0.2.4" + "45/2/0" | "0.2.." + "5/23/0" | "0.." + "0-2/5" | "5.0-2" + "0_2/5" | "5.0-2" + "0.2/5" | "5.0-2" + end + + with_them do + it 'ellipcizes different variants' do + project = double("project") + allow(project).to receive(:full_path).and_return(full_path) + allow(project).to receive(:id).and_return(12345) + # Set a max length that gives only 5 chars for the project full path + max_length = "12345..#{Gitlab.config.gitlab.host}".length + 5 + list_id = create_list_id_string(project, max_length) + + expect(list_id).to eq("12345.#{list_id_path}.#{Gitlab.config.gitlab.host}") + expect(list_id).to satisfy { |s| s.length <= max_length } + end + end + end + + describe 'Create realistic List-Id identifier' do + using RSpec::Parameterized::TableSyntax + + where(:full_path, :list_id_path) do + "gitlab-org/gitlab-ce" | "gitlab-ce.gitlab-org" + "project-name/subproject_name/my.project" | "my-project.subproject-name.project-name" + end + + with_them do + it 'Produces the right List-Id' do + project = double("project") + allow(project).to receive(:full_path).and_return(full_path) + allow(project).to receive(:id).and_return(12345) + list_id = create_list_id_string(project) + + expect(list_id).to eq("12345.#{list_id_path}.#{Gitlab.config.gitlab.host}") + expect(list_id).to satisfy { |s| s.length <= 255 } + end + end + end end diff --git a/spec/helpers/sorting_helper_spec.rb b/spec/helpers/sorting_helper_spec.rb index cba0d93e144..f405268d198 100644 --- a/spec/helpers/sorting_helper_spec.rb +++ b/spec/helpers/sorting_helper_spec.rb @@ -21,7 +21,11 @@ describe SortingHelper do describe '#issuable_sort_direction_button' do before do - allow(self).to receive(:request).and_return(double(path: 'http://test.com', query_parameters: {})) + allow(self).to receive(:request).and_return(double(path: 'http://test.com', query_parameters: { label_name: 'test_label' })) + end + + it 'keeps label filter param' do + expect(issuable_sort_direction_button('created_date')).to include('label_name=test_label') end it 'returns icon with sort-highest when sort is created_date' do diff --git a/spec/javascripts/boards/boards_store_spec.js b/spec/javascripts/boards/boards_store_spec.js index 54f1edfb1f9..22f192bc7f3 100644 --- a/spec/javascripts/boards/boards_store_spec.js +++ b/spec/javascripts/boards/boards_store_spec.js @@ -65,6 +65,13 @@ describe('Store', () => { expect(list).toBeDefined(); }); + it('finds list by label ID', () => { + boardsStore.addList(listObj); + const list = boardsStore.findListByLabelId(listObj.label.id); + + expect(list.id).toBe(listObj.id); + }); + it('gets issue when new list added', done => { boardsStore.addList(listObj); const list = boardsStore.findList('id', listObj.id); diff --git a/spec/javascripts/boards/issue_spec.js b/spec/javascripts/boards/issue_spec.js index 437ab4bb3df..54fb0e8228b 100644 --- a/spec/javascripts/boards/issue_spec.js +++ b/spec/javascripts/boards/issue_spec.js @@ -55,15 +55,27 @@ describe('Issue model', () => { expect(issue.labels.length).toBe(2); }); - it('does not add existing label', () => { + it('does not add label if label id exists', () => { + issue.addLabel({ + id: 1, + title: 'test 2', + color: 'blue', + description: 'testing', + }); + + expect(issue.labels.length).toBe(1); + expect(issue.labels[0].color).toBe('red'); + }); + + it('adds other label with same title', () => { issue.addLabel({ id: 2, title: 'test', color: 'blue', - description: 'bugs!', + description: 'other test', }); - expect(issue.labels.length).toBe(1); + expect(issue.labels.length).toBe(2); }); it('finds label', () => { diff --git a/spec/javascripts/diffs/components/app_spec.js b/spec/javascripts/diffs/components/app_spec.js index 1e2f7ff4fd8..a2cbc0f3c72 100644 --- a/spec/javascripts/diffs/components/app_spec.js +++ b/spec/javascripts/diffs/components/app_spec.js @@ -1,33 +1,44 @@ -import Vue from 'vue'; -import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; +import Vuex from 'vuex'; +import { shallowMount, createLocalVue } from '@vue/test-utils'; import { TEST_HOST } from 'spec/test_constants'; import App from '~/diffs/components/app.vue'; +import NoChanges from '~/diffs/components/no_changes.vue'; +import DiffFile from '~/diffs/components/diff_file.vue'; import createDiffsStore from '../create_diffs_store'; describe('diffs/components/app', () => { const oldMrTabs = window.mrTabs; - const Component = Vue.extend(App); - + let store; let vm; - beforeEach(() => { - // setup globals (needed for component to mount :/) - window.mrTabs = jasmine.createSpyObj('mrTabs', ['resetViewContainer']); - window.mrTabs.expandViewContainer = jasmine.createSpy(); - window.location.hash = 'ABC_123'; + function createComponent(props = {}, extendStore = () => {}) { + const localVue = createLocalVue(); - // setup component - const store = createDiffsStore(); + localVue.use(Vuex); + + store = createDiffsStore(); store.state.diffs.isLoading = false; - vm = mountComponentWithStore(Component, { - store, - props: { + extendStore(store); + + vm = shallowMount(localVue.extend(App), { + localVue, + propsData: { endpoint: `${TEST_HOST}/diff/endpoint`, projectPath: 'namespace/project', currentUser: {}, + changesEmptyStateIllustration: '', + ...props, }, + store, }); + } + + beforeEach(() => { + // setup globals (needed for component to mount :/) + window.mrTabs = jasmine.createSpyObj('mrTabs', ['resetViewContainer']); + window.mrTabs.expandViewContainer = jasmine.createSpy(); + window.location.hash = 'ABC_123'; }); afterEach(() => { @@ -35,21 +46,53 @@ describe('diffs/components/app', () => { window.mrTabs = oldMrTabs; // reset component - vm.$destroy(); + vm.destroy(); }); it('does not show commit info', () => { - expect(vm.$el).not.toContainElement('.blob-commit-info'); + createComponent(); + + expect(vm.contains('.blob-commit-info')).toBe(false); }); it('sets highlighted row if hash exists in location object', done => { - vm.$props.shouldShow = true; - - vm.$nextTick() - .then(() => { - expect(vm.$store.state.diffs.highlightedRow).toBe('ABC_123'); - }) - .then(done) - .catch(done.fail); + createComponent({ + shouldShow: true, + }); + + // Component uses $nextTick so we wait until that has finished + setTimeout(() => { + expect(store.state.diffs.highlightedRow).toBe('ABC_123'); + + done(); + }); + }); + + describe('empty state', () => { + it('renders empty state when no diff files exist', () => { + createComponent(); + + expect(vm.contains(NoChanges)).toBe(true); + }); + + it('does not render empty state when diff files exist', () => { + createComponent({}, () => { + store.state.diffs.diffFiles.push({ + id: 1, + }); + }); + + expect(vm.contains(NoChanges)).toBe(false); + expect(vm.findAll(DiffFile).length).toBe(1); + }); + + it('does not render empty state when versions match', () => { + createComponent({}, () => { + store.state.diffs.startVersion = { version_index: 1 }; + store.state.diffs.mergeRequestDiff = { version_index: 1 }; + }); + + expect(vm.contains(NoChanges)).toBe(false); + }); }); }); diff --git a/spec/javascripts/diffs/components/no_changes_spec.js b/spec/javascripts/diffs/components/no_changes_spec.js index 7237274eb43..e45d34bf9d5 100644 --- a/spec/javascripts/diffs/components/no_changes_spec.js +++ b/spec/javascripts/diffs/components/no_changes_spec.js @@ -1 +1,40 @@ -// TODO: https://gitlab.com/gitlab-org/gitlab-ce/issues/48034 +import { createLocalVue, shallowMount } from '@vue/test-utils'; +import Vuex from 'vuex'; +import { createStore } from '~/mr_notes/stores'; +import NoChanges from '~/diffs/components/no_changes.vue'; + +describe('Diff no changes empty state', () => { + let vm; + + function createComponent(extendStore = () => {}) { + const localVue = createLocalVue(); + localVue.use(Vuex); + + const store = createStore(); + extendStore(store); + + vm = shallowMount(localVue.extend(NoChanges), { + localVue, + store, + propsData: { + changesEmptyStateIllustration: '', + }, + }); + } + + afterEach(() => { + vm.destroy(); + }); + + it('prevents XSS', () => { + createComponent(store => { + // eslint-disable-next-line no-param-reassign + store.state.notes.noteableData = { + source_branch: '<script>alert("test");</script>', + target_branch: '<script>alert("test");</script>', + }; + }); + + expect(vm.contains('script')).toBe(false); + }); +}); diff --git a/spec/javascripts/diffs/store/mutations_spec.js b/spec/javascripts/diffs/store/mutations_spec.js index 23e8761bc55..f3449bec6ec 100644 --- a/spec/javascripts/diffs/store/mutations_spec.js +++ b/spec/javascripts/diffs/store/mutations_spec.js @@ -277,6 +277,87 @@ describe('DiffsStoreMutations', () => { expect(state.diffFiles[0].highlighted_diff_lines[0].discussions[0].id).toEqual(1); }); + it('updates existing discussion', () => { + const diffPosition = { + base_sha: 'ed13df29948c41ba367caa757ab3ec4892509910', + head_sha: 'b921914f9a834ac47e6fd9420f78db0f83559130', + new_line: null, + new_path: '500-lines-4.txt', + old_line: 5, + old_path: '500-lines-4.txt', + start_sha: 'ed13df29948c41ba367caa757ab3ec4892509910', + }; + + const state = { + latestDiff: true, + diffFiles: [ + { + file_hash: 'ABC', + parallel_diff_lines: [ + { + left: { + line_code: 'ABC_1', + discussions: [], + }, + right: { + line_code: 'ABC_1', + discussions: [], + }, + }, + ], + highlighted_diff_lines: [ + { + line_code: 'ABC_1', + discussions: [], + }, + ], + }, + ], + }; + const discussion = { + id: 1, + line_code: 'ABC_1', + diff_discussion: true, + resolvable: true, + original_position: diffPosition, + position: diffPosition, + diff_file: { + file_hash: state.diffFiles[0].file_hash, + }, + }; + + const diffPositionByLineCode = { + ABC_1: diffPosition, + }; + + mutations[types.SET_LINE_DISCUSSIONS_FOR_FILE](state, { + discussion, + diffPositionByLineCode, + }); + + expect(state.diffFiles[0].parallel_diff_lines[0].left.discussions.length).toEqual(1); + expect(state.diffFiles[0].parallel_diff_lines[0].left.discussions[0].id).toEqual(1); + expect(state.diffFiles[0].parallel_diff_lines[0].right.discussions).toEqual([]); + + expect(state.diffFiles[0].highlighted_diff_lines[0].discussions.length).toEqual(1); + expect(state.diffFiles[0].highlighted_diff_lines[0].discussions[0].id).toEqual(1); + + mutations[types.SET_LINE_DISCUSSIONS_FOR_FILE](state, { + discussion: { + ...discussion, + resolved: true, + notes: ['test'], + }, + diffPositionByLineCode, + }); + + expect(state.diffFiles[0].parallel_diff_lines[0].left.discussions[0].notes.length).toBe(1); + expect(state.diffFiles[0].highlighted_diff_lines[0].discussions[0].notes.length).toBe(1); + + expect(state.diffFiles[0].parallel_diff_lines[0].left.discussions[0].resolved).toBe(true); + expect(state.diffFiles[0].highlighted_diff_lines[0].discussions[0].resolved).toBe(true); + }); + it('should add legacy discussions to the given line', () => { const diffPosition = { base_sha: 'ed13df29948c41ba367caa757ab3ec4892509910', @@ -356,10 +437,12 @@ describe('DiffsStoreMutations', () => { { id: 1, line_code: 'ABC_1', + notes: [], }, { id: 2, line_code: 'ABC_1', + notes: [], }, ], }, @@ -376,10 +459,12 @@ describe('DiffsStoreMutations', () => { { id: 1, line_code: 'ABC_1', + notes: [], }, { id: 2, line_code: 'ABC_1', + notes: [], }, ], }, diff --git a/spec/javascripts/filtered_search/dropdown_utils_spec.js b/spec/javascripts/filtered_search/dropdown_utils_spec.js index 6605b0a30d7..cfd0b96ec43 100644 --- a/spec/javascripts/filtered_search/dropdown_utils_spec.js +++ b/spec/javascripts/filtered_search/dropdown_utils_spec.js @@ -211,132 +211,6 @@ describe('Dropdown Utils', () => { }); }); - describe('mergeDuplicateLabels', () => { - const dataMap = { - label: { - title: 'label', - color: '#FFFFFF', - }, - }; - - it('should add label to dataMap if it is not a duplicate', () => { - const newLabel = { - title: 'new-label', - color: '#000000', - }; - - const updated = DropdownUtils.mergeDuplicateLabels(dataMap, newLabel); - - expect(updated[newLabel.title]).toEqual(newLabel); - }); - - it('should merge colors if label is a duplicate', () => { - const duplicate = { - title: 'label', - color: '#000000', - }; - - const updated = DropdownUtils.mergeDuplicateLabels(dataMap, duplicate); - - expect(updated.label.multipleColors).toEqual([dataMap.label.color, duplicate.color]); - }); - }); - - describe('duplicateLabelColor', () => { - it('should linear-gradient 2 colors', () => { - const gradient = DropdownUtils.duplicateLabelColor(['#FFFFFF', '#000000']); - - expect(gradient).toEqual( - 'linear-gradient(#FFFFFF 0%, #FFFFFF 50%, #000000 50%, #000000 100%)', - ); - }); - - it('should linear-gradient 3 colors', () => { - const gradient = DropdownUtils.duplicateLabelColor(['#FFFFFF', '#000000', '#333333']); - - expect(gradient).toEqual( - 'linear-gradient(#FFFFFF 0%, #FFFFFF 33%, #000000 33%, #000000 66%, #333333 66%, #333333 100%)', - ); - }); - - it('should linear-gradient 4 colors', () => { - const gradient = DropdownUtils.duplicateLabelColor([ - '#FFFFFF', - '#000000', - '#333333', - '#DDDDDD', - ]); - - expect(gradient).toEqual( - 'linear-gradient(#FFFFFF 0%, #FFFFFF 25%, #000000 25%, #000000 50%, #333333 50%, #333333 75%, #DDDDDD 75%, #DDDDDD 100%)', - ); - }); - - it('should not linear-gradient more than 4 colors', () => { - const gradient = DropdownUtils.duplicateLabelColor([ - '#FFFFFF', - '#000000', - '#333333', - '#DDDDDD', - '#EEEEEE', - ]); - - expect(gradient.indexOf('#EEEEEE')).toBe(-1); - }); - }); - - describe('duplicateLabelPreprocessing', () => { - it('should set preprocessed to true', () => { - const results = DropdownUtils.duplicateLabelPreprocessing([]); - - expect(results.preprocessed).toEqual(true); - }); - - it('should not mutate existing data if there are no duplicates', () => { - const data = [ - { - title: 'label1', - color: '#FFFFFF', - }, - { - title: 'label2', - color: '#000000', - }, - ]; - const results = DropdownUtils.duplicateLabelPreprocessing(data); - - expect(results.length).toEqual(2); - expect(results[0]).toEqual(data[0]); - expect(results[1]).toEqual(data[1]); - }); - - describe('duplicate labels', () => { - const data = [ - { - title: 'label', - color: '#FFFFFF', - }, - { - title: 'label', - color: '#000000', - }, - ]; - const results = DropdownUtils.duplicateLabelPreprocessing(data); - - it('should merge duplicate labels', () => { - expect(results.length).toEqual(1); - }); - - it('should convert multiple colored labels into linear-gradient', () => { - expect(results[0].color).toEqual(DropdownUtils.duplicateLabelColor(['#FFFFFF', '#000000'])); - }); - - it('should set multiple colored label text color to black', () => { - expect(results[0].text_color).toEqual('#000000'); - }); - }); - }); - describe('setDataValueIfSelected', () => { beforeEach(() => { spyOn(FilteredSearchDropdownManager, 'addWordToInput').and.callFake(() => {}); diff --git a/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js b/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js index 4f561df7943..9aa3cbaa231 100644 --- a/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js +++ b/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js @@ -909,16 +909,6 @@ describe('Filtered Search Visual Tokens', () => { expect(token.style.backgroundColor).not.toEqual(originalBackgroundColor); }); - it('should not set backgroundColor when it is a linear-gradient', () => { - const token = subject.setTokenStyle( - bugLabelToken, - 'linear-gradient(135deg, red, blue)', - 'white', - ); - - expect(token.style.backgroundColor).toEqual(bugLabelToken.style.backgroundColor); - }); - it('should set textColor', () => { const token = subject.setTokenStyle(bugLabelToken, 'white', 'black'); @@ -935,39 +925,6 @@ describe('Filtered Search Visual Tokens', () => { }); }); - describe('preprocessLabel', () => { - const endpoint = 'endpoint'; - - it('does not preprocess more than once', () => { - let labels = []; - - spyOn(DropdownUtils, 'duplicateLabelPreprocessing').and.callFake(() => []); - - labels = FilteredSearchVisualTokens.preprocessLabel(endpoint, labels); - FilteredSearchVisualTokens.preprocessLabel(endpoint, labels); - - expect(DropdownUtils.duplicateLabelPreprocessing.calls.count()).toEqual(1); - }); - - describe('not preprocessed before', () => { - it('returns preprocessed labels', () => { - let labels = []; - - expect(labels.preprocessed).not.toEqual(true); - labels = FilteredSearchVisualTokens.preprocessLabel(endpoint, labels); - - expect(labels.preprocessed).toEqual(true); - }); - - it('overrides AjaxCache with preprocessed results', () => { - spyOn(AjaxCache, 'override').and.callFake(() => {}); - FilteredSearchVisualTokens.preprocessLabel(endpoint, []); - - expect(AjaxCache.override.calls.count()).toEqual(1); - }); - }); - }); - describe('updateLabelTokenColor', () => { const jsonFixtureName = 'labels/project_labels.json'; const dummyEndpoint = '/dummy/endpoint'; diff --git a/spec/javascripts/notes/components/note_app_spec.js b/spec/javascripts/notes/components/note_app_spec.js index 0081f42c330..22bee049f9c 100644 --- a/spec/javascripts/notes/components/note_app_spec.js +++ b/spec/javascripts/notes/components/note_app_spec.js @@ -30,6 +30,8 @@ describe('note_app', () => { jasmine.addMatchers(vueMatchers); $('body').attr('data-page', 'projects:merge_requests:show'); + setFixtures('<div class="js-vue-notes-event"><div id="app"></div></div>'); + const IssueNotesApp = Vue.extend(notesApp); store = createStore(); @@ -43,6 +45,7 @@ describe('note_app', () => { return mountComponentWithStore(IssueNotesApp, { props, store, + el: document.getElementById('app'), }); }; }); @@ -283,4 +286,24 @@ describe('note_app', () => { }, 0); }); }); + + describe('emoji awards', () => { + it('dispatches toggleAward after toggleAward event', () => { + const toggleAwardEvent = new CustomEvent('toggleAward', { + detail: { + awardName: 'test', + noteId: 1, + }, + }); + + spyOn(vm.$store, 'dispatch'); + + vm.$el.parentElement.dispatchEvent(toggleAwardEvent); + + expect(vm.$store.dispatch).toHaveBeenCalledWith('toggleAward', { + awardName: 'test', + noteId: 1, + }); + }); + }); }); diff --git a/spec/javascripts/vue_shared/components/markdown/field_spec.js b/spec/javascripts/vue_shared/components/markdown/field_spec.js index abb17440c0e..79e0e756a7a 100644 --- a/spec/javascripts/vue_shared/components/markdown/field_spec.js +++ b/spec/javascripts/vue_shared/components/markdown/field_spec.js @@ -80,7 +80,7 @@ describe('Markdown field component', () => { previewLink.click(); Vue.nextTick(() => { - expect(vm.$el.querySelector('.md-preview').textContent.trim()).toContain('Loading...'); + expect(vm.$el.querySelector('.md-preview').textContent.trim()).toContain('Loading…'); done(); }); diff --git a/spec/lib/backup/repository_spec.rb b/spec/lib/backup/repository_spec.rb index fdeea814bb2..5ace5c5b1a2 100644 --- a/spec/lib/backup/repository_spec.rb +++ b/spec/lib/backup/repository_spec.rb @@ -67,6 +67,19 @@ describe Backup::Repository do end end end + + context 'restoring object pools' do + it 'schedules restoring of the pool' do + pool_repository = create(:pool_repository, :failed) + pool_repository.delete_object_pool + + subject.restore + + pool_repository.reload + expect(pool_repository).not_to be_failed + expect(pool_repository.object_pool.exists?).to be(true) + end + end end describe '#prepare_directories', :seed_helper do diff --git a/spec/lib/gitlab/ci/config/entry/except_policy_spec.rb b/spec/lib/gitlab/ci/config/entry/except_policy_spec.rb deleted file mode 100644 index d036bf2f4d1..00000000000 --- a/spec/lib/gitlab/ci/config/entry/except_policy_spec.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -describe Gitlab::Ci::Config::Entry::ExceptPolicy do - let(:entry) { described_class.new(config) } - - it_behaves_like 'correct only except policy' - - describe '.default' do - it 'does not have a default value' do - expect(described_class.default).to be_nil - end - end -end diff --git a/spec/lib/gitlab/ci/config/entry/global_spec.rb b/spec/lib/gitlab/ci/config/entry/global_spec.rb index 12f4b9dc624..61d78f86b51 100644 --- a/spec/lib/gitlab/ci/config/entry/global_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/global_spec.rb @@ -161,7 +161,8 @@ describe Gitlab::Ci::Config::Entry::Global do variables: { 'VAR' => 'value' }, ignore: false, after_script: ['make clean'], - only: { refs: %w[branches tags] } }, + only: { refs: %w[branches tags] }, + except: {} }, spinach: { name: :spinach, before_script: [], script: %w[spinach], @@ -173,7 +174,8 @@ describe Gitlab::Ci::Config::Entry::Global do variables: {}, ignore: false, after_script: ['make clean'], - only: { refs: %w[branches tags] } } + only: { refs: %w[branches tags] }, + except: {} } ) end end diff --git a/spec/lib/gitlab/ci/config/entry/job_spec.rb b/spec/lib/gitlab/ci/config/entry/job_spec.rb index c1f4a060063..8e32cede3b5 100644 --- a/spec/lib/gitlab/ci/config/entry/job_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb @@ -259,7 +259,8 @@ describe Gitlab::Ci::Config::Entry::Job do stage: 'test', ignore: false, after_script: %w[cleanup], - only: { refs: %w[branches tags] }) + only: { refs: %w[branches tags] }, + except: {}) end end end diff --git a/spec/lib/gitlab/ci/config/entry/jobs_spec.rb b/spec/lib/gitlab/ci/config/entry/jobs_spec.rb index 2a753408f54..1a2c30d3571 100644 --- a/spec/lib/gitlab/ci/config/entry/jobs_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/jobs_spec.rb @@ -68,13 +68,15 @@ describe Gitlab::Ci::Config::Entry::Jobs do commands: 'rspec', ignore: false, stage: 'test', - only: { refs: %w[branches tags] } }, + only: { refs: %w[branches tags] }, + except: {} }, spinach: { name: :spinach, script: %w[spinach], commands: 'spinach', ignore: false, stage: 'test', - only: { refs: %w[branches tags] } }) + only: { refs: %w[branches tags] }, + except: {} }) end end diff --git a/spec/lib/gitlab/ci/config/entry/only_policy_spec.rb b/spec/lib/gitlab/ci/config/entry/only_policy_spec.rb deleted file mode 100644 index 5518b68e51a..00000000000 --- a/spec/lib/gitlab/ci/config/entry/only_policy_spec.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -describe Gitlab::Ci::Config::Entry::OnlyPolicy do - let(:entry) { described_class.new(config) } - - it_behaves_like 'correct only except policy' - - describe '.default' do - it 'haa a default value' do - expect(described_class.default).to eq( { refs: %w[branches tags] } ) - end - end -end diff --git a/spec/lib/gitlab/ci/config/entry/policy_spec.rb b/spec/lib/gitlab/ci/config/entry/policy_spec.rb index cf40a22af2e..83001b7fdd8 100644 --- a/spec/lib/gitlab/ci/config/entry/policy_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/policy_spec.rb @@ -1,8 +1,173 @@ -require 'spec_helper' +require 'fast_spec_helper' +require_dependency 'active_model' describe Gitlab::Ci::Config::Entry::Policy do let(:entry) { described_class.new(config) } + context 'when using simplified policy' do + describe 'validations' do + context 'when entry config value is valid' do + context 'when config is a branch or tag name' do + let(:config) { %w[master feature/branch] } + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + end + + describe '#value' do + it 'returns refs hash' do + expect(entry.value).to eq(refs: config) + end + end + end + + context 'when config is a regexp' do + let(:config) { ['/^issue-.*$/'] } + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + end + end + + context 'when config is a special keyword' do + let(:config) { %w[tags triggers branches] } + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + end + end + end + + context 'when entry value is not valid' do + let(:config) { [1] } + + describe '#errors' do + it 'saves errors' do + expect(entry.errors) + .to include /policy config should be an array of strings or regexps/ + end + end + end + end + end + + context 'when using complex policy' do + context 'when specifying refs policy' do + let(:config) { { refs: ['master'] } } + + it 'is a correct configuraton' do + expect(entry).to be_valid + expect(entry.value).to eq(refs: %w[master]) + end + end + + context 'when specifying kubernetes policy' do + let(:config) { { kubernetes: 'active' } } + + it 'is a correct configuraton' do + expect(entry).to be_valid + expect(entry.value).to eq(kubernetes: 'active') + end + end + + context 'when specifying invalid kubernetes policy' do + let(:config) { { kubernetes: 'something' } } + + it 'reports an error about invalid policy' do + expect(entry.errors).to include /unknown value: something/ + end + end + + context 'when specifying valid variables expressions policy' do + let(:config) { { variables: ['$VAR == null'] } } + + it 'is a correct configuraton' do + expect(entry).to be_valid + expect(entry.value).to eq(config) + end + end + + context 'when specifying variables expressions in invalid format' do + let(:config) { { variables: '$MY_VAR' } } + + it 'reports an error about invalid format' do + expect(entry.errors).to include /should be an array of strings/ + end + end + + context 'when specifying invalid variables expressions statement' do + let(:config) { { variables: ['$MY_VAR =='] } } + + it 'reports an error about invalid statement' do + expect(entry.errors).to include /invalid expression syntax/ + end + end + + context 'when specifying invalid variables expressions token' do + let(:config) { { variables: ['$MY_VAR == 123'] } } + + it 'reports an error about invalid expression' do + expect(entry.errors).to include /invalid expression syntax/ + end + end + + context 'when using invalid variables expressions regexp' do + let(:config) { { variables: ['$MY_VAR =~ /some ( thing/'] } } + + it 'reports an error about invalid expression' do + expect(entry.errors).to include /invalid expression syntax/ + end + end + + context 'when specifying a valid changes policy' do + let(:config) { { changes: %w[some/* paths/**/*.rb] } } + + it 'is a correct configuraton' do + expect(entry).to be_valid + expect(entry.value).to eq(config) + end + end + + context 'when changes policy is invalid' do + let(:config) { { changes: [1, 2] } } + + it 'returns errors' do + expect(entry.errors).to include /changes should be an array of strings/ + end + end + + context 'when specifying unknown policy' do + let(:config) { { refs: ['master'], invalid: :something } } + + it 'returns error about invalid key' do + expect(entry.errors).to include /unknown keys: invalid/ + end + end + + context 'when policy is empty' do + let(:config) { {} } + + it 'is not a valid configuration' do + expect(entry.errors).to include /can't be blank/ + end + end + end + + context 'when policy strategy does not match' do + let(:config) { 'string strategy' } + + it 'returns information about errors' do + expect(entry.errors) + .to include /has to be either an array of conditions or a hash/ + end + end + describe '.default' do it 'does not have a default value' do expect(described_class.default).to be_nil diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb index b3f55a2e1bd..7213eee5675 100644 --- a/spec/lib/gitlab/workhorse_spec.rb +++ b/spec/lib/gitlab/workhorse_spec.rb @@ -246,7 +246,6 @@ describe Gitlab::Workhorse do GL_ID: "user-#{user.id}", GL_USERNAME: user.username, GL_REPOSITORY: "project-#{project.id}", - RepoPath: repo_path, ShowAllRefs: false } end @@ -261,7 +260,6 @@ describe Gitlab::Workhorse do GL_ID: "user-#{user.id}", GL_USERNAME: user.username, GL_REPOSITORY: "wiki-#{project.id}", - RepoPath: repo_path, ShowAllRefs: false } end diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index 1d17aec0ded..f6e5c9d33ac 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -4,6 +4,7 @@ require 'email_spec' describe Notify do include EmailSpec::Helpers include EmailSpec::Matchers + include EmailHelpers include RepoHelpers include_context 'gitlab email notification' @@ -27,15 +28,6 @@ describe Notify do description: 'My awesome description!') end - def have_referable_subject(referable, reply: false) - prefix = (referable.project ? "#{referable.project.name} | " : '').freeze - prefix = "Re: #{prefix}" if reply - - suffix = "#{referable.title} (#{referable.to_reference})" - - have_subject [prefix, suffix].compact.join - end - context 'for a project' do shared_examples 'an assignee email' do it 'is sent to the assignee as the author' do diff --git a/spec/models/ci/bridge_spec.rb b/spec/models/ci/bridge_spec.rb new file mode 100644 index 00000000000..741cdfef1a5 --- /dev/null +++ b/spec/models/ci/bridge_spec.rb @@ -0,0 +1,25 @@ +require 'spec_helper' + +describe Ci::Bridge do + set(:project) { create(:project) } + set(:pipeline) { create(:ci_pipeline, project: project) } + + let(:bridge) do + create(:ci_bridge, pipeline: pipeline) + end + + describe '#tags' do + it 'only has a bridge tag' do + expect(bridge.tags).to eq [:bridge] + end + end + + describe '#detailed_status' do + let(:user) { create(:user) } + let(:status) { bridge.detailed_status(user) } + + it 'returns detailed status object' do + expect(status).to be_a Gitlab::Ci::Status::Success + end + end +end diff --git a/spec/models/project_import_data_spec.rb b/spec/models/project_import_data_spec.rb new file mode 100644 index 00000000000..e9910c0a5d1 --- /dev/null +++ b/spec/models/project_import_data_spec.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe ProjectImportData do + describe '#merge_data' do + it 'writes the Hash to the attribute if it is nil' do + row = described_class.new + + row.merge_data('number' => 10) + + expect(row.data).to eq({ 'number' => 10 }) + end + + it 'merges the Hash into an existing Hash if one was present' do + row = described_class.new(data: { 'number' => 10 }) + + row.merge_data('foo' => 'bar') + + expect(row.data).to eq({ 'number' => 10, 'foo' => 'bar' }) + end + end + + describe '#merge_credentials' do + it 'writes the Hash to the attribute if it is nil' do + row = described_class.new + + row.merge_credentials('number' => 10) + + expect(row.credentials).to eq({ 'number' => 10 }) + end + + it 'merges the Hash into an existing Hash if one was present' do + row = described_class.new + + row.credentials = { 'number' => 10 } + row.merge_credentials('foo' => 'bar') + + expect(row.credentials).to eq({ 'number' => 10, 'foo' => 'bar' }) + end + end +end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 9e5b06b745a..5e63f14b720 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -4102,6 +4102,29 @@ describe Project do end end + describe '#object_pool_params' do + let(:project) { create(:project, :repository, :public) } + + subject { project.object_pool_params } + + before do + stub_application_setting(hashed_storage_enabled: true) + end + + context 'when the objects cannot be pooled' do + let(:project) { create(:project, :repository, :private) } + + it { is_expected.to be_empty } + end + + context 'when a pool is created' do + it 'returns that pool repository' do + expect(subject).not_to be_empty + expect(subject[:pool_repository]).to be_persisted + end + end + end + describe '#git_objects_poolable?' do subject { project } diff --git a/spec/models/remote_mirror_spec.rb b/spec/models/remote_mirror_spec.rb index b12ca79847c..5d3c25062d5 100644 --- a/spec/models/remote_mirror_spec.rb +++ b/spec/models/remote_mirror_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -describe RemoteMirror do +describe RemoteMirror, :mailer do include GitHelpers describe 'URL validation' do @@ -137,6 +137,43 @@ describe RemoteMirror do end end + describe '#mark_as_failed' do + let(:remote_mirror) { create(:remote_mirror) } + let(:error_message) { 'http://user:pass@test.com/root/repoC.git/' } + let(:sanitized_error_message) { 'http://*****:*****@test.com/root/repoC.git/' } + + subject do + remote_mirror.update_start + remote_mirror.mark_as_failed(error_message) + end + + it 'sets the update_status to failed' do + subject + + expect(remote_mirror.reload.update_status).to eq('failed') + end + + it 'saves the sanitized error' do + subject + + expect(remote_mirror.last_error).to eq(sanitized_error_message) + end + + context 'notifications' do + let(:user) { create(:user) } + + before do + remote_mirror.project.add_maintainer(user) + end + + it 'notifies the project maintainers' do + perform_enqueued_jobs { subject } + + should_email(user) + end + end + end + context 'when remote mirror gets destroyed' do it 'removes remote' do mirror = create_mirror(url: 'http://foo:bar@test.com') diff --git a/spec/rubocop/cop/migration/add_timestamps_spec.rb b/spec/rubocop/cop/migration/add_timestamps_spec.rb index 3a41c91add2..fae0177d5f5 100644 --- a/spec/rubocop/cop/migration/add_timestamps_spec.rb +++ b/spec/rubocop/cop/migration/add_timestamps_spec.rb @@ -11,7 +11,7 @@ describe RuboCop::Cop::Migration::AddTimestamps do subject(:cop) { described_class.new } let(:migration_with_add_timestamps) do %q( - class Users < ActiveRecord::Migration + class Users < ActiveRecord::Migration[4.2] DOWNTIME = false def change @@ -24,7 +24,7 @@ describe RuboCop::Cop::Migration::AddTimestamps do let(:migration_without_add_timestamps) do %q( - class Users < ActiveRecord::Migration + class Users < ActiveRecord::Migration[4.2] DOWNTIME = false def change @@ -36,7 +36,7 @@ describe RuboCop::Cop::Migration::AddTimestamps do let(:migration_with_add_timestamps_with_timezone) do %q( - class Users < ActiveRecord::Migration + class Users < ActiveRecord::Migration[4.2] DOWNTIME = false def change diff --git a/spec/rubocop/cop/migration/datetime_spec.rb b/spec/rubocop/cop/migration/datetime_spec.rb index 9e844325371..f2d9483d8d3 100644 --- a/spec/rubocop/cop/migration/datetime_spec.rb +++ b/spec/rubocop/cop/migration/datetime_spec.rb @@ -12,7 +12,7 @@ describe RuboCop::Cop::Migration::Datetime do let(:migration_with_datetime) do %q( - class Users < ActiveRecord::Migration + class Users < ActiveRecord::Migration[4.2] DOWNTIME = false def change @@ -25,7 +25,7 @@ describe RuboCop::Cop::Migration::Datetime do let(:migration_with_timestamp) do %q( - class Users < ActiveRecord::Migration + class Users < ActiveRecord::Migration[4.2] DOWNTIME = false def change @@ -38,7 +38,7 @@ describe RuboCop::Cop::Migration::Datetime do let(:migration_without_datetime) do %q( - class Users < ActiveRecord::Migration + class Users < ActiveRecord::Migration[4.2] DOWNTIME = false def change @@ -50,7 +50,7 @@ describe RuboCop::Cop::Migration::Datetime do let(:migration_with_datetime_with_timezone) do %q( - class Users < ActiveRecord::Migration + class Users < ActiveRecord::Migration[4.2] DOWNTIME = false def change diff --git a/spec/rubocop/cop/migration/timestamps_spec.rb b/spec/rubocop/cop/migration/timestamps_spec.rb index 685bdb21803..1812818692a 100644 --- a/spec/rubocop/cop/migration/timestamps_spec.rb +++ b/spec/rubocop/cop/migration/timestamps_spec.rb @@ -11,7 +11,7 @@ describe RuboCop::Cop::Migration::Timestamps do subject(:cop) { described_class.new } let(:migration_with_timestamps) do %q( - class Users < ActiveRecord::Migration + class Users < ActiveRecord::Migration[4.2] DOWNTIME = false def change @@ -27,7 +27,7 @@ describe RuboCop::Cop::Migration::Timestamps do let(:migration_without_timestamps) do %q( - class Users < ActiveRecord::Migration + class Users < ActiveRecord::Migration[4.2] DOWNTIME = false def change @@ -42,7 +42,7 @@ describe RuboCop::Cop::Migration::Timestamps do let(:migration_with_timestamps_with_timezone) do %q( - class Users < ActiveRecord::Migration + class Users < ActiveRecord::Migration[4.2] DOWNTIME = false def change diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb index 8b8021ecbc8..ffa47d527f7 100644 --- a/spec/services/ci/create_pipeline_service_spec.rb +++ b/spec/services/ci/create_pipeline_service_spec.rb @@ -840,6 +840,37 @@ describe Ci::CreatePipelineService do end end + context "when config uses variables for only keyword" do + let(:config) do + { + build: { + stage: 'build', + script: 'echo', + only: { + variables: %w($CI) + } + } + } + end + + context 'when merge request is specified' do + let(:merge_request) do + create(:merge_request, + source_project: project, + source_branch: ref_name, + target_project: project, + target_branch: 'master') + end + + it 'does not create a merge request pipeline' do + expect(pipeline).not_to be_persisted + + expect(pipeline.errors[:base]) + .to eq(['No stages / jobs for this pipeline.']) + end + end + end + context "when config has 'except: [tags]'" do let(:config) do { diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index 0f6c2604984..68ac3a00ab0 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -2167,6 +2167,39 @@ describe NotificationService, :mailer do end end + context 'Remote mirror notifications' do + describe '#remote_mirror_update_failed' do + let(:project) { create(:project) } + let(:remote_mirror) { create(:remote_mirror, project: project) } + let(:u_blocked) { create(:user, :blocked) } + let(:u_silence) { create_user_with_notification(:disabled, 'silent-maintainer', project) } + let(:u_owner) { project.owner } + let(:u_maintainer1) { create(:user) } + let(:u_maintainer2) { create(:user) } + let(:u_developer) { create(:user) } + + before do + project.add_maintainer(u_blocked) + project.add_maintainer(u_silence) + project.add_maintainer(u_maintainer1) + project.add_maintainer(u_maintainer2) + project.add_developer(u_developer) + + # Mock remote update + allow(project.repository).to receive(:async_remove_remote) + allow(project.repository).to receive(:add_remote) + + reset_delivered_emails! + end + + it 'emails current watching maintainers' do + notification.remote_mirror_update_failed(remote_mirror) + + should_only_email(u_maintainer1, u_maintainer2, u_owner) + end + end + end + def build_team(project) @u_watcher = create_global_setting_for(create(:user), :watch) @u_participating = create_global_setting_for(create(:user), :participating) diff --git a/spec/support/helpers/email_helpers.rb b/spec/support/helpers/email_helpers.rb index 1fb8252459f..ad6e1064499 100644 --- a/spec/support/helpers/email_helpers.rb +++ b/spec/support/helpers/email_helpers.rb @@ -34,4 +34,13 @@ module EmailHelpers def find_email_for(user) ActionMailer::Base.deliveries.find { |d| d.to.include?(user.notification_email) } end + + def have_referable_subject(referable, include_project: true, reply: false) + prefix = (include_project && referable.project ? "#{referable.project.name} | " : '').freeze + prefix = "Re: #{prefix}" if reply + + suffix = "#{referable.title} (#{referable.to_reference})" + + have_subject [prefix, suffix].compact.join + end end diff --git a/spec/support/helpers/fake_migration_classes.rb b/spec/support/helpers/fake_migration_classes.rb index b0fc8422857..c7766df7a52 100644 --- a/spec/support/helpers/fake_migration_classes.rb +++ b/spec/support/helpers/fake_migration_classes.rb @@ -1,4 +1,4 @@ -class FakeRenameReservedPathMigrationV1 < ActiveRecord::Migration +class FakeRenameReservedPathMigrationV1 < ActiveRecord::Migration[4.2] include Gitlab::Database::RenameReservedPathsMigration::V1 def version diff --git a/spec/support/shared_examples/controllers/set_sort_order_from_user_preference_shared_examples.rb b/spec/support/shared_examples/controllers/set_sort_order_from_user_preference_shared_examples.rb new file mode 100644 index 00000000000..b34948be670 --- /dev/null +++ b/spec/support/shared_examples/controllers/set_sort_order_from_user_preference_shared_examples.rb @@ -0,0 +1,32 @@ +shared_examples 'set sort order from user preference' do + describe '#set_sort_order_from_user_preference' do + # There is no issuable_sorting_field defined in any CE controllers yet, + # however any other field present in user_preferences table can be used for testing. + let(:sorting_field) { :issue_notes_filter } + let(:sorting_param) { 'any' } + + before do + allow(controller).to receive(:issuable_sorting_field).and_return(sorting_field) + end + + context 'when database is in read-only mode' do + it 'it does not update user preference' do + allow(Gitlab::Database).to receive(:read_only?).and_return(true) + + expect_any_instance_of(UserPreference).not_to receive(:update_attribute).with(sorting_field, sorting_param) + + get :index, namespace_id: project.namespace, project_id: project, sort: sorting_param + end + end + + context 'when database is not in read-only mode' do + it 'updates user preference' do + allow(Gitlab::Database).to receive(:read_only?).and_return(false) + + expect_any_instance_of(UserPreference).to receive(:update_attribute).with(sorting_field, sorting_param) + + get :index, namespace_id: project.namespace, project_id: project, sort: sorting_param + end + end + end +end diff --git a/spec/support/shared_examples/notify_shared_examples.rb b/spec/support/shared_examples/notify_shared_examples.rb index 66536e80db2..a38354060cf 100644 --- a/spec/support/shared_examples/notify_shared_examples.rb +++ b/spec/support/shared_examples/notify_shared_examples.rb @@ -1,5 +1,5 @@ shared_context 'gitlab email notification' do - set(:project) { create(:project, :repository) } + set(:project) { create(:project, :repository, name: 'a-known-name') } set(:recipient) { create(:user, email: 'recipient@example.com') } let(:gitlab_sender_display_name) { Gitlab.config.gitlab.email_display_name } @@ -62,9 +62,11 @@ end shared_examples 'an email with X-GitLab headers containing project details' do it 'has X-GitLab-Project headers' do aggregate_failures do + full_path_as_domain = "#{project.name}.#{project.namespace.path}" is_expected.to have_header('X-GitLab-Project', /#{project.name}/) is_expected.to have_header('X-GitLab-Project-Id', /#{project.id}/) is_expected.to have_header('X-GitLab-Project-Path', /#{project.full_path}/) + is_expected.to have_header('List-Id', "#{project.full_path} <#{project.id}.#{full_path_as_domain}.#{Gitlab.config.gitlab.host}>") end end end diff --git a/spec/support/shared_examples/only_except_policy_examples.rb b/spec/support/shared_examples/only_except_policy_examples.rb deleted file mode 100644 index 35240af1d74..00000000000 --- a/spec/support/shared_examples/only_except_policy_examples.rb +++ /dev/null @@ -1,167 +0,0 @@ -# frozen_string_literal: true - -shared_examples 'correct only except policy' do - context 'when using simplified policy' do - describe 'validations' do - context 'when entry config value is valid' do - context 'when config is a branch or tag name' do - let(:config) { %w[master feature/branch] } - - describe '#valid?' do - it 'is valid' do - expect(entry).to be_valid - end - end - - describe '#value' do - it 'returns refs hash' do - expect(entry.value).to eq(refs: config) - end - end - end - - context 'when config is a regexp' do - let(:config) { ['/^issue-.*$/'] } - - describe '#valid?' do - it 'is valid' do - expect(entry).to be_valid - end - end - end - - context 'when config is a special keyword' do - let(:config) { %w[tags triggers branches] } - - describe '#valid?' do - it 'is valid' do - expect(entry).to be_valid - end - end - end - end - - context 'when entry value is not valid' do - let(:config) { [1] } - - describe '#errors' do - it 'saves errors' do - expect(entry.errors) - .to include /policy config should be an array of strings or regexps/ - end - end - end - end - end - - context 'when using complex policy' do - context 'when specifying refs policy' do - let(:config) { { refs: ['master'] } } - - it 'is a correct configuraton' do - expect(entry).to be_valid - expect(entry.value).to eq(refs: %w[master]) - end - end - - context 'when specifying kubernetes policy' do - let(:config) { { kubernetes: 'active' } } - - it 'is a correct configuraton' do - expect(entry).to be_valid - expect(entry.value).to eq(kubernetes: 'active') - end - end - - context 'when specifying invalid kubernetes policy' do - let(:config) { { kubernetes: 'something' } } - - it 'reports an error about invalid policy' do - expect(entry.errors).to include /unknown value: something/ - end - end - - context 'when specifying valid variables expressions policy' do - let(:config) { { variables: ['$VAR == null'] } } - - it 'is a correct configuraton' do - expect(entry).to be_valid - expect(entry.value).to eq(config) - end - end - - context 'when specifying variables expressions in invalid format' do - let(:config) { { variables: '$MY_VAR' } } - - it 'reports an error about invalid format' do - expect(entry.errors).to include /should be an array of strings/ - end - end - - context 'when specifying invalid variables expressions statement' do - let(:config) { { variables: ['$MY_VAR =='] } } - - it 'reports an error about invalid statement' do - expect(entry.errors).to include /invalid expression syntax/ - end - end - - context 'when specifying invalid variables expressions token' do - let(:config) { { variables: ['$MY_VAR == 123'] } } - - it 'reports an error about invalid expression' do - expect(entry.errors).to include /invalid expression syntax/ - end - end - - context 'when using invalid variables expressions regexp' do - let(:config) { { variables: ['$MY_VAR =~ /some ( thing/'] } } - - it 'reports an error about invalid expression' do - expect(entry.errors).to include /invalid expression syntax/ - end - end - - context 'when specifying a valid changes policy' do - let(:config) { { changes: %w[some/* paths/**/*.rb] } } - - it 'is a correct configuraton' do - expect(entry).to be_valid - expect(entry.value).to eq(config) - end - end - - context 'when changes policy is invalid' do - let(:config) { { changes: [1, 2] } } - - it 'returns errors' do - expect(entry.errors).to include /changes should be an array of strings/ - end - end - - context 'when specifying unknown policy' do - let(:config) { { refs: ['master'], invalid: :something } } - - it 'returns error about invalid key' do - expect(entry.errors).to include /unknown keys: invalid/ - end - end - - context 'when policy is empty' do - let(:config) { {} } - - it 'is not a valid configuration' do - expect(entry.errors).to include /can't be blank/ - end - end - end - - context 'when policy strategy does not match' do - let(:config) { 'string strategy' } - - it 'returns information about errors' do - expect(entry.errors) - .to include /has to be either an array of conditions or a hash/ - end - end -end diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb index 8c4360d4cf0..3b8f7f5fe7d 100644 --- a/spec/tasks/gitlab/backup_rake_spec.rb +++ b/spec/tasks/gitlab/backup_rake_spec.rb @@ -74,6 +74,7 @@ describe 'gitlab:app namespace rake task' do it 'invokes restoration on match' do allow(YAML).to receive(:load_file) .and_return({ gitlab_version: gitlab_version }) + expect(Rake::Task['gitlab:db:drop_tables']).to receive(:invoke) expect(Rake::Task['gitlab:backup:db:restore']).to receive(:invoke) expect(Rake::Task['gitlab:backup:repo:restore']).to receive(:invoke) diff --git a/spec/workers/repository_update_remote_mirror_worker_spec.rb b/spec/workers/repository_update_remote_mirror_worker_spec.rb index 4f1ad2474f5..d73b0b53713 100644 --- a/spec/workers/repository_update_remote_mirror_worker_spec.rb +++ b/spec/workers/repository_update_remote_mirror_worker_spec.rb @@ -25,12 +25,19 @@ describe RepositoryUpdateRemoteMirrorWorker do it 'sets status as failed when update remote mirror service executes with errors' do error_message = 'fail!' - expect_any_instance_of(Projects::UpdateRemoteMirrorService).to receive(:execute).with(remote_mirror).and_return(status: :error, message: error_message) + expect_next_instance_of(Projects::UpdateRemoteMirrorService) do |service| + expect(service).to receive(:execute).with(remote_mirror).and_return(status: :error, message: error_message) + end + + # Mock the finder so that it returns an object we can set expectations on + expect_next_instance_of(RemoteMirrorFinder) do |finder| + expect(finder).to receive(:execute).and_return(remote_mirror) + end + expect(remote_mirror).to receive(:mark_as_failed).with(error_message) + expect do subject.perform(remote_mirror.id, Time.now) end.to raise_error(RepositoryUpdateRemoteMirrorWorker::UpdateError, error_message) - - expect(remote_mirror.reload.update_status).to eq('failed') end it 'does nothing if last_update_started_at is higher than the time the job was scheduled in' do diff --git a/yarn.lock b/yarn.lock index 1d10b9d5403..a6b43f785dc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -623,23 +623,23 @@ dependencies: bootstrap "4.1.3" -"@gitlab/eslint-config@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@gitlab/eslint-config/-/eslint-config-1.2.0.tgz#115568a70edabbc024f1bc13ba1ba499a9ba05a9" - integrity sha512-TnZO5T7JjLQjw30aIGtKIsAX4pRnSbqOir3Ji5zPwtCVWY53DnG6Lcesgy7WYdsnnkt3oQPXFTOZlkymUs2PsA== +"@gitlab/eslint-config@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@gitlab/eslint-config/-/eslint-config-1.4.0.tgz#2e59e55a7cd024e3a450d2a896060ec4d763a5dc" + integrity sha512-nkecTWRNS/KD9q5lHFSc3J6zO/g1/OV9DaKiay+0nLjnGO9jQVRArRIYpnzgbUz2p15jOMVToVafW0YbbHZkwg== dependencies: babel-eslint "^10.0.1" eslint-config-airbnb-base "^13.1.0" - eslint-config-prettier "^3.1.0" + eslint-config-prettier "^3.3.0" eslint-plugin-filenames "^1.3.2" eslint-plugin-import "^2.14.0" eslint-plugin-promise "^4.0.1" - eslint-plugin-vue "^5.0.0-beta.3" + eslint-plugin-vue "^5.0.0" -"@gitlab/svgs@^1.40.0": - version "1.41.0" - resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.41.0.tgz#f80e3a0e259f3550af00685556ea925e471276d3" - integrity sha512-tKUXyqe54efWBsjQBUcvNF0AvqmE2NI2No3Bnix/gKDRImzIlcgIkM67Y8zoJv1D0w4CO87WcaG5GLpIFIT1Pg== +"@gitlab/svgs@^1.42.0": + version "1.42.0" + resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.42.0.tgz#54eb88606bb79b74373a3aa49d8c10557fb1fd7a" + integrity sha512-mVm1kyV/M1fTbQcW8Edbk7BPT2syQf+ot9qwFzLFiFXAn3jXTi6xy+DS+0cgoTnglSUsXVl4qcVAQjt8YoOOOQ== "@gitlab/ui@^1.15.0": version "1.15.0" @@ -706,6 +706,16 @@ resolved "https://registry.yarnpkg.com/@types/semver/-/semver-5.5.0.tgz#146c2a29ee7d3bae4bf2fcb274636e264c813c45" integrity sha512-41qEJgBH/TWgo5NFSvBCJ1qkoi3Q6ONSF2avrHq1LVEZfYpdHmj0y9SuTK+u9ZhG1sYQKBL1AWXKyLWP4RaUoQ== +"@types/strip-bom@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/strip-bom/-/strip-bom-3.0.0.tgz#14a8ec3956c2e81edb7520790aecf21c290aebd2" + integrity sha1-FKjsOVbC6B7bdSB5CuzyHCkK69I= + +"@types/strip-json-comments@0.0.30": + version "0.0.30" + resolved "https://registry.yarnpkg.com/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz#9aa30c04db212a9a0649d6ae6fd50accc40748a1" + integrity sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ== + "@types/zen-observable@^0.8.0": version "0.8.0" resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.8.0.tgz#8b63ab7f1aa5321248aad5ac890a485656dcea4d" @@ -930,6 +940,11 @@ acorn-jsx@^4.1.1: dependencies: acorn "^5.0.3" +acorn-jsx@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.0.1.tgz#32a064fd925429216a09b141102bfdd185fae40e" + integrity sha512-HJ7CfNHrfJLlNTzIEUTj43LNWGkqpRLxm3YjAlcD0ACydk9XynzYsCBHxut+iqt+1aBXkx9UP/w/ZqMr13XIzg== + acorn-walk@^6.0.1: version "6.1.1" resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-6.1.1.tgz#d363b66f5fac5f018ff9c3a1e7b6f8e310cc3913" @@ -940,7 +955,7 @@ acorn@^5.0.0, acorn@^5.0.3, acorn@^5.5.3, acorn@^5.6.0, acorn@^5.6.2, acorn@^5.7 resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.3.tgz#67aa231bf8812974b85235a96771eb6bd07ea279" integrity sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw== -acorn@^6.0.1: +acorn@^6.0.1, acorn@^6.0.2: version "6.0.4" resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.0.4.tgz#77377e7353b72ec5104550aa2d2097a2fd40b754" integrity sha512-VY4i5EKSKkofY2I+6QLTbTTN/UvEQPCo6eiwzzSaSWfpaDhOmStMCMod6wmuPciNq+XS0faCglFu2lHZpdHUtg== @@ -955,12 +970,12 @@ ajv-errors@^1.0.0: resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.0.tgz#ecf021fa108fd17dfb5e6b383f2dd233e31ffc59" integrity sha1-7PAh+hCP0X37Xms4Py3SM+Mf/Fk= -ajv-keywords@^3.0.0, ajv-keywords@^3.1.0: +ajv-keywords@^3.1.0: version "3.2.0" resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.2.0.tgz#e86b819c602cf8821ad637413698f1dec021847a" integrity sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo= -ajv@^6.0.1, ajv@^6.1.0, ajv@^6.5.3: +ajv@^6.1.0, ajv@^6.5.3: version "6.5.3" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.5.3.tgz#71a569d189ecf4f4f321224fecb166f071dd90f9" integrity sha512-LqZ9wY+fx3UMiiPd741yB2pj3hhil+hQc8taf4o2QGRFpWgZ2V5C8HA165DY9sS3fJwsk7uT7ZlFEyC3Ig3lLg== @@ -980,6 +995,16 @@ ajv@^6.5.5: json-schema-traverse "^0.4.1" uri-js "^4.2.2" +ajv@^6.6.1: + version "6.6.1" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.6.1.tgz#6360f5ed0d80f232cc2b294c362d5dc2e538dd61" + integrity sha512-ZoJjft5B+EJBjUyu9C9Hc0OZyPZSSlOF+plzouTrg6UlA8f+e/n8NIgBFG/9tppJtpPWfthHakK7juJdNDODww== + dependencies: + fast-deep-equal "^2.0.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + amdefine@>=0.0.4: version "1.0.1" resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" @@ -1317,7 +1342,7 @@ asynckit@^0.4.0: resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= -atob@^2.0.0: +atob@^2.0.0, atob@^2.1.1: version "2.1.2" resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== @@ -1491,7 +1516,7 @@ babel-plugin-syntax-object-rest-spread@^6.13.0: resolved "https://registry.yarnpkg.com/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5" integrity sha1-/WU28rzhODb/o6VFjEkDpZe7O/U= -babel-plugin-transform-es2015-modules-commonjs@^6.26.2: +babel-plugin-transform-es2015-modules-commonjs@^6.26.0, babel-plugin-transform-es2015-modules-commonjs@^6.26.2: version "6.26.2" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz#58a793863a9e7ca870bdc5a881117ffac27db6f3" integrity sha512-CV9ROOHEdrjcwhIaJNBGMBCodN+1cfkwtM1SbUHmvyy35KGT7fohbpOxkE2uLz1o6odKK2Ck/tz47z+VqQfi9Q== @@ -2203,6 +2228,11 @@ clone-response@1.0.2: dependencies: mimic-response "^1.0.0" +clone@2.x: + version "2.1.2" + resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" + integrity sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18= + co@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" @@ -2358,7 +2388,7 @@ concat-stream@^1.5.0: readable-stream "^2.2.2" typedarray "^0.0.6" -config-chain@~1.1.5: +config-chain@^1.1.12, config-chain@~1.1.5: version "1.1.12" resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.12.tgz#0fde8d091200eb5e808caf25fe618c02f48e4efa" integrity sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA== @@ -2611,6 +2641,16 @@ css-selector-tokenizer@^0.7.0: fastparse "^1.1.1" regexpu-core "^1.0.0" +css@^2.1.0: + version "2.2.4" + resolved "https://registry.yarnpkg.com/css/-/css-2.2.4.tgz#c646755c73971f2bba6a601e2cf2fd71b1298929" + integrity sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw== + dependencies: + inherits "^2.0.3" + source-map "^0.6.1" + source-map-resolve "^0.5.2" + urix "^0.1.0" + cssesc@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-0.1.0.tgz#c814903e45623371a0477b40109aaafbeeaddbb4" @@ -2945,6 +2985,13 @@ debug@^3.1.0, debug@^3.2.5: dependencies: ms "^2.1.1" +debug@^4.0.1, debug@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.0.tgz#373687bffa678b38b1cd91f861b63850035ddc87" + integrity sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg== + dependencies: + ms "^2.1.1" + debug@~3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" @@ -3298,7 +3345,7 @@ editions@^1.3.3: resolved "https://registry.yarnpkg.com/editions/-/editions-1.3.4.tgz#3662cb592347c3168eb8e498a0ff73271d67f50b" integrity sha512-gzao+mxnYDzIysXKMQi/+M1mjy/rjestjg6OPoYTtI+3Izp23oiGZitsl9lPDPiTGXbcSIk1iJWhliSaglxnUg== -editorconfig@^0.15.0: +editorconfig@^0.15.0, editorconfig@^0.15.2: version "0.15.2" resolved "https://registry.yarnpkg.com/editorconfig/-/editorconfig-0.15.2.tgz#047be983abb9ab3c2eefe5199cb2b7c5689f0702" integrity sha512-GWjSI19PVJAM9IZRGOS+YKI8LN+/sjkSjNyvxL5ucqP9/IqtYNXBaQ/6c/hkPNYQHyOHra2KoXZI/JVpuqwmcQ== @@ -3522,10 +3569,10 @@ eslint-config-airbnb-base@^13.1.0: object.assign "^4.1.0" object.entries "^1.0.4" -eslint-config-prettier@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-3.1.0.tgz#2c26d2cdcfa3a05f0642cd7e6e4ef3316cdabfa2" - integrity sha512-QYGfmzuc4q4J6XIhlp8vRKdI/fI0tQfQPy1dME3UOLprE+v4ssH/3W9LM2Q7h5qBcy5m0ehCrBDU2YF8q6OY8w== +eslint-config-prettier@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-3.3.0.tgz#41afc8d3b852e757f06274ed6c44ca16f939a57d" + integrity sha512-Bc3bh5bAcKNvs3HOpSi6EfGA2IIp7EzWcg2tS4vP7stnXu/J1opihHDM7jI9JCIckyIDTgZLSWn7J3HY0j2JfA== dependencies: get-stdin "^6.0.0" @@ -3580,12 +3627,12 @@ eslint-plugin-filenames@^1.3.2: lodash.snakecase "4.1.1" lodash.upperfirst "4.3.1" -eslint-plugin-html@4.0.5: - version "4.0.5" - resolved "https://registry.yarnpkg.com/eslint-plugin-html/-/eslint-plugin-html-4.0.5.tgz#e8ec7e16485124460f3bff312016feb0a54d9659" - integrity sha512-yULqYldzhYXTwZEaJXM30HhfgJdtTzuVH3LeoANybESHZ5+2ztLD72BsB2wR124/kk/PvQqZofDFSdNIk+kykw== +eslint-plugin-html@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-html/-/eslint-plugin-html-5.0.0.tgz#396e30a60dedee0122fe08f11d13c5ab22f20d32" + integrity sha512-f7p/7YQdgQUFVAX3nB4dnMQbrDeTalcA01PDhuvTLk0ZadCwM4Pb+639SRuqEf1zMkIxckLY+ckCr0hVP5zl6A== dependencies: - htmlparser2 "^3.8.2" + htmlparser2 "^3.10.0" eslint-plugin-import@^2.14.0: version "2.14.0" @@ -3618,12 +3665,12 @@ eslint-plugin-promise@^4.0.1: resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-4.0.1.tgz#2d074b653f35a23d1ba89d8e976a985117d1c6a2" integrity sha512-Si16O0+Hqz1gDHsys6RtFRrW7cCTB6P7p3OJmKp3Y3dxpQE2qwOA7d3xnV+0mBmrPoi0RBnxlCKvqu70te6wjg== -eslint-plugin-vue@^5.0.0-beta.3: - version "5.0.0-beta.3" - resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-5.0.0-beta.3.tgz#f3fa9f109b76e20fc1e45a71ce7c6d567118924e" - integrity sha512-EOQo3ax4CIM6Itcl522p4cGlSBgR/KZBJo2Xc29PWknbYH/DRZorGutF8NATUpbZ4HYOG+Gcyd1nL08iyYF3Tg== +eslint-plugin-vue@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-5.0.0.tgz#4a2cc1c0e71ea45e1bd9c1a60f925bfe68bb5710" + integrity sha512-mSv2Ebz3RaPP+XJO/mu7F+SdR9lrMyGISSExnarLFqqf3pF5wTmwWNrhHW1o9zKzKI811UVTIIkWJJvgO6SsUQ== dependencies: - vue-eslint-parser "^3.2.1" + vue-eslint-parser "^4.0.2" eslint-restricted-globals@^0.1.1: version "0.1.1" @@ -3656,16 +3703,16 @@ eslint-visitor-keys@^1.0.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d" integrity sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ== -eslint@~5.6.0: - version "5.6.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-5.6.0.tgz#b6f7806041af01f71b3f1895cbb20971ea4b6223" - integrity sha512-/eVYs9VVVboX286mBK7bbKnO1yamUy2UCRjiY6MryhQL2PaaXCExsCQ2aO83OeYRhU2eCU/FMFP+tVMoOrzNrA== +eslint@~5.9.0: + version "5.9.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-5.9.0.tgz#b234b6d15ef84b5849c6de2af43195a2d59d408e" + integrity sha512-g4KWpPdqN0nth+goDNICNXGfJF7nNnepthp46CAlJoJtC5K/cLu3NgCM3AHu1CkJ5Hzt9V0Y0PBAO6Ay/gGb+w== dependencies: "@babel/code-frame" "^7.0.0" ajv "^6.5.3" chalk "^2.1.0" cross-spawn "^6.0.5" - debug "^3.1.0" + debug "^4.0.1" doctrine "^2.1.0" eslint-scope "^4.0.0" eslint-utils "^1.3.1" @@ -3692,12 +3739,12 @@ eslint@~5.6.0: path-is-inside "^1.0.2" pluralize "^7.0.0" progress "^2.0.0" - regexpp "^2.0.0" + regexpp "^2.0.1" require-uncached "^1.0.3" semver "^5.5.1" strip-ansi "^4.0.0" strip-json-comments "^2.0.1" - table "^4.0.3" + table "^5.0.2" text-table "^0.2.0" espree@^4.0.0: @@ -3708,6 +3755,15 @@ espree@^4.0.0: acorn "^5.6.0" acorn-jsx "^4.1.1" +espree@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-4.1.0.tgz#728d5451e0fd156c04384a7ad89ed51ff54eb25f" + integrity sha512-I5BycZW6FCVIub93TeVY1s7vjhP9CY6cXCznIRfiig7nRviKZYdRnj/sHEWC6A7WE9RDWOFq9+7OsWSYz8qv2w== + dependencies: + acorn "^6.0.2" + acorn-jsx "^5.0.0" + eslint-visitor-keys "^1.0.0" + esprima@2.7.x, esprima@^2.7.1: version "2.7.3" resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581" @@ -3997,6 +4053,13 @@ extglob@^2.0.4: snapdragon "^0.8.1" to-regex "^3.0.1" +extract-from-css@^0.4.4: + version "0.4.4" + resolved "https://registry.yarnpkg.com/extract-from-css/-/extract-from-css-0.4.4.tgz#1ea7df2e7c7c6eb9922fa08e8adaea486f6f8f92" + integrity sha1-HqffLnx8brmSL6COitrqSG9vj5I= + dependencies: + css "^2.1.0" + extsprintf@1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" @@ -4141,6 +4204,14 @@ finalhandler@1.1.1: statuses "~1.4.0" unpipe "~1.0.0" +find-babel-config@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/find-babel-config/-/find-babel-config-1.1.0.tgz#acc01043a6749fec34429be6b64f542ebb5d6355" + integrity sha1-rMAQQ6Z0n+w0Qpvmtk9ULrtdY1U= + dependencies: + json5 "^0.5.1" + path-exists "^3.0.0" + find-cache-dir@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-1.0.0.tgz#9288e3e9e3cc3748717d39eade17cf71fc30ee6f" @@ -4410,7 +4481,7 @@ glob-parent@^3.1.0: is-glob "^3.1.0" path-dirname "^1.0.0" -"glob@5 - 7", glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2: +"glob@5 - 7", glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3: version "7.1.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== @@ -4774,7 +4845,19 @@ html-entities@^1.2.0: resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.2.0.tgz#41948caf85ce82fed36e4e6a0ed371a6664379e2" integrity sha1-QZSMr4XOgv7Tbk5qDtNxpmZDeeI= -htmlparser2@^3.8.2, htmlparser2@^3.9.0: +htmlparser2@^3.10.0: + version "3.10.0" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.0.tgz#5f5e422dcf6119c0d983ed36260ce9ded0bee464" + integrity sha512-J1nEUGv+MkXS0weHNWVKJJ+UrLfePxRWpN3C9bEi9fLxL2+ggW94DQvgYVXsaT30PGwYRIZKNZXuyMhp3Di4bQ== + dependencies: + domelementtype "^1.3.0" + domhandler "^2.3.0" + domutils "^1.5.1" + entities "^1.1.1" + inherits "^2.0.1" + readable-stream "^3.0.6" + +htmlparser2@^3.9.0: version "3.9.2" resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.9.2.tgz#1bdf87acca0f3f9e53fa4fcceb0f4b4cbb00b338" integrity sha1-G9+HrMoPP55T+k/M6w9LTLsAszg= @@ -5975,6 +6058,17 @@ jquery.waitforimages@^2.2.0: resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.3.1.tgz#958ce29e81c9790f31be7792df5d4d95fc57fbca" integrity sha512-Ubldcmxp5np52/ENotGxlLe6aGMvmF4R8S6tZjsP6Knsaxd/xp3Zrh50cG93lR6nPXyUFwzN3ZSOQI0wRJNdGg== +js-beautify@^1.6.14: + version "1.8.9" + resolved "https://registry.yarnpkg.com/js-beautify/-/js-beautify-1.8.9.tgz#08e3c05ead3ecfbd4f512c3895b1cda76c87d523" + integrity sha512-MwPmLywK9RSX0SPsUJjN7i+RQY9w/yC17Lbrq9ViEefpLRgqAR2BgrMN2AbifkUuhDV8tRauLhLda/9+bE0YQA== + dependencies: + config-chain "^1.1.12" + editorconfig "^0.15.2" + glob "^7.1.3" + mkdirp "~0.5.0" + nopt "~4.0.1" + js-beautify@^1.8.8: version "1.8.8" resolved "https://registry.yarnpkg.com/js-beautify/-/js-beautify-1.8.8.tgz#1eb175b73a3571a5f1ed8d98e7cf2b05bfa98471" @@ -6442,7 +6536,7 @@ lodash.upperfirst@4.3.1: resolved "https://registry.yarnpkg.com/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz#1365edf431480481ef0d1c68957a5ed99d49f7ce" integrity sha1-E2Xt9DFIBIHvDRxolXpe2Z1J984= -lodash@^4.13.1, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.3.0, lodash@^4.5.0: +lodash@4.x, lodash@^4.13.1, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.3.0, lodash@^4.5.0: version "4.17.11" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== @@ -6927,6 +7021,14 @@ nice-try@^1.0.4: resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.4.tgz#d93962f6c52f2c1558c0fbda6d512819f1efe1c4" integrity sha512-2NpiFHqC87y/zFke0fC0spBXL3bBsoh/p5H1EFhshxjCR5+0g2d6BiXbUFz9v1sAcxsk2htp2eQnNIci2dIYcA== +node-cache@^4.1.1: + version "4.2.0" + resolved "https://registry.yarnpkg.com/node-cache/-/node-cache-4.2.0.tgz#48ac796a874e762582692004a376d26dfa875811" + integrity sha512-obRu6/f7S024ysheAjoYFEEBqqDWv4LOMNJEuO8vMeEw2AT4z+NCzO4hlc2lhI4vATzbCQv6kke9FVdx0RbCOw== + dependencies: + clone "2.x" + lodash "4.x" + node-fetch@1.6.3: version "1.6.3" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.6.3.tgz#dc234edd6489982d58e8f0db4f695029abcd8c04" @@ -7121,7 +7223,7 @@ oauth-sign@~0.9.0: resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== -object-assign@^4.0.1, object-assign@^4.1.0: +object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= @@ -8073,6 +8175,15 @@ read-pkg@^3.0.0: string_decoder "~1.1.1" util-deprecate "~1.0.1" +readable-stream@^3.0.6: + version "3.0.6" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.0.6.tgz#351302e4c68b5abd6a2ed55376a7f9a25be3057a" + integrity sha512-9E1oLoOWfhSXHGv6QlwXJim7uNzd9EVlWK+21tCU9Ju/kR0/p2AZYPz4qSchgO8PlLIH4FpZYfzwS+rEksZjIg== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + readable-stream@~2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.0.6.tgz#8f90341e68a53ccc928788dacfcd11b36eb9b78e" @@ -8146,10 +8257,10 @@ regex-not@^1.0.0, regex-not@^1.0.2: extend-shallow "^3.0.2" safe-regex "^1.1.0" -regexpp@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.0.tgz#b2a7534a85ca1b033bcf5ce9ff8e56d4e0755365" - integrity sha512-g2FAVtR8Uh8GO1Nv5wpxW7VFVwHcCEr4wyA8/MHiRkO8uHoR5ntAA8Uq3P1vvMTX/BeQiRVSpDGLd+Wn5HNOTA== +regexpp@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f" + integrity sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw== regexpu-core@^1.0.0: version "1.0.0" @@ -8681,11 +8792,13 @@ slash@^1.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" integrity sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU= -slice-ansi@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-1.0.0.tgz#044f1a49d8842ff307aad6b505ed178bd950134d" - integrity sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg== +slice-ansi@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.0.0.tgz#5373bdb8559b45676e8541c66916cdd6251612e7" + integrity sha512-4j2WTWjp3GsZ+AOagyzVbzp4vWGtZ0hEZ/gDY/uTvm6MTxUfTUIsnMIFb1bn8o0RuXiqUw15H1bue8f22Vw2oQ== dependencies: + ansi-styles "^3.2.0" + astral-regex "^1.0.0" is-fullwidth-code-point "^2.0.0" slugify@^1.3.1: @@ -8826,6 +8939,17 @@ source-map-resolve@^0.5.0: source-map-url "^0.4.0" urix "^0.1.0" +source-map-resolve@^0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.2.tgz#72e2cc34095543e43b2c62b2c4c10d4a9054f259" + integrity sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA== + dependencies: + atob "^2.1.1" + decode-uri-component "^0.2.0" + resolve-url "^0.2.1" + source-map-url "^0.4.0" + urix "^0.1.0" + source-map-support@^0.4.15: version "0.4.18" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f" @@ -9090,6 +9214,13 @@ string_decoder@^1.0.0, string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" +string_decoder@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.2.0.tgz#fe86e738b19544afe70469243b2a1ee9240eae8d" + integrity sha512-6YqyX6ZWEYguAxgZzHGL7SsCeGx3V2TtOTqZz1xSTSWnqsbWwbptafNyvf/ACquZUXV3DANr5BDIwNYe1mN42w== + dependencies: + safe-buffer "~5.1.0" + string_decoder@~0.10.x: version "0.10.31" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" @@ -9133,7 +9264,7 @@ strip-eof@^1.0.0: resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= -strip-json-comments@^2.0.1, strip-json-comments@~2.0.1: +strip-json-comments@^2.0.0, strip-json-comments@^2.0.1, strip-json-comments@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= @@ -9180,16 +9311,14 @@ symbol-tree@^3.2.2: resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.2.tgz#ae27db38f660a7ae2e1c3b7d1bc290819b8519e6" integrity sha1-rifbOPZgp64uHDt9G8KQgZuFGeY= -table@^4.0.3: - version "4.0.3" - resolved "http://registry.npmjs.org/table/-/table-4.0.3.tgz#00b5e2b602f1794b9acaf9ca908a76386a7813bc" - integrity sha512-S7rnFITmBH1EnyKcvxBh1LjYeQMmnZtCXSEbHcH6S0NoKit24ZuFO/T1vDcLdYsLQkM188PVVhQmzKIuThNkKg== +table@^5.0.2: + version "5.1.1" + resolved "https://registry.yarnpkg.com/table/-/table-5.1.1.tgz#92030192f1b7b51b6eeab23ed416862e47b70837" + integrity sha512-NUjapYb/qd4PeFW03HnAuOJ7OMcBkJlqeClWxeNlQ0lXGSb52oZXGzkO0/I0ARegQ2eUT1g2VDJH0eUxDRcHmw== dependencies: - ajv "^6.0.1" - ajv-keywords "^3.0.0" - chalk "^2.1.0" - lodash "^4.17.4" - slice-ansi "1.0.0" + ajv "^6.6.1" + lodash "^4.17.11" + slice-ansi "2.0.0" string-width "^2.1.1" tapable@^0.1.8: @@ -9422,6 +9551,16 @@ tryer@^1.0.0: resolved "https://registry.yarnpkg.com/tryer/-/tryer-1.0.0.tgz#027b69fa823225e551cace3ef03b11f6ab37c1d7" integrity sha1-Antp+oIyJeVRys4+8DsR9qs3wdc= +tsconfig@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/tsconfig/-/tsconfig-7.0.0.tgz#84538875a4dc216e5c4a5432b3a4dec3d54e91b7" + integrity sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw== + dependencies: + "@types/strip-bom" "^3.0.0" + "@types/strip-json-comments" "0.0.30" + strip-bom "^3.0.0" + strip-json-comments "^2.0.0" + tslib@^1.9.0: version "1.9.3" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286" @@ -9691,7 +9830,7 @@ useragent@2.2.1: lru-cache "2.2.x" tmp "0.0.x" -util-deprecate@~1.0.1: +util-deprecate@^1.0.1, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= @@ -9773,17 +9912,17 @@ vue-apollo@^3.0.0-beta.25: chalk "^2.4.1" throttle-debounce "^2.0.0" -vue-eslint-parser@^3.2.1: - version "3.2.2" - resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-3.2.2.tgz#47c971ee4c39b0ee7d7f5e154cb621beb22f7a34" - integrity sha512-dprI6ggKCTwV22r+i8dtUGquiOCn063xyDmb7BV/BjG5Oc/m5EoMNrWevpvTcrlGuFZmYVPs5fgsu8UIxmMKzg== +vue-eslint-parser@^4.0.2: + version "4.0.3" + resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-4.0.3.tgz#80cf162e484387b2640371ad21ba1f86e0c10a61" + integrity sha512-AUeQsYdO6+7QXCems+WvGlrXd37PHv/zcRQSQdY1xdOMwdFAPEnMBsv7zPvk0TPGulXkK/5p/ITgrjiYB7k3ag== dependencies: - debug "^3.1.0" + debug "^4.1.0" eslint-scope "^4.0.0" eslint-visitor-keys "^1.0.0" - espree "^4.0.0" + espree "^4.1.0" esquery "^1.0.1" - lodash "^4.17.10" + lodash "^4.17.11" vue-functional-data-merge@^2.0.5: version "2.0.6" @@ -9795,6 +9934,22 @@ vue-hot-reload-api@^2.3.0: resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.0.tgz#97976142405d13d8efae154749e88c4e358cf926" integrity sha512-2j/t+wIbyVMP5NvctQoSUvLkYKoWAAk2QlQiilrM2a6/ulzFgdcLUJfTvs4XQ/3eZhHiBmmEojbjmM4AzZj8JA== +vue-jest@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/vue-jest/-/vue-jest-3.0.1.tgz#127cded1a57cdfcf01fa8a10ce29579e2cb3a04d" + integrity sha512-otS+n341cTsp0pF7tuTu2x43b23x/+K0LZdAXV+ewKYIMZRqhuQaJTECWEt/cN/YZw2JC6hUM6xybdnOB4ZQ+g== + dependencies: + babel-plugin-transform-es2015-modules-commonjs "^6.26.0" + chalk "^2.1.0" + extract-from-css "^0.4.4" + find-babel-config "^1.1.0" + js-beautify "^1.6.14" + node-cache "^4.1.1" + object-assign "^4.1.1" + source-map "^0.5.6" + tsconfig "^7.0.0" + vue-template-es2015-compiler "^1.6.0" + vue-loader@^15.4.2: version "15.4.2" resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.4.2.tgz#812bb26e447dd3b84c485eb634190d914ce125e2" |