diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-08-26 15:10:53 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-08-26 15:10:53 +0300 |
commit | ff579119e2ecf2608370a1f24c4d791d28f269d9 (patch) | |
tree | 6f510ae943600102faf9dbc54df0f3e7f96c3417 | |
parent | 2c49951e8c1f4fb95d15cac3dd0677d6882d2add (diff) |
Add latest changes from gitlab-org/gitlab@master
50 files changed, 681 insertions, 262 deletions
diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue index 3680bebfdd0..9ed27edd71a 100644 --- a/app/assets/javascripts/diffs/components/app.vue +++ b/app/assets/javascripts/diffs/components/app.vue @@ -203,7 +203,7 @@ export default { } }, diffViewType() { - if (this.needsReload() || this.needsFirstLoad()) { + if (!this.glFeatures.unifiedDiffLines && (this.needsReload() || this.needsFirstLoad())) { this.refetchDiffData(); } this.adjustView(); diff --git a/app/assets/javascripts/diffs/components/diff_content.vue b/app/assets/javascripts/diffs/components/diff_content.vue index 087a558efdc..5df82242d2b 100644 --- a/app/assets/javascripts/diffs/components/diff_content.vue +++ b/app/assets/javascripts/diffs/components/diff_content.vue @@ -1,6 +1,7 @@ <script> import { mapActions, mapGetters, mapState } from 'vuex'; import { GlLoadingIcon } from '@gitlab/ui'; +import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import diffLineNoteFormMixin from '~/notes/mixins/diff_line_note_form'; import draftCommentsMixin from '~/diffs/mixins/draft_comments'; import DiffViewer from '~/vue_shared/components/diff_viewer/diff_viewer.vue'; @@ -32,7 +33,7 @@ export default { userAvatarLink, DiffFileDrafts, }, - mixins: [diffLineNoteFormMixin, draftCommentsMixin], + mixins: [diffLineNoteFormMixin, draftCommentsMixin, glFeatureFlagsMixin()], props: { diffFile: { type: Object, @@ -114,7 +115,11 @@ export default { <inline-diff-view v-if="isInlineView" :diff-file="diffFile" - :diff-lines="diffFile.highlighted_diff_lines || []" + :diff-lines=" + glFeatures.unifiedDiffLines + ? diffFile.parallel_diff_lines + : diffFile.highlighted_diff_lines || [] + " :help-page-path="helpPagePath" /> <parallel-diff-view diff --git a/app/assets/javascripts/diffs/components/diff_expansion_cell.vue b/app/assets/javascripts/diffs/components/diff_expansion_cell.vue index 9f27758ed27..785c84499f8 100644 --- a/app/assets/javascripts/diffs/components/diff_expansion_cell.vue +++ b/app/assets/javascripts/diffs/components/diff_expansion_cell.vue @@ -3,6 +3,7 @@ import { mapState, mapActions } from 'vuex'; import { GlIcon } from '@gitlab/ui'; import { deprecatedCreateFlash as createFlash } from '~/flash'; import { s__ } from '~/locale'; +import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import { UNFOLD_COUNT, INLINE_DIFF_VIEW_TYPE, PARALLEL_DIFF_VIEW_TYPE } from '../constants'; import * as utils from '../store/utils'; import tooltip from '../../vue_shared/directives/tooltip'; @@ -28,6 +29,7 @@ export default { components: { GlIcon, }, + mixins: [glFeatureFlagsMixin()], props: { fileHash: { type: String, @@ -59,7 +61,11 @@ export default { }, computed: { ...mapState({ - diffViewType: state => state.diffs.diffViewType, + diffViewType(state) { + return this.glFeatures.unifiedDiffLines + ? PARALLEL_DIFF_VIEW_TYPE + : state.diffs.diffViewType; + }, diffFiles: state => state.diffs.diffFiles, }), canExpandUp() { diff --git a/app/assets/javascripts/diffs/components/inline_diff_view.vue b/app/assets/javascripts/diffs/components/inline_diff_view.vue index e82d06ee385..e5197977bba 100644 --- a/app/assets/javascripts/diffs/components/inline_diff_view.vue +++ b/app/assets/javascripts/diffs/components/inline_diff_view.vue @@ -1,5 +1,6 @@ <script> import { mapGetters, mapState } from 'vuex'; +import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import draftCommentsMixin from '~/diffs/mixins/draft_comments'; import InlineDraftCommentRow from '~/batch_comments/components/inline_draft_comment_row.vue'; import inlineDiffTableRow from './inline_diff_table_row.vue'; @@ -14,7 +15,7 @@ export default { InlineDraftCommentRow, inlineDiffExpansionRow, }, - mixins: [draftCommentsMixin], + mixins: [draftCommentsMixin, glFeatureFlagsMixin()], props: { diffFile: { type: Object, @@ -63,37 +64,99 @@ export default { <col /> </colgroup> <tbody> - <template v-for="(line, index) in diffLines"> - <inline-diff-expansion-row - :key="`expand-${index}`" - :file-hash="diffFile.file_hash" - :context-lines-path="diffFile.context_lines_path" - :line="line" - :is-top="index === 0" - :is-bottom="index + 1 === diffLinesLength" - /> - <inline-diff-table-row - :key="`${line.line_code || index}`" - :file-hash="diffFile.file_hash" - :file-path="diffFile.file_path" - :line="line" - :is-bottom="index + 1 === diffLinesLength" - :is-commented="index >= commentedLines.startLine && index <= commentedLines.endLine" - /> - <inline-diff-comment-row - :key="`icr-${line.line_code || index}`" - :diff-file-hash="diffFile.file_hash" - :line="line" - :help-page-path="helpPagePath" - :has-draft="shouldRenderDraftRow(diffFile.file_hash, line) || false" - /> - <inline-draft-comment-row - v-if="shouldRenderDraftRow(diffFile.file_hash, line)" - :key="`draft_${index}`" - :draft="draftForLine(diffFile.file_hash, line)" - :diff-file="diffFile" - :line="line" - /> + <template v-if="glFeatures.unifiedDiffLines"> + <template v-for="({ left, right }, index) in diffLines"> + <inline-diff-expansion-row + :key="`expand-${index}`" + :file-hash="diffFile.file_hash" + :context-lines-path="diffFile.context_lines_path" + :line="left || right" + :is-top="index === 0" + :is-bottom="index + 1 === diffLinesLength" + /> + <template v-if="left"> + <inline-diff-table-row + :key="`${left.line_code || index}`" + :file-hash="diffFile.file_hash" + :file-path="diffFile.file_path" + :line="left" + :is-bottom="index + 1 === diffLinesLength" + :is-commented="index >= commentedLines.startLine && index <= commentedLines.endLine" + /> + <inline-diff-comment-row + :key="`icr-${left.line_code || index}`" + :diff-file-hash="diffFile.file_hash" + :line="left" + :help-page-path="helpPagePath" + :has-draft="shouldRenderDraftRow(diffFile.file_hash, left) || false" + /> + <inline-draft-comment-row + v-if="shouldRenderDraftRow(diffFile.file_hash, left)" + :key="`draft_${index}`" + :draft="draftForLine(diffFile.file_hash, left)" + :diff-file="diffFile" + :line="left" + /> + </template> + <template v-if="right && right.type === 'new'"> + <inline-diff-table-row + :key="`new-${right.line_code || index}`" + :file-hash="diffFile.file_hash" + :file-path="diffFile.file_path" + :line="right" + :is-bottom="index + 1 === diffLinesLength" + :is-commented="index >= commentedLines.startLine && index <= commentedLines.endLine" + /> + <inline-diff-comment-row + :key="`new-icr-${right.line_code || index}`" + :diff-file-hash="diffFile.file_hash" + :line="right" + :help-page-path="helpPagePath" + :has-draft="shouldRenderDraftRow(diffFile.file_hash, right) || false" + /> + <inline-draft-comment-row + v-if="shouldRenderDraftRow(diffFile.file_hash, right)" + :key="`new-draft_${index}`" + :draft="draftForLine(diffFile.file_hash, right)" + :diff-file="diffFile" + :line="right" + /> + </template> + </template> + </template> + <template v-else> + <template v-for="(line, index) in diffLines"> + <inline-diff-expansion-row + :key="`expand-${index}`" + :file-hash="diffFile.file_hash" + :context-lines-path="diffFile.context_lines_path" + :line="line" + :is-top="index === 0" + :is-bottom="index + 1 === diffLinesLength" + /> + <inline-diff-table-row + :key="`${line.line_code || index}`" + :file-hash="diffFile.file_hash" + :file-path="diffFile.file_path" + :line="line" + :is-bottom="index + 1 === diffLinesLength" + :is-commented="index >= commentedLines.startLine && index <= commentedLines.endLine" + /> + <inline-diff-comment-row + :key="`icr-${line.line_code || index}`" + :diff-file-hash="diffFile.file_hash" + :line="line" + :help-page-path="helpPagePath" + :has-draft="shouldRenderDraftRow(diffFile.file_hash, line) || false" + /> + <inline-draft-comment-row + v-if="shouldRenderDraftRow(diffFile.file_hash, line)" + :key="`draft_${index}`" + :draft="draftForLine(diffFile.file_hash, line)" + :diff-file="diffFile" + :line="line" + /> + </template> </template> </tbody> </table> diff --git a/app/assets/javascripts/diffs/store/actions.js b/app/assets/javascripts/diffs/store/actions.js index d5581474c9b..8e26f5e9ffe 100644 --- a/app/assets/javascripts/diffs/store/actions.js +++ b/app/assets/javascripts/diffs/store/actions.js @@ -74,7 +74,7 @@ export const fetchDiffFiles = ({ state, commit }) => { let returnData; if (state.useSingleDiffStyle) { - urlParams.view = state.diffViewType; + urlParams.view = window.gon?.features?.unifiedDiffLines ? 'inline' : state.diffViewType; } commit(types.SET_LOADING, true); @@ -114,7 +114,7 @@ export const fetchDiffFilesBatch = ({ commit, state, dispatch }) => { }; if (state.useSingleDiffStyle) { - urlParams.view = state.diffViewType; + urlParams.view = window.gon?.features?.unifiedDiffLines ? 'inline' : state.diffViewType; } commit(types.SET_BATCH_LOADING, true); @@ -178,7 +178,7 @@ export const fetchDiffFilesMeta = ({ commit, state }) => { const urlParams = {}; if (state.useSingleDiffStyle) { - urlParams.view = state.diffViewType; + urlParams.view = window.gon?.features?.unifiedDiffLines ? 'inline' : state.diffViewType; } commit(types.SET_LOADING, true); diff --git a/app/assets/javascripts/diffs/store/mutations.js b/app/assets/javascripts/diffs/store/mutations.js index 0d41f1c2178..3b03cce86fd 100644 --- a/app/assets/javascripts/diffs/store/mutations.js +++ b/app/assets/javascripts/diffs/store/mutations.js @@ -1,4 +1,5 @@ import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; +import { PARALLEL_DIFF_VIEW_TYPE } from '../constants'; import { findDiffFile, addLineReferences, @@ -154,7 +155,9 @@ export default { addContextLines({ inlineLines: diffFile.highlighted_diff_lines, parallelLines: diffFile.parallel_diff_lines, - diffViewType: state.diffViewType, + diffViewType: window.gon?.features?.unifiedDiffLines + ? PARALLEL_DIFF_VIEW_TYPE + : state.diffViewType, contextLines: lines, bottom, lineNumbers, diff --git a/app/assets/javascripts/diffs/store/utils.js b/app/assets/javascripts/diffs/store/utils.js index e1dc75ad2c7..9286d7fdcb0 100644 --- a/app/assets/javascripts/diffs/store/utils.js +++ b/app/assets/javascripts/diffs/store/utils.js @@ -20,6 +20,77 @@ import { } from '../constants'; import { prepareRawDiffFile } from '../diff_file'; +export const isAdded = line => ['new', 'new-nonewline'].includes(line.type); +export const isRemoved = line => ['old', 'old-nonewline'].includes(line.type); +export const isUnchanged = line => !line.type; +export const isMeta = line => ['match', 'new-nonewline', 'old-nonewline'].includes(line.type); + +/** + * Pass in the inline diff lines array which gets converted + * to the parallel diff lines. + * This allows for us to convert inline diff lines to parallel + * on the frontend without needing to send any requests + * to the API. + * + * This method has been taken from the already existing backend + * implementation at lib/gitlab/diff/parallel_diff.rb + * + * @param {Object[]} diffLines - inline diff lines + * + * @returns {Object[]} parallel lines + */ +export const parallelizeDiffLines = (diffLines = []) => { + let freeRightIndex = null; + const lines = []; + + for (let i = 0, diffLinesLength = diffLines.length, index = 0; i < diffLinesLength; i += 1) { + const line = diffLines[i]; + + if (isRemoved(line)) { + lines.push({ + [LINE_POSITION_LEFT]: line, + [LINE_POSITION_RIGHT]: null, + }); + + if (freeRightIndex === null) { + // Once we come upon a new line it can be put on the right of this old line + freeRightIndex = index; + } + index += 1; + } else if (isAdded(line)) { + if (freeRightIndex !== null) { + // If an old line came before this without a line on the right, this + // line can be put to the right of it. + lines[freeRightIndex].right = line; + + // If there are any other old lines on the left that don't yet have + // a new counterpart on the right, update the free_right_index + const nextFreeRightIndex = freeRightIndex + 1; + freeRightIndex = nextFreeRightIndex < index ? nextFreeRightIndex : null; + } else { + lines.push({ + [LINE_POSITION_LEFT]: null, + [LINE_POSITION_RIGHT]: line, + }); + + freeRightIndex = null; + index += 1; + } + } else if (isMeta(line) || isUnchanged(line)) { + // line in the right panel is the same as in the left one + lines.push({ + [LINE_POSITION_LEFT]: line, + [LINE_POSITION_RIGHT]: line, + }); + + freeRightIndex = null; + index += 1; + } + } + + return lines; +}; + export function findDiffFile(files, match, matchKey = 'file_hash') { return files.find(file => file[matchKey] === match); } @@ -280,6 +351,13 @@ function mergeTwoFiles(target, source) { } function ensureBasicDiffFileLines(file) { + if (window.gon?.features?.unifiedDiffLines) { + return Object.assign(file, { + highlighted_diff_lines: [], + parallel_diff_lines: parallelizeDiffLines(file.highlighted_diff_lines || []), + }); + } + const missingInline = !file.highlighted_diff_lines; const missingParallel = !file.parallel_diff_lines; @@ -717,74 +795,3 @@ export const getDefaultWhitespace = (queryString, cookie) => { if (cookie === NO_SHOW_WHITESPACE) return false; return true; }; - -export const isAdded = line => ['new', 'new-nonewline'].includes(line.type); -export const isRemoved = line => ['old', 'old-nonewline'].includes(line.type); -export const isUnchanged = line => !line.type; -export const isMeta = line => ['match', 'new-nonewline', 'old-nonewline'].includes(line.type); - -/** - * Pass in the inline diff lines array which gets converted - * to the parallel diff lines. - * This allows for us to convert inline diff lines to parallel - * on the frontend without needing to send any requests - * to the API. - * - * This method has been taken from the already existing backend - * implementation at lib/gitlab/diff/parallel_diff.rb - * - * @param {Object[]} diffLines - inline diff lines - * - * @returns {Object[]} parallel lines - */ -export const parallelizeDiffLines = (diffLines = []) => { - let freeRightIndex = null; - const lines = []; - - for (let i = 0, diffLinesLength = diffLines.length, index = 0; i < diffLinesLength; i += 1) { - const line = diffLines[i]; - - if (isRemoved(line)) { - lines.push({ - [LINE_POSITION_LEFT]: line, - [LINE_POSITION_RIGHT]: null, - }); - - if (freeRightIndex === null) { - // Once we come upon a new line it can be put on the right of this old line - freeRightIndex = index; - } - index += 1; - } else if (isAdded(line)) { - if (freeRightIndex !== null) { - // If an old line came before this without a line on the right, this - // line can be put to the right of it. - lines[freeRightIndex].right = line; - - // If there are any other old lines on the left that don't yet have - // a new counterpart on the right, update the free_right_index - const nextFreeRightIndex = freeRightIndex + 1; - freeRightIndex = nextFreeRightIndex < index ? nextFreeRightIndex : null; - } else { - lines.push({ - [LINE_POSITION_LEFT]: null, - [LINE_POSITION_RIGHT]: line, - }); - - freeRightIndex = null; - index += 1; - } - } else if (isMeta(line) || isUnchanged(line)) { - // line in the right panel is the same as in the left one - lines.push({ - [LINE_POSITION_LEFT]: line, - [LINE_POSITION_RIGHT]: line, - }); - - freeRightIndex = null; - index += 1; - } - } - - return lines; -}; diff --git a/app/assets/javascripts/vue_shared/components/url_sync.vue b/app/assets/javascripts/vue_shared/components/url_sync.vue index 389d42f0829..2844d9e9e94 100644 --- a/app/assets/javascripts/vue_shared/components/url_sync.vue +++ b/app/assets/javascripts/vue_shared/components/url_sync.vue @@ -1,6 +1,6 @@ <script> import { historyPushState } from '~/lib/utils/common_utils'; -import { setUrlParams } from '~/lib/utils/url_utility'; +import { mergeUrlParams } from '~/lib/utils/url_utility'; export default { props: { @@ -14,7 +14,7 @@ export default { immediate: true, deep: true, handler(newQuery) { - historyPushState(setUrlParams(newQuery, window.location.href, true)); + historyPushState(mergeUrlParams(newQuery, window.location.href, { spreadArrays: true })); }, }, }, diff --git a/app/assets/stylesheets/components/batch_comments/review_bar.scss b/app/assets/stylesheets/components/batch_comments/review_bar.scss new file mode 100644 index 00000000000..76bf7ac81e8 --- /dev/null +++ b/app/assets/stylesheets/components/batch_comments/review_bar.scss @@ -0,0 +1,122 @@ +.review-bar-component { + position: fixed; + bottom: 0; + left: 0; + width: 100%; + background: $white; + z-index: 300; + padding: 7px 0 6px; // to keep aligned with "collapse sidebar" button on the left sidebar + border-top: 1px solid $border-color; + padding-left: $contextual-sidebar-width; + padding-right: $gutter_collapsed_width; + transition: padding $sidebar-transition-duration; + + .page-with-icon-sidebar & { + padding-left: $contextual-sidebar-collapsed-width; + } + + .right-sidebar-expanded & { + padding-right: $gutter_width; + } + + @media (max-width: map-get($grid-breakpoints, sm)-1) { + padding-left: 0; + padding-right: 0; + } + + .dropdown { + margin-left: $grid-size; + } +} + +.review-bar-content { + max-width: $limited-layout-width; + padding: 0 $gl-padding; + width: 100%; + margin: 0 auto; +} + +.review-preview-dropdown { + .review-preview-item.menu-item { + display: flex; + flex-wrap: wrap; + padding: 8px 16px; + cursor: pointer; + + &:not(.is-last) { + border-bottom: 1px solid $list-border; + } + } + + .dropdown-menu { + top: auto; + bottom: 36px; + + &.show { + max-height: 400px; + + @include media-breakpoint-down(xs) { + width: calc(100vw - 32px); + } + } + } + + .dropdown-content { + max-height: 300px; + } + + .dropdown-title { + padding: $grid-size 25px $gl-padding; + margin-bottom: 0; + } + + .dropdown-footer { + margin-top: 0; + } + + .dropdown-menu-close { + top: 6px; + } +} + +.review-preview-dropdown-toggle { + svg.s16 { + width: 15px; + height: 15px; + margin-top: -1px; + top: 3px; + margin-left: 4px; + } +} + +.review-preview-item-header { + display: flex; + align-items: center; + width: 100%; + margin-bottom: 4px; + + > .bold { + display: flex; + min-width: 0; + line-height: 16px; + } +} + +.review-preview-item-footer { + display: flex; + align-items: center; + margin-top: 4px; +} + +.review-preview-item-content { + width: 100%; + + p { + display: block; + width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + margin-bottom: 0; + } +} diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index 7e92940ff30..fc3d0053859 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -32,7 +32,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController end def integrations - @integrations = Service.find_or_initialize_all(Service.instances).sort_by(&:title) + @integrations = Service.find_or_initialize_all(Service.for_instance).sort_by(&:title) end def update diff --git a/app/controllers/admin/services_controller.rb b/app/controllers/admin/services_controller.rb index 1bc82e98ab8..1f4250639c4 100644 --- a/app/controllers/admin/services_controller.rb +++ b/app/controllers/admin/services_controller.rb @@ -8,7 +8,7 @@ class Admin::ServicesController < Admin::ApplicationController def index @services = Service.find_or_create_templates.sort_by(&:title) - @existing_instance_types = Service.instances.pluck(:type) # rubocop: disable CodeReuse/ActiveRecord + @existing_instance_types = Service.for_instance.pluck(:type) # rubocop: disable CodeReuse/ActiveRecord end def edit diff --git a/app/controllers/groups/settings/integrations_controller.rb b/app/controllers/groups/settings/integrations_controller.rb index 844b19a4343..103ab860aac 100644 --- a/app/controllers/groups/settings/integrations_controller.rb +++ b/app/controllers/groups/settings/integrations_controller.rb @@ -8,7 +8,7 @@ module Groups before_action :authorize_admin_group! def index - @integrations = Service.find_or_initialize_all(Service.by_group(group)).sort_by(&:title) + @integrations = Service.find_or_initialize_all(Service.for_group(group)).sort_by(&:title) end private diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index e77d2f0f5ee..43baa849007 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -40,6 +40,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo push_frontend_feature_flag(:approvals_commented_by, @project, default_enabled: true) push_frontend_feature_flag(:hide_jump_to_next_unresolved_in_threads, default_enabled: true) push_frontend_feature_flag(:merge_request_widget_graphql, @project) + push_frontend_feature_flag(:unified_diff_lines, @project) end before_action do diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index b70340a98cd..de0dbd295c1 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -150,10 +150,10 @@ class MergeRequestDiff < ApplicationRecord # All diff information is collected from repository after object is created. # It allows you to override variables like head_commit_sha before getting diff. after_create :save_git_content, unless: :importing? - after_create :set_count_columns after_create_commit :set_as_latest_diff, unless: :importing? after_save :update_external_diff_store + after_save :set_count_columns def self.find_by_diff_refs(diff_refs) find_by(start_commit_sha: diff_refs.start_sha, head_commit_sha: diff_refs.head_sha, base_commit_sha: diff_refs.base_sha) diff --git a/app/models/project.rb b/app/models/project.rb index e1b6a9c41dd..f55bb30964a 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -2533,11 +2533,11 @@ class Project < ApplicationRecord end def services_templates - @services_templates ||= Service.templates + @services_templates ||= Service.for_template end def services_instances - @services_instances ||= Service.instances + @services_instances ||= Service.for_instance end def closest_namespace_setting(name) diff --git a/app/models/service.rb b/app/models/service.rb index b877f8b4143..75ad2cdbc83 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -63,9 +63,9 @@ class Service < ApplicationRecord scope :active, -> { where(active: true) } scope :by_type, -> (type) { where(type: type) } scope :by_active_flag, -> (flag) { where(active: flag) } - scope :by_group, -> (group) { where(group_id: group, type: available_services_types) } - scope :templates, -> { where(template: true, type: available_services_types) } - scope :instances, -> { where(instance: true, type: available_services_types) } + scope :for_group, -> (group) { where(group_id: group, type: available_services_types) } + scope :for_template, -> { where(template: true, type: available_services_types) } + scope :for_instance, -> { where(instance: true, type: available_services_types) } scope :push_hooks, -> { where(push_events: true, active: true) } scope :tag_push_hooks, -> { where(tag_push_events: true, active: true) } @@ -291,11 +291,11 @@ class Service < ApplicationRecord def self.find_or_create_templates create_nonexistent_templates - templates + for_template end private_class_method def self.create_nonexistent_templates - nonexistent_services = list_nonexistent_services_for(templates) + nonexistent_services = list_nonexistent_services_for(for_template) return if nonexistent_services.empty? # Create within a transaction to perform the lowest possible SQL queries. diff --git a/app/services/projects/after_rename_service.rb b/app/services/projects/after_rename_service.rb index 2a35a07d555..f9022b3afe3 100644 --- a/app/services/projects/after_rename_service.rb +++ b/app/services/projects/after_rename_service.rb @@ -96,9 +96,23 @@ module Projects .rename_project(path_before, project_path, namespace_full_path) end - Gitlab::PagesTransfer - .new - .rename_project(path_before, project_path, namespace_full_path) + if ::Feature.enabled?(:async_pages_move_project_rename, project) + # Block will be evaluated in the context of project so we need + # to bind to a local variable to capture it, as the instance + # variable and method aren't available on Project + path_before_local = @path_before + + project.run_after_commit_or_now do + Gitlab::PagesTransfer + .new + .async + .rename_project(path_before_local, path, namespace.full_path) + end + else + Gitlab::PagesTransfer + .new + .rename_project(path_before, project_path, namespace_full_path) + end end def log_completion diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml index 57f09fbfc2f..5ce588c9dd8 100644 --- a/app/workers/all_queues.yml +++ b/app/workers/all_queues.yml @@ -1540,6 +1540,14 @@ :weight: 1 :idempotent: :tags: [] +- :name: pages_transfer + :feature_category: :pages + :has_external_dependencies: + :urgency: :low + :resource_boundary: :unknown + :weight: 1 + :idempotent: + :tags: [] - :name: pages_update_configuration :feature_category: :pages :has_external_dependencies: diff --git a/app/workers/delete_diff_files_worker.rb b/app/workers/delete_diff_files_worker.rb index a6759a9d7c4..a31cf650b83 100644 --- a/app/workers/delete_diff_files_worker.rb +++ b/app/workers/delete_diff_files_worker.rb @@ -12,11 +12,11 @@ class DeleteDiffFilesWorker # rubocop:disable Scalability/IdempotentWorker return if merge_request_diff.without_files? MergeRequestDiff.transaction do - merge_request_diff.clean! - MergeRequestDiffFile .where(merge_request_diff_id: merge_request_diff.id) .delete_all + + merge_request_diff.clean! end end # rubocop: enable CodeReuse/ActiveRecord diff --git a/app/workers/pages_transfer_worker.rb b/app/workers/pages_transfer_worker.rb new file mode 100644 index 00000000000..f78564cc69d --- /dev/null +++ b/app/workers/pages_transfer_worker.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +class PagesTransferWorker # rubocop:disable Scalability/IdempotentWorker + include ApplicationWorker + + TransferFailedError = Class.new(StandardError) + + feature_category :pages + loggable_arguments 0, 1 + + def perform(method, args) + return unless Gitlab::PagesTransfer::Async::METHODS.include?(method) + + result = Gitlab::PagesTransfer.new.public_send(method, *args) # rubocop:disable GitlabSecurity/PublicSend + + # If result isn't truthy, the move failed. Promote this to an + # exception so that it will be logged and retried appropriately + raise TransferFailedError unless result + end +end diff --git a/changelogs/unreleased/240921-fix-merge-request-diff-files-count.yml b/changelogs/unreleased/240921-fix-merge-request-diff-files-count.yml new file mode 100644 index 00000000000..34193383cdd --- /dev/null +++ b/changelogs/unreleased/240921-fix-merge-request-diff-files-count.yml @@ -0,0 +1,5 @@ +--- +title: Fix incorrect merge request diff file count after deletion +merge_request: 40384 +author: +type: fixed diff --git a/changelogs/unreleased/ph-240235-moveReviewCSS.yml b/changelogs/unreleased/ph-240235-moveReviewCSS.yml new file mode 100644 index 00000000000..0fca2e71a85 --- /dev/null +++ b/changelogs/unreleased/ph-240235-moveReviewCSS.yml @@ -0,0 +1,5 @@ +--- +title: Fixed merge request review styles not loading in FOSS +merge_request: 40479 +author: +type: fixed diff --git a/config/feature_flags/development/async_pages_move_project_rename.yml b/config/feature_flags/development/async_pages_move_project_rename.yml new file mode 100644 index 00000000000..6f5d30a6420 --- /dev/null +++ b/config/feature_flags/development/async_pages_move_project_rename.yml @@ -0,0 +1,7 @@ +--- +name: async_pages_move_project_rename +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/40087 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/235802 +group: team::Scalability +type: development +default_enabled: false diff --git a/config/feature_flags/development/unified_diff_lines.yml b/config/feature_flags/development/unified_diff_lines.yml new file mode 100644 index 00000000000..a676f0732dd --- /dev/null +++ b/config/feature_flags/development/unified_diff_lines.yml @@ -0,0 +1,7 @@ +--- +name: unified_diff_lines +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/40131 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/241188 +group: group::source code +type: development +default_enabled: false diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml index 2e04522c824..a3e98c77746 100644 --- a/config/sidekiq_queues.yml +++ b/config/sidekiq_queues.yml @@ -180,6 +180,8 @@ - 1 - - pages_remove - 1 +- - pages_transfer + - 1 - - pages_update_configuration - 1 - - personal_access_tokens diff --git a/db/post_migrate/20200806100713_schedule_populate_resolved_on_default_branch_column.rb b/db/post_migrate/20200806100713_schedule_populate_resolved_on_default_branch_column.rb deleted file mode 100644 index 396b95257e8..00000000000 --- a/db/post_migrate/20200806100713_schedule_populate_resolved_on_default_branch_column.rb +++ /dev/null @@ -1,29 +0,0 @@ -# frozen_string_literal: true - -class SchedulePopulateResolvedOnDefaultBranchColumn < ActiveRecord::Migration[6.0] - include Gitlab::Database::MigrationHelpers - - DOWNTIME = false - BATCH_SIZE = 100 - DELAY_INTERVAL = 5.minutes.to_i - MIGRATION_CLASS = 'PopulateResolvedOnDefaultBranchColumn' - - disable_ddl_transaction! - - def up - return unless run_migration? - - EE::Gitlab::BackgroundMigration::PopulateResolvedOnDefaultBranchColumn::Vulnerability.distinct.each_batch(of: BATCH_SIZE, column: :project_id) do |batch, index| - project_ids = batch.pluck(:project_id) - migrate_in(index * DELAY_INTERVAL, MIGRATION_CLASS, project_ids) - end - end - - def down; end - - private - - def run_migration? - Gitlab.ee? && table_exists?(:projects) && table_exists?(:vulnerabilities) - end -end diff --git a/db/schema_migrations/20200806100713 b/db/schema_migrations/20200806100713 deleted file mode 100644 index cdfcfe5e022..00000000000 --- a/db/schema_migrations/20200806100713 +++ /dev/null @@ -1 +0,0 @@ -fdcce45050f972d8edf2c645022f517ff6b9f4c76767e6cebe45a11fe34dd388
\ No newline at end of file diff --git a/doc/development/contributing/index.md b/doc/development/contributing/index.md index cea9043a333..7e10e152304 100644 --- a/doc/development/contributing/index.md +++ b/doc/development/contributing/index.md @@ -67,6 +67,11 @@ we credit the original author by adding a changelog entry crediting the author and optionally include the original author on at least one of the commits within the MR. +## Closing policy for inactive bugs + +GitLab values the time spent by contributors on reporting bugs. However, if a bug remains inactive for a very long period, +it will qualify for auto-closure. Please refer to the [auto-close inactive bugs](https://about.gitlab.com/handbook/engineering/quality/triage-operations/#auto-close-inactive-bugs) section in our handbook to understand the complete workflow. + ## Helping others Help other GitLab users when you can. diff --git a/doc/development/testing_guide/best_practices.md b/doc/development/testing_guide/best_practices.md index a4b51d191eb..8a1ee74a7fc 100644 --- a/doc/development/testing_guide/best_practices.md +++ b/doc/development/testing_guide/best_practices.md @@ -57,14 +57,24 @@ bundle exec guard When using spring and guard together, use `SPRING=1 bundle exec guard` instead to make use of spring. -Use [Factory Doctor](https://test-prof.evilmartians.io/#/profilers/factory_doctor) to find cases on un-necessary database manipulation, which can cause slow tests. +Use [Factory Doctor](https://test-prof.evilmartians.io/#/profilers/factory_doctor) to find cases where database persistence is not needed in a given test. ```shell # run test for path FDOC=1 bin/rspec spec/[path]/[to]/[spec].rb ``` -[Factory Profiler](https://test-prof.evilmartians.io/#/profilers/factory_prof) can help to identify unnecessary factory creation. +A common change is to use `build` instead of `create`: + +```ruby +# Old +let(:project) { create(:project) } + +# New +let(:project) { build(:project) } +``` + +[Factory Profiler](https://test-prof.evilmartians.io/#/profilers/factory_prof) can help to identify repetitive database persistance via factories. ```shell # run test for path @@ -74,14 +84,14 @@ FPROF=1 bin/rspec spec/[path]/[to]/[spec].rb FPROF=flamegraph bin/rspec spec/[path]/[to]/[spec].rb ``` -A common change is to use [`let_it_be`](#common-test-setup). +A common change is to use [`let_it_be`](#common-test-setup): ```ruby - # Old - let(:project) { create(:project) } +# Old +let(:project) { create(:project) } - # New - let_it_be(:project) { create(:project) } +# New +let_it_be(:project) { create(:project) } ``` ### General guidelines diff --git a/lib/gitlab/background_migration/populate_resolved_on_default_branch_column.rb b/lib/gitlab/background_migration/populate_resolved_on_default_branch_column.rb deleted file mode 100644 index eb72ef1de33..00000000000 --- a/lib/gitlab/background_migration/populate_resolved_on_default_branch_column.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module BackgroundMigration - # rubocop:disable Style/Documentation - class PopulateResolvedOnDefaultBranchColumn - def perform(*); end - end - end -end - -Gitlab::BackgroundMigration::PopulateResolvedOnDefaultBranchColumn.prepend_if_ee('EE::Gitlab::BackgroundMigration::PopulateResolvedOnDefaultBranchColumn') diff --git a/lib/gitlab/ci/config/entry/job.rb b/lib/gitlab/ci/config/entry/job.rb index f960cec1f26..ecc2c5cb729 100644 --- a/lib/gitlab/ci/config/entry/job.rb +++ b/lib/gitlab/ci/config/entry/job.rb @@ -122,39 +122,9 @@ module Gitlab :needs, :retry, :parallel, :start_in, :interruptible, :timeout, :resource_group, :release - Matcher = Struct.new(:name, :config) do - def applies? - job_is_not_hidden? && - config_is_a_hash? && - has_job_keys? - end - - private - - def job_is_not_hidden? - !name.to_s.start_with?('.') - end - - def config_is_a_hash? - config.is_a?(Hash) - end - - def has_job_keys? - if name == :default - config.key?(:script) - else - (ALLOWED_KEYS & config.keys).any? - end - end - end - def self.matching?(name, config) - if Gitlab::Ci::Features.job_entry_matches_all_keys? - Matcher.new(name, config).applies? - else - !name.to_s.start_with?('.') && - config.is_a?(Hash) && config.key?(:script) - end + !name.to_s.start_with?('.') && + config.is_a?(Hash) && config.key?(:script) end def self.visible? diff --git a/lib/gitlab/ci/config/entry/jobs.rb b/lib/gitlab/ci/config/entry/jobs.rb index 1d3036189b0..b5ce42969a5 100644 --- a/lib/gitlab/ci/config/entry/jobs.rb +++ b/lib/gitlab/ci/config/entry/jobs.rb @@ -14,8 +14,8 @@ module Gitlab validates :config, type: Hash validate do - unless has_valid_jobs? - errors.add(:config, 'should contain valid jobs') + each_unmatched_job do |name| + errors.add(name, 'config should implement a script: or a trigger: keyword') end unless has_visible_job? @@ -23,9 +23,9 @@ module Gitlab end end - def has_valid_jobs? - config.all? do |name, value| - Jobs.find_type(name, value) + def each_unmatched_job + config.each do |name, value| + yield(name) unless Jobs.find_type(name, value) end end diff --git a/lib/gitlab/ci/config/entry/root.rb b/lib/gitlab/ci/config/entry/root.rb index 19d6a470941..2d93f1ab06e 100644 --- a/lib/gitlab/ci/config/entry/root.rb +++ b/lib/gitlab/ci/config/entry/root.rb @@ -134,7 +134,7 @@ module Gitlab @jobs_config = @config .except(*self.class.reserved_nodes_names) .select do |name, config| - Entry::Jobs.find_type(name, config).present? + Entry::Jobs.find_type(name, config).present? || ALLOWED_KEYS.exclude?(name) end @config = @config.except(*@jobs_config.keys) diff --git a/lib/gitlab/ci/features.rb b/lib/gitlab/ci/features.rb index ca3a927f5a3..386788c751f 100644 --- a/lib/gitlab/ci/features.rb +++ b/lib/gitlab/ci/features.rb @@ -64,10 +64,6 @@ module Gitlab ::Feature.enabled?(:ci_plan_needs_size_limit, project, default_enabled: true) end - def self.job_entry_matches_all_keys? - ::Feature.enabled?(:ci_job_entry_matches_all_keys) - end - def self.lint_creates_pipeline_with_dry_run?(project) ::Feature.enabled?(:ci_lint_creates_pipeline_with_dry_run, project, default_enabled: true) end @@ -76,10 +72,6 @@ module Gitlab ::Feature.enabled?(:reset_ci_minutes_for_all_namespaces, default_enabled: false) end - def self.expand_names_for_cross_pipeline_artifacts?(project) - ::Feature.enabled?(:ci_expand_names_for_cross_pipeline_artifacts, project) - end - def self.project_transactionless_destroy?(project) Feature.enabled?(:project_transactionless_destroy, project, default_enabled: false) end diff --git a/lib/gitlab/danger/roulette.rb b/lib/gitlab/danger/roulette.rb index 2e6181d1cab..a6866868e6c 100644 --- a/lib/gitlab/danger/roulette.rb +++ b/lib/gitlab/danger/roulette.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require_relative 'teammate' -require_relative 'request_helper' +require_relative 'request_helper' unless defined?(Gitlab::Danger::RequestHelper) module Gitlab module Danger diff --git a/lib/gitlab/pages_transfer.rb b/lib/gitlab/pages_transfer.rb index a70dc826f97..8ae0ec5a78a 100644 --- a/lib/gitlab/pages_transfer.rb +++ b/lib/gitlab/pages_transfer.rb @@ -1,7 +1,26 @@ # frozen_string_literal: true +# To make a call happen in a new Sidekiq job, add `.async` before the call. For +# instance: +# +# PagesTransfer.new.async.move_namespace(...) +# module Gitlab class PagesTransfer < ProjectTransfer + class Async + METHODS = %w[move_namespace move_project rename_project rename_namespace].freeze + + METHODS.each do |meth| + define_method meth do |*args| + PagesTransferWorker.perform_async(meth, args) + end + end + end + + def async + @async ||= Async.new + end + def root_dir Gitlab.config.pages.path end diff --git a/spec/controllers/projects/ci/lints_controller_spec.rb b/spec/controllers/projects/ci/lints_controller_spec.rb index 7c10b386169..20025c9b045 100644 --- a/spec/controllers/projects/ci/lints_controller_spec.rb +++ b/spec/controllers/projects/ci/lints_controller_spec.rb @@ -150,7 +150,10 @@ RSpec.describe Projects::Ci::LintsController do it 'assigns result with errors' do subject - expect(assigns[:result].errors).to eq(['root config contains unknown keys: rubocop']) + expect(assigns[:result].errors).to match_array([ + 'jobs rubocop config should implement a script: or a trigger: keyword', + 'jobs config should contain at least one visible job' + ]) end context 'with dry_run mode' do @@ -159,7 +162,7 @@ RSpec.describe Projects::Ci::LintsController do it 'assigns result with errors' do subject - expect(assigns[:result].errors).to eq(['root config contains unknown keys: rubocop']) + expect(assigns[:result].errors).to eq(['jobs rubocop config should implement a script: or a trigger: keyword']) end end end diff --git a/spec/features/admin/services/admin_visits_service_templates_spec.rb b/spec/features/admin/services/admin_visits_service_templates_spec.rb index 8e02538ece0..a37e57304aa 100644 --- a/spec/features/admin/services/admin_visits_service_templates_spec.rb +++ b/spec/features/admin/services/admin_visits_service_templates_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' RSpec.describe 'Admin visits service templates' do let(:admin) { create(:user, :admin) } - let(:slack_service) { Service.templates.find { |s| s.type == 'SlackService' } } + let(:slack_service) { Service.for_template.find { |s| s.type == 'SlackService' } } before do sign_in(admin) diff --git a/spec/lib/gitlab/ci/config/entry/job_spec.rb b/spec/lib/gitlab/ci/config/entry/job_spec.rb index ca02eaee0a0..ab760b107f8 100644 --- a/spec/lib/gitlab/ci/config/entry/job_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb @@ -74,16 +74,6 @@ RSpec.describe Gitlab::Ci::Config::Entry::Job do it { is_expected.to be_falsey } end - context 'when config does not contain script' do - let(:name) { :build } - - let(:config) do - { before_script: "cd ${PROJ_DIR} " } - end - - it { is_expected.to be_truthy } - end - context 'when using the default job without script' do let(:name) { :default } let(:config) do @@ -104,14 +94,6 @@ RSpec.describe Gitlab::Ci::Config::Entry::Job do it { is_expected.to be_truthy } end - - context 'there are no shared keys between jobs and bridges' do - subject(:shared_values) do - described_class::ALLOWED_KEYS & Gitlab::Ci::Config::Entry::Bridge::ALLOWED_KEYS - end - - it { is_expected.to be_empty } - end end describe 'validations' do diff --git a/spec/lib/gitlab/ci/config/entry/jobs_spec.rb b/spec/lib/gitlab/ci/config/entry/jobs_spec.rb index 8561bd330b7..ac6b589ec6b 100644 --- a/spec/lib/gitlab/ci/config/entry/jobs_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/jobs_spec.rb @@ -68,7 +68,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Jobs do let(:config) { { rspec: nil } } it 'reports error' do - expect(entry.errors).to include "jobs config should contain valid jobs" + expect(entry.errors).to include 'jobs rspec config should implement a script: or a trigger: keyword' end end diff --git a/spec/lib/gitlab/ci/config/entry/root_spec.rb b/spec/lib/gitlab/ci/config/entry/root_spec.rb index 140b3c4f55b..252bda6461d 100644 --- a/spec/lib/gitlab/ci/config/entry/root_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/root_spec.rb @@ -344,9 +344,9 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do end describe '#errors' do - it 'reports errors about missing script' do + it 'reports errors about missing script or trigger' do expect(root.errors) - .to include "root config contains unknown keys: rspec" + .to include 'jobs rspec config should implement a script: or a trigger: keyword' end end end diff --git a/spec/lib/gitlab/ci/lint_spec.rb b/spec/lib/gitlab/ci/lint_spec.rb index 03bb76676e0..a895ac45bc6 100644 --- a/spec/lib/gitlab/ci/lint_spec.rb +++ b/spec/lib/gitlab/ci/lint_spec.rb @@ -72,7 +72,7 @@ RSpec.describe Gitlab::Ci::Lint do it 'returns a result with errors' do expect(subject).not_to be_valid - expect(subject.errors).to include(/root config contains unknown keys/) + expect(subject.errors).to include(/jobs build config should implement a script: or a trigger: keyword/) end end diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb index 99e6bdc6625..6e8652b3857 100644 --- a/spec/lib/gitlab/ci/yaml_processor_spec.rb +++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb @@ -2409,7 +2409,7 @@ module Gitlab context 'returns error if job configuration is invalid' do let(:config) { YAML.dump({ extra: "bundle update" }) } - it_behaves_like 'returns errors', 'root config contains unknown keys: extra' + it_behaves_like 'returns errors', 'jobs extra config should implement a script: or a trigger: keyword' end context 'returns errors if services configuration is not correct' do @@ -2427,7 +2427,7 @@ module Gitlab context 'returns errors if the job script is not defined' do let(:config) { YAML.dump({ rspec: { before_script: "test" } }) } - it_behaves_like 'returns errors', "jobs:rspec script can't be blank" + it_behaves_like 'returns errors', 'jobs rspec config should implement a script: or a trigger: keyword' end context 'returns errors if there are no visible jobs defined' do diff --git a/spec/lib/gitlab/pages_transfer_spec.rb b/spec/lib/gitlab/pages_transfer_spec.rb new file mode 100644 index 00000000000..4f0ee76b244 --- /dev/null +++ b/spec/lib/gitlab/pages_transfer_spec.rb @@ -0,0 +1,137 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::PagesTransfer do + describe '#async' do + let(:async) { subject.async } + + context 'when receiving an allowed method' do + it 'schedules a PagesTransferWorker', :aggregate_failures do + described_class::Async::METHODS.each do |meth| + expect(PagesTransferWorker) + .to receive(:perform_async).with(meth, %w[foo bar]) + + async.public_send(meth, 'foo', 'bar') + end + end + end + + context 'when receiving a private method' do + it 'raises NoMethodError' do + expect { async.move('foo', 'bar') }.to raise_error(NoMethodError) + end + end + + context 'when receiving a non-existent method' do + it 'raises NoMethodError' do + expect { async.foo('bar') }.to raise_error(NoMethodError) + end + end + end + + RSpec.shared_examples 'moving a pages directory' do |parameter| + let!(:pages_path_before) { project.pages_path } + let(:config_path_before) { File.join(pages_path_before, 'config.json') } + let(:pages_path_after) { project.reload.pages_path } + let(:config_path_after) { File.join(pages_path_after, 'config.json') } + + before do + FileUtils.mkdir_p(pages_path_before) + FileUtils.touch(config_path_before) + end + + after do + FileUtils.remove_entry(pages_path_before, true) + FileUtils.remove_entry(pages_path_after, true) + end + + it 'moves the directory' do + subject.public_send(meth, *args) + + expect(File.exist?(config_path_before)).to be(false) + expect(File.exist?(config_path_after)).to be(true) + end + + it 'returns false if it fails to move the directory' do + # Move the directory once, so it can't be moved again + subject.public_send(meth, *args) + + expect(subject.public_send(meth, *args)).to be(false) + end + end + + describe '#move_namespace' do + # Can't use let_it_be because we change the path + let(:group_1) { create(:group) } + let(:group_2) { create(:group) } + let(:subgroup) { create(:group, parent: group_1) } + let(:project) { create(:project, group: subgroup) } + let(:new_path) { "#{group_2.path}/#{subgroup.path}" } + let(:meth) { 'move_namespace' } + + # Store the path before we change it + let!(:args) { [project.path, subgroup.full_path, new_path] } + + before do + # We need to skip hooks, otherwise the directory will be moved + # via an ActiveRecord callback + subgroup.update_columns(parent_id: group_2.id) + subgroup.route.update!(path: new_path) + end + + include_examples 'moving a pages directory' + end + + describe '#move_project' do + # Can't use let_it_be because we change the path + let(:group_1) { create(:group) } + let(:group_2) { create(:group) } + let(:project) { create(:project, group: group_1) } + let(:new_path) { group_2.path } + let(:meth) { 'move_project' } + let(:args) { [project.path, group_1.full_path, group_2.full_path] } + + include_examples 'moving a pages directory' do + before do + project.update!(group: group_2) + end + end + end + + describe '#rename_project' do + # Can't use let_it_be because we change the path + let(:project) { create(:project) } + let(:new_path) { project.path.succ } + let(:meth) { 'rename_project' } + + # Store the path before we change it + let!(:args) { [project.path, new_path, project.namespace.full_path] } + + include_examples 'moving a pages directory' do + before do + project.update!(path: new_path) + end + end + end + + describe '#rename_namespace' do + # Can't use let_it_be because we change the path + let(:group) { create(:group) } + let(:project) { create(:project, group: group) } + let(:new_path) { project.namespace.full_path.succ } + let(:meth) { 'rename_namespace' } + + # Store the path before we change it + let!(:args) { [project.namespace.full_path, new_path] } + + before do + # We need to skip hooks, otherwise the directory will be moved + # via an ActiveRecord callback + group.update_columns(path: new_path) + group.route.update!(path: new_path) + end + + include_examples 'moving a pages directory' + end +end diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb index 6dddcb0eca4..0c356fc5118 100644 --- a/spec/models/service_spec.rb +++ b/spec/models/service_spec.rb @@ -92,12 +92,12 @@ RSpec.describe Service do end end - describe '.by_group' do + describe '.for_group' do let!(:service1) { create(:jira_service, project_id: nil, group_id: group.id) } let!(:service2) { create(:jira_service) } it 'returns the right group service' do - expect(described_class.by_group(group)).to match_array([service1]) + expect(described_class.for_group(group)).to match_array([service1]) end end @@ -215,11 +215,11 @@ RSpec.describe Service do describe '.find_or_initialize_all' do shared_examples 'service instances' do it 'returns the available service instances' do - expect(Service.find_or_initialize_all(Service.instances).pluck(:type)).to match_array(Service.available_services_types) + expect(Service.find_or_initialize_all(Service.for_instance).pluck(:type)).to match_array(Service.available_services_types) end it 'does not create service instances' do - expect { Service.find_or_initialize_all(Service.instances) }.not_to change { Service.count } + expect { Service.find_or_initialize_all(Service.for_instance) }.not_to change { Service.count } end end diff --git a/spec/services/ci/create_pipeline_service/creation_errors_and_warnings_spec.rb b/spec/services/ci/create_pipeline_service/creation_errors_and_warnings_spec.rb index 3be5ac1f739..b5b3832ac00 100644 --- a/spec/services/ci/create_pipeline_service/creation_errors_and_warnings_spec.rb +++ b/spec/services/ci/create_pipeline_service/creation_errors_and_warnings_spec.rb @@ -101,7 +101,7 @@ RSpec.describe Ci::CreatePipelineService do end it 'contains only errors' do - error_message = 'root config contains unknown keys: invalid' + error_message = 'jobs invalid config should implement a script: or a trigger: keyword' expect(pipeline.yaml_errors).to eq(error_message) expect(pipeline.error_messages.map(&:content)).to contain_exactly(error_message) expect(pipeline.errors.full_messages).to contain_exactly(error_message) diff --git a/spec/services/projects/after_rename_service_spec.rb b/spec/services/projects/after_rename_service_spec.rb index 52136b37c66..11e3604c38a 100644 --- a/spec/services/projects/after_rename_service_spec.rb +++ b/spec/services/projects/after_rename_service_spec.rb @@ -62,11 +62,21 @@ RSpec.describe Projects::AfterRenameService do context 'gitlab pages' do before do - expect(project_storage).to receive(:rename_repo) { true } + allow(project_storage).to receive(:rename_repo) { true } + end + + context 'when async_pages_move_project_rename is disabled' do + it 'moves pages folder to new location' do + stub_feature_flags(async_pages_move_project_rename: false) + + expect_any_instance_of(Gitlab::PagesTransfer).to receive(:rename_project) + + service_execute + end end - it 'moves pages folder to new location' do - expect_any_instance_of(Gitlab::PagesTransfer).to receive(:rename_project) + it 'schedules a move of the pages directory' do + expect(PagesTransferWorker).to receive(:perform_async).with('rename_project', anything) service_execute end @@ -160,8 +170,18 @@ RSpec.describe Projects::AfterRenameService do end context 'gitlab pages' do - it 'moves pages folder to new location' do - expect_any_instance_of(Gitlab::PagesTransfer).to receive(:rename_project) + context 'when async_pages_move_project_rename is disabled' do + it 'moves pages folder to new location' do + stub_feature_flags(async_pages_move_project_rename: false) + + expect_any_instance_of(Gitlab::PagesTransfer).to receive(:rename_project) + + service_execute + end + end + + it 'schedules a move of the pages directory' do + expect(PagesTransferWorker).to receive(:perform_async).with('rename_project', anything) service_execute end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index e7c0a4a43ed..adf21c1c965 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -194,6 +194,10 @@ RSpec.configure do |config| stub_feature_flags(vue_issuable_sidebar: false) stub_feature_flags(vue_issuable_epic_sidebar: false) + # The following can be removed once we are confident the + # unified diff lines works as expected + stub_feature_flags(unified_diff_lines: false) + enable_rugged = example.metadata[:enable_rugged].present? # Disable Rugged features by default diff --git a/spec/workers/delete_diff_files_worker_spec.rb b/spec/workers/delete_diff_files_worker_spec.rb index b3b01588e0b..cf26dbabb97 100644 --- a/spec/workers/delete_diff_files_worker_spec.rb +++ b/spec/workers/delete_diff_files_worker_spec.rb @@ -19,6 +19,12 @@ RSpec.describe DeleteDiffFilesWorker do .from('collected').to('without_files') end + it 'resets the files_count of the diff' do + expect { described_class.new.perform(merge_request_diff.id) } + .to change { merge_request_diff.reload.files_count } + .from(20).to(0) + end + it 'does nothing if diff was already marked as "without_files"' do merge_request_diff.clean! diff --git a/spec/workers/pages_transfer_worker_spec.rb b/spec/workers/pages_transfer_worker_spec.rb new file mode 100644 index 00000000000..248a3713bf6 --- /dev/null +++ b/spec/workers/pages_transfer_worker_spec.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe PagesTransferWorker do + describe '#perform' do + Gitlab::PagesTransfer::Async::METHODS.each do |meth| + context "when method is #{meth}" do + let(:args) { [1, 2, 3] } + + it 'calls the service with the given arguments' do + expect_next_instance_of(Gitlab::PagesTransfer) do |service| + expect(service).to receive(meth).with(*args).and_return(true) + end + + subject.perform(meth, args) + end + + it 'raises an error when the service returns false' do + expect_next_instance_of(Gitlab::PagesTransfer) do |service| + expect(service).to receive(meth).with(*args).and_return(false) + end + + expect { subject.perform(meth, args) } + .to raise_error(described_class::TransferFailedError) + end + end + end + + describe 'when method is not allowed' do + it 'does nothing' do + expect(Gitlab::PagesTransfer).not_to receive(:new) + + subject.perform('object_id', []) + end + end + end +end |