diff options
206 files changed, 2304 insertions, 1465 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2249115e82a..71ddebef662 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -364,10 +364,11 @@ update-tests-metadata: - rspec_flaky/ policy: push script: - - retry gem install fog-aws mime-types + - retry gem install fog-aws mime-types activesupport - scripts/merge-reports ${KNAPSACK_RSPEC_SUITE_REPORT_PATH} knapsack/${CI_PROJECT_NAME}/rspec-pg_node_*.json - scripts/merge-reports ${KNAPSACK_SPINACH_SUITE_REPORT_PATH} knapsack/${CI_PROJECT_NAME}/spinach-pg_node_*.json - scripts/merge-reports ${FLAKY_RSPEC_SUITE_REPORT_PATH} rspec_flaky/all_*_*.json + - FLAKY_RSPEC_GENERATE_REPORT=1 scripts/prune-old-flaky-specs ${FLAKY_RSPEC_SUITE_REPORT_PATH} - '[[ -z ${TESTS_METADATA_S3_BUCKET} ]] || scripts/sync-reports put $TESTS_METADATA_S3_BUCKET $KNAPSACK_RSPEC_SUITE_REPORT_PATH $KNAPSACK_SPINACH_SUITE_REPORT_PATH' - '[[ -z ${TESTS_METADATA_S3_BUCKET} ]] || scripts/sync-reports put $TESTS_METADATA_S3_BUCKET $FLAKY_RSPEC_SUITE_REPORT_PATH' - rm -f knapsack/${CI_PROJECT_NAME}/*_node_*.json diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION index fcdb2e109f6..ee74734aa22 100644 --- a/GITLAB_WORKHORSE_VERSION +++ b/GITLAB_WORKHORSE_VERSION @@ -1 +1 @@ -4.0.0 +4.1.0 diff --git a/Gemfile.rails5.lock b/Gemfile.rails5.lock index 03fe5f2ed26..c953b9708a0 100644 --- a/Gemfile.rails5.lock +++ b/Gemfile.rails5.lock @@ -321,6 +321,9 @@ GEM rubyntlm (~> 0.5) globalid (0.4.1) activesupport (>= 4.2.0) + goldiloader (2.0.1) + activerecord (>= 4.2, < 5.2) + activesupport (>= 4.2, < 5.2) gollum-grit_adapter (1.0.1) gitlab-grit (~> 2.7, >= 2.7.1) gollum-lib (4.2.7) @@ -878,7 +881,7 @@ GEM simplecov-html (~> 0.10.0) simplecov-html (0.10.2) slack-notifier (1.5.1) - spinach (0.10.1) + spinach (0.8.10) colorize gherkin-ruby (>= 0.3.2) json @@ -1072,6 +1075,7 @@ DEPENDENCIES gitlab-markup (~> 1.6.2) gitlab-styles (~> 2.3) gitlab_omniauth-ldap (~> 2.0.4) + goldiloader (~> 2.0) gollum-lib (~> 4.2) gollum-rugged_adapter (~> 0.4.4) gon (~> 6.1.0) diff --git a/app/assets/javascripts/ide/components/repo_editor.vue b/app/assets/javascripts/ide/components/repo_editor.vue index 6aa44ca2c11..711bafa17a9 100644 --- a/app/assets/javascripts/ide/components/repo_editor.vue +++ b/app/assets/javascripts/ide/components/repo_editor.vue @@ -22,7 +22,7 @@ export default { ...mapState(['rightPanelCollapsed', 'viewer', 'delayViewerUpdated', 'panelResizing']), ...mapGetters(['currentMergeRequest']), shouldHideEditor() { - return this.file && this.file.binary && !this.file.raw; + return this.file && this.file.binary && !this.file.content; }, editTabCSS() { return { @@ -212,7 +212,7 @@ export default { <content-viewer v-if="shouldHideEditor || file.viewMode === 'preview'" :content="file.content || file.raw" - :path="file.rawPath" + :path="file.rawPath || file.path" :file-size="file.size" :project-path="file.projectId"/> </div> diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index ac70ddb3ff4..b0573510ff9 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -1190,12 +1190,12 @@ export default class Notes { addForm = false; let lineTypeSelector = ''; rowCssToAdd = - '<tr class="notes_holder js-temp-notes-holder"><td class="notes_line" colspan="2"></td><td class="notes_content"><div class="content discussion-notes"></div></td></tr>'; + '<tr class="notes_holder js-temp-notes-holder"><td class="notes_line" colspan="2"></td><td class="notes_content"><div class="content"></div></td></tr>'; // In parallel view, look inside the correct left/right pane if (this.isParallelView()) { lineTypeSelector = `.${lineType}`; rowCssToAdd = - '<tr class="notes_holder js-temp-notes-holder"><td class="notes_line old"></td><td class="notes_content parallel old"><div class="content discussion-notes"></div></td><td class="notes_line new"></td><td class="notes_content parallel new"><div class="content discussion-notes"></div></td></tr>'; + '<tr class="notes_holder js-temp-notes-holder"><td class="notes_line old"></td><td class="notes_content parallel old"><div class="content"></div></td><td class="notes_line new"></td><td class="notes_content parallel new"><div class="content"></div></td></tr>'; } const notesContentSelector = `.notes_content${lineTypeSelector} .content`; let notesContent = targetRow.find(notesContentSelector); diff --git a/app/assets/javascripts/notes/components/noteable_discussion.vue b/app/assets/javascripts/notes/components/noteable_discussion.vue index 476b15aca4a..e0f883a8e08 100644 --- a/app/assets/javascripts/notes/components/noteable_discussion.vue +++ b/app/assets/javascripts/notes/components/noteable_discussion.vue @@ -258,7 +258,9 @@ Please check your network connection and try again.`; :key="note.id" /> </ul> - <div class="discussion-reply-holder"> + <div + :class="{ 'is-replying': isReplying }" + class="discussion-reply-holder"> <template v-if="!isReplying && canReply"> <div class="btn-group-justified discussion-with-resolve-btn" diff --git a/app/assets/javascripts/pages/projects/edit/index.js b/app/assets/javascripts/pages/projects/edit/index.js index be37df36be8..628913483c6 100644 --- a/app/assets/javascripts/pages/projects/edit/index.js +++ b/app/assets/javascripts/pages/projects/edit/index.js @@ -1,12 +1,12 @@ import initSettingsPanels from '~/settings_panels'; import setupProjectEdit from '~/project_edit'; import initConfirmDangerModal from '~/confirm_danger_modal'; -import ProjectNew from '../shared/project_new'; +import initProjectLoadingSpinner from '../shared/save_project_loader'; import projectAvatar from '../shared/project_avatar'; import initProjectPermissionsSettings from '../shared/permissions'; document.addEventListener('DOMContentLoaded', () => { - new ProjectNew(); // eslint-disable-line no-new + initProjectLoadingSpinner(); setupProjectEdit(); // Initialize expandable settings panels initSettingsPanels(); diff --git a/app/assets/javascripts/pages/projects/new/index.js b/app/assets/javascripts/pages/projects/new/index.js index ea6fd961393..7db644e2477 100644 --- a/app/assets/javascripts/pages/projects/new/index.js +++ b/app/assets/javascripts/pages/projects/new/index.js @@ -1,9 +1,9 @@ -import ProjectNew from '../shared/project_new'; +import initProjectLoadingSpinner from '../shared/save_project_loader'; import initProjectVisibilitySelector from '../../../project_visibility'; import initProjectNew from '../../../projects/project_new'; document.addEventListener('DOMContentLoaded', () => { - new ProjectNew(); // eslint-disable-line no-new + initProjectLoadingSpinner(); initProjectVisibilitySelector(); initProjectNew.bindEvents(); }); diff --git a/app/assets/javascripts/pages/projects/shared/project_new.js b/app/assets/javascripts/pages/projects/shared/project_new.js deleted file mode 100644 index 56d5574aa2f..00000000000 --- a/app/assets/javascripts/pages/projects/shared/project_new.js +++ /dev/null @@ -1,152 +0,0 @@ -/* eslint-disable func-names, no-var, no-underscore-dangle, prefer-template, prefer-arrow-callback*/ - -import $ from 'jquery'; -import VisibilitySelect from '../../../visibility_select'; - -function highlightChanges($elm) { - $elm.addClass('highlight-changes'); - setTimeout(() => $elm.removeClass('highlight-changes'), 10); -} - -export default class ProjectNew { - constructor() { - this.toggleSettings = this.toggleSettings.bind(this); - this.$selects = $('.features select'); - this.$repoSelects = this.$selects.filter('.js-repo-select'); - this.$projectSelects = this.$selects.not('.js-repo-select'); - - $('.project-edit-container').on('ajax:before', () => { - $('.project-edit-container').hide(); - return $('.save-project-loader').show(); - }); - - this.initVisibilitySelect(); - - this.toggleSettings(); - this.toggleSettingsOnclick(); - this.toggleRepoVisibility(); - } - - initVisibilitySelect() { - const visibilityContainer = document.querySelector('.js-visibility-select'); - if (!visibilityContainer) return; - const visibilitySelect = new VisibilitySelect(visibilityContainer); - visibilitySelect.init(); - - const $visibilitySelect = $(visibilityContainer).find('select'); - let projectVisibility = $visibilitySelect.val(); - const PROJECT_VISIBILITY_PRIVATE = '0'; - - $visibilitySelect.on('change', () => { - const newProjectVisibility = $visibilitySelect.val(); - - if (projectVisibility !== newProjectVisibility) { - this.$projectSelects.each((idx, select) => { - const $select = $(select); - const $options = $select.find('option'); - const values = $.map($options, e => e.value); - - // if switched to "private", limit visibility options - if (newProjectVisibility === PROJECT_VISIBILITY_PRIVATE) { - if ($select.val() !== values[0] && $select.val() !== values[1]) { - $select.val(values[1]).trigger('change'); - highlightChanges($select); - } - $options.slice(2).disable(); - } - - // if switched from "private", increase visibility for non-disabled options - if (projectVisibility === PROJECT_VISIBILITY_PRIVATE) { - $options.enable(); - if ($select.val() !== values[0] && $select.val() !== values[values.length - 1]) { - $select.val(values[values.length - 1]).trigger('change'); - highlightChanges($select); - } - } - }); - - projectVisibility = newProjectVisibility; - } - }); - } - - toggleSettings() { - this.$selects.each(function () { - var $select = $(this); - var className = $select.data('field') - .replace(/_/g, '-') - .replace('access-level', 'feature'); - ProjectNew._showOrHide($select, '.' + className); - }); - } - - toggleSettingsOnclick() { - this.$selects.on('change', this.toggleSettings); - } - - static _showOrHide(checkElement, container) { - const $container = $(container); - - if ($(checkElement).val() !== '0') { - return $container.show(); - } - return $container.hide(); - } - - toggleRepoVisibility() { - var $repoAccessLevel = $('.js-repo-access-level select'); - var $lfsEnabledOption = $('.js-lfs-enabled select'); - var containerRegistry = document.querySelectorAll('.js-container-registry')[0]; - var containerRegistryCheckbox = document.getElementById('project_container_registry_enabled'); - var prevSelectedVal = parseInt($repoAccessLevel.val(), 10); - - this.$repoSelects.find("option[value='" + $repoAccessLevel.val() + "']") - .nextAll() - .hide(); - - $repoAccessLevel - .off('change') - .on('change', function () { - var selectedVal = parseInt($repoAccessLevel.val(), 10); - - this.$repoSelects.each(function () { - var $this = $(this); - var repoSelectVal = parseInt($this.val(), 10); - - $this.find('option').enable(); - - if (selectedVal < repoSelectVal || repoSelectVal === prevSelectedVal) { - $this.val(selectedVal).trigger('change'); - highlightChanges($this); - } - - $this.find("option[value='" + selectedVal + "']").nextAll().disable(); - }); - - if (selectedVal) { - this.$repoSelects.removeClass('disabled'); - - if ($lfsEnabledOption.length) { - $lfsEnabledOption.removeClass('disabled'); - highlightChanges($lfsEnabledOption); - } - if (containerRegistry) { - containerRegistry.style.display = ''; - } - } else { - this.$repoSelects.addClass('disabled'); - - if ($lfsEnabledOption.length) { - $lfsEnabledOption.val('false').addClass('disabled'); - highlightChanges($lfsEnabledOption); - } - if (containerRegistry) { - containerRegistry.style.display = 'none'; - containerRegistryCheckbox.checked = false; - } - } - - prevSelectedVal = selectedVal; - }.bind(this)); - } -} diff --git a/app/assets/javascripts/pages/projects/shared/save_project_loader.js b/app/assets/javascripts/pages/projects/shared/save_project_loader.js new file mode 100644 index 00000000000..aa3589ac88d --- /dev/null +++ b/app/assets/javascripts/pages/projects/shared/save_project_loader.js @@ -0,0 +1,12 @@ +import $ from 'jquery'; + +export default function initProjectLoadingSpinner() { + const $formContainer = $('.project-edit-container'); + const $loadingSpinner = $('.save-project-loader'); + + // show loading spinner when saving + $formContainer.on('ajax:before', () => { + $formContainer.hide(); + $loadingSpinner.show(); + }); +} diff --git a/app/assets/javascripts/pages/sessions/new/signin_tabs_memoizer.js b/app/assets/javascripts/pages/sessions/new/signin_tabs_memoizer.js index 08f0afdcce3..d321892d2d2 100644 --- a/app/assets/javascripts/pages/sessions/new/signin_tabs_memoizer.js +++ b/app/assets/javascripts/pages/sessions/new/signin_tabs_memoizer.js @@ -5,7 +5,7 @@ import AccessorUtilities from '~/lib/utils/accessor'; * Does that setting the current selected tab in the localStorage */ export default class SigninTabsMemoizer { - constructor({ currentTabKey = 'current_signin_tab', tabSelector = 'ul.nav-tabs' } = {}) { + constructor({ currentTabKey = 'current_signin_tab', tabSelector = 'ul.new-session-tabs' } = {}) { this.currentTabKey = currentTabKey; this.tabSelector = tabSelector; this.isLocalStorageAvailable = AccessorUtilities.isLocalStorageAccessSafe(); diff --git a/app/assets/javascripts/pipelines/components/stage.vue b/app/assets/javascripts/pipelines/components/stage.vue index 8bc7a1f20b2..b3fcaf0ccd1 100644 --- a/app/assets/javascripts/pipelines/components/stage.vue +++ b/app/assets/javascripts/pipelines/components/stage.vue @@ -13,16 +13,16 @@ * 3. Merge request widget * 4. Commit widget */ - + import axios from '../../lib/utils/axios_utils'; import Flash from '../../flash'; - import icon from '../../vue_shared/components/icon.vue'; - import loadingIcon from '../../vue_shared/components/loading_icon.vue'; + import Icon from '../../vue_shared/components/icon.vue'; + import LoadingIcon from '../../vue_shared/components/loading_icon.vue'; import tooltip from '../../vue_shared/directives/tooltip'; export default { components: { - loadingIcon, - icon, + LoadingIcon, + Icon, }, directives: { @@ -88,9 +88,8 @@ }, fetchJobs() { - this.$http.get(this.stage.dropdown_path) - .then(response => response.json()) - .then((data) => { + axios.get(this.stage.dropdown_path) + .then(({ data }) => { this.dropdownContent = data.html; this.isLoading = false; }) @@ -98,8 +97,7 @@ this.closeDropdown(); this.isLoading = false; - const flash = new Flash('Something went wrong on our end.'); - return flash; + Flash('Something went wrong on our end.'); }); }, diff --git a/app/assets/javascripts/pipelines/pipeline_details_mediator.js b/app/assets/javascripts/pipelines/pipeline_details_mediator.js index 621969cd622..5633e54b28a 100644 --- a/app/assets/javascripts/pipelines/pipeline_details_mediator.js +++ b/app/assets/javascripts/pipelines/pipeline_details_mediator.js @@ -40,10 +40,8 @@ export default class pipelinesMediator { } successCallback(response) { - return response.json().then((data) => { - this.state.isLoading = false; - this.store.storePipeline(data); - }); + this.state.isLoading = false; + this.store.storePipeline(response.data); } errorCallback() { diff --git a/app/assets/javascripts/pipelines/services/pipeline_service.js b/app/assets/javascripts/pipelines/services/pipeline_service.js index 3e0c52c7726..a53a9cc8365 100644 --- a/app/assets/javascripts/pipelines/services/pipeline_service.js +++ b/app/assets/javascripts/pipelines/services/pipeline_service.js @@ -1,19 +1,16 @@ -import Vue from 'vue'; -import VueResource from 'vue-resource'; - -Vue.use(VueResource); +import axios from '../../lib/utils/axios_utils'; export default class PipelineService { constructor(endpoint) { - this.pipeline = Vue.resource(endpoint); + this.pipeline = endpoint; } getPipeline() { - return this.pipeline.get(); + return axios.get(this.pipeline); } - // eslint-disable-next-line + // eslint-disable-next-line class-methods-use-this postAction(endpoint) { - return Vue.http.post(`${endpoint}.json`); + return axios.post(`${endpoint}.json`); } } diff --git a/app/assets/javascripts/sidebar/components/time_tracking/estimate_only_pane.js b/app/assets/javascripts/sidebar/components/time_tracking/estimate_only_pane.js deleted file mode 100644 index 2d324c71379..00000000000 --- a/app/assets/javascripts/sidebar/components/time_tracking/estimate_only_pane.js +++ /dev/null @@ -1,17 +0,0 @@ -export default { - name: 'time-tracking-estimate-only-pane', - props: { - timeEstimateHumanReadable: { - type: String, - required: true, - }, - }, - template: ` - <div class="time-tracking-estimate-only-pane"> - <span class="bold"> - {{ s__('TimeTracking|Estimated:') }} - </span> - {{ timeEstimateHumanReadable }} - </div> - `, -}; diff --git a/app/assets/javascripts/sidebar/components/time_tracking/estimate_only_pane.vue b/app/assets/javascripts/sidebar/components/time_tracking/estimate_only_pane.vue new file mode 100644 index 00000000000..08fce597e50 --- /dev/null +++ b/app/assets/javascripts/sidebar/components/time_tracking/estimate_only_pane.vue @@ -0,0 +1,20 @@ +<script> +export default { + name: 'TimeTrackingEstimateOnlyPane', + props: { + timeEstimateHumanReadable: { + type: String, + required: true, + }, + }, +}; +</script> + +<template> + <div class="time-tracking-estimate-only-pane"> + <span class="bold"> + {{ s__('TimeTracking|Estimated:') }} + </span> + {{ timeEstimateHumanReadable }} + </div> +</template> diff --git a/app/assets/javascripts/sidebar/components/time_tracking/help_state.js b/app/assets/javascripts/sidebar/components/time_tracking/help_state.vue index 19f74ad3c6d..825063d9ba6 100644 --- a/app/assets/javascripts/sidebar/components/time_tracking/help_state.js +++ b/app/assets/javascripts/sidebar/components/time_tracking/help_state.vue @@ -1,7 +1,8 @@ +<script> import { sprintf, s__ } from '../../../locale'; export default { - name: 'time-tracking-help-state', + name: 'TimeTrackingHelpState', props: { rootPath: { type: String, @@ -27,26 +28,28 @@ export default { ); }, }, - template: ` - <div class="time-tracking-help-state"> - <div class="time-tracking-info"> - <h4> - {{ __('Track time with quick actions') }} - </h4> - <p> - {{ __('Quick actions can be used in the issues description and comment boxes.') }} - </p> - <p v-html="estimateText"> - </p> - <p v-html="spendText"> - </p> - <a - class="btn btn-default learn-more-button" - :href="href" - > - {{ __('Learn more') }} - </a> - </div> - </div> - `, }; +</script> + +<template> + <div class="time-tracking-help-state"> + <div class="time-tracking-info"> + <h4> + {{ __('Track time with quick actions') }} + </h4> + <p> + {{ __('Quick actions can be used in the issues description and comment boxes.') }} + </p> + <p v-html="estimateText"> + </p> + <p v-html="spendText"> + </p> + <a + class="btn btn-default learn-more-button" + :href="href" + > + {{ __('Learn more') }} + </a> + </div> + </div> +</template> diff --git a/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue b/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue index 1c641c73ea3..71dca498b3d 100644 --- a/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue +++ b/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue @@ -1,9 +1,9 @@ <script> -import timeTrackingHelpState from './help_state'; +import TimeTrackingHelpState from './help_state.vue'; import TimeTrackingCollapsedState from './collapsed_state.vue'; import timeTrackingSpentOnlyPane from './spent_only_pane'; import timeTrackingNoTrackingPane from './no_tracking_pane'; -import timeTrackingEstimateOnlyPane from './estimate_only_pane'; +import TimeTrackingEstimateOnlyPane from './estimate_only_pane.vue'; import TimeTrackingComparisonPane from './comparison_pane.vue'; import eventHub from '../../event_hub'; @@ -12,11 +12,11 @@ export default { name: 'IssuableTimeTracker', components: { TimeTrackingCollapsedState, - 'time-tracking-estimate-only-pane': timeTrackingEstimateOnlyPane, + TimeTrackingEstimateOnlyPane, 'time-tracking-spent-only-pane': timeTrackingSpentOnlyPane, 'time-tracking-no-tracking-pane': timeTrackingNoTrackingPane, TimeTrackingComparisonPane, - 'time-tracking-help-state': timeTrackingHelpState, + TimeTrackingHelpState, }, props: { time_estimate: { diff --git a/app/assets/javascripts/visibility_select.js b/app/assets/javascripts/visibility_select.js deleted file mode 100644 index 0c928d0d5f6..00000000000 --- a/app/assets/javascripts/visibility_select.js +++ /dev/null @@ -1,21 +0,0 @@ -export default class VisibilitySelect { - constructor(container) { - if (!container) throw new Error('VisibilitySelect requires a container element as argument 1'); - this.container = container; - this.helpBlock = this.container.querySelector('.help-block'); - this.select = this.container.querySelector('select'); - } - - init() { - if (this.select) { - this.updateHelpText(); - this.select.addEventListener('change', this.updateHelpText.bind(this)); - } else { - this.helpBlock.textContent = this.container.querySelector('.js-locked').dataset.helpBlock; - } - } - - updateHelpText() { - this.helpBlock.textContent = this.select.querySelector('option:checked').dataset.description; - } -} diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/unresolved_discussions.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/unresolved_discussions.vue index 9ade6a91747..a1f7e696795 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/unresolved_discussions.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/unresolved_discussions.vue @@ -7,7 +7,10 @@ export default { statusIcon, }, props: { - mr: { type: Object, required: true }, + mr: { + type: Object, + required: true, + }, }, }; </script> @@ -20,13 +23,14 @@ export default { /> <div class="media-body space-children"> <span class="bold"> - There are unresolved discussions. Please resolve these discussions + {{ s__("mrWidget|There are unresolved discussions. Please resolve these discussions") }} </span> <a v-if="mr.createIssueToResolveDiscussionsPath" :href="mr.createIssueToResolveDiscussionsPath" - class="btn btn-default btn-xs js-create-issue"> - Create an issue to resolve them later + class="btn btn-default btn-xs js-create-issue" + > + {{ s__("mrWidget|Create an issue to resolve them later") }} </a> </div> </div> diff --git a/app/assets/javascripts/vue_shared/components/markdown/header.vue b/app/assets/javascripts/vue_shared/components/markdown/header.vue index d91fe3cf0c5..db453c30576 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/header.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/header.vue @@ -27,20 +27,22 @@ $(document).off('markdown-preview:hide.vue', this.writeMarkdownTab); }, methods: { - isMarkdownForm(form) { - return form && !form.find('.js-vue-markdown-field').length; + isValid(form) { + return !form || + form.find('.js-vue-markdown-field').length || + $(this.$el).closest('form') === form[0]; }, previewMarkdownTab(event, form) { if (event.target.blur) event.target.blur(); - if (this.isMarkdownForm(form)) return; + if (!this.isValid(form)) return; this.$emit('preview-markdown'); }, writeMarkdownTab(event, form) { if (event.target.blur) event.target.blur(); - if (this.isMarkdownForm(form)) return; + if (!this.isValid(form)) return; this.$emit('write-markdown'); }, diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 679f783b1b6..7f037582ca0 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -813,7 +813,6 @@ } .discussion-notes { - padding: 0 $gl-padding $gl-padding; min-height: 35px; &:first-child { diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index b2250a1ce2f..97303d02666 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -154,26 +154,10 @@ a { width: 100%; font-size: 18px; - margin-right: 0; - - &:hover { - border: 1px solid transparent; - } } - &.active { - border-bottom: 1px solid $border-color; - - a { - border: 0; - border-bottom: 2px solid $link-underline-blue; - margin-right: 0; - color: $black; - - &:hover { - border-bottom: 2px solid $link-underline-blue; - } - } + &.active > a { + cursor: default; } } } diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss index 8720f821ce9..4a528bc2bb1 100644 --- a/app/assets/stylesheets/pages/note_form.scss +++ b/app/assets/stylesheets/pages/note_form.scss @@ -173,7 +173,11 @@ } .discussion-form { - padding-top: $gl-padding-top; + background-color: $white-light; +} + +.discussion-form-container { + padding: $gl-padding-top $gl-padding $gl-padding; } .discussion-notes .disabled-comment { @@ -233,7 +237,12 @@ .discussion-body, .diff-file { .discussion-reply-holder { - padding-top: $gl-padding; + background-color: $white-light; + padding: 10px 16px; + + &.is-replying { + padding-bottom: $gl-padding; + } } } diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index 9d9cbecc958..81e98f358a8 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -47,7 +47,7 @@ ul.notes { } .timeline-entry-inner { - padding: $gl-padding 0; + padding: $gl-padding $gl-btn-padding; border-bottom: 1px solid $white-normal; } @@ -94,6 +94,12 @@ ul.notes { } } + &.note-discussion { + .timeline-entry-inner { + padding: $gl-padding 10px; + } + } + .editing-spinner { display: none; } @@ -346,8 +352,6 @@ ul.notes { } .discussion-notes { - background-color: $white-light; - &:not(:first-child) { border-top: 1px solid $white-normal; margin-top: 20px; @@ -359,6 +363,10 @@ ul.notes { } } + .notes { + background-color: $white-light; + } + a code { top: 0; margin-right: 0; @@ -639,6 +647,8 @@ ul.notes { border-bottom: 1px solid $white-normal; .timeline-entry-inner { + padding-left: $gl-padding; + padding-right: $gl-padding; border-bottom: 0; } } diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 8d5eb2e8c5a..2c840cb407a 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -344,7 +344,6 @@ svg { vertical-align: middle; - margin-right: 3px; } .stage-column { @@ -495,17 +494,12 @@ svg { fill: $gl-text-color-secondary; position: relative; - left: 1px; top: -1px; - width: 16px; - height: 16px; } &.play { svg { - width: 16px; - height: 16px; - left: 3px; + left: 2px; } } } diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 790e91e4431..d7d343b088a 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -935,11 +935,6 @@ pre.light-well { } } - .dropdown-menu-toggle { - width: 100%; - max-width: 300px; - } - .flash-container { padding: 0; } diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index 4dfb397e82c..145f74d9e59 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -56,7 +56,9 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController end def application_setting_params + params[:application_setting] ||= {} import_sources = params[:application_setting][:import_sources] + if import_sources.nil? params[:application_setting][:import_sources] = [] else diff --git a/app/controllers/concerns/notes_actions.rb b/app/controllers/concerns/notes_actions.rb index 839cac3687c..ad4e936a3d4 100644 --- a/app/controllers/concerns/notes_actions.rb +++ b/app/controllers/concerns/notes_actions.rb @@ -41,7 +41,7 @@ module NotesActions @note = Notes::CreateService.new(note_project, current_user, create_params).execute if @note.is_a?(Note) - Notes::RenderService.new(current_user).execute([@note], @project) + Notes::RenderService.new(current_user).execute([@note]) end respond_to do |format| @@ -56,7 +56,7 @@ module NotesActions @note = Notes::UpdateService.new(project, current_user, note_params).execute(note) if @note.is_a?(Note) - Notes::RenderService.new(current_user).execute([@note], @project) + Notes::RenderService.new(current_user).execute([@note]) end respond_to do |format| diff --git a/app/controllers/concerns/renders_notes.rb b/app/controllers/concerns/renders_notes.rb index e7ef297879f..36e3d76ecfe 100644 --- a/app/controllers/concerns/renders_notes.rb +++ b/app/controllers/concerns/renders_notes.rb @@ -4,7 +4,7 @@ module RendersNotes preload_noteable_for_regular_notes(notes) preload_max_access_for_authors(notes, @project) preload_first_time_contribution_for_authors(noteable, notes) - Notes::RenderService.new(current_user).execute(notes, @project) + Notes::RenderService.new(current_user).execute(notes) notes end diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index 283c3e5f1e0..5ac4b8710e2 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -173,7 +173,9 @@ class GroupsController < Groups::ApplicationController .new(@projects, offset: params[:offset].to_i, filter: event_filter) .to_a - Events::RenderService.new(current_user).execute(@events, atom_request: request.format.atom?) + Events::RenderService + .new(current_user) + .execute(@events, atom_request: request.format.atom?) end def user_actions diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb index effb484ef0f..b7f548e0e63 100644 --- a/app/controllers/projects/commit_controller.rb +++ b/app/controllers/projects/commit_controller.rb @@ -34,6 +34,7 @@ class Projects::CommitController < Projects::ApplicationController def pipelines @pipelines = @commit.pipelines.order(id: :desc) + @pipelines = @pipelines.where(ref: params[:ref]) if params[:ref] respond_to do |format| format.html diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb index dd41b9648e8..86c50d88a2a 100644 --- a/app/controllers/projects/notes_controller.rb +++ b/app/controllers/projects/notes_controller.rb @@ -68,7 +68,7 @@ class Projects::NotesController < Projects::ApplicationController private def render_json_with_notes_serializer - Notes::RenderService.new(current_user).execute([note], project) + Notes::RenderService.new(current_user).execute([note]) render json: note_serializer.represent(note) end diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index 61c72aa22a8..7ed9b1fc6d0 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -159,7 +159,10 @@ class IssuableFinder finder_options = { include_subgroups: params[:include_subgroups], only_owned: true } GroupProjectsFinder.new(group: group, current_user: current_user, options: finder_options).execute else - ProjectsFinder.new(current_user: current_user, project_ids_relation: item_project_ids(items)).execute + opts = { current_user: current_user } + opts[:project_ids_relation] = item_project_ids(items) if items + + ProjectsFinder.new(opts).execute end @projects = projects.with_feature_available_for_user(klass, current_user).reorder(nil) @@ -316,9 +319,9 @@ class IssuableFinder def by_project(items) items = if project? - items.of_projects(projects(items)).references_project - elsif projects(items) - items.merge(projects(items).reorder(nil)).join_project + items.of_projects(projects).references_project + elsif projects + items.merge(projects.reorder(nil)).join_project else items.none end diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb index 636316da80a..f0afcac5986 100644 --- a/app/helpers/ci_status_helper.rb +++ b/app/helpers/ci_status_helper.rb @@ -94,7 +94,7 @@ module CiStatusHelper def render_project_pipeline_status(pipeline_status, tooltip_placement: 'auto left') project = pipeline_status.project - path = pipelines_project_commit_path(project, pipeline_status.sha) + path = pipelines_project_commit_path(project, pipeline_status.sha, ref: pipeline_status.ref) render_status_with_link( 'commit', @@ -105,7 +105,7 @@ module CiStatusHelper def render_commit_status(commit, ref: nil, tooltip_placement: 'auto left') project = commit.project - path = pipelines_project_commit_path(project, commit) + path = pipelines_project_commit_path(project, commit, ref: ref) render_status_with_link( 'commit', diff --git a/app/helpers/markup_helper.rb b/app/helpers/markup_helper.rb index 2fe1927a189..39e7a7fd396 100644 --- a/app/helpers/markup_helper.rb +++ b/app/helpers/markup_helper.rb @@ -256,7 +256,7 @@ module MarkupHelper return '' unless html.present? context.merge!( - current_user: (current_user if defined?(current_user)), + current_user: (current_user if defined?(current_user)), # RelativeLinkFilter commit: @commit, diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 15f48e43a28..a64b2acdd77 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -157,40 +157,6 @@ module ProjectsHelper current_user&.recent_push(@project) end - def project_feature_access_select(field) - # Don't show option "everyone with access" if project is private - options = project_feature_options - - level = @project.project_feature.public_send(field) # rubocop:disable GitlabSecurity/PublicSend - - if @project.private? - disabled_option = ProjectFeature::ENABLED - highest_available_option = ProjectFeature::PRIVATE if level == disabled_option - end - - options = options_for_select( - options.invert, - selected: highest_available_option || level, - disabled: disabled_option - ) - - content_tag :div, class: "select-wrapper" do - concat( - content_tag( - :select, - options, - name: "project[project_feature_attributes][#{field}]", - id: "project_project_feature_attributes_#{field}", - class: "pull-right form-control select-control #{repo_children_classes(field)} ", - data: { field: field } - ) - ) - concat( - icon('chevron-down') - ) - end.html_safe - end - def link_to_autodeploy_doc link_to _('About auto deploy'), help_page_path('ci/autodeploy/index'), target: '_blank' end @@ -274,16 +240,6 @@ module ProjectsHelper private - def repo_children_classes(field) - needs_repo_check = [:merge_requests_access_level, :builds_access_level] - return unless needs_repo_check.include?(field) - - classes = "project-repo-select js-repo-select" - classes << " disabled" unless @project.feature_available?(:repository, current_user) - - classes - end - def get_project_nav_tabs(project, current_user) nav_tabs = [:home] @@ -447,14 +403,6 @@ module ProjectsHelper filtered_message.gsub(project.repository_storage_path.chomp('/'), "[REPOS PATH]") end - def project_feature_options - { - ProjectFeature::DISABLED => s_('ProjectFeature|Disabled'), - ProjectFeature::PRIVATE => s_('ProjectFeature|Only team members'), - ProjectFeature::ENABLED => s_('ProjectFeature|Everyone with access') - } - end - def project_child_container_class(view_path) view_path == "projects/issues/issues" ? "prepend-top-default" : "project-show-#{view_path}" end @@ -463,20 +411,6 @@ module ProjectsHelper IssuesFinder.new(current_user, project_id: project.id).execute end - def visibility_select_options(project, selected_level) - level_options = Gitlab::VisibilityLevel.values.each_with_object([]) do |level, level_options| - next if restricted_levels.include?(level) - - level_options << [ - visibility_level_label(level), - { data: { description: visibility_level_description(level, project) } }, - level - ] - end - - options_for_select(level_options, selected_level) - end - def restricted_levels return [] if current_user.admin? diff --git a/app/models/broadcast_message.rb b/app/models/broadcast_message.rb index 0b561203914..4aa236555cb 100644 --- a/app/models/broadcast_message.rb +++ b/app/models/broadcast_message.rb @@ -19,7 +19,7 @@ class BroadcastMessage < ActiveRecord::Base after_commit :flush_redis_cache def self.current - messages = Rails.cache.fetch(CACHE_KEY) { current_and_future_messages.to_a } + messages = Rails.cache.fetch(CACHE_KEY, expires_in: cache_expires_in) { current_and_future_messages.to_a } return messages if messages.empty? @@ -36,6 +36,10 @@ class BroadcastMessage < ActiveRecord::Base where('ends_at > :now', now: Time.zone.now).order_id_asc end + def self.cache_expires_in + nil + end + def active? started? && !ended? end diff --git a/app/models/event.rb b/app/models/event.rb index 3805f6cf857..741a84194e2 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -110,7 +110,10 @@ class Event < ActiveRecord::Base end end + # Remove this method when removing Gitlab.rails5? code. def subclass_from_attributes(attrs) + return super if Gitlab.rails5? + # Without this Rails will keep calling this method on the returned class, # resulting in an infinite loop. return unless self == Event diff --git a/app/services/events/render_service.rb b/app/services/events/render_service.rb index 0b62d8aedf1..bb72d7685dd 100644 --- a/app/services/events/render_service.rb +++ b/app/services/events/render_service.rb @@ -1,15 +1,17 @@ module Events class RenderService < BaseRenderer def execute(events, atom_request: false) - events.map(&:note).compact.group_by(&:project).each do |project, notes| - render_notes(notes, project, atom_request) - end + notes = events.map(&:note).compact + + render_notes(notes, atom_request) end private - def render_notes(notes, project, atom_request) - Notes::RenderService.new(current_user).execute(notes, project, render_options(atom_request)) + def render_notes(notes, atom_request) + Notes::RenderService + .new(current_user) + .execute(notes, render_options(atom_request)) end def render_options(atom_request) diff --git a/app/services/notes/render_service.rb b/app/services/notes/render_service.rb index a77e98c2b07..efc9d6da2aa 100644 --- a/app/services/notes/render_service.rb +++ b/app/services/notes/render_service.rb @@ -3,19 +3,18 @@ module Notes # Renders a collection of Note instances. # # notes - The notes to render. - # project - The project to use for redacting. - # user - The user viewing the notes. - + # # Possible options: + # # requested_path - The request path. # project_wiki - The project's wiki. # ref - The current Git reference. # only_path - flag to turn relative paths into absolute ones. # xhtml - flag to save the html in XHTML - def execute(notes, project, **opts) - renderer = Banzai::ObjectRenderer.new(project, current_user, **opts) - - renderer.render(notes, :note) + def execute(notes, options = {}) + Banzai::ObjectRenderer + .new(user: current_user, redaction_context: options) + .render(notes, :note) end end end diff --git a/app/views/admin/users/_user.html.haml b/app/views/admin/users/_user.html.haml index bbfeceff5b9..2ff4221efbd 100644 --- a/app/views/admin/users/_user.html.haml +++ b/app/views/admin/users/_user.html.haml @@ -33,7 +33,7 @@ = link_to 'Block', block_admin_user_path(user), data: { confirm: 'USER WILL BE BLOCKED! Are you sure?' }, method: :put - if user.access_locked? %li - = link_to 'Unlock', unlock_admin_user_path(user), method: :put, class: 'btn-grouped btn btn-xs btn-success', data: { confirm: 'Are you sure?' } + = link_to _('Unlock'), unlock_admin_user_path(user), method: :put, data: { confirm: _('Are you sure?') } - if can?(current_user, :destroy_user, user) %li.divider - if user.can_be_removed? diff --git a/app/views/devise/shared/_tab_single.html.haml b/app/views/devise/shared/_tab_single.html.haml index f943d25e41a..7bd414d64c3 100644 --- a/app/views/devise/shared/_tab_single.html.haml +++ b/app/views/devise/shared/_tab_single.html.haml @@ -1,3 +1,3 @@ -%ul.nav-links.nav-tabs.new-session-tabs.single-tab +%ul.nav-links.new-session-tabs.single-tab %li.active %a= tab_title diff --git a/app/views/devise/shared/_tabs_ldap.html.haml b/app/views/devise/shared/_tabs_ldap.html.haml index 270191f9452..f50e0724e09 100644 --- a/app/views/devise/shared/_tabs_ldap.html.haml +++ b/app/views/devise/shared/_tabs_ldap.html.haml @@ -1,4 +1,4 @@ -%ul.new-session-tabs.nav-links.nav-tabs{ class: ('custom-provider-tabs' if form_based_providers.any?) } +%ul.nav-links.new-session-tabs{ class: ('custom-provider-tabs' if form_based_providers.any?) } - if crowd_enabled? %li.active = link_to "Crowd", "#crowd", 'data-toggle' => 'tab' diff --git a/app/views/devise/shared/_tabs_normal.html.haml b/app/views/devise/shared/_tabs_normal.html.haml index 1ba6d390875..fa3c3df7f60 100644 --- a/app/views/devise/shared/_tabs_normal.html.haml +++ b/app/views/devise/shared/_tabs_normal.html.haml @@ -1,4 +1,4 @@ -%ul.nav-links.new-session-tabs.nav-tabs{ role: 'tablist' } +%ul.nav-links.new-session-tabs{ role: 'tablist' } %li.active{ role: 'presentation' } %a{ href: '#login-pane', data: { toggle: 'tab' }, role: 'tab' } Sign in - if allow_signup? diff --git a/app/views/projects/_visibility_select.html.haml b/app/views/projects/_visibility_select.html.haml deleted file mode 100644 index 4026b9e3c46..00000000000 --- a/app/views/projects/_visibility_select.html.haml +++ /dev/null @@ -1,9 +0,0 @@ -- if can_change_visibility_level?(@project, current_user) - .select-wrapper - = form.select(model_method, visibility_select_options(@project, selected_level), {}, class: 'form-control visibility-select select-control') - = icon('chevron-down') -- else - .info.js-locked{ data: { help_block: visibility_level_description(@project.visibility_level, @project) } } - = visibility_level_icon(@project.visibility_level) - %strong - = visibility_level_label(@project.visibility_level) diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml index 163432c9263..289bfdd69bc 100644 --- a/app/views/projects/commits/_commit.html.haml +++ b/app/views/projects/commits/_commit.html.haml @@ -5,6 +5,7 @@ - link = commit_path(project, commit, merge_request: merge_request) - cache_key = [project.full_path, + ref, commit.id, Gitlab::CurrentSettings.current_application_settings, @path.presence, @@ -54,7 +55,7 @@ - if commit.status(ref) = render_commit_status(commit, ref: ref) - .js-commit-pipeline-status{ data: { endpoint: pipelines_project_commit_path(project, commit.id) } } + .js-commit-pipeline-status{ data: { endpoint: pipelines_project_commit_path(project, commit.id, ref: ref) } } .commit-sha-group .label.label-monospace diff --git a/app/views/projects/jobs/show.html.haml b/app/views/projects/jobs/show.html.haml index 8beb4ffef45..cbbcc8f1db5 100644 --- a/app/views/projects/jobs/show.html.haml +++ b/app/views/projects/jobs/show.html.haml @@ -55,7 +55,7 @@ - else Job has been erased #{time_ago_with_tooltip(@build.erased_at)} - - if @build.has_trace? + - if @build.running? || @build.has_trace? .build-trace-container.prepend-top-default .top-bar.js-top-bar .js-truncated-info.truncated-info.hidden-xs.pull-left.hidden< diff --git a/app/views/projects/protected_branches/shared/_branches_list.html.haml b/app/views/projects/protected_branches/shared/_branches_list.html.haml index a09c13176c3..300055a4207 100644 --- a/app/views/projects/protected_branches/shared/_branches_list.html.haml +++ b/app/views/projects/protected_branches/shared/_branches_list.html.haml @@ -1,4 +1,4 @@ -.panel.panel-default.protected-branches-list.js-protected-branches-list +.protected-branches-list.js-protected-branches-list - if @protected_branches.empty? .panel-heading %h3.panel-title diff --git a/app/views/projects/protected_tags/shared/_tags_list.html.haml b/app/views/projects/protected_tags/shared/_tags_list.html.haml index 02908e16dc5..3ed82e51dbe 100644 --- a/app/views/projects/protected_tags/shared/_tags_list.html.haml +++ b/app/views/projects/protected_tags/shared/_tags_list.html.haml @@ -1,4 +1,4 @@ -.panel.panel-default.protected-tags-list.js-protected-tags-list +.protected-tags-list.js-protected-tags-list - if @protected_tags.empty? .panel-heading %h3.panel-title diff --git a/app/views/shared/notes/_form.html.haml b/app/views/shared/notes/_form.html.haml index 725bf916592..71c0d740bc8 100644 --- a/app/views/shared/notes/_form.html.haml +++ b/app/views/shared/notes/_form.html.haml @@ -24,20 +24,21 @@ -# DiffNote = f.hidden_field :position - = render layout: 'projects/md_preview', locals: { url: preview_url, referenced_users: true } do - = render 'projects/zen', f: f, - attr: :note, - classes: 'note-textarea js-note-text', - placeholder: "Write a comment or drag your files here...", - supports_quick_actions: supports_quick_actions, - supports_autocomplete: supports_autocomplete - = render 'shared/notes/hints', supports_quick_actions: supports_quick_actions - .error-alert - - .note-form-actions.clearfix - = render partial: 'shared/notes/comment_button' - - = yield(:note_actions) - - %a.btn.btn-cancel.js-note-discard{ role: "button", data: {cancel_text: "Cancel" } } - Discard draft + .discussion-form-container + = render layout: 'projects/md_preview', locals: { url: preview_url, referenced_users: true } do + = render 'projects/zen', f: f, + attr: :note, + classes: 'note-textarea js-note-text', + placeholder: "Write a comment or drag your files here...", + supports_quick_actions: supports_quick_actions, + supports_autocomplete: supports_autocomplete + = render 'shared/notes/hints', supports_quick_actions: supports_quick_actions + .error-alert + + .note-form-actions.clearfix + = render partial: 'shared/notes/comment_button' + + = yield(:note_actions) + + %a.btn.btn-cancel.js-note-discard{ role: "button", data: {cancel_text: "Cancel" } } + Discard draft diff --git a/bin/spinach b/bin/spinach index 474050e29d1..eda81c9ed8a 100755 --- a/bin/spinach +++ b/bin/spinach @@ -1,4 +1,9 @@ #!/usr/bin/env ruby + +# Remove this block when removing rails5? code. +gemfile = %w[1 true].include?(ENV["RAILS5"]) ? "Gemfile.rails5" : "Gemfile" +ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../#{gemfile}", __dir__) + begin load File.expand_path('../spring', __FILE__) rescue LoadError => e diff --git a/changelogs/unreleased/41436-use-simpler-env-vars-for-auto-devops-replicas.yml b/changelogs/unreleased/41436-use-simpler-env-vars-for-auto-devops-replicas.yml new file mode 100644 index 00000000000..ea007670332 --- /dev/null +++ b/changelogs/unreleased/41436-use-simpler-env-vars-for-auto-devops-replicas.yml @@ -0,0 +1,5 @@ +--- +title: 'Introduce simpler env vars for auto devops REPLICAS and CANARY_REPLICAS #41436' +merge_request: 18036 +author: +type: added diff --git a/changelogs/unreleased/41748-vertical-misalignment-login-box.yml b/changelogs/unreleased/41748-vertical-misalignment-login-box.yml new file mode 100644 index 00000000000..77a97400323 --- /dev/null +++ b/changelogs/unreleased/41748-vertical-misalignment-login-box.yml @@ -0,0 +1,5 @@ +--- +title: Refactor CSS to eliminate vertical misalignment of login nav +merge_request: 16275 +author: Takuya Noguchi +type: fixed diff --git a/changelogs/unreleased/44541-fix-file-tree-commit-status-cache.yml b/changelogs/unreleased/44541-fix-file-tree-commit-status-cache.yml new file mode 100644 index 00000000000..ff734fe0c05 --- /dev/null +++ b/changelogs/unreleased/44541-fix-file-tree-commit-status-cache.yml @@ -0,0 +1,5 @@ +--- +title: Fix pipeline status in branch/tag tree page +merge_request: 17995 +author: +type: fixed diff --git a/changelogs/unreleased/44697-prevue.yml b/changelogs/unreleased/44697-prevue.yml new file mode 100644 index 00000000000..9fdce5869ae --- /dev/null +++ b/changelogs/unreleased/44697-prevue.yml @@ -0,0 +1,5 @@ +--- +title: Make toggle markdown preview shortcut only toggle selected field +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/44870-remove-extra-space-around-comment-form-on-merge-requests.yml b/changelogs/unreleased/44870-remove-extra-space-around-comment-form-on-merge-requests.yml deleted file mode 100644 index 53e4ebdb996..00000000000 --- a/changelogs/unreleased/44870-remove-extra-space-around-comment-form-on-merge-requests.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Refactor and tweak margin for note forms on Issuable -merge_request: 18120 -author: Takuya Noguchi -type: fixed diff --git a/changelogs/unreleased/45159-fix-illustration.yml b/changelogs/unreleased/45159-fix-illustration.yml new file mode 100644 index 00000000000..3b9cb45b916 --- /dev/null +++ b/changelogs/unreleased/45159-fix-illustration.yml @@ -0,0 +1,5 @@ +--- +title: Adds illustration for when job log was erased +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/45287-align-icons.yml b/changelogs/unreleased/45287-align-icons.yml new file mode 100644 index 00000000000..0a1cccf9ca6 --- /dev/null +++ b/changelogs/unreleased/45287-align-icons.yml @@ -0,0 +1,5 @@ +--- +title: Align action icons in pipeline graph +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/ab-37125-assigned-issues-query.yml b/changelogs/unreleased/ab-37125-assigned-issues-query.yml new file mode 100644 index 00000000000..5d4aad08764 --- /dev/null +++ b/changelogs/unreleased/ab-37125-assigned-issues-query.yml @@ -0,0 +1,5 @@ +--- +title: Reduce complexity of issuable finder query. +merge_request: 18219 +author: +type: performance diff --git a/changelogs/unreleased/blackst0ne-replace-spinach-project-commits-branches-feature.yml b/changelogs/unreleased/blackst0ne-replace-spinach-project-commits-branches-feature.yml new file mode 100644 index 00000000000..bcfba4ae70d --- /dev/null +++ b/changelogs/unreleased/blackst0ne-replace-spinach-project-commits-branches-feature.yml @@ -0,0 +1,5 @@ +--- +title: "Replace the `project/commits/branches.feature` spinach test with an rspec analog" +merge_request: 18302 +author: "@blackst0ne" +type: other diff --git a/changelogs/unreleased/blackst0ne-replace-spinach-project-issues-milestones-feature.yml b/changelogs/unreleased/blackst0ne-replace-spinach-project-issues-milestones-feature.yml new file mode 100644 index 00000000000..0dcac0a80eb --- /dev/null +++ b/changelogs/unreleased/blackst0ne-replace-spinach-project-issues-milestones-feature.yml @@ -0,0 +1,5 @@ +--- +title: Replace the `project/issues/milestones.feature` spinach test with an rspec analog +merge_request: 18300 +author: "@blackst0ne" +type: other diff --git a/changelogs/unreleased/fix-wiki-find-file-gitaly.yml b/changelogs/unreleased/fix-wiki-find-file-gitaly.yml new file mode 100644 index 00000000000..5c536be7ae5 --- /dev/null +++ b/changelogs/unreleased/fix-wiki-find-file-gitaly.yml @@ -0,0 +1,5 @@ +--- +title: Fix finding wiki file when Gitaly is enabled +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/fl-pipelines-details-axios.yml b/changelogs/unreleased/fl-pipelines-details-axios.yml new file mode 100644 index 00000000000..0b72e54cba3 --- /dev/null +++ b/changelogs/unreleased/fl-pipelines-details-axios.yml @@ -0,0 +1,5 @@ +--- +title: Replace vue resource with axios for pipelines details page +merge_request: +author: +type: other diff --git a/changelogs/unreleased/move-estimate-only-pane-vue-component.yml b/changelogs/unreleased/move-estimate-only-pane-vue-component.yml new file mode 100644 index 00000000000..b6c538f70b3 --- /dev/null +++ b/changelogs/unreleased/move-estimate-only-pane-vue-component.yml @@ -0,0 +1,5 @@ +--- +title: Move TimeTrackingEstimateOnlyPane vue component +merge_request: 18318 +author: George Tsiolis +type: performance diff --git a/changelogs/unreleased/move-help-state-vue-component.yml b/changelogs/unreleased/move-help-state-vue-component.yml new file mode 100644 index 00000000000..6108368cde0 --- /dev/null +++ b/changelogs/unreleased/move-help-state-vue-component.yml @@ -0,0 +1,5 @@ +--- +title: Move TimeTrackingHelpState vue component +merge_request: 18319 +author: George Tsiolis +type: performance diff --git a/changelogs/unreleased/rendering-markdown-multiple-projects.yml b/changelogs/unreleased/rendering-markdown-multiple-projects.yml new file mode 100644 index 00000000000..8685772c089 --- /dev/null +++ b/changelogs/unreleased/rendering-markdown-multiple-projects.yml @@ -0,0 +1,5 @@ +--- +title: Support Markdown rendering using multiple projects +merge_request: +author: +type: performance diff --git a/changelogs/unreleased/unresolved-discussions-vue-component-i18n-and-tests.yml b/changelogs/unreleased/unresolved-discussions-vue-component-i18n-and-tests.yml new file mode 100644 index 00000000000..d99a9c93c0b --- /dev/null +++ b/changelogs/unreleased/unresolved-discussions-vue-component-i18n-and-tests.yml @@ -0,0 +1,5 @@ +--- +title: Add i18n and update specs for UnresolvedDiscussions vue component +merge_request: 17866 +author: George Tsiolis +type: performance diff --git a/changelogs/unreleased/winh-dropdown-entry-unlocking.yml b/changelogs/unreleased/winh-dropdown-entry-unlocking.yml new file mode 100644 index 00000000000..fc669af1f57 --- /dev/null +++ b/changelogs/unreleased/winh-dropdown-entry-unlocking.yml @@ -0,0 +1,5 @@ +--- +title: Remove green background from unlock button in admin area +merge_request: 18288 +author: +type: changed diff --git a/changelogs/unreleased/zj-branch-containing-sha-opt-out.yml b/changelogs/unreleased/zj-branch-containing-sha-opt-out.yml new file mode 100644 index 00000000000..3d11ee588ae --- /dev/null +++ b/changelogs/unreleased/zj-branch-containing-sha-opt-out.yml @@ -0,0 +1,5 @@ +--- +title: Detecting branchnames containing a commit uses Gitaly by default +merge_request: +author: +type: performance diff --git a/changelogs/unreleased/zj-tag-containing-sha-opt-out.yml b/changelogs/unreleased/zj-tag-containing-sha-opt-out.yml new file mode 100644 index 00000000000..4774c7811d1 --- /dev/null +++ b/changelogs/unreleased/zj-tag-containing-sha-opt-out.yml @@ -0,0 +1,5 @@ +--- +title: Detecting tags containing a commit uses Gitaly by default +merge_request: +author: +type: performance diff --git a/config/karma.config.js b/config/karma.config.js index c378e621953..61f02294157 100644 --- a/config/karma.config.js +++ b/config/karma.config.js @@ -1,5 +1,6 @@ var path = require('path'); var webpack = require('webpack'); +var argumentsParser = require('commander'); var webpackConfig = require('./webpack.config.js'); var ROOT_PATH = path.resolve(__dirname, '..'); @@ -14,6 +15,24 @@ if (webpackConfig.plugins) { }); } +var testFiles = argumentsParser + .option( + '-f, --filter-spec [filter]', + 'Filter run spec files by path. Multiple filters are like a logical OR.', + (val, memo) => { + memo.push(val); + return memo; + }, + [] + ) + .parse(process.argv).filterSpec; + +webpackConfig.plugins.push( + new webpack.DefinePlugin({ + 'process.env.TEST_FILES': JSON.stringify(testFiles), + }) +); + webpackConfig.devtool = 'cheap-inline-source-map'; // Karma configuration diff --git a/db/fixtures/development/01_admin.rb b/db/fixtures/development/01_admin.rb index dfb50c195c1..1e260236dc5 100644 --- a/db/fixtures/development/01_admin.rb +++ b/db/fixtures/development/01_admin.rb @@ -1,14 +1,14 @@ require './spec/support/sidekiq' Gitlab::Seeder.quiet do - User.seed do |s| - s.id = 1 - s.name = 'Administrator' - s.email = 'admin@example.com' - s.notification_email = 'admin@example.com' - s.username = 'root' - s.password = '5iveL!fe' - s.admin = true - s.confirmed_at = DateTime.now - end + User.create!( + name: 'Administrator', + email: 'admin@example.com', + username: 'root', + password: '5iveL!fe', + admin: true, + confirmed_at: DateTime.now + ) + + print '.' end diff --git a/db/fixtures/development/19_environments.rb b/db/fixtures/development/19_environments.rb index c1bbc9af6d6..00a14f458d1 100644 --- a/db/fixtures/development/19_environments.rb +++ b/db/fixtures/development/19_environments.rb @@ -28,7 +28,11 @@ class Gitlab::Seeder::Environments end def create_merge_request_review_deployments! - @project.merge_requests.sample(4).map do |merge_request| + @project + .merge_requests + .select { |mr| mr.source_branch.match(/\p{Alnum}+/) } + .sample(4) + .each do |merge_request| next unless merge_request.diff_head_sha create_deployment!( diff --git a/doc/README.md b/doc/README.md index 604f7244a34..178e6567845 100644 --- a/doc/README.md +++ b/doc/README.md @@ -159,8 +159,12 @@ applications are always responsive and available. GitLab collects and displays performance metrics for deployed apps using Prometheus so you can know in an instant how code changes impact your production environment. +- [GitLab Prometheus](administration/monitoring/prometheus/index.md): Configure the bundled Prometheus to collect various metrics from your GitLab instance. +- [Prometheus project integration](user/project/integrations/prometheus.md): Configure the Prometheus integration per project and monitor your CI/CD environments. +- [Prometheus metrics](user/project/integrations/prometheus_library/metrics.md): Let Prometheus collect metrics from various services, like Kubernetes, NGINX, NGINX ingress controller, HAProxy, and Amazon Cloud Watch. +- [GitLab Performance Monitoring](administration/monitoring/performance/index.md): Use InfluxDB and Grafana to monitor the performance of your GitLab instance (will be eventually replaced by Prometheus). +- [Health check](user/admin_area/monitoring/health_check.md): GitLab provides liveness and readiness probes to indicate service health and reachability to required services. - [GitLab Cycle Analytics](user/project/cycle_analytics.md): Cycle Analytics measures the time it takes to go from an [idea to production](https://about.gitlab.com/2016/08/05/continuous-integration-delivery-and-deployment-with-gitlab/#from-idea-to-production-with-gitlab) for each project you have. -- [GitLab Performance Monitoring](administration/monitoring/performance/index.md) ## Getting started with GitLab diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 68aa64b3834..bd7ffb2befb 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -869,37 +869,29 @@ skip the download step. - Introduced in GitLab Runner v0.7.0 for non-Windows platforms. - Windows support was added in GitLab Runner v.1.0.0. - From GitLab 9.2, caches are restored before artifacts. -- Currently not all executors are supported. +- Not all executors are [supported](https://docs.gitlab.com/runner/executors/#compatibility-chart). - Job artifacts are only collected for successful jobs by default. `artifacts` is used to specify a list of files and directories which should be -attached to the job after success. You can only use paths that are within the -project workspace. To pass artifacts between different jobs, see [dependencies](#dependencies). -Below are some examples. +attached to the job after success. -Send all files in `binaries` and `.config`: +The artifacts will be sent to GitLab after the job finishes successfully and will +be available for download in the GitLab UI. -```yaml -artifacts: - paths: - - binaries/ - - .config -``` +[Read more about artifacts.](../../user/project/pipelines/job_artifacts.md) -Send all Git untracked files: +### `artifacts:paths` -```yaml -artifacts: - untracked: true -``` +You can only use paths that are within the project workspace. To pass artifacts +between different jobs, see [dependencies](#dependencies). -Send all Git untracked files and files in `binaries`: +Send all files in `binaries` and `.config`: ```yaml artifacts: - untracked: true paths: - binaries/ + - .config ``` To disable artifact passing, define the job with empty [dependencies](#dependencies): @@ -933,11 +925,6 @@ release-job: - tags ``` -The artifacts will be sent to GitLab after the job finishes successfully and will -be available for download in the GitLab UI. - -[Read more about artifacts.](../../user/project/pipelines/job_artifacts.md) - ### `artifacts:name` > Introduced in GitLab 8.6 and GitLab Runner v1.1.0. @@ -954,26 +941,30 @@ To create an archive with a name of the current job: job: artifacts: name: "$CI_JOB_NAME" + paths: + - binaries/ ``` To create an archive with a name of the current branch or tag including only -the files that are untracked by Git: +the binaries directory: ```yaml job: artifacts: name: "$CI_COMMIT_REF_NAME" - untracked: true + paths: + - binaries/ ``` To create an archive with a name of the current job and the current branch or -tag including only the files that are untracked by Git: +tag including only the binaries directory: ```yaml job: artifacts: name: "$CI_JOB_NAME-$CI_COMMIT_REF_NAME" - untracked: true + paths: + - binaries/ ``` To create an archive with a name of the current [stage](#stages) and branch name: @@ -982,7 +973,8 @@ To create an archive with a name of the current [stage](#stages) and branch name job: artifacts: name: "$CI_JOB_STAGE-$CI_COMMIT_REF_NAME" - untracked: true + paths: + - binaries/ ``` --- @@ -994,7 +986,8 @@ If you use **Windows Batch** to run your shell scripts you need to replace job: artifacts: name: "%CI_JOB_STAGE%-%CI_COMMIT_REF_NAME%" - untracked: true + paths: + - binaries/ ``` If you use **Windows PowerShell** to run your shell scripts you need to replace @@ -1004,7 +997,33 @@ If you use **Windows PowerShell** to run your shell scripts you need to replace job: artifacts: name: "$env:CI_JOB_STAGE-$env:CI_COMMIT_REF_NAME" - untracked: true + paths: + - binaries/ +``` + +### `artifacts:untracked` + +`artifacts:untracked` is used to add all Git untracked files as artifacts (along +to the paths defined in `artifacts:paths`). + +NOTE: **Note:** +To exclude the folders/files which should not be a part of `untracked` just +add them to `.gitignore`. + +Send all Git untracked files: + +```yaml +artifacts: + untracked: true +``` + +Send all Git untracked files and files in `binaries`: + +```yaml +artifacts: + untracked: true + paths: + - binaries/ ``` ### `artifacts:when` diff --git a/doc/development/i18n/externalization.md b/doc/development/i18n/externalization.md index 856ef882453..b1bec84a2f3 100644 --- a/doc/development/i18n/externalization.md +++ b/doc/development/i18n/externalization.md @@ -131,6 +131,9 @@ There is also and alternative method to [translate messages from validation erro ### Interpolation +Placeholders in translated text should match the code style of the respective source file. +For example use `%{created_at}` in Ruby but `%{createdAt}` in JavaScript. + - In Ruby/HAML: ```ruby @@ -141,11 +144,19 @@ There is also and alternative method to [translate messages from validation erro ```js import { __, sprintf } from '~/locale'; - sprintf(__('Hello %{username}'), { username: 'Joe' }) => 'Hello Joe' + + sprintf(__('Hello %{username}'), { username: 'Joe' }); // => 'Hello Joe' ``` -The placeholders should match the code style of the respective source file. -For example use `%{created_at}` in Ruby but `%{createdAt}` in JavaScript. + By default, `sprintf` escapes the placeholder values. + If you want to take care of that yourself, you can pass `false` as third argument. + + ```js + import { __, sprintf } from '~/locale'; + + sprintf(__('This is %{value}'), { value: '<strong>bold</strong>' }); // => 'This is <strong>bold</strong>' + sprintf(__('This is %{value}'), { value: '<strong>bold</strong>' }, false); // => 'This is <strong>bold</strong>' + ``` ### Plurals diff --git a/doc/development/testing_guide/frontend_testing.md b/doc/development/testing_guide/frontend_testing.md index 0c63f51cb45..0a6f402d5d2 100644 --- a/doc/development/testing_guide/frontend_testing.md +++ b/doc/development/testing_guide/frontend_testing.md @@ -152,19 +152,33 @@ is sufficient (and saves you some time). ### Live testing and focused testing While developing locally, it may be helpful to keep karma running so that you -can get instant feedback on as you write tests and modify code. To do this -you can start karma with `npm run karma-start`. It will compile the javascript +can get instant feedback on as you write tests and modify code. To do this +you can start karma with `yarn run karma-start`. It will compile the javascript assets and run a server at `http://localhost:9876/` where it will automatically -run the tests on any browser which connects to it. You can enter that url on +run the tests on any browser which connects to it. You can enter that url on multiple browsers at once to have it run the tests on each in parallel. While karma is running, any changes you make will instantly trigger a recompile and retest of the entire test suite, so you can see instantly if you've broken -a test with your changes. You can use [jasmine focused][jasmine-focus] or +a test with your changes. You can use [jasmine focused][jasmine-focus] or excluded tests (with `fdescribe` or `xdescribe`) to get karma to run only the tests you want while you're working on a specific feature, but make sure to remove these directives when you commit your code. +It is also possible to only run karma on specific folders or files by filtering +the run tests via the argument `--filter-spec` or short `-f`: + +```bash +# Run all files +yarn karma-start +# Run specific spec files +yarn karma-start --filter-spec profile/account/components/update_username_spec.js +# Run specific spec folder +yarn karma-start --filter-spec profile/account/components/ +# Run all specs which path contain vue_shared or vie +yarn karma-start -f vue_shared -f vue_mr_widget +``` + ## RSpec feature integration tests Information on setting up and running RSpec integration tests with @@ -176,7 +190,7 @@ Information on setting up and running RSpec integration tests with Similar errors will be thrown if you're using JavaScript features not yet supported by the PhantomJS test runner which is used for both Karma and RSpec -tests. We polyfill some JavaScript objects for older browsers, but some +tests. We polyfill some JavaScript objects for older browsers, but some features are still unavailable: - Array.from @@ -188,7 +202,7 @@ features are still unavailable: - Symbol/Symbol.iterator - Spread -Until these are polyfilled appropriately, they should not be used. Please +Until these are polyfilled appropriately, they should not be used. Please update this list with additional unsupported features. ### RSpec errors due to JavaScript @@ -223,7 +237,7 @@ end ### Spinach errors due to missing JavaScript NOTE: **Note:** Since we are discouraging the use of Spinach when writing new -feature tests, you shouldn't ever need to use this. This information is kept +feature tests, you shouldn't ever need to use this. This information is kept available for legacy purposes only. In Spinach, the JavaScript driver is enabled differently. In the `*.feature` diff --git a/doc/install/installation.md b/doc/install/installation.md index 3cf6f7b7ddf..85174b64ff7 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -301,7 +301,7 @@ sudo usermod -aG redis git ### Clone the Source # Clone GitLab repository - sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 10-6-stable gitlab + sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 10-7-stable gitlab **Note:** You can change `10-6-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server! diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md index e88b787187c..89b8ea209b3 100644 --- a/doc/topics/autodevops/index.md +++ b/doc/topics/autodevops/index.md @@ -455,17 +455,19 @@ The following variables can be used for setting up the Auto DevOps domain, providing a custom Helm chart, or scaling your application. PostgreSQL can be also be customized, and you can easily use a [custom buildpack](#custom-buildpacks). -| **Variable** | **Description** | -| ------------ | --------------- | -| `AUTO_DEVOPS_DOMAIN` | The [Auto DevOps domain](#auto-devops-domain); by default set automatically by the [Auto DevOps setting](#enabling-auto-devops). | -| `AUTO_DEVOPS_CHART` | The Helm Chart used to deploy your apps; defaults to the one [provided by GitLab](https://gitlab.com/charts/charts.gitlab.io/tree/master/charts/auto-deploy-app). | -| `PRODUCTION_REPLICAS` | The number of replicas to deploy in the production environment; defaults to 1. | -| `CANARY_PRODUCTION_REPLICAS`| The number of canary replicas to deploy for [Canary Deployments](https://docs.gitlab.com/ee/user/project/canary_deployments.html) in the production environment. | -| `POSTGRES_ENABLED` | Whether PostgreSQL is enabled; defaults to `"true"`. Set to `false` to disable the automatic deployment of PostgreSQL. | -| `POSTGRES_USER` | The PostgreSQL user; defaults to `user`. Set it to use a custom username. | -| `POSTGRES_PASSWORD` | The PostgreSQL password; defaults to `testing-password`. Set it to use a custom password. | -| `POSTGRES_DB` | The PostgreSQL database name; defaults to the value of [`$CI_ENVIRONMENT_SLUG`](../../ci/variables/README.md#predefined-variables-environment-variables). Set it to use a custom database name. | -| `BUILDPACK_URL` | The buildpack's full URL. It can point to either Git repositories or a tarball URL. For Git repositories, it is possible to point to a specific `ref`, for example `https://github.com/heroku/heroku-buildpack-ruby.git#v142`| +| **Variable** | **Description** | +| ------------ | --------------- | +| `AUTO_DEVOPS_DOMAIN` | The [Auto DevOps domain](#auto-devops-domain); by default set automatically by the [Auto DevOps setting](#enabling-auto-devops). | +| `AUTO_DEVOPS_CHART` | The Helm Chart used to deploy your apps; defaults to the one [provided by GitLab](https://gitlab.com/charts/charts.gitlab.io/tree/master/charts/auto-deploy-app). | +| `REPLICAS` | The number of replicas to deploy; defaults to 1. | +| `PRODUCTION_REPLICAS` | The number of replicas to deploy in the production environment. This takes precedence over `REPLICAS`; defaults to 1. | +| `CANARY_REPLICAS` | The number of canary replicas to deploy for [Canary Deployments](https://docs.gitlab.com/ee/user/project/canary_deployments.html); defaults to 1 | +| `CANARY_PRODUCTION_REPLICAS` | The number of canary replicas to deploy for [Canary Deployments](https://docs.gitlab.com/ee/user/project/canary_deployments.html) in the production environment. This takes precedence over `CANARY_REPLICAS`; defaults to 1 | +| `POSTGRES_ENABLED` | Whether PostgreSQL is enabled; defaults to `"true"`. Set to `false` to disable the automatic deployment of PostgreSQL. | +| `POSTGRES_USER` | The PostgreSQL user; defaults to `user`. Set it to use a custom username. | +| `POSTGRES_PASSWORD` | The PostgreSQL password; defaults to `testing-password`. Set it to use a custom password. | +| `POSTGRES_DB` | The PostgreSQL database name; defaults to the value of [`$CI_ENVIRONMENT_SLUG`](../../ci/variables/README.md#predefined-variables-environment-variables). Set it to use a custom database name. | +| `BUILDPACK_URL` | The buildpack's full URL. It can point to either Git repositories or a tarball URL. For Git repositories, it is possible to point to a specific `ref`, for example `https://github.com/heroku/heroku-buildpack-ruby.git#v142` | TIP: **Tip:** Set up the replica variables using a @@ -496,8 +498,9 @@ The general rule is: `TRACK_ENV_REPLICAS`. Where: That way, you can define your own `TRACK_ENV_REPLICAS` variables with which you will be able to scale the pod's replicas easily. -In the example below, the environment's name is `qa` which would result in -looking for the `QA_REPLICAS` environment variable: +In the example below, the environment's name is `qa` and it deploys the track +`foo` which would result in looking for the `FOO_QA_REPLICAS` environment +variable: ```yaml QA testing: @@ -505,11 +508,11 @@ QA testing: environment: name: qa script: - - deploy qa + - deploy foo ``` -If, in addition, there was also a `track: foo` defined in the application's Helm -chart, like: +The track `foo` being referenced would also need to be defined in the +application's Helm chart, like: ```yaml replicaCount: 1 @@ -531,8 +534,6 @@ service: internalPort: 5000 ``` -then the environment variable would be `FOO_QA_REPLICAS`. - ## Currently supported languages NOTE: **Note:** diff --git a/doc/update/10.6-to-10.7.md b/doc/update/10.6-to-10.7.md new file mode 100644 index 00000000000..4a76ae14d2e --- /dev/null +++ b/doc/update/10.6-to-10.7.md @@ -0,0 +1,361 @@ +--- +comments: false +--- + +# From 10.6 to 10.7 + +Make sure you view this update guide from the tag (version) of GitLab you would +like to install. In most cases this should be the highest numbered production +tag (without rc in it). You can select the tag in the version dropdown at the +top left corner of GitLab (below the menu bar). + +If the highest number stable branch is unclear please check the +[GitLab Blog](https://about.gitlab.com/blog/archives.html) for installation +guide links by version. + +### 1. Stop server + +```bash +sudo service gitlab stop +``` + +### 2. Backup + +NOTE: If you installed GitLab from source, make sure `rsync` is installed. + +```bash +cd /home/git/gitlab + +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +### 3. Update Ruby + +NOTE: GitLab 9.0 and higher only support Ruby 2.3.x and dropped support for Ruby 2.1.x. Be +sure to upgrade your interpreter if necessary. + +You can check which version you are running with `ruby -v`. + +Download and compile Ruby: + +```bash +mkdir /tmp/ruby && cd /tmp/ruby +curl --remote-name --progress https://cache.ruby-lang.org/pub/ruby/2.3/ruby-2.3.6.tar.gz +echo '4e6a0f828819e15d274ae58485585fc8b7caace0 ruby-2.3.6.tar.gz' | shasum -c - && tar xzf ruby-2.3.6.tar.gz +cd ruby-2.3.6 +./configure --disable-install-rdoc +make +sudo make install +``` + +Install Bundler: + +```bash +sudo gem install bundler --no-ri --no-rdoc +``` + +### 4. Update Node + +GitLab utilizes [webpack](http://webpack.js.org) to compile frontend assets. +This requires a minimum version of node v6.0.0. + +You can check which version you are running with `node -v`. If you are running +a version older than `v6.0.0` you will need to update to a newer version. You +can find instructions to install from community maintained packages or compile +from source at the nodejs.org website. + +<https://nodejs.org/en/download/> + +GitLab also requires the use of yarn `>= v1.2.0` to manage JavaScript +dependencies. + +```bash +curl --silent --show-error https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add - +echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list +sudo apt-get update +sudo apt-get install yarn +``` + +More information can be found on the [yarn website](https://yarnpkg.com/en/docs/install). + +### 5. Update Go + +NOTE: GitLab 9.2 and higher only supports Go 1.8.3 and dropped support for Go +1.5.x through 1.7.x. Be sure to upgrade your installation if necessary. + +You can check which version you are running with `go version`. + +Download and install Go: + +```bash +# Remove former Go installation folder +sudo rm -rf /usr/local/go + +curl --remote-name --progress https://storage.googleapis.com/golang/go1.8.3.linux-amd64.tar.gz +echo '1862f4c3d3907e59b04a757cfda0ea7aa9ef39274af99a784f5be843c80c6772 go1.8.3.linux-amd64.tar.gz' | shasum -a256 -c - && \ + sudo tar -C /usr/local -xzf go1.8.3.linux-amd64.tar.gz +sudo ln -sf /usr/local/go/bin/{go,godoc,gofmt} /usr/local/bin/ +rm go1.8.3.linux-amd64.tar.gz +``` + +### 6. Get latest code + +```bash +cd /home/git/gitlab + +sudo -u git -H git fetch --all --prune +sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically +sudo -u git -H git checkout -- locale +``` + +For GitLab Community Edition: + +```bash +cd /home/git/gitlab + +sudo -u git -H git checkout 10-7-stable +``` + +OR + +For GitLab Enterprise Edition: + +```bash +cd /home/git/gitlab + +sudo -u git -H git checkout 10-7-stable-ee +``` + +### 7. Update gitlab-shell + +```bash +cd /home/git/gitlab-shell + +sudo -u git -H git fetch --all --tags --prune +sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_SHELL_VERSION) +sudo -u git -H bin/compile +``` + +### 8. Update gitlab-workhorse + +Install and compile gitlab-workhorse. GitLab-Workhorse uses +[GNU Make](https://www.gnu.org/software/make/). +If you are not using Linux you may have to run `gmake` instead of +`make` below. + +```bash +cd /home/git/gitlab-workhorse + +sudo -u git -H git fetch --all --tags --prune +sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_WORKHORSE_VERSION) +sudo -u git -H make +``` + +### 9. Update Gitaly + +#### New Gitaly configuration options required + +In order to function Gitaly needs some additional configuration information. Below we assume you installed Gitaly in `/home/git/gitaly` and GitLab Shell in `/home/git/gitlab-shell`. + +```shell +echo ' +[gitaly-ruby] +dir = "/home/git/gitaly/ruby" + +[gitlab-shell] +dir = "/home/git/gitlab-shell" +' | sudo -u git tee -a /home/git/gitaly/config.toml +``` + +#### Check Gitaly configuration + +Due to a bug in the `rake gitlab:gitaly:install` script your Gitaly +configuration file may contain syntax errors. The block name +`[[storages]]`, which may occur more than once in your `config.toml` +file, should be `[[storage]]` instead. + +```shell +sudo -u git -H sed -i.pre-10.1 's/\[\[storages\]\]/[[storage]]/' /home/git/gitaly/config.toml +``` + +#### Compile Gitaly + +```shell +cd /home/git/gitaly +sudo -u git -H git fetch --all --tags --prune +sudo -u git -H git checkout v$(</home/git/gitlab/GITALY_SERVER_VERSION) +sudo -u git -H make +``` + +### 10. Update MySQL permissions + +If you are using MySQL you need to grant the GitLab user the necessary +permissions on the database: + +```bash +mysql -u root -p -e "GRANT TRIGGER ON \`gitlabhq_production\`.* TO 'git'@'localhost';" +``` + +If you use MySQL with replication, or just have MySQL configured with binary logging, +you will need to also run the following on all of your MySQL servers: + +```bash +mysql -u root -p -e "SET GLOBAL log_bin_trust_function_creators = 1;" +``` + +You can make this setting permanent by adding it to your `my.cnf`: + +``` +log_bin_trust_function_creators=1 +``` + +### 11. Update configuration files + +#### New configuration options for `gitlab.yml` + +There might be configuration options available for [`gitlab.yml`][yaml]. View them with the command below and apply them manually to your current `gitlab.yml`: + +```sh +cd /home/git/gitlab + +git diff origin/10-6-stable:config/gitlab.yml.example origin/10-7-stable:config/gitlab.yml.example +``` + +#### Nginx configuration + +Ensure you're still up-to-date with the latest NGINX configuration changes: + +```sh +cd /home/git/gitlab + +# For HTTPS configurations +git diff origin/10-6-stable:lib/support/nginx/gitlab-ssl origin/10-7-stable:lib/support/nginx/gitlab-ssl + +# For HTTP configurations +git diff origin/10-6-stable:lib/support/nginx/gitlab origin/10-7-stable:lib/support/nginx/gitlab +``` + +If you are using Strict-Transport-Security in your installation to continue using it you must enable it in your Nginx +configuration as GitLab application no longer handles setting it. + +If you are using Apache instead of NGINX please see the updated [Apache templates]. +Also note that because Apache does not support upstreams behind Unix sockets you +will need to let gitlab-workhorse listen on a TCP port. You can do this +via [/etc/default/gitlab]. + +[Apache templates]: https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/web-server/apache +[/etc/default/gitlab]: https://gitlab.com/gitlab-org/gitlab-ce/blob/10-7-stable/lib/support/init.d/gitlab.default.example#L38 + +#### SMTP configuration + +If you're installing from source and use SMTP to deliver mail, you will need to add the following line +to config/initializers/smtp_settings.rb: + +```ruby +ActionMailer::Base.delivery_method = :smtp +``` + +See [smtp_settings.rb.sample] as an example. + +[smtp_settings.rb.sample]: https://gitlab.com/gitlab-org/gitlab-ce/blob/10-7-stable/config/initializers/smtp_settings.rb.sample#L13 + +#### Init script + +There might be new configuration options available for [`gitlab.default.example`][gl-example]. View them with the command below and apply them manually to your current `/etc/default/gitlab`: + +```sh +cd /home/git/gitlab + +git diff origin/10-6-stable:lib/support/init.d/gitlab.default.example origin/10-7-stable:lib/support/init.d/gitlab.default.example +``` + +Ensure you're still up-to-date with the latest init script changes: + +```bash +cd /home/git/gitlab + +sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab +``` + +For Ubuntu 16.04.1 LTS: + +```bash +sudo systemctl daemon-reload +``` + +### 12. Install libs, migrations, etc. + +```bash +cd /home/git/gitlab + +# MySQL installations (note: the line below states '--without postgres') +sudo -u git -H bundle install --without postgres development test --deployment + +# PostgreSQL installations (note: the line below states '--without mysql') +sudo -u git -H bundle install --without mysql development test --deployment + +# Optional: clean up old gems +sudo -u git -H bundle clean + +# Run database migrations +sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production + +# Compile GetText PO files + +sudo -u git -H bundle exec rake gettext:compile RAILS_ENV=production + +# Update node dependencies and recompile assets +sudo -u git -H bundle exec rake yarn:install gitlab:assets:clean gitlab:assets:compile RAILS_ENV=production NODE_ENV=production + +# Clean up cache +sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production +``` + +**MySQL installations**: Run through the `MySQL strings limits` and `Tables and data conversion to utf8mb4` [tasks](../install/database_mysql.md). + +### 13. Start application + +```bash +sudo service gitlab start +sudo service nginx restart +``` + +### 14. Check application status + +Check if GitLab and its environment are configured correctly: + +```bash +cd /home/git/gitlab + +sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production +``` + +To make sure you didn't miss anything run a more thorough check: + +```bash +cd /home/git/gitlab + +sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production +``` + +If all items are green, then congratulations, the upgrade is complete! + +## Things went south? Revert to previous version (10.5) + +### 1. Revert the code to the previous version + +Follow the [upgrade guide from 10.5 to 10.6](10.5-to-10.6.md), except for the +database migration (the backup is already migrated to the previous version). + +### 2. Restore from the backup + +```bash +cd /home/git/gitlab + +sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production +``` + +If you have more than one backup `*.tar` file(s) please add `BACKUP=timestamp_of_backup` to the command above. + +[yaml]: https://gitlab.com/gitlab-org/gitlab-ce/blob/10-7-stable/config/gitlab.yml.example +[gl-example]: https://gitlab.com/gitlab-org/gitlab-ce/blob/10-7-stable/lib/support/init.d/gitlab.default.example diff --git a/doc/user/discussions/index.md b/doc/user/discussions/index.md index eacfe2baa27..159109e8954 100644 --- a/doc/user/discussions/index.md +++ b/doc/user/discussions/index.md @@ -14,6 +14,10 @@ The comment area supports [Markdown] and [quick actions]. One can edit their own comment at any time, and anyone with [Master access level][permissions] or higher can also edit a comment made by someone else. +You could also reply to the notification email in order to reply to a comment, +provided that [Reply by email] is configured by your GitLab admin. This also +supports [Markdown] and [quick actions] as if replied from the web. + Apart from the standard comments, you also have the option to create a comment in the form of a resolvable or threaded discussion. @@ -283,3 +287,4 @@ edit existing comments. Non-team members are restricted from adding or editing c [markdown]: ../markdown.md [quick actions]: ../project/quick_actions.md [permissions]: ../permissions.md +[Reply by email]: ../../administration/reply_by_email.md diff --git a/doc/user/project/integrations/prometheus_library/cloudwatch.md b/doc/user/project/integrations/prometheus_library/cloudwatch.md index 34a0b97a171..bf6c0dc0e7e 100644 --- a/doc/user/project/integrations/prometheus_library/cloudwatch.md +++ b/doc/user/project/integrations/prometheus_library/cloudwatch.md @@ -6,7 +6,7 @@ GitLab has support for automatically detecting and monitoring AWS resources, sta ## Requirements -The [Prometheus service](../prometheus/index.md) must be enabled. +The [Prometheus service](../prometheus.md) must be enabled. ## Metrics supported diff --git a/doc/user/project/integrations/prometheus_library/haproxy.md b/doc/user/project/integrations/prometheus_library/haproxy.md index 518018e5839..cd398f7c0fd 100644 --- a/doc/user/project/integrations/prometheus_library/haproxy.md +++ b/doc/user/project/integrations/prometheus_library/haproxy.md @@ -5,7 +5,7 @@ GitLab has support for automatically detecting and monitoring HAProxy. This is p ## Requirements -The [Prometheus service](../prometheus/index.md) must be enabled. +The [Prometheus service](../prometheus.md) must be enabled. ## Metrics supported diff --git a/doc/user/project/integrations/prometheus_library/metrics.md b/doc/user/project/integrations/prometheus_library/metrics.md index f09ecf9ff2d..96a22316265 100644 --- a/doc/user/project/integrations/prometheus_library/metrics.md +++ b/doc/user/project/integrations/prometheus_library/metrics.md @@ -1,4 +1,5 @@ # Prometheus Metrics library + > [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8935) in GitLab 9.0 GitLab offers automatic detection of select [Prometheus exporters](https://prometheus.io/docs/instrumenting/exporters/). Currently supported exporters are: @@ -15,7 +16,7 @@ We have tried to surface the most important metrics for each exporter, and will GitLab retrieves performance data from the configured Prometheus server, and attempts to identifying the presence of known metrics. Once identified, GitLab then needs to be able to map the data to a particular environment. In order to isolate and only display relevant metrics for a given environment, GitLab needs a method to detect which labels are associated. To do that, -GitLab uses the defined queries and fills in the environment specific variables. Typically this involves looking for the [$CI_ENVIRONMENT_SLUG](https://docs.gitlab.com/ee/ci/variables/#predefined-variables-environment-variables), but may also include other information such as the project's Kubernetes namespace. Each search query is defined in the [exporter specific documentation](#prometheus-metrics-library). +GitLab uses the defined queries and fills in the environment specific variables. Typically this involves looking for the [$CI_ENVIRONMENT_SLUG](../../../../ci/variables/README.md#predefined-variables-environment-variables), but may also include other information such as the project's Kubernetes namespace. Each search query is defined in the [exporter specific documentation](#prometheus-metrics-library). ## Adding to the library diff --git a/doc/user/project/integrations/prometheus_library/nginx.md b/doc/user/project/integrations/prometheus_library/nginx.md index 7fb8369d3c1..fea3231006b 100644 --- a/doc/user/project/integrations/prometheus_library/nginx.md +++ b/doc/user/project/integrations/prometheus_library/nginx.md @@ -6,7 +6,7 @@ GitLab has support for automatically detecting and monitoring NGINX. This is pro ## Requirements -The [Prometheus service](../prometheus/index.md) must be enabled. +The [Prometheus service](../prometheus.md) must be enabled. ## Metrics supported diff --git a/doc/user/project/integrations/prometheus_library/nginx_ingress.md b/doc/user/project/integrations/prometheus_library/nginx_ingress.md index 49b34c82ae6..590b1c4275a 100644 --- a/doc/user/project/integrations/prometheus_library/nginx_ingress.md +++ b/doc/user/project/integrations/prometheus_library/nginx_ingress.md @@ -6,7 +6,7 @@ GitLab has support for automatically detecting and monitoring the Kubernetes NGI ## Requirements -[Prometheus integration](../prometheus/index.md) must be active. +[Prometheus integration](../prometheus.md) must be active. ## Metrics supported @@ -27,7 +27,7 @@ For other deployments, there is [some configuration](#manually-setting-up-nginx- ### About managed NGINX Ingress deployments -NGINX Ingress is deployed into the `gitlab-managed-apps` namespace, using the [official Helm chart](https://github.com/kubernetes/charts/tree/master/stable/nginx-ingress). NGINX Ingress will be [externally reachable via the Load Balancer's IP](https://docs.gitlab.com/ce/user/project/clusters/index.html#getting-the-external-ip-address). +NGINX Ingress is deployed into the `gitlab-managed-apps` namespace, using the [official Helm chart](https://github.com/kubernetes/charts/tree/master/stable/nginx-ingress). NGINX Ingress will be [externally reachable via the Load Balancer's IP](../../clusters/index.md#getting-the-external-ip-address). NGINX is configured for Prometheus monitoring, by setting: * `enable-vts-status: "true"`, to export Prometheus metrics @@ -51,4 +51,4 @@ Managing these settings depends on how NGINX ingress has been deployed. If you h In order to isolate and only display relevant metrics for a given environment, GitLab needs a method to detect which labels are associated. To do this, GitLab will search for metrics with appropriate labels. In this case, the `upstream` label must be of the form `<KUBE_NAMESPACE>-<CI_ENVIRONMENT_SLUG>-*`. -If you have used [Auto Deploy](https://docs.gitlab.com/ee/ci/autodeploy/index.html) to deploy your app, this format will be used automatically and metrics will be detected with no action on your part. +If you have used [Auto Deploy](../../../../topics/autodevops/index.md#auto-deploy) to deploy your app, this format will be used automatically and metrics will be detected with no action on your part. diff --git a/features/project/commits/branches.feature b/features/project/commits/branches.feature deleted file mode 100644 index c57376aecff..00000000000 --- a/features/project/commits/branches.feature +++ /dev/null @@ -1,42 +0,0 @@ -@project_commits -Feature: Project Commits Branches - Background: - Given I sign in as a user - And I own project "Shop" - And project "Shop" has protected branches - - Scenario: I can see project all git branches - Given I visit project branches page - Then I should see "Shop" all branches list - - Scenario: I can see project protected git branches - Given I visit project protected branches page - Then I should see "Shop" protected branches list - - @javascript - Scenario: I create a branch - Given I visit project branches page - And I click new branch link - And I submit new branch form - Then I should see new branch created - - @javascript - Scenario: I delete a branch - Given I visit project branches page - And I filter for branch improve/awesome - And I click branch 'improve/awesome' delete link - Then I should not see branch 'improve/awesome' - - @javascript - Scenario: I create a branch with invalid name - Given I visit project branches page - And I click new branch link - And I submit new branch form with invalid name - Then I should see new an error that branch is invalid - - @javascript - Scenario: I create a branch that already exists - Given I visit project branches page - And I click new branch link - And I submit new branch form with branch that already exists - Then I should see new an error that branch already exists diff --git a/features/project/issues/milestones.feature b/features/project/issues/milestones.feature deleted file mode 100644 index 77c8ed6e5bf..00000000000 --- a/features/project/issues/milestones.feature +++ /dev/null @@ -1,43 +0,0 @@ -@project_issues -Feature: Project Issues Milestones - Background: - Given I sign in as a user - And I own project "Shop" - And project "Shop" has milestone "v2.2" - Given I visit project "Shop" milestones page - - Scenario: I should see active milestones - Then I should see milestone "v2.2" - - Scenario: I should see milestone - Given I click link "v2.2" - Then I should see milestone "v2.2" - - @javascript - Scenario: I create and delete new milestone - Given I click link "New Milestone" - And I submit new milestone "v2.3" - Then I should see milestone "v2.3" - Given I click button to remove milestone - And I confirm in modal - When I visit project "Shop" activity page - Then I should see deleted milestone activity - - @javascript - Scenario: I delete new milestone - Given I click button to remove milestone - And I confirm in modal - And I should see no milestones - - @javascript - Scenario: Listing closed issues - Given the milestone has open and closed issues - And I click link "v2.2" - Then I should see 3 issues - - # Markdown - - Scenario: Headers inside the description should have ids generated for them. - Given I click link "v2.2" - # PLEASE USE the `have_header_with_correct_id_and_link(level, text, id, parent)` matcher on migrating this spec to rspec. - Then Header "Description header" should have correct id and link diff --git a/features/steps/project/commits/branches.rb b/features/steps/project/commits/branches.rb index c3ae33d2aa9..3ecd4c8b672 100644 --- a/features/steps/project/commits/branches.rb +++ b/features/steps/project/commits/branches.rb @@ -7,37 +7,14 @@ class Spinach::Features::ProjectCommitsBranches < Spinach::FeatureSteps click_link "All" end - step 'I should see "Shop" all branches list' do - expect(page).to have_content "Branches" - expect(page).to have_content "master" - end - step 'I click link "Protected"' do click_link "Protected" end - step 'I should see "Shop" protected branches list' do - page.within ".protected-branches-list" do - expect(page).to have_content "stable" - expect(page).not_to have_content "master" - end - end - - step 'project "Shop" has protected branches' do - project = Project.find_by(name: "Shop") - create(:protected_branch, project: project, name: "stable") - end - step 'I click new branch link' do click_link "New branch" end - step 'I submit new branch form' do - fill_in 'branch_name', with: 'deploy_keys' - select_branch('master') - click_button 'Create branch' - end - step 'I submit new branch form with invalid name' do fill_in 'branch_name', with: '1.0 stable' page.find("body").click # defocus the branch_name input @@ -45,40 +22,6 @@ class Spinach::Features::ProjectCommitsBranches < Spinach::FeatureSteps click_button 'Create branch' end - step 'I submit new branch form with branch that already exists' do - fill_in 'branch_name', with: 'master' - select_branch('master') - click_button 'Create branch' - end - - step 'I should see new branch created' do - expect(page).to have_content 'deploy_keys' - end - - step 'I should see new an error that branch is invalid' do - expect(page).to have_content 'Branch name is invalid' - expect(page).to have_content "can't contain spaces" - end - - step 'I should see new an error that branch already exists' do - expect(page).to have_content 'Branch already exists' - end - - step 'I filter for branch improve/awesome' do - fill_in 'branch-search', with: 'improve/awesome' - find('#branch-search').native.send_keys(:enter) - end - - step "I click branch 'improve/awesome' delete link" do - page.within '.js-branch-improve\/awesome' do - accept_alert { find('.btn-remove').click } - end - end - - step "I should not see branch 'improve/awesome'" do - expect(page).to have_css('.js-branch-improve\\/awesome', visible: :hidden) - end - def select_branch(branch_name) find('.git-revision-dropdown-toggle').click diff --git a/features/steps/project/issues/milestones.rb b/features/steps/project/issues/milestones.rb index 4ce67aa651c..30927306a4f 100644 --- a/features/steps/project/issues/milestones.rb +++ b/features/steps/project/issues/milestones.rb @@ -4,35 +4,6 @@ class Spinach::Features::ProjectIssuesMilestones < Spinach::FeatureSteps include SharedPaths include SharedMarkdown - step 'I should see milestone "v2.2"' do - milestone = @project.milestones.find_by(title: "v2.2") - expect(page).to have_content(milestone.title[0..10]) - expect(page).to have_content(milestone.expires_at) - expect(page).to have_content("Issues") - end - - step 'I click link "v2.2"' do - click_link "v2.2" - end - - step 'I click link "New Milestone"' do - page.within('.nav-controls') do - click_link "New milestone" - end - end - - step 'I submit new milestone "v2.3"' do - fill_in "milestone_title", with: "v2.3" - click_button "Create milestone" - end - - step 'I should see milestone "v2.3"' do - milestone = @project.milestones.find_by(title: "v2.3") - expect(page).to have_content(milestone.title[0..10]) - expect(page).to have_content(milestone.expires_at) - expect(page).to have_content("Issues") - end - step 'project "Shop" has milestone "v2.2"' do project = Project.find_by(name: "Shop") milestone = create(:milestone, @@ -43,36 +14,7 @@ class Spinach::Features::ProjectIssuesMilestones < Spinach::FeatureSteps 3.times { create(:issue, project: project, milestone: milestone) } end - step 'the milestone has open and closed issues' do - project = Project.find_by(name: "Shop") - milestone = project.milestones.find_by(title: 'v2.2') - - # 3 Open issues created above; create one closed issue - create(:closed_issue, project: project, milestone: milestone) - end - - step 'I should see deleted milestone activity' do - expect(page).to have_content('opened milestone in') - expect(page).to have_content('destroyed milestone in') - end - When 'I click link "All Issues"' do click_link 'All Issues' end - - step 'I should see 3 issues' do - expect(page).to have_selector('#tab-issues li.issuable-row', count: 4) - end - - step 'I click button to remove milestone' do - click_button 'Delete' - end - - step 'I confirm in modal' do - click_button 'Delete milestone' - end - - step 'I should see no milestones' do - expect(page).to have_content('No milestones to show') - end end diff --git a/features/steps/shared/markdown.rb b/features/steps/shared/markdown.rb index c66280127e9..9d522936fb6 100644 --- a/features/steps/shared/markdown.rb +++ b/features/steps/shared/markdown.rb @@ -10,10 +10,6 @@ module SharedMarkdown expect(find(:xpath, "#{node.path}/..").text).to eq text end - step 'Header "Description header" should have correct id and link' do - header_should_have_correct_id_and_link(1, 'Description header', 'description-header') - end - step 'I should not see the Markdown preview' do expect(find('.gfm-form .js-md-preview')).not_to be_visible end diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb index cc893b8391e..3b4c839bcef 100644 --- a/features/steps/shared/paths.rb +++ b/features/steps/shared/paths.rb @@ -264,10 +264,6 @@ module SharedPaths visit project_path(project) end - step 'I visit project "Shop" activity page' do - visit activity_project_path(project) - end - step 'I visit project "Forked Shop" merge requests page' do visit project_merge_requests_path(@forked_project) end @@ -276,14 +272,6 @@ module SharedPaths visit edit_project_path(project) end - step 'I visit project branches page' do - visit project_branches_path(@project) - end - - step 'I visit project protected branches page' do - visit project_protected_branches_path(@project) - end - step 'I visit compare refs page' do visit project_compare_index_path(@project) end @@ -381,10 +369,6 @@ module SharedPaths visit project_merge_requests_path(project) end - step 'I visit project "Shop" milestones page' do - visit project_milestones_path(project) - end - step 'I visit project "Shop" team page' do visit project_project_members_path(project) end diff --git a/lib/banzai/commit_renderer.rb b/lib/banzai/commit_renderer.rb index f5ff95e3eb3..c351a155ae5 100644 --- a/lib/banzai/commit_renderer.rb +++ b/lib/banzai/commit_renderer.rb @@ -3,7 +3,7 @@ module Banzai ATTRIBUTES = [:description, :title].freeze def self.render(commits, project, user = nil) - obj_renderer = ObjectRenderer.new(project, user) + obj_renderer = ObjectRenderer.new(user: user, default_project: project) ATTRIBUTES.each { |attr| obj_renderer.render(commits, attr) } end diff --git a/lib/banzai/filter/issuable_state_filter.rb b/lib/banzai/filter/issuable_state_filter.rb index 8f541dcfdb2..1a415232545 100644 --- a/lib/banzai/filter/issuable_state_filter.rb +++ b/lib/banzai/filter/issuable_state_filter.rb @@ -11,7 +11,8 @@ module Banzai def call return doc unless context[:issuable_state_filter_enabled] - extractor = Banzai::IssuableExtractor.new(project, current_user) + context = RenderContext.new(project, current_user) + extractor = Banzai::IssuableExtractor.new(context) issuables = extractor.extract([doc]) issuables.each do |node, issuable| diff --git a/lib/banzai/filter/redactor_filter.rb b/lib/banzai/filter/redactor_filter.rb index 9f9882b3b40..caf11fe94c4 100644 --- a/lib/banzai/filter/redactor_filter.rb +++ b/lib/banzai/filter/redactor_filter.rb @@ -7,7 +7,11 @@ module Banzai # class RedactorFilter < HTML::Pipeline::Filter def call - Redactor.new(project, current_user).redact([doc]) unless context[:skip_redaction] + unless context[:skip_redaction] + context = RenderContext.new(project, current_user) + + Redactor.new(context).redact([doc]) + end doc end diff --git a/lib/banzai/issuable_extractor.rb b/lib/banzai/issuable_extractor.rb index 49603d0b363..ae7dc71e7eb 100644 --- a/lib/banzai/issuable_extractor.rb +++ b/lib/banzai/issuable_extractor.rb @@ -12,11 +12,11 @@ module Banzai [@data-reference-type="issue" or @data-reference-type="merge_request"] ).freeze - attr_reader :project, :user + attr_reader :context - def initialize(project, user) - @project = project - @user = user + # context - An instance of Banzai::RenderContext. + def initialize(context) + @context = context end # Returns Hash in the form { node => issuable_instance } @@ -25,8 +25,10 @@ module Banzai document.xpath(QUERY) end - issue_parser = Banzai::ReferenceParser::IssueParser.new(project, user) - merge_request_parser = Banzai::ReferenceParser::MergeRequestParser.new(project, user) + issue_parser = Banzai::ReferenceParser::IssueParser.new(context) + + merge_request_parser = + Banzai::ReferenceParser::MergeRequestParser.new(context) issuables_for_nodes = issue_parser.records_for_nodes(nodes).merge( merge_request_parser.records_for_nodes(nodes) diff --git a/lib/banzai/object_renderer.rb b/lib/banzai/object_renderer.rb index 2691be81623..a176f1e261b 100644 --- a/lib/banzai/object_renderer.rb +++ b/lib/banzai/object_renderer.rb @@ -13,14 +13,13 @@ module Banzai # As an example, rendering the attribute `note` would place the unredacted # HTML into `note_html` and the redacted HTML into `redacted_note_html`. class ObjectRenderer - attr_reader :project, :user + attr_reader :context - # project - A Project to use for redacting Markdown. + # default_project - A default Project to use for redacting Markdown. # user - The user viewing the Markdown/HTML documents, if any. # redaction_context - A Hash containing extra attributes to use during redaction - def initialize(project, user = nil, redaction_context = {}) - @project = project - @user = user + def initialize(default_project: nil, user: nil, redaction_context: {}) + @context = RenderContext.new(default_project, user) @redaction_context = base_context.merge(redaction_context) end @@ -48,17 +47,21 @@ module Banzai pipeline = HTML::Pipeline.new([]) objects.map do |object| - pipeline.to_document(Banzai.render_field(object, attribute)) + document = pipeline.to_document(Banzai.render_field(object, attribute)) + + context.associate_document(document, object) + + document end end def post_process_documents(documents, objects, attribute) # Called here to populate cache, refer to IssuableExtractor docs - IssuableExtractor.new(project, user).extract(documents) + IssuableExtractor.new(context).extract(documents) documents.zip(objects).map do |document, object| - context = context_for(object, attribute) - Banzai::Pipeline[:post_process].to_document(document, context) + pipeline_context = context_for(document, object, attribute) + Banzai::Pipeline[:post_process].to_document(document, pipeline_context) end end @@ -66,20 +69,21 @@ module Banzai # # Returns an Array containing the redacted documents. def redact_documents(documents) - redactor = Redactor.new(project, user) + redactor = Redactor.new(context) redactor.redact(documents) end # Returns a Banzai context for the given object and attribute. - def context_for(object, attribute) - @redaction_context.merge(object.banzai_render_context(attribute)) + def context_for(document, object, attribute) + @redaction_context.merge(object.banzai_render_context(attribute)).merge( + project: context.project_for_node(document) + ) end def base_context { - current_user: user, - project: project, + current_user: context.current_user, skip_redaction: true } end diff --git a/lib/banzai/redactor.rb b/lib/banzai/redactor.rb index fd457bebf03..28928d6f376 100644 --- a/lib/banzai/redactor.rb +++ b/lib/banzai/redactor.rb @@ -2,13 +2,15 @@ module Banzai # Class for removing Markdown references a certain user is not allowed to # view. class Redactor - attr_reader :user, :project + attr_reader :context - # project - A Project to use for redacting links. - # user - The currently logged in user (if any). - def initialize(project, user = nil) - @project = project - @user = user + # context - An instance of `Banzai::RenderContext`. + def initialize(context) + @context = context + end + + def user + context.current_user end # Redacts the references in the given Array of documents. @@ -70,11 +72,11 @@ module Banzai end def redact_cross_project_references(documents) - extractor = Banzai::IssuableExtractor.new(project, user) + extractor = Banzai::IssuableExtractor.new(context) issuables = extractor.extract(documents) issuables.each do |node, issuable| - next if issuable.project == project + next if issuable.project == context.project_for_node(node) node['class'] = node['class'].gsub('has-tooltip', '') node['title'] = nil @@ -95,7 +97,7 @@ module Banzai end per_type.each do |type, nodes| - parser = Banzai::ReferenceParser[type].new(project, user) + parser = Banzai::ReferenceParser[type].new(context) visible.merge(parser.nodes_visible_to_user(user, nodes)) end diff --git a/lib/banzai/reference_extractor.rb b/lib/banzai/reference_extractor.rb index 7e6357f8a00..78588299c18 100644 --- a/lib/banzai/reference_extractor.rb +++ b/lib/banzai/reference_extractor.rb @@ -10,8 +10,8 @@ module Banzai end def references(type, project, current_user = nil) - processor = Banzai::ReferenceParser[type] - .new(project, current_user) + context = RenderContext.new(project, current_user) + processor = Banzai::ReferenceParser[type].new(context) processor.process(html_documents) end diff --git a/lib/banzai/reference_parser/base_parser.rb b/lib/banzai/reference_parser/base_parser.rb index 279fca8d043..68752f5bb5a 100644 --- a/lib/banzai/reference_parser/base_parser.rb +++ b/lib/banzai/reference_parser/base_parser.rb @@ -45,9 +45,13 @@ module Banzai @data_attribute ||= "data-#{reference_type.to_s.dasherize}" end - def initialize(project = nil, current_user = nil) - @project = project - @current_user = current_user + # context - An instance of `Banzai::RenderContext`. + def initialize(context) + @context = context + end + + def project_for_node(node) + context.project_for_node(node) end # Returns all the nodes containing references that the user can refer to. @@ -224,7 +228,11 @@ module Banzai private - attr_reader :current_user, :project + attr_reader :context + + def current_user + context.current_user + end # When a feature is disabled or visible only for # team members we should not allow team members diff --git a/lib/banzai/reference_parser/issue_parser.rb b/lib/banzai/reference_parser/issue_parser.rb index 230827129b6..6bee5ea15b9 100644 --- a/lib/banzai/reference_parser/issue_parser.rb +++ b/lib/banzai/reference_parser/issue_parser.rb @@ -5,15 +5,10 @@ module Banzai def nodes_visible_to_user(user, nodes) issues = records_for_nodes(nodes) - issues_to_check = issues.values + issues_to_check, cross_project_issues = partition_issues(issues, user) - unless can?(user, :read_cross_project) - issues_to_check, cross_project_issues = issues_to_check.partition do |issue| - issue.project == project - end - end - - readable_issues = Ability.issues_readable_by_user(issues_to_check, user).to_set + readable_issues = + Ability.issues_readable_by_user(issues_to_check, user).to_set nodes.select do |node| issue_in_node = issues[node] @@ -25,7 +20,7 @@ module Banzai # but not the issue. if readable_issues.include?(issue_in_node) true - elsif cross_project_issues&.include?(issue_in_node) + elsif cross_project_issues.include?(issue_in_node) can_read_reference?(user, issue_in_node) else false @@ -33,6 +28,32 @@ module Banzai end end + # issues - A Hash mapping HTML nodes to their corresponding Issue + # instances. + # user - The current User. + def partition_issues(issues, user) + return [issues.values, []] if can?(user, :read_cross_project) + + issues_to_check = [] + cross_project_issues = [] + + # We manually partition the data since our input is a Hash and our + # output has to be an Array of issues; not an Array of (node, issue) + # pairs. + issues.each do |node, issue| + target = + if issue.project == project_for_node(node) + issues_to_check + else + cross_project_issues + end + + target << issue + end + + [issues_to_check, cross_project_issues] + end + def records_for_nodes(nodes) @issues_for_nodes ||= grouped_objects_for_nodes( nodes, diff --git a/lib/banzai/reference_parser/user_parser.rb b/lib/banzai/reference_parser/user_parser.rb index 8932d4f2905..ceb7f1d165c 100644 --- a/lib/banzai/reference_parser/user_parser.rb +++ b/lib/banzai/reference_parser/user_parser.rb @@ -58,7 +58,7 @@ module Banzai def can_read_project_reference?(node) node_id = node.attr('data-project').to_i - project && project.id == node_id + project_for_node(node)&.id == node_id end def nodes_user_can_reference(current_user, nodes) @@ -71,6 +71,7 @@ module Banzai nodes.select do |node| project_id = node.attr(project_attr) user_id = node.attr(author_attr) + project = project_for_node(node) if project && project_id && project.id == project_id.to_i true diff --git a/lib/banzai/render_context.rb b/lib/banzai/render_context.rb new file mode 100644 index 00000000000..e30fc9f469b --- /dev/null +++ b/lib/banzai/render_context.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module Banzai + # Object storing the current user, project, and other details used when + # parsing Markdown references. + class RenderContext + attr_reader :current_user + + # default_project - The default project to use for all documents, if any. + # current_user - The user viewing the document, if any. + def initialize(default_project = nil, current_user = nil) + @current_user = current_user + @projects = Hash.new(default_project) + end + + # Associates an HTML document with a Project. + # + # document - The HTML document to map to a Project. + # object - The object that produced the HTML document. + def associate_document(document, object) + # XML nodes respond to "document" but will return a Document instance, + # even when they belong to a DocumentFragment. + document = document.document if document.fragment? + + @projects[document] = object.project if object.respond_to?(:project) + end + + def project_for_node(node) + @projects[node.document] + end + end +end diff --git a/lib/gitlab/ci/status/build/common.rb b/lib/gitlab/ci/status/build/common.rb index c0c7c7f5b5d..c1fc70ac266 100644 --- a/lib/gitlab/ci/status/build/common.rb +++ b/lib/gitlab/ci/status/build/common.rb @@ -3,6 +3,14 @@ module Gitlab module Status module Build module Common + def illustration + { + image: 'illustrations/skipped-job_empty.svg', + size: 'svg-430', + title: _('This job does not have a trace.') + } + end + def has_details? can?(user, :read_build, subject) end diff --git a/lib/gitlab/ci/status/build/erased.rb b/lib/gitlab/ci/status/build/erased.rb index 3a5113b16b6..495227c2ffb 100644 --- a/lib/gitlab/ci/status/build/erased.rb +++ b/lib/gitlab/ci/status/build/erased.rb @@ -5,7 +5,7 @@ module Gitlab class Erased < Status::Extended def illustration { - image: 'illustrations/skipped-job_empty.svg', + image: 'illustrations/erased-log_empty.svg', size: 'svg-430', title: _('Job has been erased') } diff --git a/lib/gitlab/ci/variables/collection/item.rb b/lib/gitlab/ci/variables/collection/item.rb index 23ed71db8b0..d00e5b07f95 100644 --- a/lib/gitlab/ci/variables/collection/item.rb +++ b/lib/gitlab/ci/variables/collection/item.rb @@ -3,12 +3,9 @@ module Gitlab module Variables class Collection class Item - def initialize(**options) + def initialize(key:, value:, public: true, file: false) @variable = { - key: options.fetch(:key), - value: options.fetch(:value), - public: options.fetch(:public, true), - file: options.fetch(:files, false) + key: key, value: value, public: public, file: file } end diff --git a/lib/gitlab/git.rb b/lib/gitlab/git.rb index d4e893b881c..c9abea90d21 100644 --- a/lib/gitlab/git.rb +++ b/lib/gitlab/git.rb @@ -1,5 +1,9 @@ module Gitlab module Git + # The ID of empty tree. + # See http://stackoverflow.com/a/40884093/1856239 and + # https://github.com/git/git/blob/3ad8b5bf26362ac67c9020bf8c30eee54a84f56d/cache.h#L1011-L1012 + EMPTY_TREE_ID = '4b825dc642cb6eb9a060e54bf8d69288fbee4904'.freeze BLANK_SHA = ('0' * 40).freeze TAG_REF_PREFIX = "refs/tags/".freeze BRANCH_REF_PREFIX = "refs/heads/".freeze diff --git a/lib/gitlab/git/attributes_parser.rb b/lib/gitlab/git/attributes_parser.rb index d8aeabb6cba..08f4d7d4f5c 100644 --- a/lib/gitlab/git/attributes_parser.rb +++ b/lib/gitlab/git/attributes_parser.rb @@ -3,12 +3,8 @@ module Gitlab # Class for parsing Git attribute files and extracting the attributes for # file patterns. class AttributesParser - def initialize(attributes_data) + def initialize(attributes_data = "") @data = attributes_data || "" - - if @data.is_a?(File) - @patterns = parse_file - end end # Returns all the Git attributes for the given path. @@ -28,7 +24,7 @@ module Gitlab # Returns a Hash containing the file patterns and their attributes. def patterns - @patterns ||= parse_file + @patterns ||= parse_data end # Parses an attribute string. @@ -91,8 +87,8 @@ module Gitlab private - # Parses the Git attributes file. - def parse_file + # Parses the Git attributes file contents. + def parse_data pairs = [] comment = '#' diff --git a/lib/gitlab/git/info_attributes.rb b/lib/gitlab/git/info_attributes.rb deleted file mode 100644 index e79a440950b..00000000000 --- a/lib/gitlab/git/info_attributes.rb +++ /dev/null @@ -1,49 +0,0 @@ -# Gitaly note: JV: not sure what to make of this class. Why does it use -# the full disk path of the repository to look up attributes This is -# problematic in Gitaly, because Gitaly hides the full disk path to the -# repository from gitlab-ce. - -module Gitlab - module Git - # Parses gitattributes at `$GIT_DIR/info/attributes` - # - # Unlike Rugged this parser only needs a single IO call (a call to `open`), - # vastly reducing the time spent in extracting attributes. - # - # This class _only_ supports parsing the attributes file located at - # `$GIT_DIR/info/attributes` as GitLab doesn't use any other files - # (`.gitattributes` is copied to this particular path). - # - # Basic usage: - # - # attributes = Gitlab::Git::InfoAttributes.new(some_repo.path) - # - # attributes.attributes('README.md') # => { "eol" => "lf } - class InfoAttributes - delegate :attributes, :patterns, to: :parser - - # path - The path to the Git repository. - def initialize(path) - @repo_path = File.expand_path(path) - end - - def parser - @parser ||= begin - if File.exist?(attributes_path) - File.open(attributes_path, 'r') do |file_handle| - AttributesParser.new(file_handle) - end - else - AttributesParser.new("") - end - end - end - - private - - def attributes_path - @attributes_path ||= File.join(@repo_path, 'info/attributes') - end - end - end -end diff --git a/lib/gitlab/git/raw_diff_change.rb b/lib/gitlab/git/raw_diff_change.rb new file mode 100644 index 00000000000..eb3d8819239 --- /dev/null +++ b/lib/gitlab/git/raw_diff_change.rb @@ -0,0 +1,60 @@ +module Gitlab + module Git + # This class behaves like a struct with fields :blob_id, :blob_size, :operation, :old_path, :new_path + # All those fields are (binary) strings or integers + class RawDiffChange + attr_reader :blob_id, :blob_size, :old_path, :new_path, :operation + + def initialize(raw_change) + parse(raw_change) + end + + private + + # Input data has the following format: + # + # When a file has been modified: + # 7e3e39ebb9b2bf433b4ad17313770fbe4051649c 669 M\tfiles/ruby/popen.rb + # + # When a file has been renamed: + # 85bc2f9753afd5f4fc5d7c75f74f8d526f26b4f3 107 R060\tfiles/js/commit.js.coffee\tfiles/js/commit.coffee + def parse(raw_change) + @blob_id, @blob_size, @raw_operation, raw_paths = raw_change.split(' ', 4) + @operation = extract_operation + @old_path, @new_path = extract_paths(raw_paths) + end + + def extract_paths(file_path) + case operation + when :renamed + file_path.split(/\t/) + when :deleted + [file_path, nil] + when :added + [nil, file_path] + else + [file_path, file_path] + end + end + + def extract_operation + case @raw_operation&.first(1) + when 'A' + :added + when 'C' + :copied + when 'D' + :deleted + when 'M' + :modified + when 'R' + :renamed + when 'T' + :type_changed + else + :unknown + end + end + end + end +end diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index f1b575bd872..36992cbcca0 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -105,7 +105,6 @@ module Gitlab ) @path = File.join(storage_path, @relative_path) @name = @relative_path.split("/").last - @attributes = Gitlab::Git::InfoAttributes.new(path) end def ==(other) @@ -559,6 +558,24 @@ module Gitlab count_commits(from: from, to: to, **options) end + # old_rev and new_rev are commit ID's + # the result of this method is an array of Gitlab::Git::RawDiffChange + def raw_changes_between(old_rev, new_rev) + result = [] + + circuit_breaker.perform do + Open3.pipeline_r(git_diff_cmd(old_rev, new_rev), format_git_cat_file_script, git_cat_file_cmd) do |last_stdout, wait_threads| + last_stdout.each_line { |line| result << ::Gitlab::Git::RawDiffChange.new(line.chomp!) } + + if wait_threads.any? { |waiter| !waiter.value&.success? } + raise ::Gitlab::Git::Repository::GitError, "Unabled to obtain changes between #{old_rev} and #{new_rev}" + end + end + end + + result + end + # Returns the SHA of the most recent common ancestor of +from+ and +to+ def merge_base(from, to) gitaly_migrate(:merge_base) do |is_enabled| @@ -993,11 +1010,32 @@ module Gitlab raise InvalidRef end + def info_attributes + return @info_attributes if @info_attributes + + content = + gitaly_migrate(:get_info_attributes) do |is_enabled| + if is_enabled + gitaly_repository_client.info_attributes + else + attributes_path = File.join(File.expand_path(@path), 'info', 'attributes') + + if File.exist?(attributes_path) + File.read(attributes_path) + else + "" + end + end + end + + @info_attributes = AttributesParser.new(content) + end + # Returns the Git attributes for the given file path. # # See `Gitlab::Git::Attributes` for more information. def attributes(path) - @attributes.attributes(path) + info_attributes.attributes(path) end def gitattribute(path, name) @@ -1385,7 +1423,8 @@ module Gitlab end def branch_names_contains_sha(sha) - gitaly_migrate(:branch_names_contains_sha) do |is_enabled| + gitaly_migrate(:branch_names_contains_sha, + status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled| if is_enabled gitaly_ref_client.branch_names_contains_sha(sha) else @@ -1395,7 +1434,8 @@ module Gitlab end def tag_names_contains_sha(sha) - gitaly_migrate(:tag_names_contains_sha) do |is_enabled| + gitaly_migrate(:tag_names_contains_sha, + status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled| if is_enabled gitaly_ref_client.tag_names_contains_sha(sha) else @@ -2461,6 +2501,35 @@ module Gitlab result.to_s(16) end + + def build_git_cmd(*args) + object_directories = alternate_object_directories.join(File::PATH_SEPARATOR) + + env = { 'PWD' => self.path } + env['GIT_ALTERNATE_OBJECT_DIRECTORIES'] = object_directories if object_directories.present? + + [ + env, + ::Gitlab.config.git.bin_path, + *args, + { chdir: self.path } + ] + end + + def git_diff_cmd(old_rev, new_rev) + old_rev = old_rev == ::Gitlab::Git::BLANK_SHA ? ::Gitlab::Git::EMPTY_TREE_ID : old_rev + + build_git_cmd('diff', old_rev, new_rev, '--raw') + end + + def git_cat_file_cmd + format = '%(objectname) %(objectsize) %(rest)' + build_git_cmd('cat-file', "--batch-check=#{format}") + end + + def format_git_cat_file_script + File.expand_path('../support/format-git-cat-file-input', __FILE__) + end end end end diff --git a/lib/gitlab/git/support/format-git-cat-file-input b/lib/gitlab/git/support/format-git-cat-file-input new file mode 100755 index 00000000000..2e93c646d0f --- /dev/null +++ b/lib/gitlab/git/support/format-git-cat-file-input @@ -0,0 +1,21 @@ +#!/usr/bin/env ruby + +# This script formats the output of the `git diff <old_rev> <new_rev> --raw` +# command so it can be processed by `git cat-file` + +# We need to convert this: +# ":100644 100644 5f53439... 85bc2f9... R060\tfiles/js/commit.js.coffee\tfiles/js/commit.coffee" +# To: +# "85bc2f9 R\tfiles/js/commit.js.coffee\tfiles/js/commit.coffee" + +ARGF.each do |line| + _, _, old_blob_id, new_blob_id, rest = line.split(/\s/, 5) + + old_blob_id.gsub!(/[^\h]/, '') + new_blob_id.gsub!(/[^\h]/, '') + + # We can't pass '0000000...' to `git cat-file` given it will not return info about the deleted file + blob_id = new_blob_id =~ /\A0+\z/ ? old_blob_id : new_blob_id + + $stdout.puts "#{blob_id} #{rest}" +end diff --git a/lib/gitlab/gitaly_client/commit_service.rb b/lib/gitlab/gitaly_client/commit_service.rb index 456a8a1a2d6..a36e6c822f9 100644 --- a/lib/gitlab/gitaly_client/commit_service.rb +++ b/lib/gitlab/gitaly_client/commit_service.rb @@ -3,10 +3,6 @@ module Gitlab class CommitService include Gitlab::EncodingHelper - # The ID of empty tree. - # See http://stackoverflow.com/a/40884093/1856239 and https://github.com/git/git/blob/3ad8b5bf26362ac67c9020bf8c30eee54a84f56d/cache.h#L1011-L1012 - EMPTY_TREE_ID = '4b825dc642cb6eb9a060e54bf8d69288fbee4904'.freeze - def initialize(repository) @gitaly_repo = repository.gitaly_repository @repository = repository @@ -37,7 +33,7 @@ module Gitlab def diff(from, to, options = {}) from_id = case from when NilClass - EMPTY_TREE_ID + Gitlab::Git::EMPTY_TREE_ID else if from.respond_to?(:oid) # This is meant to match a Rugged::Commit. This should be impossible in @@ -50,7 +46,7 @@ module Gitlab to_id = case to when NilClass - EMPTY_TREE_ID + Gitlab::Git::EMPTY_TREE_ID else if to.respond_to?(:oid) # This is meant to match a Rugged::Commit. This should be impossible in @@ -352,7 +348,7 @@ module Gitlab end def diff_from_parent_request_params(commit, options = {}) - parent_id = commit.parent_ids.first || EMPTY_TREE_ID + parent_id = commit.parent_ids.first || Gitlab::Git::EMPTY_TREE_ID diff_between_commits_request_params(parent_id, commit.id, options) end diff --git a/lib/gitlab/gitaly_client/repository_service.rb b/lib/gitlab/gitaly_client/repository_service.rb index 6441065f5fe..39057beefba 100644 --- a/lib/gitlab/gitaly_client/repository_service.rb +++ b/lib/gitlab/gitaly_client/repository_service.rb @@ -50,6 +50,15 @@ module Gitlab GitalyClient.call(@storage, :repository_service, :apply_gitattributes, request) end + def info_attributes + request = Gitaly::GetInfoAttributesRequest.new(repository: @gitaly_repo) + + response = GitalyClient.call(@storage, :repository_service, :get_info_attributes, request) + response.each_with_object("") do |message, attributes| + attributes << message.attributes + end + end + def fetch_remote(remote, ssh_auth:, forced:, no_tags:, timeout:, prune: true) request = Gitaly::FetchRemoteRequest.new( repository: @gitaly_repo, remote: remote, force: forced, diff --git a/lib/gitlab/gitaly_client/wiki_service.rb b/lib/gitlab/gitaly_client/wiki_service.rb index 0d8dd5cb8f4..7a698e4b3f3 100644 --- a/lib/gitlab/gitaly_client/wiki_service.rb +++ b/lib/gitlab/gitaly_client/wiki_service.rb @@ -136,7 +136,7 @@ module Gitlab wiki_file = nil response.each do |message| - next unless message.name.present? + next unless message.name.present? || wiki_file if wiki_file wiki_file.raw_data << message.raw_data diff --git a/lib/gitlab/utils.rb b/lib/gitlab/utils.rb index b0a492eaa58..aeda66763e8 100644 --- a/lib/gitlab/utils.rb +++ b/lib/gitlab/utils.rb @@ -73,6 +73,10 @@ module Gitlab nil end + def bytes_to_megabytes(bytes) + bytes.to_f / Numeric::MEGABYTE + end + # Used in EE # Accepts either an Array or a String and returns an array def ensure_array_from_string(string_or_array) diff --git a/lib/rspec_flaky/config.rb b/lib/rspec_flaky/config.rb index a17ae55910e..06e96f969f1 100644 --- a/lib/rspec_flaky/config.rb +++ b/lib/rspec_flaky/config.rb @@ -1,9 +1,7 @@ -require 'json' - module RspecFlaky class Config def self.generate_report? - ENV['FLAKY_RSPEC_GENERATE_REPORT'] == 'true' + !!(ENV['FLAKY_RSPEC_GENERATE_REPORT'] =~ /1|true/) end def self.suite_flaky_examples_report_path diff --git a/lib/rspec_flaky/flaky_examples_collection.rb b/lib/rspec_flaky/flaky_examples_collection.rb index 973c95b0212..dea23c325be 100644 --- a/lib/rspec_flaky/flaky_examples_collection.rb +++ b/lib/rspec_flaky/flaky_examples_collection.rb @@ -1,11 +1,9 @@ -require 'json' +require 'active_support/hash_with_indifferent_access' + +require_relative 'flaky_example' module RspecFlaky class FlakyExamplesCollection < SimpleDelegator - def self.from_json(json) - new(JSON.parse(json)) - end - def initialize(collection = {}) unless collection.is_a?(Hash) raise ArgumentError, "`collection` must be a Hash, #{collection.class} given!" @@ -22,7 +20,7 @@ module RspecFlaky super(Hash[collection_of_flaky_examples]) end - def to_report + def to_h Hash[map { |uid, example| [uid, example.to_h] }].deep_symbolize_keys end diff --git a/lib/rspec_flaky/listener.rb b/lib/rspec_flaky/listener.rb index 4a5bfec9967..5b5e4f7c7de 100644 --- a/lib/rspec_flaky/listener.rb +++ b/lib/rspec_flaky/listener.rb @@ -1,5 +1,11 @@ require 'json' +require_relative 'config' +require_relative 'example' +require_relative 'flaky_example' +require_relative 'flaky_examples_collection' +require_relative 'report' + module RspecFlaky class Listener # - suite_flaky_examples: contains all the currently tracked flacky example @@ -9,7 +15,7 @@ module RspecFlaky attr_reader :suite_flaky_examples, :flaky_examples def initialize(suite_flaky_examples_json = nil) - @flaky_examples = FlakyExamplesCollection.new + @flaky_examples = RspecFlaky::FlakyExamplesCollection.new @suite_flaky_examples = init_suite_flaky_examples(suite_flaky_examples_json) end @@ -18,47 +24,36 @@ module RspecFlaky return unless current_example.attempts > 1 - flaky_example = suite_flaky_examples.fetch(current_example.uid) { FlakyExample.new(current_example) } + flaky_example = suite_flaky_examples.fetch(current_example.uid) { RspecFlaky::FlakyExample.new(current_example) } flaky_example.update_flakiness!(last_attempts_count: current_example.attempts) flaky_examples[current_example.uid] = flaky_example end def dump_summary(_) - write_report_file(flaky_examples, RspecFlaky::Config.flaky_examples_report_path) + RspecFlaky::Report.new(flaky_examples).write(RspecFlaky::Config.flaky_examples_report_path) + # write_report_file(flaky_examples, RspecFlaky::Config.flaky_examples_report_path) new_flaky_examples = flaky_examples - suite_flaky_examples if new_flaky_examples.any? Rails.logger.warn "\nNew flaky examples detected:\n" - Rails.logger.warn JSON.pretty_generate(new_flaky_examples.to_report) + Rails.logger.warn JSON.pretty_generate(new_flaky_examples.to_h) - write_report_file(new_flaky_examples, RspecFlaky::Config.new_flaky_examples_report_path) + RspecFlaky::Report.new(new_flaky_examples).write(RspecFlaky::Config.new_flaky_examples_report_path) + # write_report_file(new_flaky_examples, RspecFlaky::Config.new_flaky_examples_report_path) end end - def to_report(examples) - Hash[examples.map { |k, ex| [k, ex.to_h] }] - end - private def init_suite_flaky_examples(suite_flaky_examples_json = nil) - unless suite_flaky_examples_json + if suite_flaky_examples_json + RspecFlaky::Report.load_json(suite_flaky_examples_json).flaky_examples + else return {} unless File.exist?(RspecFlaky::Config.suite_flaky_examples_report_path) - suite_flaky_examples_json = File.read(RspecFlaky::Config.suite_flaky_examples_report_path) + RspecFlaky::Report.load(RspecFlaky::Config.suite_flaky_examples_report_path).flaky_examples end - - FlakyExamplesCollection.from_json(suite_flaky_examples_json) - end - - def write_report_file(examples_collection, file_path) - return unless RspecFlaky::Config.generate_report? - - report_path_dir = File.dirname(file_path) - FileUtils.mkdir_p(report_path_dir) unless Dir.exist?(report_path_dir) - - File.write(file_path, JSON.pretty_generate(examples_collection.to_report)) end end end diff --git a/lib/rspec_flaky/report.rb b/lib/rspec_flaky/report.rb new file mode 100644 index 00000000000..a8730d3b7c7 --- /dev/null +++ b/lib/rspec_flaky/report.rb @@ -0,0 +1,54 @@ +require 'json' +require 'time' + +require_relative 'config' +require_relative 'flaky_examples_collection' + +module RspecFlaky + # This class is responsible for loading/saving JSON reports, and pruning + # outdated examples. + class Report < SimpleDelegator + OUTDATED_DAYS_THRESHOLD = 90 + + attr_reader :flaky_examples + + def self.load(file_path) + load_json(File.read(file_path)) + end + + def self.load_json(json) + new(RspecFlaky::FlakyExamplesCollection.new(JSON.parse(json))) + end + + def initialize(flaky_examples) + unless flaky_examples.is_a?(RspecFlaky::FlakyExamplesCollection) + raise ArgumentError, "`flaky_examples` must be a RspecFlaky::FlakyExamplesCollection, #{flaky_examples.class} given!" + end + + @flaky_examples = flaky_examples + super(flaky_examples) + end + + def write(file_path) + unless RspecFlaky::Config.generate_report? + puts "! Generating reports is disabled. To enable it, please set the `FLAKY_RSPEC_GENERATE_REPORT=1` !" # rubocop:disable Rails/Output + return + end + + report_path_dir = File.dirname(file_path) + FileUtils.mkdir_p(report_path_dir) unless Dir.exist?(report_path_dir) + + File.write(file_path, JSON.pretty_generate(flaky_examples.to_h)) + end + + def prune_outdated(days: OUTDATED_DAYS_THRESHOLD) + outdated_date_threshold = Time.now - (3600 * 24 * days) + updated_hash = flaky_examples.dup + .delete_if do |uid, hash| + hash[:last_flaky_at] && Time.parse(hash[:last_flaky_at]).to_i < outdated_date_threshold.to_i + end + + self.class.new(RspecFlaky::FlakyExamplesCollection.new(updated_hash)) + end + end +end diff --git a/package.json b/package.json index 27f612aca38..7bac672a0b2 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,8 @@ "eslint": "eslint --max-warnings 0 --ext .js,.vue .", "eslint-fix": "eslint --max-warnings 0 --ext .js,.vue --fix .", "eslint-report": "eslint --max-warnings 0 --ext .js,.vue --format html --output-file ./eslint-report.html .", - "karma": "karma start config/karma.config.js --single-run", - "karma-coverage": "BABEL_ENV=coverage karma start config/karma.config.js --single-run", + "karma": "karma start --single-run true config/karma.config.js", + "karma-coverage": "BABEL_ENV=coverage karma start --single-run true config/karma.config.js", "karma-start": "karma start config/karma.config.js", "prettier-staged": "node ./scripts/frontend/prettier.js", "prettier-staged-save": "node ./scripts/frontend/prettier.js save", @@ -98,6 +98,7 @@ "axios-mock-adapter": "^1.10.0", "babel-eslint": "^8.0.2", "babel-plugin-istanbul": "^4.1.5", + "commander": "^2.15.1", "eslint": "^3.18.0", "eslint-config-airbnb-base": "^10.0.1", "eslint-import-resolver-webpack": "^0.8.3", diff --git a/scripts/prune-old-flaky-specs b/scripts/prune-old-flaky-specs new file mode 100755 index 00000000000..f7451fbd428 --- /dev/null +++ b/scripts/prune-old-flaky-specs @@ -0,0 +1,24 @@ +#!/usr/bin/env ruby + +# lib/rspec_flaky/flaky_examples_collection.rb is requiring +# `active_support/hash_with_indifferent_access`, and we install the `activesupport` +# gem manually on the CI +require 'rubygems' + +require_relative '../lib/rspec_flaky/report' + +report_file = ARGV.shift +unless report_file + puts 'usage: prune-old-flaky-specs <report-file> <new-report-file>' + exit 1 +end + +new_report_file = ARGV.shift || report_file +report = RspecFlaky::Report.load(report_file) +puts "Loading #{report_file}..." +puts "Current report has #{report.size} entries." + +new_report = report.prune_outdated + +puts "New report has #{new_report.size} entries: #{report.size - new_report.size} entries older than 90 days were removed." +puts "Saved #{new_report_file}." if new_report.write(new_report_file) diff --git a/spec/features/dashboard/projects_spec.rb b/spec/features/dashboard/projects_spec.rb index 986f864f0b5..257a3822503 100644 --- a/spec/features/dashboard/projects_spec.rb +++ b/spec/features/dashboard/projects_spec.rb @@ -89,7 +89,7 @@ feature 'Dashboard Projects' do end describe 'with a pipeline', :clean_gitlab_redis_shared_state do - let(:pipeline) { create(:ci_pipeline, project: project, sha: project.commit.sha) } + let(:pipeline) { create(:ci_pipeline, project: project, sha: project.commit.sha, ref: project.default_branch) } before do # Since the cache isn't updated when a new pipeline is created @@ -102,7 +102,7 @@ feature 'Dashboard Projects' do visit dashboard_projects_path page.within('.controls') do - expect(page).to have_xpath("//a[@href='#{pipelines_project_commit_path(project, project.commit)}']") + expect(page).to have_xpath("//a[@href='#{pipelines_project_commit_path(project, project.commit, ref: pipeline.ref)}']") expect(page).to have_css('.ci-status-link') expect(page).to have_css('.ci-status-icon-success') expect(page).to have_link('Commit: passed') diff --git a/spec/features/milestones/show_spec.rb b/spec/features/milestones/show_spec.rb deleted file mode 100644 index 50c5e0bb65f..00000000000 --- a/spec/features/milestones/show_spec.rb +++ /dev/null @@ -1,26 +0,0 @@ -require 'rails_helper' - -describe 'Milestone show' do - let(:user) { create(:user) } - let(:project) { create(:project) } - let(:milestone) { create(:milestone, project: project) } - let(:labels) { create_list(:label, 2, project: project) } - let(:issue_params) { { project: project, assignees: [user], author: user, milestone: milestone, labels: labels } } - - before do - project.add_user(user, :developer) - sign_in(user) - end - - def visit_milestone - visit project_milestone_path(project, milestone) - end - - it 'avoids N+1 database queries' do - create(:labeled_issue, issue_params) - control = ActiveRecord::QueryRecorder.new { visit_milestone } - create_list(:labeled_issue, 10, issue_params) - - expect { visit_milestone }.not_to exceed_query_limit(control) - end -end diff --git a/spec/features/milestones/user_creates_milestone_spec.rb b/spec/features/milestones/user_creates_milestone_spec.rb new file mode 100644 index 00000000000..8fd057d587c --- /dev/null +++ b/spec/features/milestones/user_creates_milestone_spec.rb @@ -0,0 +1,29 @@ +require "rails_helper" + +describe "User creates milestone", :js do + set(:user) { create(:user) } + set(:project) { create(:project) } + + before do + project.add_developer(user) + sign_in(user) + + visit(new_project_milestone_path(project)) + end + + it "creates milestone" do + TITLE = "v2.3".freeze + + fill_in("Title", with: TITLE) + fill_in("Description", with: "# Description header") + click_button("Create milestone") + + expect(page).to have_content(TITLE) + .and have_content("Issues") + .and have_header_with_correct_id_and_link(1, "Description header", "description-header") + + visit(activity_project_path(project)) + + expect(page).to have_content("#{user.name} opened milestone") + end +end diff --git a/spec/features/milestones/user_deletes_milestone_spec.rb b/spec/features/milestones/user_deletes_milestone_spec.rb new file mode 100644 index 00000000000..414702daba4 --- /dev/null +++ b/spec/features/milestones/user_deletes_milestone_spec.rb @@ -0,0 +1,25 @@ +require "rails_helper" + +describe "User deletes milestone", :js do + set(:user) { create(:user) } + set(:project) { create(:project) } + set(:milestone) { create(:milestone, project: project) } + + before do + project.add_developer(user) + sign_in(user) + + visit(project_milestones_path(project)) + end + + it "deletes milestone" do + click_button("Delete") + click_button("Delete milestone") + + expect(page).to have_content("No milestones to show") + + visit(activity_project_path(project)) + + expect(page).to have_content("#{user.name} destroyed milestone") + end +end diff --git a/spec/features/milestones/user_views_milestone_spec.rb b/spec/features/milestones/user_views_milestone_spec.rb new file mode 100644 index 00000000000..83d8e2ff9e9 --- /dev/null +++ b/spec/features/milestones/user_views_milestone_spec.rb @@ -0,0 +1,31 @@ +require "rails_helper" + +describe "User views milestone" do + set(:user) { create(:user) } + set(:project) { create(:project) } + set(:milestone) { create(:milestone, project: project) } + set(:labels) { create_list(:label, 2, project: project) } + + before do + project.add_developer(user) + sign_in(user) + end + + it "avoids N+1 database queries" do + ISSUE_PARAMS = { project: project, assignees: [user], author: user, milestone: milestone, labels: labels }.freeze + + create(:labeled_issue, ISSUE_PARAMS) + + control = ActiveRecord::QueryRecorder.new { visit_milestone } + + create(:labeled_issue, ISSUE_PARAMS) + + expect { visit_milestone }.not_to exceed_query_limit(control) + end + + private + + def visit_milestone + visit(project_milestone_path(project, milestone)) + end +end diff --git a/spec/features/milestones/user_views_milestones_spec.rb b/spec/features/milestones/user_views_milestones_spec.rb new file mode 100644 index 00000000000..bebe40f73fd --- /dev/null +++ b/spec/features/milestones/user_views_milestones_spec.rb @@ -0,0 +1,35 @@ +require "rails_helper" + +describe "User views milestones" do + set(:user) { create(:user) } + set(:project) { create(:project) } + set(:milestone) { create(:milestone, project: project) } + + before do + project.add_developer(user) + sign_in(user) + + visit(project_milestones_path(project)) + end + + it "shows milestone" do + expect(page).to have_content(milestone.title) + .and have_content(milestone.expires_at) + .and have_content("Issues") + end + + context "with issues" do + set(:issue) { create(:issue, project: project, milestone: milestone) } + set(:closed_issue) { create(:closed_issue, project: project, milestone: milestone) } + + it "opens milestone" do + click_link(milestone.title) + + expect(current_path).to eq(project_milestone_path(project, milestone)) + expect(page).to have_content(milestone.title) + .and have_selector("#tab-issues li.issuable-row", count: 2) + .and have_content(issue.title) + .and have_content(closed_issue.title) + end + end +end diff --git a/spec/features/projects/branches/user_creates_branch_spec.rb b/spec/features/projects/branches/user_creates_branch_spec.rb new file mode 100644 index 00000000000..b706ad64954 --- /dev/null +++ b/spec/features/projects/branches/user_creates_branch_spec.rb @@ -0,0 +1,46 @@ +require "spec_helper" + +describe "User creates branch", :js do + include Spec::Support::Helpers::Features::BranchesHelpers + + let(:user) { create(:user) } + let(:project) { create(:project, :repository) } + + before do + project.add_developer(user) + sign_in(user) + + visit(new_project_branch_path(project)) + end + + it "creates new branch" do + BRANCH_NAME = "deploy_keys".freeze + + create_branch(BRANCH_NAME) + + expect(page).to have_content(BRANCH_NAME) + end + + context "when branch name is invalid" do + it "does not create new branch" do + INVALID_BRANCH_NAME = "1.0 stable".freeze + + fill_in("branch_name", with: INVALID_BRANCH_NAME) + page.find("body").click # defocus the branch_name input + + select_branch("master") + click_button("Create branch") + + expect(page).to have_content("Branch name is invalid") + expect(page).to have_content("can't contain spaces") + end + end + + context "when branch name already exists" do + it "does not create new branch" do + create_branch("master") + + expect(page).to have_content("Branch already exists") + end + end +end diff --git a/spec/features/projects/branches/user_deletes_branch_spec.rb b/spec/features/projects/branches/user_deletes_branch_spec.rb new file mode 100644 index 00000000000..96f215e1606 --- /dev/null +++ b/spec/features/projects/branches/user_deletes_branch_spec.rb @@ -0,0 +1,23 @@ +require "spec_helper" + +describe "User deletes branch", :js do + set(:user) { create(:user) } + set(:project) { create(:project, :repository) } + + before do + project.add_developer(user) + sign_in(user) + + visit(project_branches_path(project)) + end + + it "deletes branch" do + fill_in("branch-search", with: "improve/awesome").native.send_keys(:enter) + + page.within(".js-branch-improve\\/awesome") do + accept_alert { find(".btn-remove").click } + end + + expect(page).to have_css(".js-branch-improve\\/awesome", visible: :hidden) + end +end diff --git a/spec/features/projects/branches/user_views_branches_spec.rb b/spec/features/projects/branches/user_views_branches_spec.rb new file mode 100644 index 00000000000..62ae793151c --- /dev/null +++ b/spec/features/projects/branches/user_views_branches_spec.rb @@ -0,0 +1,34 @@ +require "spec_helper" + +describe "User views branches" do + set(:project) { create(:project, :repository) } + set(:user) { project.owner } + + before do + sign_in(user) + end + + context "all branches" do + before do + visit(project_branches_path(project)) + end + + it "shows branches" do + expect(page).to have_content("Branches").and have_content("master") + end + end + + context "protected branches" do + set(:protected_branch) { create(:protected_branch, project: project) } + + before do + visit(project_protected_branches_path(project)) + end + + it "shows branches" do + page.within(".protected-branches-list") do + expect(page).to have_content(protected_branch.name).and have_no_content("master") + end + end + end +end diff --git a/spec/features/projects/files/user_reads_pipeline_status_spec.rb b/spec/features/projects/files/user_reads_pipeline_status_spec.rb new file mode 100644 index 00000000000..2fb9da2f0a2 --- /dev/null +++ b/spec/features/projects/files/user_reads_pipeline_status_spec.rb @@ -0,0 +1,46 @@ +require 'spec_helper' + +describe 'user reads pipeline status', :js do + let(:project) { create(:project, :repository) } + let(:user) { create(:user) } + let(:v110_pipeline) { create_pipeline('v1.1.0', 'success') } + let(:x110_pipeline) { create_pipeline('x1.1.0', 'failed') } + + before do + project.add_master(user) + + project.repository.add_tag(user, 'x1.1.0', 'v1.1.0') + v110_pipeline + x110_pipeline + + sign_in(user) + end + + shared_examples 'visiting project tree' do + scenario 'sees the correct pipeline status' do + visit project_tree_path(project, expected_pipeline.ref) + wait_for_requests + + page.within('.blob-commit-info') do + expect(page).to have_link('', href: project_pipeline_path(project, expected_pipeline)) + expect(page).to have_selector(".ci-status-icon-#{expected_pipeline.status}") + end + end + end + + it_behaves_like 'visiting project tree' do + let(:expected_pipeline) { v110_pipeline } + end + + it_behaves_like 'visiting project tree' do + let(:expected_pipeline) { x110_pipeline } + end + + def create_pipeline(ref, status) + create(:ci_pipeline, + project: project, + ref: ref, + sha: project.commit(ref).sha, + status: status) + end +end diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb index 749a1b81872..a460024542c 100644 --- a/spec/features/projects/jobs_spec.rb +++ b/spec/features/projects/jobs_spec.rb @@ -464,6 +464,17 @@ feature 'Jobs' do expect(page).to have_content('This job has been skipped') end end + + context 'when job is failed but has no trace' do + let(:job) { create(:ci_build, :failed, pipeline: pipeline) } + + it 'renders empty state' do + visit project_job_path(project, job) + + expect(job).not_to have_trace + expect(page).to have_content('This job does not have a trace.') + end + end end describe "POST /:project/jobs/:id/cancel", :js do diff --git a/spec/features/users/login_spec.rb b/spec/features/users/login_spec.rb index bc75dc5d19b..9e10bfb2adc 100644 --- a/spec/features/users/login_spec.rb +++ b/spec/features/users/login_spec.rb @@ -392,7 +392,7 @@ feature 'Login' do end def ensure_one_active_tab - expect(page).to have_selector('.nav-tabs > li.active', count: 1) + expect(page).to have_selector('ul.new-session-tabs > li.active', count: 1) end def ensure_one_active_pane diff --git a/spec/fixtures/big-image.png b/spec/fixtures/big-image.png Binary files differnew file mode 100644 index 00000000000..a333363ac36 --- /dev/null +++ b/spec/fixtures/big-image.png diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb index ce96e90e2d7..46c55da24f8 100644 --- a/spec/helpers/projects_helper_spec.rb +++ b/spec/helpers/projects_helper_spec.rb @@ -322,74 +322,6 @@ describe ProjectsHelper do end end - describe "#project_feature_access_select" do - let(:project) { create(:project, :public) } - let(:user) { create(:user) } - - context "when project is internal or public" do - it "shows all options" do - helper.instance_variable_set(:@project, project) - result = helper.project_feature_access_select(:issues_access_level) - expect(result).to include("Disabled") - expect(result).to include("Only team members") - expect(result).to include("Everyone with access") - end - end - - context "when project is private" do - before do - project.update_attributes(visibility_level: Gitlab::VisibilityLevel::PRIVATE) - end - - it "shows only allowed options" do - helper.instance_variable_set(:@project, project) - result = helper.project_feature_access_select(:issues_access_level) - expect(result).to include("Disabled") - expect(result).to include("Only team members") - expect(result).to have_selector('option[disabled]', text: "Everyone with access") - end - end - - context "when project moves from public to private" do - before do - project.update_attributes(visibility_level: Gitlab::VisibilityLevel::PRIVATE) - end - - it "shows the highest allowed level selected" do - helper.instance_variable_set(:@project, project) - result = helper.project_feature_access_select(:issues_access_level) - - expect(result).to include("Disabled") - expect(result).to include("Only team members") - expect(result).to have_selector('option[disabled]', text: "Everyone with access") - expect(result).to have_selector('option[selected]', text: "Only team members") - end - end - end - - describe "#visibility_select_options" do - let(:project) { create(:project, :repository) } - let(:user) { create(:user) } - - before do - allow(helper).to receive(:current_user).and_return(user) - - stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC]) - end - - it "does not include the Public restricted level" do - expect(helper.send(:visibility_select_options, project, Gitlab::VisibilityLevel::PRIVATE)).not_to include('Public') - end - - it "includes the Internal level" do - expect(helper.send(:visibility_select_options, project, Gitlab::VisibilityLevel::PRIVATE)).to include('Internal') - end - - it "includes the Private level" do - expect(helper.send(:visibility_select_options, project, Gitlab::VisibilityLevel::PRIVATE)).to include('Private') - end - end - describe '#get_project_nav_tabs' do let(:project) { create(:project) } let(:user) { create(:user) } diff --git a/spec/javascripts/fixtures/linked_tabs.html.haml b/spec/javascripts/fixtures/linked_tabs.html.haml index 93c0cf97ff0..c38fe8b1f25 100644 --- a/spec/javascripts/fixtures/linked_tabs.html.haml +++ b/spec/javascripts/fixtures/linked_tabs.html.haml @@ -1,4 +1,4 @@ -%ul.nav.nav-tabs.linked-tabs +%ul.nav-links.new-session-tabs.linked-tabs %li %a{ href: 'foo/bar/1', data: { target: 'div#tab1', action: 'tab1', toggle: 'tab' } } Tab 1 diff --git a/spec/javascripts/fixtures/signin_tabs.html.haml b/spec/javascripts/fixtures/signin_tabs.html.haml index 12b8d423cbe..2e00fe7865e 100644 --- a/spec/javascripts/fixtures/signin_tabs.html.haml +++ b/spec/javascripts/fixtures/signin_tabs.html.haml @@ -1,5 +1,5 @@ -%ul.nav-tabs +%ul.nav-links.new-session-tabs + %li.active + %a{ href: '#ldap' } LDAP %li - %a.active{ id: 'standard', href: '#standard'} Standard - %li - %a{ id: 'ldap', href: '#ldap'} Ldap + %a{ href: '#login-pane'} Standard diff --git a/spec/javascripts/ide/components/repo_editor_spec.js b/spec/javascripts/ide/components/repo_editor_spec.js index 63a3d2c6cd5..310d222377f 100644 --- a/spec/javascripts/ide/components/repo_editor_spec.js +++ b/spec/javascripts/ide/components/repo_editor_spec.js @@ -1,9 +1,12 @@ import Vue from 'vue'; +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; import store from '~/ide/stores'; import repoEditor from '~/ide/components/repo_editor.vue'; import monacoLoader from '~/ide/monaco_loader'; import Editor from '~/ide/lib/editor'; import { createComponentWithStore } from '../../helpers/vue_mount_component_helper'; +import setTimeoutPromise from '../../helpers/set_timeout_promise_helper'; import { file, resetStore } from '../helpers'; describe('RepoEditor', () => { @@ -35,7 +38,7 @@ describe('RepoEditor', () => { resetStore(vm.$store); - Editor.editorInstance.modelManager.dispose(); + Editor.editorInstance.dispose(); }); it('renders an ide container', done => { @@ -79,16 +82,30 @@ describe('RepoEditor', () => { }); describe('when file is markdown and viewer mode is review', () => { + let mock; + beforeEach(done => { + mock = new MockAdapter(axios); + + vm.file.projectId = 'namespace/project'; vm.file.previewMode = { id: 'markdown', previewTitle: 'Preview Markdown', }; + vm.file.content = 'testing 123'; vm.$store.state.viewer = 'diff'; + mock.onPost(/(.*)\/preview_markdown/).reply(200, { + body: '<p>testing 123</p>', + }); + vm.$nextTick(done); }); + afterEach(() => { + mock.restore(); + }); + it('renders an Edit and a Preview Tab', done => { Vue.nextTick(() => { const tabs = vm.$el.querySelectorAll('.ide-mode-tabs .nav-links li'); @@ -99,6 +116,26 @@ describe('RepoEditor', () => { done(); }); }); + + it('renders markdown for tempFile', done => { + vm.file.tempFile = true; + vm.file.path = `${vm.file.path}.md`; + vm.$store.state.entries[vm.file.path] = vm.file; + + vm + .$nextTick() + .then(() => { + vm.$el.querySelectorAll('.ide-mode-tabs .nav-links a')[1].click(); + }) + .then(setTimeoutPromise) + .then(() => { + expect(vm.$el.querySelector('.preview-container').innerHTML).toContain( + '<p>testing 123</p>', + ); + }) + .then(done) + .catch(done.fail); + }); }); describe('when open file is binary and not raw', () => { diff --git a/spec/javascripts/pipelines/pipeline_details_mediator_spec.js b/spec/javascripts/pipelines/pipeline_details_mediator_spec.js index e58a8018ed5..61ee2dc13ca 100644 --- a/spec/javascripts/pipelines/pipeline_details_mediator_spec.js +++ b/spec/javascripts/pipelines/pipeline_details_mediator_spec.js @@ -1,42 +1,36 @@ -import _ from 'underscore'; -import Vue from 'vue'; +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; import PipelineMediator from '~/pipelines/pipeline_details_mediator'; describe('PipelineMdediator', () => { let mediator; + let mock; + beforeEach(() => { - mediator = new PipelineMediator({ endpoint: 'foo' }); + mock = new MockAdapter(axios); + mediator = new PipelineMediator({ endpoint: 'foo.json' }); + }); + + afterEach(() => { + mock.restore(); }); it('should set defaults', () => { - expect(mediator.options).toEqual({ endpoint: 'foo' }); + expect(mediator.options).toEqual({ endpoint: 'foo.json' }); expect(mediator.state.isLoading).toEqual(false); expect(mediator.store).toBeDefined(); expect(mediator.service).toBeDefined(); }); describe('request and store data', () => { - const interceptor = (request, next) => { - next(request.respondWith(JSON.stringify({ foo: 'bar' }), { - status: 200, - })); - }; - - beforeEach(() => { - Vue.http.interceptors.push(interceptor); - }); - - afterEach(() => { - Vue.http.interceptors = _.without(Vue.http.interceptor, interceptor); - }); - - it('should store received data', (done) => { + it('should store received data', done => { + mock.onGet('foo.json').reply(200, { id: '121123' }); mediator.fetchPipeline(); setTimeout(() => { - expect(mediator.store.state.pipeline).toEqual({ foo: 'bar' }); + expect(mediator.store.state.pipeline).toEqual({ id: '121123' }); done(); - }); + }, 0); }); }); }); diff --git a/spec/javascripts/pipelines/stage_spec.js b/spec/javascripts/pipelines/stage_spec.js index 61c2f783acc..c2ed2e9a31b 100644 --- a/spec/javascripts/pipelines/stage_spec.js +++ b/spec/javascripts/pipelines/stage_spec.js @@ -1,27 +1,35 @@ -import _ from 'underscore'; import Vue from 'vue'; +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; import stage from '~/pipelines/components/stage.vue'; +import mountComponent from 'spec/helpers/vue_mount_component_helper'; describe('Pipelines stage component', () => { let StageComponent; let component; + let mock; beforeEach(() => { + mock = new MockAdapter(axios); + StageComponent = Vue.extend(stage); - component = new StageComponent({ - propsData: { - stage: { - status: { - group: 'success', - icon: 'icon_status_success', - title: 'success', - }, - dropdown_path: 'foo', + component = mountComponent(StageComponent, { + stage: { + status: { + group: 'success', + icon: 'icon_status_success', + title: 'success', }, - updateDropdown: false, + dropdown_path: 'path.json', }, - }).$mount(); + updateDropdown: false, + }); + }); + + afterEach(() => { + component.$destroy(); + mock.restore(); }); it('should render a dropdown with the status icon', () => { @@ -31,23 +39,11 @@ describe('Pipelines stage component', () => { }); describe('with successfull request', () => { - const interceptor = (request, next) => { - next(request.respondWith(JSON.stringify({ html: 'foo' }), { - status: 200, - })); - }; - beforeEach(() => { - Vue.http.interceptors.push(interceptor); - }); - - afterEach(() => { - Vue.http.interceptors = _.without( - Vue.http.interceptors, interceptor, - ); + mock.onGet('path.json').reply(200, { html: 'foo' }); }); - it('should render the received data', (done) => { + it('should render the received data', done => { component.$el.querySelector('button').click(); setTimeout(() => { @@ -60,20 +56,8 @@ describe('Pipelines stage component', () => { }); describe('when request fails', () => { - const interceptor = (request, next) => { - next(request.respondWith(JSON.stringify({}), { - status: 500, - })); - }; - beforeEach(() => { - Vue.http.interceptors.push(interceptor); - }); - - afterEach(() => { - Vue.http.interceptors = _.without( - Vue.http.interceptors, interceptor, - ); + mock.onGet('path.json').reply(500); }); it('should close the dropdown', () => { @@ -86,33 +70,18 @@ describe('Pipelines stage component', () => { }); describe('update endpoint correctly', () => { - const updatedInterceptor = (request, next) => { - if (request.url === 'bar') { - next(request.respondWith(JSON.stringify({ html: 'this is the updated content' }), { - status: 200, - })); - } - next(); - }; - beforeEach(() => { - Vue.http.interceptors.push(updatedInterceptor); - }); - - afterEach(() => { - Vue.http.interceptors = _.without( - Vue.http.interceptors, updatedInterceptor, - ); + mock.onGet('bar.json').reply(200, { html: 'this is the updated content' }); }); - it('should update the stage to request the new endpoint provided', (done) => { + it('should update the stage to request the new endpoint provided', done => { component.stage = { status: { group: 'running', icon: 'running', title: 'running', }, - dropdown_path: 'bar', + dropdown_path: 'bar.json', }; Vue.nextTick(() => { @@ -121,7 +90,7 @@ describe('Pipelines stage component', () => { setTimeout(() => { expect( component.$el.querySelector('.js-builds-dropdown-container ul').textContent.trim(), - ).toEqual('this is the updated content'); + ).toEqual('this is the updated content'); done(); }); }); diff --git a/spec/javascripts/signin_tabs_memoizer_spec.js b/spec/javascripts/signin_tabs_memoizer_spec.js index b1b03ef1e09..423432c9e5d 100644 --- a/spec/javascripts/signin_tabs_memoizer_spec.js +++ b/spec/javascripts/signin_tabs_memoizer_spec.js @@ -4,7 +4,7 @@ import SigninTabsMemoizer from '~/pages/sessions/new/signin_tabs_memoizer'; (() => { describe('SigninTabsMemoizer', () => { const fixtureTemplate = 'static/signin_tabs.html.raw'; - const tabSelector = 'ul.nav-tabs'; + const tabSelector = 'ul.new-session-tabs'; const currentTabKey = 'current_signin_tab'; let memo; @@ -27,7 +27,7 @@ import SigninTabsMemoizer from '~/pages/sessions/new/signin_tabs_memoizer'; it('does nothing if no tab was previously selected', () => { createMemoizer(); - expect(document.querySelector('li a.active').getAttribute('id')).toEqual('standard'); + expect(document.querySelector(`${tabSelector} > li.active a`).getAttribute('href')).toEqual('#ldap'); }); it('shows last selected tab on boot', () => { @@ -48,9 +48,9 @@ import SigninTabsMemoizer from '~/pages/sessions/new/signin_tabs_memoizer'; it('saves last selected tab on change', () => { createMemoizer(); - document.getElementById('standard').click(); + document.querySelector('a[href="#login-pane"]').click(); - expect(memo.readData()).toEqual('#standard'); + expect(memo.readData()).toEqual('#login-pane'); }); it('overrides last selected tab with hash tag when given', () => { diff --git a/spec/javascripts/test_bundle.js b/spec/javascripts/test_bundle.js index d158786e484..14bff05e537 100644 --- a/spec/javascripts/test_bundle.js +++ b/spec/javascripts/test_bundle.js @@ -5,6 +5,7 @@ import '~/commons'; import Vue from 'vue'; import VueResource from 'vue-resource'; +import Translate from '~/vue_shared/translate'; import { getDefaultAdapter } from '~/lib/utils/axios_utils'; import { FIXTURES_PATH, TEST_HOST } from './test_constants'; @@ -22,12 +23,13 @@ Vue.config.warnHandler = (msg, vm, trace) => { }; let hasVueErrors = false; -Vue.config.errorHandler = function (err) { +Vue.config.errorHandler = function(err) { hasVueErrors = true; fail(err); }; Vue.use(VueResource); +Vue.use(Translate); // enable test fixtures jasmine.getFixtures().fixturesPath = FIXTURES_PATH; @@ -43,10 +45,11 @@ window.gl = window.gl || {}; window.gl.TEST_HOST = TEST_HOST; window.gon = window.gon || {}; window.gon.test_env = true; +gon.relative_url_root = ''; let hasUnhandledPromiseRejections = false; -window.addEventListener('unhandledrejection', (event) => { +window.addEventListener('unhandledrejection', event => { hasUnhandledPromiseRejections = true; console.error('Unhandled promise rejection:'); console.error(event.reason.stack || event.reason); @@ -69,15 +72,25 @@ beforeEach(() => { const axiosDefaultAdapter = getDefaultAdapter(); +let testFiles = process.env.TEST_FILES || []; +if (testFiles.length > 0) { + testFiles = testFiles.map(path => path.replace(/^spec\/javascripts\//, '').replace(/\.js$/, '')); + console.log(`Running only tests matching: ${testFiles}`); +} else { + console.log('Running all tests'); +} + // render all of our tests const testsContext = require.context('.', true, /_spec$/); -testsContext.keys().forEach(function (path) { +testsContext.keys().forEach(function(path) { try { - testsContext(path); + if (testFiles.length === 0 || testFiles.some(p => path.includes(p))) { + testsContext(path); + } } catch (err) { console.error('[ERROR] Unable to load spec: ', path); - describe('Test bundle', function () { - it(`includes '${path}'`, function () { + describe('Test bundle', function() { + it(`includes '${path}'`, function() { expect(err).toBeNull(); }); }); @@ -85,7 +98,7 @@ testsContext.keys().forEach(function (path) { }); describe('test errors', () => { - beforeAll((done) => { + beforeAll(done => { if (hasUnhandledPromiseRejections || hasVueWarnings || hasVueErrors) { setTimeout(done, 1000); } else { @@ -149,18 +162,18 @@ if (process.env.BABEL_ENV === 'coverage') { './issue_show/index.js', ]; - describe('Uncovered files', function () { + describe('Uncovered files', function() { const sourceFiles = require.context('~', true, /\.js$/); $.holdReady(true); - sourceFiles.keys().forEach(function (path) { + sourceFiles.keys().forEach(function(path) { // ignore if there is a matching spec file if (testsContext.keys().indexOf(`${path.replace(/\.js$/, '')}_spec`) > -1) { return; } - it(`includes '${path}'`, function () { + it(`includes '${path}'`, function() { try { sourceFiles(path); } catch (err) { diff --git a/spec/javascripts/visibility_select_spec.js b/spec/javascripts/visibility_select_spec.js deleted file mode 100644 index 82714cb69bd..00000000000 --- a/spec/javascripts/visibility_select_spec.js +++ /dev/null @@ -1,98 +0,0 @@ -import VisibilitySelect from '~/visibility_select'; - -(() => { - describe('VisibilitySelect', function () { - const lockedElement = document.createElement('div'); - lockedElement.dataset.helpBlock = 'lockedHelpBlock'; - - const checkedElement = document.createElement('div'); - checkedElement.dataset.description = 'checkedDescription'; - - const mockElements = { - container: document.createElement('div'), - select: document.createElement('div'), - '.help-block': document.createElement('div'), - '.js-locked': lockedElement, - 'option:checked': checkedElement, - }; - - beforeEach(function () { - spyOn(Element.prototype, 'querySelector').and.callFake(selector => mockElements[selector]); - }); - - describe('constructor', function () { - beforeEach(function () { - this.visibilitySelect = new VisibilitySelect(mockElements.container); - }); - - it('sets the container member', function () { - expect(this.visibilitySelect.container).toEqual(mockElements.container); - }); - - it('queries and sets the helpBlock member', function () { - expect(Element.prototype.querySelector).toHaveBeenCalledWith('.help-block'); - expect(this.visibilitySelect.helpBlock).toEqual(mockElements['.help-block']); - }); - - it('queries and sets the select member', function () { - expect(Element.prototype.querySelector).toHaveBeenCalledWith('select'); - expect(this.visibilitySelect.select).toEqual(mockElements.select); - }); - - describe('if there is no container element provided', function () { - it('throws an error', function () { - expect(() => new VisibilitySelect()).toThrowError('VisibilitySelect requires a container element as argument 1'); - }); - }); - }); - - describe('init', function () { - describe('if there is a select', function () { - beforeEach(function () { - this.visibilitySelect = new VisibilitySelect(mockElements.container); - }); - - it('calls updateHelpText', function () { - spyOn(VisibilitySelect.prototype, 'updateHelpText'); - this.visibilitySelect.init(); - expect(this.visibilitySelect.updateHelpText).toHaveBeenCalled(); - }); - - it('adds a change event listener', function () { - spyOn(this.visibilitySelect.select, 'addEventListener'); - this.visibilitySelect.init(); - expect(this.visibilitySelect.select.addEventListener.calls.argsFor(0)).toContain('change'); - }); - }); - - describe('if there is no select', function () { - beforeEach(function () { - mockElements.select = undefined; - this.visibilitySelect = new VisibilitySelect(mockElements.container); - this.visibilitySelect.init(); - }); - - it('updates the helpBlock text to the locked `data-help-block` messaged', function () { - expect(this.visibilitySelect.helpBlock.textContent) - .toEqual(lockedElement.dataset.helpBlock); - }); - - afterEach(function () { - mockElements.select = document.createElement('div'); - }); - }); - }); - - describe('updateHelpText', function () { - beforeEach(function () { - this.visibilitySelect = new VisibilitySelect(mockElements.container); - this.visibilitySelect.init(); - }); - - it('updates the helpBlock text to the selected options `data-description`', function () { - expect(this.visibilitySelect.helpBlock.textContent) - .toEqual(checkedElement.dataset.description); - }); - }); - }); -})(); diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_unresolved_discussions_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_unresolved_discussions_spec.js index 046968fbc1f..d797f1266df 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_unresolved_discussions_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_unresolved_discussions_spec.js @@ -1,47 +1,37 @@ import Vue from 'vue'; import UnresolvedDiscussions from '~/vue_merge_request_widget/components/states/unresolved_discussions.vue'; +import mountComponent from 'spec/helpers/vue_mount_component_helper'; describe('UnresolvedDiscussions', () => { - describe('props', () => { - it('should have props', () => { - const { mr } = UnresolvedDiscussions.props; + const Component = Vue.extend(UnresolvedDiscussions); + let vm; - expect(mr.type instanceof Object).toBeTruthy(); - expect(mr.required).toBeTruthy(); - }); + afterEach(() => { + vm.$destroy(); }); - describe('template', () => { - let el; - let vm; - const path = 'foo/bar'; - + describe('with discussions path', () => { beforeEach(() => { - const Component = Vue.extend(UnresolvedDiscussions); - const mr = { - createIssueToResolveDiscussionsPath: path, - }; - vm = new Component({ - el: document.createElement('div'), - propsData: { mr }, - }); - el = vm.$el; + vm = mountComponent(Component, { mr: { + createIssueToResolveDiscussionsPath: gl.TEST_HOST, + } }); }); it('should have correct elements', () => { - expect(el.classList.contains('mr-widget-body')).toBeTruthy(); - expect(el.innerText).toContain('There are unresolved discussions. Please resolve these discussions'); - expect(el.innerText).toContain('Create an issue to resolve them later'); - expect(el.querySelector('.js-create-issue').getAttribute('href')).toEqual(path); + expect(vm.$el.innerText).toContain('There are unresolved discussions. Please resolve these discussions'); + expect(vm.$el.innerText).toContain('Create an issue to resolve them later'); + expect(vm.$el.querySelector('.js-create-issue').getAttribute('href')).toEqual(gl.TEST_HOST); }); + }); - it('should not show create issue button if user cannot create issue', (done) => { - vm.mr.createIssueToResolveDiscussionsPath = ''; + describe('without discussions path', () => { + beforeEach(() => { + vm = mountComponent(Component, { mr: {} }); + }); - Vue.nextTick(() => { - expect(el.querySelector('.js-create-issue')).toEqual(null); - done(); - }); + it('should not show create issue link if user cannot create issue', () => { + expect(vm.$el.innerText).toContain('There are unresolved discussions. Please resolve these discussions'); + expect(vm.$el.querySelector('.js-create-issue')).toEqual(null); }); }); }); diff --git a/spec/javascripts/vue_shared/components/markdown/header_spec.js b/spec/javascripts/vue_shared/components/markdown/header_spec.js index edebd822295..02117638b63 100644 --- a/spec/javascripts/vue_shared/components/markdown/header_spec.js +++ b/spec/javascripts/vue_shared/components/markdown/header_spec.js @@ -1,10 +1,11 @@ import Vue from 'vue'; +import $ from 'jquery'; import headerComponent from '~/vue_shared/components/markdown/header.vue'; describe('Markdown field header component', () => { let vm; - beforeEach((done) => { + beforeEach(done => { const Component = Vue.extend(headerComponent); vm = new Component({ @@ -17,24 +18,18 @@ describe('Markdown field header component', () => { }); it('renders markdown buttons', () => { - expect( - vm.$el.querySelectorAll('.js-md').length, - ).toBe(7); + expect(vm.$el.querySelectorAll('.js-md').length).toBe(7); }); it('renders `write` link as active when previewMarkdown is false', () => { - expect( - vm.$el.querySelector('li:nth-child(1)').classList.contains('active'), - ).toBeTruthy(); + expect(vm.$el.querySelector('li:nth-child(1)').classList.contains('active')).toBeTruthy(); }); - it('renders `preview` link as active when previewMarkdown is true', (done) => { + it('renders `preview` link as active when previewMarkdown is true', done => { vm.previewMarkdown = true; Vue.nextTick(() => { - expect( - vm.$el.querySelector('li:nth-child(2)').classList.contains('active'), - ).toBeTruthy(); + expect(vm.$el.querySelector('li:nth-child(2)').classList.contains('active')).toBeTruthy(); done(); }); @@ -52,16 +47,24 @@ describe('Markdown field header component', () => { expect(vm.$emit).toHaveBeenCalledWith('write-markdown'); }); - it('blurs preview link after click', (done) => { + it('does not emit toggle markdown event when triggered from another form', () => { + spyOn(vm, '$emit'); + + $(document).triggerHandler('markdown-preview:show', [ + $('<form><textarea class="markdown-area"></textarea></textarea></form>'), + ]); + + expect(vm.$emit).not.toHaveBeenCalled(); + }); + + it('blurs preview link after click', done => { const link = vm.$el.querySelector('li:nth-child(2) a'); spyOn(HTMLElement.prototype, 'blur'); link.click(); setTimeout(() => { - expect( - link.blur, - ).toHaveBeenCalled(); + expect(link.blur).toHaveBeenCalled(); done(); }); diff --git a/spec/lib/banzai/commit_renderer_spec.rb b/spec/lib/banzai/commit_renderer_spec.rb index e7ebb2a332f..1f53657c59c 100644 --- a/spec/lib/banzai/commit_renderer_spec.rb +++ b/spec/lib/banzai/commit_renderer_spec.rb @@ -6,7 +6,10 @@ describe Banzai::CommitRenderer do user = build(:user) project = create(:project, :repository) - expect(Banzai::ObjectRenderer).to receive(:new).with(project, user).and_call_original + expect(Banzai::ObjectRenderer) + .to receive(:new) + .with(user: user, default_project: project) + .and_call_original described_class::ATTRIBUTES.each do |attr| expect_any_instance_of(Banzai::ObjectRenderer).to receive(:render).with([project.commit], attr).once.and_call_original diff --git a/spec/lib/banzai/issuable_extractor_spec.rb b/spec/lib/banzai/issuable_extractor_spec.rb index 69763476dac..f42951d9781 100644 --- a/spec/lib/banzai/issuable_extractor_spec.rb +++ b/spec/lib/banzai/issuable_extractor_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Banzai::IssuableExtractor do let(:project) { create(:project) } let(:user) { create(:user) } - let(:extractor) { described_class.new(project, user) } + let(:extractor) { described_class.new(Banzai::RenderContext.new(project, user)) } let(:issue) { create(:issue, project: project) } let(:merge_request) { create(:merge_request, source_project: project) } let(:issue_link) do diff --git a/spec/lib/banzai/object_renderer_spec.rb b/spec/lib/banzai/object_renderer_spec.rb index 074d521a5c6..1fe034ae9a2 100644 --- a/spec/lib/banzai/object_renderer_spec.rb +++ b/spec/lib/banzai/object_renderer_spec.rb @@ -3,7 +3,14 @@ require 'spec_helper' describe Banzai::ObjectRenderer do let(:project) { create(:project, :repository) } let(:user) { project.owner } - let(:renderer) { described_class.new(project, user, custom_value: 'value') } + let(:renderer) do + described_class.new( + default_project: project, + user: user, + redaction_context: { custom_value: 'value' } + ) + end + let(:object) { Note.new(note: 'hello', note_html: '<p dir="auto">hello</p>', cached_markdown_version: CacheMarkdownField::CACHE_VERSION) } describe '#render' do diff --git a/spec/lib/banzai/redactor_spec.rb b/spec/lib/banzai/redactor_spec.rb index 441f3725985..aaeec953e4b 100644 --- a/spec/lib/banzai/redactor_spec.rb +++ b/spec/lib/banzai/redactor_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Banzai::Redactor do let(:user) { create(:user) } let(:project) { build(:project) } - let(:redactor) { described_class.new(project, user) } + let(:redactor) { described_class.new(Banzai::RenderContext.new(project, user)) } describe '#redact' do context 'when reference not visible to user' do @@ -54,7 +54,7 @@ describe Banzai::Redactor do context 'when project is in pending delete' do let!(:issue) { create(:issue, project: project) } - let(:redactor) { described_class.new(project, user) } + let(:redactor) { described_class.new(Banzai::RenderContext.new(project, user)) } before do project.update(pending_delete: true) diff --git a/spec/lib/banzai/reference_parser/base_parser_spec.rb b/spec/lib/banzai/reference_parser/base_parser_spec.rb index 6175d4c4ca9..4e6e8eca38a 100644 --- a/spec/lib/banzai/reference_parser/base_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/base_parser_spec.rb @@ -5,13 +5,14 @@ describe Banzai::ReferenceParser::BaseParser do let(:user) { create(:user) } let(:project) { create(:project, :public) } + let(:context) { Banzai::RenderContext.new(project, user) } subject do klass = Class.new(described_class) do self.reference_type = :foo end - klass.new(project, user) + klass.new(context) end describe '.reference_type=' do @@ -23,6 +24,19 @@ describe Banzai::ReferenceParser::BaseParser do end end + describe '#project_for_node' do + it 'returns the Project for a node' do + document = instance_double('document', fragment?: false) + project = instance_double('project') + object = instance_double('object', project: project) + node = instance_double('node', document: document) + + context.associate_document(document, object) + + expect(subject.project_for_node(node)).to eq(project) + end + end + describe '#nodes_visible_to_user' do let(:link) { empty_html_link } @@ -164,7 +178,7 @@ describe Banzai::ReferenceParser::BaseParser do self.reference_type = :test end - instance = dummy.new(project, user) + instance = dummy.new(Banzai::RenderContext.new(project, user)) document = Nokogiri::HTML.fragment('<a class="gfm"></a><a class="gfm" data-reference-type="test"></a>') expect(instance).to receive(:gather_references) diff --git a/spec/lib/banzai/reference_parser/commit_parser_spec.rb b/spec/lib/banzai/reference_parser/commit_parser_spec.rb index 3505659c2c3..cca53a8b9b9 100644 --- a/spec/lib/banzai/reference_parser/commit_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/commit_parser_spec.rb @@ -5,7 +5,7 @@ describe Banzai::ReferenceParser::CommitParser do let(:project) { create(:project, :public) } let(:user) { create(:user) } - subject { described_class.new(project, user) } + subject { described_class.new(Banzai::RenderContext.new(project, user)) } let(:link) { empty_html_link } describe '#nodes_visible_to_user' do diff --git a/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb b/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb index 21813177deb..23e16fe0213 100644 --- a/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb @@ -5,7 +5,7 @@ describe Banzai::ReferenceParser::CommitRangeParser do let(:project) { create(:project, :public) } let(:user) { create(:user) } - subject { described_class.new(project, user) } + subject { described_class.new(Banzai::RenderContext.new(project, user)) } let(:link) { empty_html_link } describe '#nodes_visible_to_user' do diff --git a/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb b/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb index 25969b65168..1cb31e57114 100644 --- a/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb @@ -5,7 +5,7 @@ describe Banzai::ReferenceParser::ExternalIssueParser do let(:project) { create(:project, :public) } let(:user) { create(:user) } - subject { described_class.new(project, user) } + subject { described_class.new(Banzai::RenderContext.new(project, user)) } let(:link) { empty_html_link } describe '#nodes_visible_to_user' do diff --git a/spec/lib/banzai/reference_parser/issue_parser_spec.rb b/spec/lib/banzai/reference_parser/issue_parser_spec.rb index cb7f8b20dda..77c2064caba 100644 --- a/spec/lib/banzai/reference_parser/issue_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/issue_parser_spec.rb @@ -7,7 +7,7 @@ describe Banzai::ReferenceParser::IssueParser do let(:user) { create(:user) } let(:issue) { create(:issue, project: project) } let(:link) { empty_html_link } - subject { described_class.new(project, user) } + subject { described_class.new(Banzai::RenderContext.new(project, user)) } describe '#nodes_visible_to_user' do context 'when the link has a data-issue attribute' do diff --git a/spec/lib/banzai/reference_parser/label_parser_spec.rb b/spec/lib/banzai/reference_parser/label_parser_spec.rb index b700161d6c2..e4df2533821 100644 --- a/spec/lib/banzai/reference_parser/label_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/label_parser_spec.rb @@ -6,7 +6,7 @@ describe Banzai::ReferenceParser::LabelParser do let(:project) { create(:project, :public) } let(:user) { create(:user) } let(:label) { create(:label, project: project) } - subject { described_class.new(project, user) } + subject { described_class.new(Banzai::RenderContext.new(project, user)) } let(:link) { empty_html_link } describe '#nodes_visible_to_user' do diff --git a/spec/lib/banzai/reference_parser/merge_request_parser_spec.rb b/spec/lib/banzai/reference_parser/merge_request_parser_spec.rb index 14542342cf6..5417b1f00be 100644 --- a/spec/lib/banzai/reference_parser/merge_request_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/merge_request_parser_spec.rb @@ -6,7 +6,7 @@ describe Banzai::ReferenceParser::MergeRequestParser do let(:user) { create(:user) } let(:project) { create(:project, :public) } let(:merge_request) { create(:merge_request, source_project: project) } - subject { described_class.new(project, user) } + subject { described_class.new(Banzai::RenderContext.new(merge_request.target_project, user)) } let(:link) { empty_html_link } describe '#nodes_visible_to_user' do diff --git a/spec/lib/banzai/reference_parser/milestone_parser_spec.rb b/spec/lib/banzai/reference_parser/milestone_parser_spec.rb index 7dacdf8d629..751d042ffde 100644 --- a/spec/lib/banzai/reference_parser/milestone_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/milestone_parser_spec.rb @@ -6,7 +6,7 @@ describe Banzai::ReferenceParser::MilestoneParser do let(:project) { create(:project, :public) } let(:user) { create(:user) } let(:milestone) { create(:milestone, project: project) } - subject { described_class.new(project, user) } + subject { described_class.new(Banzai::RenderContext.new(project, user)) } let(:link) { empty_html_link } describe '#nodes_visible_to_user' do diff --git a/spec/lib/banzai/reference_parser/snippet_parser_spec.rb b/spec/lib/banzai/reference_parser/snippet_parser_spec.rb index 69ec3f66aa8..d410bd4c164 100644 --- a/spec/lib/banzai/reference_parser/snippet_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/snippet_parser_spec.rb @@ -9,7 +9,7 @@ describe Banzai::ReferenceParser::SnippetParser do let(:external_user) { create(:user, :external) } let(:project_member) { create(:user) } - subject { described_class.new(project, user) } + subject { described_class.new(Banzai::RenderContext.new(project, user)) } let(:link) { empty_html_link } def visible_references(snippet_visibility, user = nil) diff --git a/spec/lib/banzai/reference_parser/user_parser_spec.rb b/spec/lib/banzai/reference_parser/user_parser_spec.rb index b079a3be029..112447f098e 100644 --- a/spec/lib/banzai/reference_parser/user_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/user_parser_spec.rb @@ -6,7 +6,7 @@ describe Banzai::ReferenceParser::UserParser do let(:group) { create(:group) } let(:user) { create(:user) } let(:project) { create(:project, :public, group: group, creator: user) } - subject { described_class.new(project, user) } + subject { described_class.new(Banzai::RenderContext.new(project, user)) } let(:link) { empty_html_link } describe '#referenced_by' do diff --git a/spec/lib/banzai/render_context_spec.rb b/spec/lib/banzai/render_context_spec.rb new file mode 100644 index 00000000000..ad17db11613 --- /dev/null +++ b/spec/lib/banzai/render_context_spec.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Banzai::RenderContext do + let(:document) { Nokogiri::HTML.fragment('<p>hello</p>') } + + describe '#project_for_node' do + it 'returns the default project if no associated project was found' do + project = instance_double('project') + context = described_class.new(project) + + expect(context.project_for_node(document)).to eq(project) + end + + it 'returns the associated project if one was associated explicitly' do + project = instance_double('project') + obj = instance_double('object', project: project) + context = described_class.new + + context.associate_document(document, obj) + + expect(context.project_for_node(document)).to eq(project) + end + + it 'returns the project associated with a DocumentFragment when using a node' do + project = instance_double('project') + obj = instance_double('object', project: project) + context = described_class.new + node = document.children.first + + context.associate_document(document, obj) + + expect(context.project_for_node(node)).to eq(project) + end + end +end diff --git a/spec/lib/gitlab/ci/status/build/common_spec.rb b/spec/lib/gitlab/ci/status/build/common_spec.rb index 2cce7a23ea7..ca3c66f0152 100644 --- a/spec/lib/gitlab/ci/status/build/common_spec.rb +++ b/spec/lib/gitlab/ci/status/build/common_spec.rb @@ -38,4 +38,10 @@ describe Gitlab::Ci::Status::Build::Common do expect(subject.details_path).to include "jobs/#{build.id}" end end + + describe '#illustration' do + it 'provides a fallback empty state illustration' do + expect(subject.illustration).not_to be_empty + end + end end diff --git a/spec/lib/gitlab/ci/status/build/factory_spec.rb b/spec/lib/gitlab/ci/status/build/factory_spec.rb index 6d5b73bb01b..d53a7d468e3 100644 --- a/spec/lib/gitlab/ci/status/build/factory_spec.rb +++ b/spec/lib/gitlab/ci/status/build/factory_spec.rb @@ -75,7 +75,8 @@ describe Gitlab::Ci::Status::Build::Factory do it 'matches correct extended statuses' do expect(factory.extended_statuses) - .to eq [Gitlab::Ci::Status::Build::Retryable, Gitlab::Ci::Status::Build::Failed] + .to eq [Gitlab::Ci::Status::Build::Retryable, + Gitlab::Ci::Status::Build::Failed] end it 'fabricates a failed build status' do @@ -94,7 +95,7 @@ describe Gitlab::Ci::Status::Build::Factory do end context 'when build is allowed to fail' do - let(:build) { create(:ci_build, :failed, :allowed_to_fail) } + let(:build) { create(:ci_build, :failed, :allowed_to_fail, :trace_artifact) } it 'matches correct core status' do expect(factory.core_status).to be_a Gitlab::Ci::Status::Failed diff --git a/spec/lib/gitlab/ci/variables/collection/item_spec.rb b/spec/lib/gitlab/ci/variables/collection/item_spec.rb index bf9208f1ff4..e79f0a7f257 100644 --- a/spec/lib/gitlab/ci/variables/collection/item_spec.rb +++ b/spec/lib/gitlab/ci/variables/collection/item_spec.rb @@ -5,6 +5,18 @@ describe Gitlab::Ci::Variables::Collection::Item do { key: 'VAR', value: 'something', public: true } end + describe '.new' do + it 'raises error if unknown key i specified' do + expect { described_class.new(key: 'VAR', value: 'abc', files: true) } + .to raise_error ArgumentError, 'unknown keyword: files' + end + + it 'raises error when required keywords are not specified' do + expect { described_class.new(key: 'VAR') } + .to raise_error ArgumentError, 'missing keyword: value' + end + end + describe '.fabricate' do it 'supports using a hash' do resource = described_class.fabricate(variable) @@ -47,12 +59,25 @@ describe Gitlab::Ci::Variables::Collection::Item do end describe '#to_runner_variable' do - it 'returns a runner-compatible hash representation' do - runner_variable = described_class - .new(**variable) - .to_runner_variable + context 'when variable is not a file-related' do + it 'returns a runner-compatible hash representation' do + runner_variable = described_class + .new(**variable) + .to_runner_variable + + expect(runner_variable).to eq variable + end + end + + context 'when variable is file-related' do + it 'appends file description component' do + runner_variable = described_class + .new(key: 'VAR', value: 'value', file: true) + .to_runner_variable - expect(runner_variable).to eq variable + expect(runner_variable) + .to eq(key: 'VAR', value: 'value', public: true, file: true) + end end end end diff --git a/spec/lib/gitlab/email/handler_spec.rb b/spec/lib/gitlab/email/handler_spec.rb index 386d73e6115..cedbfcc0d18 100644 --- a/spec/lib/gitlab/email/handler_spec.rb +++ b/spec/lib/gitlab/email/handler_spec.rb @@ -25,12 +25,12 @@ describe Gitlab::Email::Handler do described_class.for('email', address).class end - expect(matched_handlers.uniq).to match_array(Gitlab::Email::Handler::HANDLERS) + expect(matched_handlers.uniq).to match_array(ce_handlers) end it 'can pick exactly one handler for each address' do addresses.each do |address| - matched_handlers = Gitlab::Email::Handler::HANDLERS.select do |handler| + matched_handlers = ce_handlers.select do |handler| handler.new('email', address).can_handle? end @@ -38,4 +38,10 @@ describe Gitlab::Email::Handler do end end end + + def ce_handlers + @ce_handlers ||= Gitlab::Email::Handler::HANDLERS.reject do |handler| + handler.name.start_with?('Gitlab::Email::Handler::EE::') + end + end end diff --git a/spec/lib/gitlab/git/attributes_parser_spec.rb b/spec/lib/gitlab/git/attributes_parser_spec.rb index 323334e99a5..2d103123998 100644 --- a/spec/lib/gitlab/git/attributes_parser_spec.rb +++ b/spec/lib/gitlab/git/attributes_parser_spec.rb @@ -66,18 +66,6 @@ describe Gitlab::Git::AttributesParser, seed_helper: true do end end - context 'when attributes data is a file handle' do - subject do - File.open(attributes_path, 'r') do |file_handle| - described_class.new(file_handle) - end - end - - it 'returns the attributes as a Hash' do - expect(subject.attributes('test.txt')).to eq({ 'text' => true }) - end - end - context 'when attributes data is nil' do let(:data) { nil } diff --git a/spec/lib/gitlab/git/info_attributes_spec.rb b/spec/lib/gitlab/git/info_attributes_spec.rb deleted file mode 100644 index ea84909c3e0..00000000000 --- a/spec/lib/gitlab/git/info_attributes_spec.rb +++ /dev/null @@ -1,43 +0,0 @@ -require 'spec_helper' - -describe Gitlab::Git::InfoAttributes, seed_helper: true do - let(:path) do - File.join(SEED_STORAGE_PATH, 'with-git-attributes.git') - end - - subject { described_class.new(path) } - - describe '#attributes' do - context 'using a path with attributes' do - it 'returns the attributes as a Hash' do - expect(subject.attributes('test.txt')).to eq({ 'text' => true }) - end - - it 'returns an empty Hash for a defined path without attributes' do - expect(subject.attributes('bla/bla.txt')).to eq({}) - end - end - end - - describe '#parser' do - it 'parses a file with entries' do - expect(subject.patterns).to be_an_instance_of(Hash) - expect(subject.patterns["/*.txt"]).to eq({ 'text' => true }) - end - - it 'does not parse anything when the attributes file does not exist' do - expect(File).to receive(:exist?) - .with(File.join(path, 'info/attributes')) - .and_return(false) - - expect(subject.patterns).to eq({}) - end - - it 'does not parse attributes files with unsupported encoding' do - path = File.join(SEED_STORAGE_PATH, 'with-invalid-git-attributes.git') - subject = described_class.new(path) - - expect(subject.patterns).to eq({}) - end - end -end diff --git a/spec/lib/gitlab/git/raw_diff_change_spec.rb b/spec/lib/gitlab/git/raw_diff_change_spec.rb new file mode 100644 index 00000000000..eedde34534f --- /dev/null +++ b/spec/lib/gitlab/git/raw_diff_change_spec.rb @@ -0,0 +1,66 @@ +require 'spec_helper' + +describe Gitlab::Git::RawDiffChange do + let(:raw_change) { } + let(:change) { described_class.new(raw_change) } + + context 'bad input' do + let(:raw_change) { 'foo' } + + it 'does not set most of the attrs' do + expect(change.blob_id).to eq('foo') + expect(change.operation).to eq(:unknown) + expect(change.old_path).to be_blank + expect(change.new_path).to be_blank + expect(change.blob_size).to be_blank + end + end + + context 'adding a file' do + let(:raw_change) { '93e123ac8a3e6a0b600953d7598af629dec7b735 59 A bar/branch-test.txt' } + + it 'initialize the proper attrs' do + expect(change.operation).to eq(:added) + expect(change.old_path).to be_blank + expect(change.new_path).to eq('bar/branch-test.txt') + expect(change.blob_id).to be_present + expect(change.blob_size).to be_present + end + end + + context 'renaming a file' do + let(:raw_change) { "85bc2f9753afd5f4fc5d7c75f74f8d526f26b4f3 107 R060\tfiles/js/commit.js.coffee\tfiles/js/commit.coffee" } + + it 'initialize the proper attrs' do + expect(change.operation).to eq(:renamed) + expect(change.old_path).to eq('files/js/commit.js.coffee') + expect(change.new_path).to eq('files/js/commit.coffee') + expect(change.blob_id).to be_present + expect(change.blob_size).to be_present + end + end + + context 'modifying a file' do + let(:raw_change) { 'c60514b6d3d6bf4bec1030f70026e34dfbd69ad5 824 M README.md' } + + it 'initialize the proper attrs' do + expect(change.operation).to eq(:modified) + expect(change.old_path).to eq('README.md') + expect(change.new_path).to eq('README.md') + expect(change.blob_id).to be_present + expect(change.blob_size).to be_present + end + end + + context 'deleting a file' do + let(:raw_change) { '60d7a906c2fd9e4509aeb1187b98d0ea7ce827c9 15364 D files/.DS_Store' } + + it 'initialize the proper attrs' do + expect(change.operation).to eq(:deleted) + expect(change.old_path).to eq('files/.DS_Store') + expect(change.new_path).to be_nil + expect(change.blob_id).to be_present + expect(change.blob_size).to be_present + end + end +end diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index d3ab61746f4..1e00e8d2739 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -1043,6 +1043,44 @@ describe Gitlab::Git::Repository, seed_helper: true do it { is_expected.to eq(17) } end + describe '#raw_changes_between' do + let(:old_rev) { } + let(:new_rev) { } + let(:changes) { repository.raw_changes_between(old_rev, new_rev) } + + context 'initial commit' do + let(:old_rev) { Gitlab::Git::BLANK_SHA } + let(:new_rev) { '1a0b36b3cdad1d2ee32457c102a8c0b7056fa863' } + + it 'returns the changes' do + expect(changes).to be_present + expect(changes.size).to eq(3) + end + end + + context 'with an invalid rev' do + let(:old_rev) { 'foo' } + let(:new_rev) { 'bar' } + + it 'returns an error' do + expect { changes }.to raise_error(Gitlab::Git::Repository::GitError) + end + end + + context 'with valid revs' do + let(:old_rev) { 'fa1b1e6c004a68b7d8763b86455da9e6b23e36d6' } + let(:new_rev) { '4b4918a572fa86f9771e5ba40fbd48e1eb03e2c6' } + + it 'returns the changes' do + expect(changes.size).to eq(9) + expect(changes.first.operation).to eq(:modified) + expect(changes.first.new_path).to eq('.gitmodules') + expect(changes.last.operation).to eq(:added) + expect(changes.last.new_path).to eq('files/lfs/picture-invalid.png') + end + end + end + describe '#merge_base' do shared_examples '#merge_base' do where(:from, :to, :result) do diff --git a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb index 9be3fa633a7..7951cbe7b1d 100644 --- a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb @@ -33,7 +33,7 @@ describe Gitlab::GitalyClient::CommitService do initial_commit = project.commit('1a0b36b3cdad1d2ee32457c102a8c0b7056fa863').raw request = Gitaly::CommitDiffRequest.new( repository: repository_message, - left_commit_id: '4b825dc642cb6eb9a060e54bf8d69288fbee4904', + left_commit_id: Gitlab::Git::EMPTY_TREE_ID, right_commit_id: initial_commit.id, collapse_diffs: true, enforce_limits: true, @@ -77,7 +77,7 @@ describe Gitlab::GitalyClient::CommitService do initial_commit = project.commit('1a0b36b3cdad1d2ee32457c102a8c0b7056fa863') request = Gitaly::CommitDeltaRequest.new( repository: repository_message, - left_commit_id: '4b825dc642cb6eb9a060e54bf8d69288fbee4904', + left_commit_id: Gitlab::Git::EMPTY_TREE_ID, right_commit_id: initial_commit.id ) @@ -90,7 +90,7 @@ describe Gitlab::GitalyClient::CommitService do describe '#between' do let(:from) { 'master' } - let(:to) { '4b825dc642cb6eb9a060e54bf8d69288fbee4904' } + let(:to) { Gitlab::Git::EMPTY_TREE_ID } it 'sends an RPC request' do request = Gitaly::CommitsBetweenRequest.new( @@ -155,7 +155,7 @@ describe Gitlab::GitalyClient::CommitService do end describe '#find_commit' do - let(:revision) { '4b825dc642cb6eb9a060e54bf8d69288fbee4904' } + let(:revision) { Gitlab::Git::EMPTY_TREE_ID } it 'sends an RPC request' do request = Gitaly::FindCommitRequest.new( repository: repository_message, revision: revision diff --git a/spec/lib/gitlab/gitaly_client/repository_service_spec.rb b/spec/lib/gitlab/gitaly_client/repository_service_spec.rb index 21592688bf0..074323d47d2 100644 --- a/spec/lib/gitlab/gitaly_client/repository_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/repository_service_spec.rb @@ -84,6 +84,17 @@ describe Gitlab::GitalyClient::RepositoryService do end end + describe '#info_attributes' do + it 'reads the info attributes' do + expect_any_instance_of(Gitaly::RepositoryService::Stub) + .to receive(:get_info_attributes) + .with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash)) + .and_return([]) + + client.info_attributes + end + end + describe '#has_local_branches?' do it 'sends a has_local_branches message' do expect_any_instance_of(Gitaly::RepositoryService::Stub) diff --git a/spec/lib/gitlab/utils_spec.rb b/spec/lib/gitlab/utils_spec.rb index 71a743495a2..4ba99009855 100644 --- a/spec/lib/gitlab/utils_spec.rb +++ b/spec/lib/gitlab/utils_spec.rb @@ -1,7 +1,8 @@ require 'spec_helper' describe Gitlab::Utils do - delegate :to_boolean, :boolean_to_yes_no, :slugify, :random_string, :which, :ensure_array_from_string, to: :described_class + delegate :to_boolean, :boolean_to_yes_no, :slugify, :random_string, :which, :ensure_array_from_string, + :bytes_to_megabytes, to: :described_class describe '.slugify' do { @@ -97,4 +98,12 @@ describe Gitlab::Utils do expect(ensure_array_from_string(str)).to eq(%w[seven eight 9 10]) end end + + describe '.bytes_to_megabytes' do + it 'converts bytes to megabytes' do + bytes = 1.megabyte + + expect(bytes_to_megabytes(bytes)).to eq(1) + end + end end diff --git a/spec/lib/rspec_flaky/config_spec.rb b/spec/lib/rspec_flaky/config_spec.rb index 83556787e85..4a71b1feebd 100644 --- a/spec/lib/rspec_flaky/config_spec.rb +++ b/spec/lib/rspec_flaky/config_spec.rb @@ -16,23 +16,25 @@ describe RspecFlaky::Config, :aggregate_failures do end end - context "when ENV['FLAKY_RSPEC_GENERATE_REPORT'] is set to 'false'" do - before do - stub_env('FLAKY_RSPEC_GENERATE_REPORT', 'false') - end - - it 'returns false' do - expect(described_class).not_to be_generate_report + context "when ENV['FLAKY_RSPEC_GENERATE_REPORT'] is set" do + using RSpec::Parameterized::TableSyntax + + where(:env_value, :result) do + '1' | true + 'true' | true + 'foo' | false + '0' | false + 'false' | false end - end - context "when ENV['FLAKY_RSPEC_GENERATE_REPORT'] is set to 'true'" do - before do - stub_env('FLAKY_RSPEC_GENERATE_REPORT', 'true') - end + with_them do + before do + stub_env('FLAKY_RSPEC_GENERATE_REPORT', env_value) + end - it 'returns true' do - expect(described_class).to be_generate_report + it 'returns false' do + expect(described_class.generate_report?).to be(result) + end end end end diff --git a/spec/lib/rspec_flaky/flaky_examples_collection_spec.rb b/spec/lib/rspec_flaky/flaky_examples_collection_spec.rb index 06a8ba0d02e..6731a27ed17 100644 --- a/spec/lib/rspec_flaky/flaky_examples_collection_spec.rb +++ b/spec/lib/rspec_flaky/flaky_examples_collection_spec.rb @@ -24,14 +24,6 @@ describe RspecFlaky::FlakyExamplesCollection, :aggregate_failures do } end - describe '.from_json' do - it 'accepts a JSON' do - collection = described_class.from_json(JSON.pretty_generate(collection_hash)) - - expect(collection.to_report).to eq(described_class.new(collection_hash).to_report) - end - end - describe '#initialize' do it 'accepts no argument' do expect { described_class.new }.not_to raise_error @@ -46,11 +38,11 @@ describe RspecFlaky::FlakyExamplesCollection, :aggregate_failures do end end - describe '#to_report' do + describe '#to_h' do it 'calls #to_h on the values' do collection = described_class.new(collection_hash) - expect(collection.to_report).to eq(collection_report) + expect(collection.to_h).to eq(collection_report) end end @@ -61,7 +53,7 @@ describe RspecFlaky::FlakyExamplesCollection, :aggregate_failures do a: { example_id: 'spec/foo/bar_spec.rb:2' }, c: { example_id: 'spec/bar/baz_spec.rb:4' }) - expect((collection2 - collection1).to_report).to eq( + expect((collection2 - collection1).to_h).to eq( c: { example_id: 'spec/bar/baz_spec.rb:4', first_flaky_at: nil, diff --git a/spec/lib/rspec_flaky/listener_spec.rb b/spec/lib/rspec_flaky/listener_spec.rb index bfb7648b486..ef085445081 100644 --- a/spec/lib/rspec_flaky/listener_spec.rb +++ b/spec/lib/rspec_flaky/listener_spec.rb @@ -4,7 +4,7 @@ describe RspecFlaky::Listener, :aggregate_failures do let(:already_flaky_example_uid) { '6e869794f4cfd2badd93eb68719371d1' } let(:suite_flaky_example_report) do { - already_flaky_example_uid => { + "#{already_flaky_example_uid}": { example_id: 'spec/foo/bar_spec.rb:2', file: 'spec/foo/bar_spec.rb', line: 2, @@ -55,8 +55,7 @@ describe RspecFlaky::Listener, :aggregate_failures do it 'returns a valid Listener instance' do listener = described_class.new - expect(listener.to_report(listener.suite_flaky_examples)) - .to eq(expected_suite_flaky_examples) + expect(listener.suite_flaky_examples.to_h).to eq(expected_suite_flaky_examples) expect(listener.flaky_examples).to eq({}) end end @@ -65,25 +64,35 @@ describe RspecFlaky::Listener, :aggregate_failures do it_behaves_like 'a valid Listener instance' end - context 'when a report file exists and set by SUITE_FLAKY_RSPEC_REPORT_PATH' do - let(:report_file) do - Tempfile.new(%w[rspec_flaky_report .json]).tap do |f| - f.write(JSON.pretty_generate(suite_flaky_example_report)) - f.rewind - end - end + context 'when SUITE_FLAKY_RSPEC_REPORT_PATH is set' do + let(:report_file_path) { 'foo/report.json' } before do - stub_env('SUITE_FLAKY_RSPEC_REPORT_PATH', report_file.path) + stub_env('SUITE_FLAKY_RSPEC_REPORT_PATH', report_file_path) end - after do - report_file.close - report_file.unlink + context 'and report file exists' do + before do + expect(File).to receive(:exist?).with(report_file_path).and_return(true) + end + + it 'delegates the load to RspecFlaky::Report' do + report = RspecFlaky::Report.new(RspecFlaky::FlakyExamplesCollection.new(suite_flaky_example_report)) + + expect(RspecFlaky::Report).to receive(:load).with(report_file_path).and_return(report) + expect(described_class.new.suite_flaky_examples.to_h).to eq(report.flaky_examples.to_h) + end end - it_behaves_like 'a valid Listener instance' do - let(:expected_suite_flaky_examples) { suite_flaky_example_report } + context 'and report file does not exist' do + before do + expect(File).to receive(:exist?).with(report_file_path).and_return(false) + end + + it 'return an empty hash' do + expect(RspecFlaky::Report).not_to receive(:load) + expect(described_class.new.suite_flaky_examples.to_h).to eq({}) + end end end end @@ -186,74 +195,21 @@ describe RspecFlaky::Listener, :aggregate_failures do let(:notification_already_flaky_rspec_example) { double(example: already_flaky_rspec_example) } context 'when a report file path is set by FLAKY_RSPEC_REPORT_PATH' do - let(:report_file_path) { Rails.root.join('tmp', 'rspec_flaky_report.json') } - let(:new_report_file_path) { Rails.root.join('tmp', 'rspec_flaky_new_report.json') } + it 'delegates the writes to RspecFlaky::Report' do + listener.example_passed(notification_new_flaky_rspec_example) + listener.example_passed(notification_already_flaky_rspec_example) - before do - stub_env('FLAKY_RSPEC_REPORT_PATH', report_file_path) - stub_env('NEW_FLAKY_RSPEC_REPORT_PATH', new_report_file_path) - FileUtils.rm(report_file_path) if File.exist?(report_file_path) - FileUtils.rm(new_report_file_path) if File.exist?(new_report_file_path) - end + report1 = double + report2 = double - after do - FileUtils.rm(report_file_path) if File.exist?(report_file_path) - FileUtils.rm(new_report_file_path) if File.exist?(new_report_file_path) - end + expect(RspecFlaky::Report).to receive(:new).with(listener.flaky_examples).and_return(report1) + expect(report1).to receive(:write).with(RspecFlaky::Config.flaky_examples_report_path) - context 'when FLAKY_RSPEC_GENERATE_REPORT == "false"' do - before do - stub_env('FLAKY_RSPEC_GENERATE_REPORT', 'false') - end - - it 'does not write any report file' do - listener.example_passed(notification_new_flaky_rspec_example) + expect(RspecFlaky::Report).to receive(:new).with(listener.flaky_examples - listener.suite_flaky_examples).and_return(report2) + expect(report2).to receive(:write).with(RspecFlaky::Config.new_flaky_examples_report_path) - listener.dump_summary(nil) - - expect(File.exist?(report_file_path)).to be(false) - expect(File.exist?(new_report_file_path)).to be(false) - end + listener.dump_summary(nil) end - - context 'when FLAKY_RSPEC_GENERATE_REPORT == "true"' do - before do - stub_env('FLAKY_RSPEC_GENERATE_REPORT', 'true') - end - - around do |example| - Timecop.freeze { example.run } - end - - it 'writes the report files' do - listener.example_passed(notification_new_flaky_rspec_example) - listener.example_passed(notification_already_flaky_rspec_example) - - listener.dump_summary(nil) - - expect(File.exist?(report_file_path)).to be(true) - expect(File.exist?(new_report_file_path)).to be(true) - - expect(File.read(report_file_path)) - .to eq(JSON.pretty_generate(listener.to_report(listener.flaky_examples))) - - new_example = RspecFlaky::Example.new(notification_new_flaky_rspec_example) - new_flaky_example = RspecFlaky::FlakyExample.new(new_example) - new_flaky_example.update_flakiness! - - expect(File.read(new_report_file_path)) - .to eq(JSON.pretty_generate(listener.to_report(new_example.uid => new_flaky_example))) - end - end - end - end - - describe '#to_report' do - let(:listener) { described_class.new(suite_flaky_example_report.to_json) } - - it 'transforms the internal hash to a JSON-ready hash' do - expect(listener.to_report(already_flaky_example_uid => already_flaky_example)) - .to match(hash_including(suite_flaky_example_report)) end end end diff --git a/spec/lib/rspec_flaky/report_spec.rb b/spec/lib/rspec_flaky/report_spec.rb new file mode 100644 index 00000000000..7d57d99f7e5 --- /dev/null +++ b/spec/lib/rspec_flaky/report_spec.rb @@ -0,0 +1,125 @@ +require 'spec_helper' + +describe RspecFlaky::Report, :aggregate_failures do + let(:a_hundred_days) { 3600 * 24 * 100 } + let(:collection_hash) do + { + a: { example_id: 'spec/foo/bar_spec.rb:2' }, + b: { example_id: 'spec/foo/baz_spec.rb:3', first_flaky_at: (Time.now - a_hundred_days).to_s, last_flaky_at: (Time.now - a_hundred_days).to_s } + } + end + let(:suite_flaky_example_report) do + { + '6e869794f4cfd2badd93eb68719371d1': { + example_id: 'spec/foo/bar_spec.rb:2', + file: 'spec/foo/bar_spec.rb', + line: 2, + description: 'hello world', + first_flaky_at: 1234, + last_flaky_at: 4321, + last_attempts_count: 3, + flaky_reports: 1, + last_flaky_job: nil + } + } + end + let(:flaky_examples) { RspecFlaky::FlakyExamplesCollection.new(collection_hash) } + let(:report) { described_class.new(flaky_examples) } + + describe '.load' do + let!(:report_file) do + Tempfile.new(%w[rspec_flaky_report .json]).tap do |f| + f.write(JSON.pretty_generate(suite_flaky_example_report)) + f.rewind + end + end + + after do + report_file.close + report_file.unlink + end + + it 'loads the report file' do + expect(described_class.load(report_file.path).flaky_examples.to_h).to eq(suite_flaky_example_report) + end + end + + describe '.load_json' do + let(:report_json) do + JSON.pretty_generate(suite_flaky_example_report) + end + + it 'loads the report file' do + expect(described_class.load_json(report_json).flaky_examples.to_h).to eq(suite_flaky_example_report) + end + end + + describe '#initialize' do + it 'accepts a RspecFlaky::FlakyExamplesCollection' do + expect { report }.not_to raise_error + end + + it 'does not accept anything else' do + expect { described_class.new([1, 2, 3]) }.to raise_error(ArgumentError, "`flaky_examples` must be a RspecFlaky::FlakyExamplesCollection, Array given!") + end + end + + it 'delegates to #flaky_examples using SimpleDelegator' do + expect(report.__getobj__).to eq(flaky_examples) + end + + describe '#write' do + let(:report_file_path) { Rails.root.join('tmp', 'rspec_flaky_report.json') } + + before do + FileUtils.rm(report_file_path) if File.exist?(report_file_path) + end + + after do + FileUtils.rm(report_file_path) if File.exist?(report_file_path) + end + + context 'when RspecFlaky::Config.generate_report? is false' do + before do + allow(RspecFlaky::Config).to receive(:generate_report?).and_return(false) + end + + it 'does not write any report file' do + report.write(report_file_path) + + expect(File.exist?(report_file_path)).to be(false) + end + end + + context 'when RspecFlaky::Config.generate_report? is true' do + before do + allow(RspecFlaky::Config).to receive(:generate_report?).and_return(true) + end + + it 'delegates the writes to RspecFlaky::Report' do + report.write(report_file_path) + + expect(File.exist?(report_file_path)).to be(true) + expect(File.read(report_file_path)) + .to eq(JSON.pretty_generate(report.flaky_examples.to_h)) + end + end + end + + describe '#prune_outdated' do + it 'returns a new collection without the examples older than 90 days by default' do + new_report = flaky_examples.to_h.dup.tap { |r| r.delete(:b) } + new_flaky_examples = report.prune_outdated + + expect(new_flaky_examples).to be_a(described_class) + expect(new_flaky_examples.to_h).to eq(new_report) + expect(flaky_examples).to have_key(:b) + end + + it 'accepts a given number of days' do + new_flaky_examples = report.prune_outdated(days: 200) + + expect(new_flaky_examples.to_h).to eq(report.to_h) + end + end +end diff --git a/spec/models/broadcast_message_spec.rb b/spec/models/broadcast_message_spec.rb index 461e754dc1f..5326f9cb8c0 100644 --- a/spec/models/broadcast_message_spec.rb +++ b/spec/models/broadcast_message_spec.rb @@ -51,7 +51,11 @@ describe BroadcastMessage do expect(described_class).to receive(:where).and_call_original.once - 2.times { described_class.current } + described_class.current + + Timecop.travel(1.year) do + described_class.current + end end it 'includes messages that need to be displayed in the future' do diff --git a/spec/models/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb index d87c1ca14f0..374a157bec0 100644 --- a/spec/models/project_wiki_spec.rb +++ b/spec/models/project_wiki_spec.rb @@ -172,11 +172,12 @@ describe ProjectWiki do describe '#find_file' do shared_examples 'finding a wiki file' do + let(:image) { File.open(Rails.root.join('spec', 'fixtures', 'big-image.png')) } + before do - file = File.open(Rails.root.join('spec', 'fixtures', 'dk.png')) subject.wiki # Make sure the wiki repo exists - BareRepoOperations.new(subject.repository.path_to_repo).commit_file(file, 'image.png') + BareRepoOperations.new(subject.repository.path_to_repo).commit_file(image, 'image.png') end it 'returns the latest version of the file if it exists' do @@ -192,6 +193,13 @@ describe ProjectWiki do file = subject.find_file('image.png') expect(file).to be_a Gitlab::Git::WikiFile end + + it 'returns the whole file' do + file = subject.find_file('image.png') + image.rewind + + expect(file.raw_data.b).to eq(image.read.b) + end end context 'when Gitaly wiki_find_file is enabled' do diff --git a/spec/services/events/render_service_spec.rb b/spec/services/events/render_service_spec.rb index b4a4a44d07b..075cb45e46c 100644 --- a/spec/services/events/render_service_spec.rb +++ b/spec/services/events/render_service_spec.rb @@ -9,9 +9,7 @@ describe Events::RenderService do context 'when the request format is atom' do it 'renders the note inside events' do expect(Banzai::ObjectRenderer).to receive(:new) - .with(event.project, user, - only_path: false, - xhtml: true) + .with(user: user, redaction_context: { only_path: false, xhtml: true }) .and_call_original expect_any_instance_of(Banzai::ObjectRenderer) @@ -24,7 +22,7 @@ describe Events::RenderService do context 'when the request format is not atom' do it 'renders the note inside events' do expect(Banzai::ObjectRenderer).to receive(:new) - .with(event.project, user, {}) + .with(user: user, redaction_context: {}) .and_call_original expect_any_instance_of(Banzai::ObjectRenderer) diff --git a/spec/services/notes/render_service_spec.rb b/spec/services/notes/render_service_spec.rb index faac498037f..f771620bc0d 100644 --- a/spec/services/notes/render_service_spec.rb +++ b/spec/services/notes/render_service_spec.rb @@ -4,23 +4,28 @@ describe Notes::RenderService do describe '#execute' do it 'renders a Note' do note = double(:note) - project = double(:project) wiki = double(:wiki) user = double(:user) - expect(Banzai::ObjectRenderer).to receive(:new) - .with(project, user, - requested_path: 'foo', - project_wiki: wiki, - ref: 'bar', - only_path: nil, - xhtml: false) + expect(Banzai::ObjectRenderer) + .to receive(:new) + .with( + user: user, + redaction_context: { + requested_path: 'foo', + project_wiki: wiki, + ref: 'bar', + only_path: nil, + xhtml: false + } + ) .and_call_original expect_any_instance_of(Banzai::ObjectRenderer) - .to receive(:render).with([note], :note) + .to receive(:render) + .with([note], :note) - described_class.new(user).execute([note], project, + described_class.new(user).execute([note], requested_path: 'foo', project_wiki: wiki, ref: 'bar', diff --git a/spec/support/bare_repo_operations.rb b/spec/support/bare_repo_operations.rb index 8eeaa37d3c5..3f4a4243cb6 100644 --- a/spec/support/bare_repo_operations.rb +++ b/spec/support/bare_repo_operations.rb @@ -1,19 +1,15 @@ require 'zlib' class BareRepoOperations - # The ID of empty tree. - # See http://stackoverflow.com/a/40884093/1856239 and https://github.com/git/git/blob/3ad8b5bf26362ac67c9020bf8c30eee54a84f56d/cache.h#L1011-L1012 - EMPTY_TREE_ID = '4b825dc642cb6eb9a060e54bf8d69288fbee4904'.freeze - include Gitlab::Popen def initialize(path_to_repo) @path_to_repo = path_to_repo end - def commit_tree(tree_id, msg, parent: EMPTY_TREE_ID) + def commit_tree(tree_id, msg, parent: Gitlab::Git::EMPTY_TREE_ID) commit_tree_args = ['commit-tree', tree_id, '-m', msg] - commit_tree_args += ['-p', parent] unless parent == EMPTY_TREE_ID + commit_tree_args += ['-p', parent] unless parent == Gitlab::Git::EMPTY_TREE_ID commit_id = execute(commit_tree_args) commit_id[0] @@ -21,7 +17,7 @@ class BareRepoOperations # Based on https://stackoverflow.com/a/25556917/1856239 def commit_file(file, dst_path, branch = 'master') - head_id = execute(['show', '--format=format:%H', '--no-patch', branch], allow_failure: true)[0] || EMPTY_TREE_ID + head_id = execute(['show', '--format=format:%H', '--no-patch', branch], allow_failure: true)[0] || Gitlab::Git::EMPTY_TREE_ID execute(['read-tree', '--empty']) execute(['read-tree', head_id]) diff --git a/spec/support/filter_spec_helper.rb b/spec/support/filter_spec_helper.rb index b871b7ffc90..721d359c2ee 100644 --- a/spec/support/filter_spec_helper.rb +++ b/spec/support/filter_spec_helper.rb @@ -18,6 +18,11 @@ module FilterSpecHelper context.reverse_merge!(project: project) end + render_context = Banzai::RenderContext + .new(context[:project], context[:current_user]) + + context = context.merge(render_context: render_context) + described_class.call(html, context) end diff --git a/spec/support/helpers/features/branches_helpers.rb b/spec/support/helpers/features/branches_helpers.rb new file mode 100644 index 00000000000..3525d9a70a7 --- /dev/null +++ b/spec/support/helpers/features/branches_helpers.rb @@ -0,0 +1,33 @@ +# These helpers allow you to manipulate with sorting features. +# +# Usage: +# describe "..." do +# include Spec::Support::Helpers::Features::BranchesHelpers +# ... +# +# create_branch("feature") +# select_branch("master") +# +module Spec + module Support + module Helpers + module Features + module BranchesHelpers + def create_branch(branch_name, source_branch_name = "master") + fill_in("branch_name", with: branch_name) + select_branch(source_branch_name) + click_button("Create branch") + end + + def select_branch(branch_name) + find(".git-revision-dropdown-toggle").click + + page.within("#new-branch-form .dropdown-menu") do + click_link(branch_name) + end + end + end + end + end + end +end diff --git a/spec/support/reference_parser_helpers.rb b/spec/support/reference_parser_helpers.rb index 5d5e80851e6..c01897ed1a1 100644 --- a/spec/support/reference_parser_helpers.rb +++ b/spec/support/reference_parser_helpers.rb @@ -5,9 +5,11 @@ module ReferenceParserHelpers shared_examples 'no N+1 queries' do it 'avoids N+1 queries in #nodes_visible_to_user', :request_store do + context = Banzai::RenderContext.new(project, user) + record_queries = lambda do |links| ActiveRecord::QueryRecorder.new do - described_class.new(project, user).nodes_visible_to_user(user, links) + described_class.new(context).nodes_visible_to_user(user, links) end end @@ -19,9 +21,11 @@ module ReferenceParserHelpers end it 'avoids N+1 queries in #records_for_nodes', :request_store do + context = Banzai::RenderContext.new(project, user) + record_queries = lambda do |links| ActiveRecord::QueryRecorder.new do - described_class.new(project, user).records_for_nodes(links) + described_class.new(context).records_for_nodes(links) end end diff --git a/vendor/gitignore/Android.gitignore b/vendor/gitignore/Android.gitignore index d57137223ed..39b6783cef8 100644 --- a/vendor/gitignore/Android.gitignore +++ b/vendor/gitignore/Android.gitignore @@ -37,8 +37,10 @@ captures/ .idea/workspace.xml .idea/tasks.xml .idea/gradle.xml +.idea/assetWizardSettings.xml .idea/dictionaries .idea/libraries +.idea/caches # Keystore files # Uncomment the following line if you do not want to check your keystore files in. diff --git a/vendor/gitignore/Elixir.gitignore b/vendor/gitignore/Elixir.gitignore index b6d65867dac..86e4c3f3905 100644 --- a/vendor/gitignore/Elixir.gitignore +++ b/vendor/gitignore/Elixir.gitignore @@ -6,3 +6,4 @@ erl_crash.dump *.ez *.beam +/config/*.secret.exs diff --git a/vendor/gitignore/Global/JetBrains.gitignore b/vendor/gitignore/Global/JetBrains.gitignore index 9c01e12b050..a83a428c844 100644 --- a/vendor/gitignore/Global/JetBrains.gitignore +++ b/vendor/gitignore/Global/JetBrains.gitignore @@ -1,12 +1,12 @@ # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 -# User-specific stuff: +# User-specific stuff .idea/**/workspace.xml .idea/**/tasks.xml .idea/dictionaries -# Sensitive or high-churn files: +# Sensitive or high-churn files .idea/**/dataSources/ .idea/**/dataSources.ids .idea/**/dataSources.local.xml @@ -14,7 +14,7 @@ .idea/**/dynamic.xml .idea/**/uiDesigner.xml -# Gradle: +# Gradle .idea/**/gradle.xml .idea/**/libraries @@ -22,14 +22,12 @@ cmake-build-debug/ cmake-build-release/ -# Mongo Explorer plugin: +# Mongo Explorer plugin .idea/**/mongoSettings.xml -## File-based project format: +# File-based project format *.iws -## Plugin-specific files: - # IntelliJ out/ @@ -47,3 +45,6 @@ com_crashlytics_export_strings.xml crashlytics.properties crashlytics-build.properties fabric.properties + +# Editor-based Rest Client +.idea/httpRequests diff --git a/vendor/gitignore/Global/Windows.gitignore b/vendor/gitignore/Global/Windows.gitignore index 846a1db836c..0251dd21ad8 100644 --- a/vendor/gitignore/Global/Windows.gitignore +++ b/vendor/gitignore/Global/Windows.gitignore @@ -15,6 +15,7 @@ $RECYCLE.BIN/ # Windows Installer files *.cab *.msi +*.msix *.msm *.msp diff --git a/vendor/gitignore/Godot.gitignore b/vendor/gitignore/Godot.gitignore new file mode 100644 index 00000000000..ba45ca4582e --- /dev/null +++ b/vendor/gitignore/Godot.gitignore @@ -0,0 +1,8 @@ + +# Godot-specific ignores +.import/ +export.cfg +export_presets.cfg + +# Mono-specific ignores +.mono/ diff --git a/vendor/gitignore/Joomla.gitignore b/vendor/gitignore/Joomla.gitignore index b6bf3a9c96a..378c158bddf 100644 --- a/vendor/gitignore/Joomla.gitignore +++ b/vendor/gitignore/Joomla.gitignore @@ -1,4 +1,3 @@ -/.gitignore /.htaccess /administrator/cache/* /administrator/components/com_admin/* diff --git a/vendor/gitignore/KiCad.gitignore b/vendor/gitignore/KiCad.gitignore index 208bc4fc591..198392e551e 100644 --- a/vendor/gitignore/KiCad.gitignore +++ b/vendor/gitignore/KiCad.gitignore @@ -1,4 +1,5 @@ # For PCBs designed using KiCad: http://www.kicad-pcb.org/ +# Format documentation: http://kicad-pcb.org/help/file-formats/ # Temporary files *.000 @@ -8,6 +9,10 @@ *~ _autosave-* *.tmp +*-cache.lib +*-rescue.lib +*-save.pro +*-save.kicad_pcb # Netlist files (exported from Eeschema) *.net diff --git a/vendor/gitignore/Leiningen.gitignore b/vendor/gitignore/Leiningen.gitignore index a9fe6fba80d..a4cb69a32cc 100644 --- a/vendor/gitignore/Leiningen.gitignore +++ b/vendor/gitignore/Leiningen.gitignore @@ -11,3 +11,4 @@ pom.xml.asc .lein-plugins/ .lein-failures .nrepl-port +.cpcache/ diff --git a/vendor/gitignore/Node.gitignore b/vendor/gitignore/Node.gitignore index d1bed128fa8..ad46b30886f 100644 --- a/vendor/gitignore/Node.gitignore +++ b/vendor/gitignore/Node.gitignore @@ -36,7 +36,7 @@ build/Release node_modules/ jspm_packages/ -# Typescript v1 declaration files +# TypeScript v1 declaration files typings/ # Optional npm cache directory diff --git a/vendor/gitignore/Python.gitignore b/vendor/gitignore/Python.gitignore index b989be6ca15..894a44cc066 100644 --- a/vendor/gitignore/Python.gitignore +++ b/vendor/gitignore/Python.gitignore @@ -53,9 +53,8 @@ coverage.xml # Django stuff: *.log -.static_storage/ -.media/ local_settings.py +db.sqlite3 # Flask stuff: instance/ diff --git a/vendor/gitignore/Rails.gitignore b/vendor/gitignore/Rails.gitignore index 828ab1d556a..e62f78e17bc 100644 --- a/vendor/gitignore/Rails.gitignore +++ b/vendor/gitignore/Rails.gitignore @@ -14,6 +14,7 @@ pickle-email-*.html # TODO Comment out this rule if you are OK with secrets being uploaded to the repo config/initializers/secret_token.rb +config/master.key # Only include if you have production secrets in this file, which is no longer a Rails default # config/secrets.yml diff --git a/vendor/gitignore/Rust.gitignore b/vendor/gitignore/Rust.gitignore index 50281a44270..088ba6ba7d3 100644 --- a/vendor/gitignore/Rust.gitignore +++ b/vendor/gitignore/Rust.gitignore @@ -3,7 +3,7 @@ /target/ # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html Cargo.lock # These are backup files generated by rustfmt diff --git a/vendor/gitignore/TeX.gitignore b/vendor/gitignore/TeX.gitignore index 78c1c5cd26e..c560658e45c 100644 --- a/vendor/gitignore/TeX.gitignore +++ b/vendor/gitignore/TeX.gitignore @@ -153,7 +153,9 @@ _minted* *.mw # nomencl +*.nlg *.nlo +*.nls # pax *.pax diff --git a/vendor/gitignore/Unity.gitignore b/vendor/gitignore/Unity.gitignore index 75e5b1405da..a7c0c70a0b4 100644 --- a/vendor/gitignore/Unity.gitignore +++ b/vendor/gitignore/Unity.gitignore @@ -5,7 +5,7 @@ [Bb]uilds/ Assets/AssetStoreTools* -# Visual Studio 2015 cache directory +# Visual Studio cache directory /.vs/ # Autogenerated VS/MD/Consulo solution and project files diff --git a/vendor/gitignore/VisualStudio.gitignore b/vendor/gitignore/VisualStudio.gitignore index 8e930f59c47..29063cf6072 100644 --- a/vendor/gitignore/VisualStudio.gitignore +++ b/vendor/gitignore/VisualStudio.gitignore @@ -64,8 +64,10 @@ StyleCopReport.xml *.ilk *.meta *.obj +*.iobj *.pch *.pdb +*.ipdb *.pgc *.pgd *.rsp @@ -248,6 +250,7 @@ ServiceFabricBackup/ *.rdl.data *.bim.layout *.bim_*.settings +*.rptproj.rsuser # Microsoft Fakes FakesAssemblies/ @@ -319,3 +322,8 @@ ASALocalRun/ # MSBuild Binary and Structured Log *.binlog +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ diff --git a/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml b/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml index 4223dc18933..4810035a9e3 100644 --- a/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml @@ -50,9 +50,9 @@ stages: build: stage: build - image: docker:git + image: docker:stable-git services: - - docker:dind + - docker:stable-dind variables: DOCKER_DRIVER: overlay2 script: @@ -76,12 +76,12 @@ test: - branches codequality: - image: docker:latest + image: docker:stable variables: DOCKER_DRIVER: overlay2 allow_failure: true services: - - docker:dind + - docker:stable-dind script: - setup_docker - codeclimate @@ -90,12 +90,12 @@ codequality: performance: stage: performance - image: docker:latest + image: docker:stable variables: DOCKER_DRIVER: overlay2 allow_failure: true services: - - docker:dind + - docker:stable-dind script: - setup_docker - performance @@ -109,25 +109,37 @@ performance: kubernetes: active sast: - image: docker:latest + image: docker:stable variables: DOCKER_DRIVER: overlay2 allow_failure: true services: - - docker:dind + - docker:stable-dind script: - setup_docker - sast artifacts: paths: [gl-sast-report.json] +dependency_scanning: + image: docker:stable + variables: + DOCKER_DRIVER: overlay2 + allow_failure: true + services: + - docker:stable-dind + script: + - setup_docker + - dependency_scanning + artifacts: + paths: [gl-dependency-scanning-report.json] sast:container: - image: docker:latest + image: docker:stable variables: DOCKER_DRIVER: overlay2 allow_failure: true services: - - docker:dind + - docker:stable-dind script: - setup_docker - sast_container @@ -324,7 +336,6 @@ production: fi docker run --env SAST_CONFIDENCE_LEVEL="${SAST_CONFIDENCE_LEVEL:-3}" \ - --env SAST_DISABLE_REMOTE_CHECKS="${SAST_DISABLE_REMOTE_CHECKS:-false}" \ --volume "$PWD:/code" \ --volume /var/run/docker.sock:/var/run/docker.sock \ "registry.gitlab.com/gitlab-org/security-products/sast:$SP_VERSION" /app/bin/run /code @@ -335,6 +346,20 @@ production: esac } + function dependency_scanning() { + case "$CI_SERVER_VERSION" in + *-ee) + docker run --env DEP_SCAN_DISABLE_REMOTE_CHECKS="${DEP_SCAN_DISABLE_REMOTE_CHECKS:-false}" \ + --volume "$PWD:/code" \ + --volume /var/run/docker.sock:/var/run/docker.sock \ + "registry.gitlab.com/gitlab-org/security-products/dependency-scanning:$SP_VERSION" /code + ;; + *) + echo "GitLab EE is required" + ;; + esac + } + function deploy() { track="${1-stable}" name="$CI_ENVIRONMENT_SLUG" @@ -355,10 +380,16 @@ production: if [[ "$track" == "stable" ]]; then # for stable track get number of replicas from `PRODUCTION_REPLICAS` eval new_replicas=\$${env_slug}_REPLICAS + if [[ -z "$new_replicas" ]]; then + new_replicas=$REPLICAS + fi service_enabled="true" else # for all tracks get number of replicas from `CANARY_PRODUCTION_REPLICAS` eval new_replicas=\$${env_track}_${env_slug}_REPLICAS + if [[ -z "$new_replicas" ]]; then + eval new_replicas=\${env_track}_REPLICAS + fi fi if [[ -n "$new_replicas" ]]; then replicas="$new_replicas" diff --git a/vendor/gitlab-ci-yml/Laravel.gitlab-ci.yml b/vendor/gitlab-ci-yml/Laravel.gitlab-ci.yml index 0ad662cf704..0688f77a1d2 100644 --- a/vendor/gitlab-ci-yml/Laravel.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Laravel.gitlab-ci.yml @@ -32,7 +32,7 @@ before_script: - apt-get install git nodejs libcurl4-gnutls-dev libicu-dev libmcrypt-dev libvpx-dev libjpeg-dev libpng-dev libxpm-dev zlib1g-dev libfreetype6-dev libxml2-dev libexpat1-dev libbz2-dev libgmp3-dev libldap2-dev unixodbc-dev libpq-dev libsqlite3-dev libaspell-dev libsnmp-dev libpcre3-dev libtidy-dev -yqq # Install php extensions - - docker-php-ext-install mbstring mcrypt pdo_mysql curl json intl gd xml zip bz2 opcache + - docker-php-ext-install mbstring pdo_mysql curl json intl gd xml zip bz2 opcache # Install & enable Xdebug for code coverage reports - pecl install xdebug diff --git a/vendor/gitlab-ci-yml/Pages/Gatsby.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/Gatsby.gitlab-ci.yml new file mode 100644 index 00000000000..9df2a4797b2 --- /dev/null +++ b/vendor/gitlab-ci-yml/Pages/Gatsby.gitlab-ci.yml @@ -0,0 +1,17 @@ +image: node:latest + +# This folder is cached between builds +# http://docs.gitlab.com/ce/ci/yaml/README.html#cache +cache: + paths: + - node_modules/ + +pages: + script: + - yarn install + - ./node_modules/.bin/gatsby build --prefix-paths + artifacts: + paths: + - public + only: + - master diff --git a/vendor/gitlab-ci-yml/Pages/Hugo.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/Hugo.gitlab-ci.yml index a72b8281401..b8cfb0f56f6 100644 --- a/vendor/gitlab-ci-yml/Pages/Hugo.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Pages/Hugo.gitlab-ci.yml @@ -1,5 +1,5 @@ # Full project: https://gitlab.com/pages/hugo -image: publysher/hugo +image: dettmering/hugo-build pages: script: diff --git a/vendor/gitlab-ci-yml/Python.gitlab-ci.yml b/vendor/gitlab-ci-yml/Python.gitlab-ci.yml index a2882a5407d..2e0589de652 100644 --- a/vendor/gitlab-ci-yml/Python.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Python.gitlab-ci.yml @@ -1,8 +1,27 @@ -# This file is a template, and might need editing before it works on your project. +# Official language image. Look for the different tagged releases at: +# https://hub.docker.com/r/library/python/tags/ image: python:latest +# Change pip's cache directory to be inside the project directory since we can +# only cache local items. +variables: + PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache" + +# Pip's cache doesn't store the python packages +# https://pip.pypa.io/en/stable/reference/pip_install/#caching +# +# If you want to also cache the installed packages, you have to install +# them in a virtualenv and cache it as well. +cache: + paths: + - .cache/pip + - venv/ + before_script: - - python -V # Print out python version for debugging + - python -V # Print out python version for debugging + - pip install virtualenv + - virtualenv venv + - source venv/bin/activate test: script: diff --git a/yarn.lock b/yarn.lock index 243b83f4471..c4d682016a0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1859,9 +1859,9 @@ combined-stream@1.0.6, combined-stream@^1.0.5, combined-stream@~1.0.5: dependencies: delayed-stream "~1.0.0" -commander@^2.13.0, commander@^2.9.0: - version "2.14.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.14.1.tgz#2235123e37af8ca3c65df45b026dbd357b01b9aa" +commander@^2.13.0, commander@^2.15.1, commander@^2.9.0: + version "2.15.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f" commondir@^1.0.1: version "1.0.1" |