diff options
96 files changed, 740 insertions, 257 deletions
diff --git a/Dangerfile b/Dangerfile index 3e8cb456003..9e3a08949b0 100644 --- a/Dangerfile +++ b/Dangerfile @@ -1,3 +1,4 @@ +# frozen_string_literal: true danger.import_plugin('danger/plugins/helper.rb') unless helper.release_automation? @@ -16,4 +17,5 @@ unless helper.release_automation? danger.import_dangerfile(path: 'danger/roulette') danger.import_dangerfile(path: 'danger/single_codebase') danger.import_dangerfile(path: 'danger/gitlab_ui_wg') + danger.import_dangerfile(path: 'danger/ce_ee_vue_templates') end diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 2b17ffd5042..a2d87226ac2 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -1.34.0 +1.35.0
\ No newline at end of file diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION index acd405b1d62..df5119ec64e 100644 --- a/GITLAB_WORKHORSE_VERSION +++ b/GITLAB_WORKHORSE_VERSION @@ -1 +1 @@ -8.6.0 +8.7.0 @@ -416,11 +416,11 @@ group :ed25519 do end # Gitaly GRPC client -gem 'gitaly-proto', '~> 1.19.0', require: 'gitaly' +gem 'gitaly-proto', '~> 1.22.0', require: 'gitaly' -gem 'grpc', '~> 1.15.0' +gem 'grpc', '~> 1.19.0' -gem 'google-protobuf', '~> 3.6' +gem 'google-protobuf', '~> 3.7.1' gem 'toml-rb', '~> 1.0.0', require: false diff --git a/Gemfile.lock b/Gemfile.lock index 15ed261526a..64f2f78a4f8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -280,7 +280,7 @@ GEM gettext_i18n_rails (>= 0.7.1) po_to_json (>= 1.0.0) rails (>= 3.2.0) - gitaly-proto (1.19.0) + gitaly-proto (1.22.0) grpc (~> 1.0) github-markup (1.7.0) gitlab-default_value_for (3.1.1) @@ -316,8 +316,8 @@ GEM mime-types (~> 3.0) representable (~> 3.0) retriable (>= 2.0, < 4.0) - google-protobuf (3.6.1) - googleapis-common-protos-types (1.0.3) + google-protobuf (3.7.1) + googleapis-common-protos-types (1.0.4) google-protobuf (~> 3.0) googleauth (0.6.6) faraday (~> 0.12) @@ -348,7 +348,7 @@ GEM railties sprockets-rails graphql (1.8.1) - grpc (1.15.0) + grpc (1.19.0) google-protobuf (~> 3.1) googleapis-common-protos-types (~> 1.0.0) haml (5.0.4) @@ -1052,7 +1052,7 @@ DEPENDENCIES gettext (~> 3.2.2) gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails_js (~> 1.3) - gitaly-proto (~> 1.19.0) + gitaly-proto (~> 1.22.0) github-markup (~> 1.7.0) gitlab-default_value_for (~> 3.1.1) gitlab-labkit (~> 0.1.2) @@ -1062,7 +1062,7 @@ DEPENDENCIES gitlab_omniauth-ldap (~> 2.1.1) gon (~> 6.2) google-api-client (~> 0.23) - google-protobuf (~> 3.6) + google-protobuf (~> 3.7.1) gpgme (~> 2.0.18) grape (~> 1.1.0) grape-entity (~> 0.7.1) @@ -1070,7 +1070,7 @@ DEPENDENCIES grape_logging (~> 1.7) graphiql-rails (~> 1.4.10) graphql (~> 1.8.0) - grpc (~> 1.15.0) + grpc (~> 1.19.0) haml_lint (~> 0.28.0) hamlit (~> 2.8.8) hangouts-chat (~> 0.0.5) diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js index 8754c253881..e583a8affd4 100644 --- a/app/assets/javascripts/api.js +++ b/app/assets/javascripts/api.js @@ -1,6 +1,7 @@ import $ from 'jquery'; import _ from 'underscore'; import axios from './lib/utils/axios_utils'; +import { joinPaths } from './lib/utils/url_utility'; const Api = { groupsPath: '/api/:version/groups.json', @@ -339,11 +340,7 @@ const Api = { }, buildUrl(url) { - let urlRoot = ''; - if (gon.relative_url_root != null) { - urlRoot = gon.relative_url_root; - } - return urlRoot + url.replace(':version', gon.api_version); + return joinPaths(gon.relative_url_root || '', url.replace(':version', gon.api_version)); }, }; diff --git a/app/assets/javascripts/behaviors/markdown/gfm_auto_complete.js b/app/assets/javascripts/behaviors/markdown/gfm_auto_complete.js index 55c68139ded..b7200150925 100644 --- a/app/assets/javascripts/behaviors/markdown/gfm_auto_complete.js +++ b/app/assets/javascripts/behaviors/markdown/gfm_auto_complete.js @@ -1,6 +1,6 @@ import $ from 'jquery'; import { parseBoolean } from '~/lib/utils/common_utils'; -import GfmAutoComplete from '~/gfm_auto_complete'; +import GfmAutoComplete from 'ee_else_ce/gfm_auto_complete'; export default function initGFMInput() { $('.js-gfm-input:not(.js-vue-textarea)').each((i, el) => { diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js index f1e26cdfa21..f437954881c 100644 --- a/app/assets/javascripts/gfm_auto_complete.js +++ b/app/assets/javascripts/gfm_auto_complete.js @@ -477,6 +477,16 @@ class GfmAutoComplete { } return null; }, + highlighter(li, query) { + // override default behaviour to escape dot character + // see https://github.com/ichord/At.js/pull/576 + if (!query) { + return li; + } + const escapedQuery = query.replace(/[.+]/, '\\$&'); + const regexp = new RegExp(`>\\s*([^<]*?)(${escapedQuery})([^<]*)\\s*<`, 'ig'); + return li.replace(regexp, (str, $1, $2, $3) => `> ${$1}<strong>${$2}</strong>${$3} <`); + }, }; } diff --git a/app/assets/javascripts/gl_form.js b/app/assets/javascripts/gl_form.js index f5e2e46237f..5a6d44ef838 100644 --- a/app/assets/javascripts/gl_form.js +++ b/app/assets/javascripts/gl_form.js @@ -1,6 +1,6 @@ import $ from 'jquery'; import autosize from 'autosize'; -import GfmAutoComplete, * as GFMConfig from './gfm_auto_complete'; +import GfmAutoComplete, { defaultAutocompleteConfig } from 'ee_else_ce/gfm_auto_complete'; import dropzoneInput from './dropzone_input'; import { addMarkdownListeners, removeMarkdownListeners } from './lib/utils/text_markdown'; @@ -8,7 +8,7 @@ export default class GLForm { constructor(form, enableGFM = {}) { this.form = form; this.textarea = this.form.find('textarea.js-gfm-input'); - this.enableGFM = Object.assign({}, GFMConfig.defaultAutocompleteConfig, enableGFM); + this.enableGFM = Object.assign({}, defaultAutocompleteConfig, enableGFM); // Disable autocomplete for keywords which do not have dataSources available const dataSources = (gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources) || {}; Object.keys(this.enableGFM).forEach(item => { diff --git a/app/assets/javascripts/ide/constants.js b/app/assets/javascripts/ide/constants.js index 7c560c89695..e30670e119f 100644 --- a/app/assets/javascripts/ide/constants.js +++ b/app/assets/javascripts/ide/constants.js @@ -72,4 +72,11 @@ export const modalTypes = { tree: 'tree', }; +export const commitActionTypes = { + move: 'move', + delete: 'delete', + create: 'create', + update: 'update', +}; + export const packageJsonPath = 'package.json'; diff --git a/app/assets/javascripts/ide/ide_router.js b/app/assets/javascripts/ide/ide_router.js index 229ef168926..518a9cf7a0f 100644 --- a/app/assets/javascripts/ide/ide_router.js +++ b/app/assets/javascripts/ide/ide_router.js @@ -1,6 +1,6 @@ import Vue from 'vue'; import VueRouter from 'vue-router'; -import { join as joinPath } from 'path'; +import { joinPaths } from '~/lib/utils/url_utility'; import flash from '~/flash'; import store from './stores'; @@ -34,7 +34,7 @@ const EmptyRouterComponent = { const router = new VueRouter({ mode: 'history', - base: `${gon.relative_url_root}/-/ide/`, + base: joinPaths(gon.relative_url_root || '', '/-/ide/'), routes: [ { path: '/project/:namespace+/:project', @@ -46,11 +46,11 @@ const router = new VueRouter({ }, { path: ':targetmode(edit|tree|blob)/:branchid+/', - redirect: to => joinPath(to.path, '/-/'), + redirect: to => joinPaths(to.path, '/-/'), }, { path: ':targetmode(edit|tree|blob)', - redirect: to => joinPath(to.path, '/master/-/'), + redirect: to => joinPaths(to.path, '/master/-/'), }, { path: 'merge_requests/:mrid', @@ -58,7 +58,7 @@ const router = new VueRouter({ }, { path: '', - redirect: to => joinPath(to.path, '/edit/master/-/'), + redirect: to => joinPaths(to.path, '/edit/master/-/'), }, ], }, diff --git a/app/assets/javascripts/ide/stores/utils.js b/app/assets/javascripts/ide/stores/utils.js index 66c5180b782..bcc9ca60d9b 100644 --- a/app/assets/javascripts/ide/stores/utils.js +++ b/app/assets/javascripts/ide/stores/utils.js @@ -1,3 +1,5 @@ +import { commitActionTypes } from '../constants'; + export const dataStructure = () => ({ id: '', // Key will contain a mixture of ID and path @@ -114,14 +116,14 @@ export const setPageTitle = title => { export const commitActionForFile = file => { if (file.prevPath) { - return 'move'; + return commitActionTypes.move; } else if (file.deleted) { - return 'delete'; + return commitActionTypes.delete; } else if (file.tempFile) { - return 'create'; + return commitActionTypes.create; } - return 'update'; + return commitActionTypes.update; }; export const getCommitFiles = stagedFiles => diff --git a/app/assets/javascripts/issuable_form.js b/app/assets/javascripts/issuable_form.js index 9336b71cfd7..7576d36f27d 100644 --- a/app/assets/javascripts/issuable_form.js +++ b/app/assets/javascripts/issuable_form.js @@ -1,8 +1,8 @@ import $ from 'jquery'; import Pikaday from 'pikaday'; +import GfmAutoComplete from 'ee_else_ce/gfm_auto_complete'; import Autosave from './autosave'; import UsersSelect from './users_select'; -import GfmAutoComplete from './gfm_auto_complete'; import ZenMode from './zen_mode'; import AutoWidthDropdownSelect from './issuable/auto_width_dropdown_select'; import { parsePikadayDate, pikadayToString } from './lib/utils/datetime_utility'; diff --git a/app/assets/javascripts/lib/graphql.js b/app/assets/javascripts/lib/graphql.js index ae02559415c..498c2348ca2 100644 --- a/app/assets/javascripts/lib/graphql.js +++ b/app/assets/javascripts/lib/graphql.js @@ -3,10 +3,17 @@ import { InMemoryCache } from 'apollo-cache-inmemory'; import { createUploadLink } from 'apollo-upload-client'; import csrf from '~/lib/utils/csrf'; -export default (resolvers = {}) => - new ApolloClient({ +export default (resolvers = {}, baseUrl = '') => { + let uri = `${gon.relative_url_root}/api/graphql`; + + if (baseUrl) { + // Prepend baseUrl and ensure that `///` are replaced with `/` + uri = `${baseUrl}${uri}`.replace(/\/{3,}/g, '/'); + } + + return new ApolloClient({ link: createUploadLink({ - uri: `${gon.relative_url_root}/api/graphql`, + uri, headers: { [csrf.headerKey]: csrf.token, }, @@ -14,3 +21,4 @@ export default (resolvers = {}) => cache: new InMemoryCache(), resolvers, }); +}; diff --git a/app/assets/javascripts/lib/utils/url_utility.js b/app/assets/javascripts/lib/utils/url_utility.js index 4ba84589705..bdfd06fc250 100644 --- a/app/assets/javascripts/lib/utils/url_utility.js +++ b/app/assets/javascripts/lib/utils/url_utility.js @@ -120,3 +120,5 @@ export function webIDEUrl(route = undefined) { } return returnUrl; } + +export { join as joinPaths } from 'path'; diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue index f2bd4150b6d..00547abd7bc 100644 --- a/app/assets/javascripts/monitoring/components/dashboard.vue +++ b/app/assets/javascripts/monitoring/components/dashboard.vue @@ -221,6 +221,7 @@ export default { <gl-dropdown-item v-for="environment in store.environmentsData" :key="environment.id" + :href="environment.metrics_path" :active="environment.name === currentEnvironmentName" active-class="is-active" >{{ environment.name }}</gl-dropdown-item diff --git a/app/assets/javascripts/monitoring/monitoring_bundle.js b/app/assets/javascripts/monitoring/monitoring_bundle.js index ed794779ff2..08dc57d545c 100644 --- a/app/assets/javascripts/monitoring/monitoring_bundle.js +++ b/app/assets/javascripts/monitoring/monitoring_bundle.js @@ -1,6 +1,6 @@ import Vue from 'vue'; import { parseBoolean } from '~/lib/utils/common_utils'; -import Dashboard from './components/dashboard.vue'; +import Dashboard from 'ee_else_ce/monitoring/components/dashboard.vue'; export default (props = {}) => { const el = document.getElementById('prometheus-graphs'); diff --git a/app/assets/javascripts/mr_popover/index.js b/app/assets/javascripts/mr_popover/index.js index 9a97e98f9db..18c0e201300 100644 --- a/app/assets/javascripts/mr_popover/index.js +++ b/app/assets/javascripts/mr_popover/index.js @@ -22,13 +22,10 @@ const handleUserPopoverMouseOut = ({ target }) => { * Adds a MergeRequestPopover component to the body, hands over as much data as the target element has in data attributes. * loads based on data-project-path and data-iid more data about an MR from the API and sets it on the popover */ -const handleMRPopoverMount = apolloProvider => ({ target }) => { +const handleMRPopoverMount = ({ apolloProvider, projectPath, mrTitle, iid }) => ({ target }) => { // Add listener to actually remove it again target.addEventListener('mouseleave', handleUserPopoverMouseOut); - const { projectPath, mrTitle, iid } = target.dataset; - const mergeRequest = {}; - renderFn = setTimeout(() => { const MRPopoverComponent = Vue.extend(MRPopover); renderedPopover = new MRPopoverComponent({ @@ -36,7 +33,6 @@ const handleMRPopoverMount = apolloProvider => ({ target }) => { target, projectPath, mergeRequestIID: iid, - mergeRequest, mergeRequestTitle: mrTitle, }, apolloProvider, @@ -57,8 +53,13 @@ export default elements => { const listenerAddedAttr = 'data-mr-listener-added'; mrLinks.forEach(el => { - if (!el.getAttribute(listenerAddedAttr)) { - el.addEventListener('mouseenter', handleMRPopoverMount(apolloProvider)); + const { projectPath, mrTitle, iid } = el.dataset; + + if (!el.getAttribute(listenerAddedAttr) && projectPath && mrTitle && iid) { + el.addEventListener( + 'mouseenter', + handleMRPopoverMount({ apolloProvider, projectPath, mrTitle, iid }), + ); el.setAttribute(listenerAddedAttr, true); } }); diff --git a/app/assets/javascripts/pages/profiles/show/index.js b/app/assets/javascripts/pages/profiles/show/index.js index 0dd0d5336fc..e726ab0e220 100644 --- a/app/assets/javascripts/pages/profiles/show/index.js +++ b/app/assets/javascripts/pages/profiles/show/index.js @@ -1,6 +1,6 @@ import $ from 'jquery'; import createFlash from '~/flash'; -import GfmAutoComplete from '~/gfm_auto_complete'; +import GfmAutoComplete from 'ee_else_ce/gfm_auto_complete'; import emojiRegex from 'emoji-regex'; import EmojiMenu from './emoji_menu'; diff --git a/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue b/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue index 7f86741ed29..c0659a0173a 100644 --- a/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue +++ b/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue @@ -2,7 +2,7 @@ import $ from 'jquery'; import createFlash from '~/flash'; import Icon from '~/vue_shared/components/icon.vue'; -import GfmAutoComplete from '~/gfm_auto_complete'; +import GfmAutoComplete from 'ee_else_ce/gfm_auto_complete'; import { __, s__ } from '~/locale'; import Api from '~/api'; import { GlModal, GlTooltipDirective } from '@gitlab/ui'; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue index bb76eb1030d..851939d5d4e 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue @@ -3,6 +3,7 @@ import successSvg from 'icons/_icon_status_success.svg'; import warningSvg from 'icons/_icon_status_warning.svg'; import simplePoll from '~/lib/utils/simple_poll'; import { __ } from '~/locale'; +import readyToMergeMixin from 'ee_else_ce/vue_merge_request_widget/mixins/ready_to_merge'; import MergeRequest from '../../../merge_request'; import Flash from '../../../flash'; import statusIcon from '../mr_widget_status_icon.vue'; @@ -21,6 +22,7 @@ export default { CommitEdit, CommitMessageDropdown, }, + mixins: [readyToMergeMixin], props: { mr: { type: Object, required: true }, service: { type: Object, required: true }, @@ -94,15 +96,6 @@ export default { shouldShowMergeOptionsDropdown() { return this.mr.isPipelineActive && !this.mr.onlyAllowMergeIfPipelineSucceeds; }, - isMergeButtonDisabled() { - const { commitMessage } = this; - return Boolean( - !commitMessage.length || - !this.shouldShowMergeControls || - this.isMakingRequest || - this.mr.preventMerge, - ); - }, isRemoveSourceBranchButtonDisabled() { return this.isMergeButtonDisabled; }, diff --git a/app/assets/javascripts/vue_merge_request_widget/mixins/ready_to_merge.js b/app/assets/javascripts/vue_merge_request_widget/mixins/ready_to_merge.js new file mode 100644 index 00000000000..b2e64506472 --- /dev/null +++ b/app/assets/javascripts/vue_merge_request_widget/mixins/ready_to_merge.js @@ -0,0 +1,13 @@ +export default { + computed: { + isMergeButtonDisabled() { + const { commitMessage } = this; + return Boolean( + !commitMessage.length || + !this.shouldShowMergeControls || + this.isMakingRequest || + this.mr.preventMerge, + ); + }, + }, +}; diff --git a/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue b/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue index f9773622001..4dbfd1ba6f4 100644 --- a/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue +++ b/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue @@ -1,11 +1,13 @@ <script> import { GlPopover, GlSkeletonLoading } from '@gitlab/ui'; +import Icon from '~/vue_shared/components/icon.vue'; import UserAvatarImage from '../user_avatar/user_avatar_image.vue'; import { glEmojiTag } from '../../../emoji'; export default { name: 'UserPopover', components: { + Icon, GlPopover, GlSkeletonLoading, UserAvatarImage, @@ -68,16 +70,27 @@ export default { <gl-skeleton-loading v-else :lines="1" class="animation-container-small mb-1" /> </div> <div class="text-secondary"> - <div v-if="user.bio" class="js-bio">{{ user.bio }}</div> - <div v-if="user.organization" class="js-organization">{{ user.organization }}</div> + <div v-if="user.bio" class="js-bio d-flex mb-1"> + <icon name="profile" css-classes="category-icon" /> + <span class="ml-1">{{ user.bio }}</span> + </div> + <div v-if="user.organization" class="js-organization d-flex mb-1"> + <icon v-show="!jobInfoIsLoading" name="work" css-classes="category-icon" /> + <span class="ml-1">{{ user.organization }}</span> + </div> <gl-skeleton-loading v-if="jobInfoIsLoading" :lines="1" class="animation-container-small mb-1" /> </div> - <div class="text-secondary"> - {{ user.location }} + <div class="js-location text-secondary d-flex"> + <icon + v-show="!locationIsLoading && user.location" + name="location" + css-classes="category-icon" + /> + <span class="ml-1">{{ user.location }}</span> <gl-skeleton-loading v-if="locationIsLoading" :lines="1" diff --git a/app/assets/stylesheets/components/popover.scss b/app/assets/stylesheets/components/popover.scss index 7d46b262a69..838bf5d343b 100644 --- a/app/assets/stylesheets/components/popover.scss +++ b/app/assets/stylesheets/components/popover.scss @@ -5,6 +5,10 @@ padding: $gl-padding-8; font-size: $gl-font-size-small; line-height: $gl-line-height; + + .category-icon { + color: $gray-600; + } } } diff --git a/app/controllers/concerns/authenticates_with_two_factor.rb b/app/controllers/concerns/authenticates_with_two_factor.rb index d5c4712bd78..4926062f9ca 100644 --- a/app/controllers/concerns/authenticates_with_two_factor.rb +++ b/app/controllers/concerns/authenticates_with_two_factor.rb @@ -8,13 +8,6 @@ module AuthenticatesWithTwoFactor extend ActiveSupport::Concern - included do - # This action comes from DeviseController, but because we call `sign_in` - # manually, not skipping this action would cause a "You are already signed - # in." error message to be shown upon successful login. - skip_before_action :require_no_authentication, only: [:create], raise: false - end - # Store the user's ID in the session for later retrieval and render the # two factor code prompt # diff --git a/app/controllers/projects/settings/ci_cd_controller.rb b/app/controllers/projects/settings/ci_cd_controller.rb index d1c5cef76fa..c4dff95a4b9 100644 --- a/app/controllers/projects/settings/ci_cd_controller.rb +++ b/app/controllers/projects/settings/ci_cd_controller.rb @@ -19,7 +19,7 @@ module Projects redirect_to project_settings_ci_cd_path(@project) else - render 'show' + redirect_to project_settings_ci_cd_path(@project), alert: result[:message] end end end diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb index 88910c91763..fa5bdbc7d49 100644 --- a/app/controllers/projects/wikis_controller.rb +++ b/app/controllers/projects/wikis_controller.rb @@ -17,7 +17,7 @@ class Projects::WikisController < Projects::ApplicationController def pages @wiki_pages = Kaminari.paginate_array( - @project_wiki.pages(sort: params[:sort], direction: params[:direction]) + @project_wiki.list_pages(sort: params[:sort], direction: params[:direction]) ).page(params[:page]) @wiki_entries = WikiPage.group_by_directory(@wiki_pages) @@ -118,7 +118,7 @@ class Projects::WikisController < Projects::ApplicationController @sidebar_page = @project_wiki.find_sidebar(params[:version_id]) unless @sidebar_page # Fallback to default sidebar - @sidebar_wiki_entries = WikiPage.group_by_directory(@project_wiki.pages(limit: 15)) + @sidebar_wiki_entries = WikiPage.group_by_directory(@project_wiki.list_pages(limit: 15)) end rescue ProjectWiki::CouldNotCreateWikiError flash[:notice] = _("Could not create Wiki Repository at this time. Please try again later.") diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 6943795e8ac..6fea61cf45d 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -8,6 +8,8 @@ class SessionsController < Devise::SessionsController include Recaptcha::Verify skip_before_action :check_two_factor_requirement, only: [:destroy] + # replaced with :require_no_authentication_without_flash + skip_before_action :require_no_authentication, only: [:new, :create] prepend_before_action :check_initial_setup, only: [:new] prepend_before_action :authenticate_with_two_factor, @@ -15,6 +17,8 @@ class SessionsController < Devise::SessionsController prepend_before_action :check_captcha, only: [:create] prepend_before_action :store_redirect_uri, only: [:new] prepend_before_action :ldap_servers, only: [:new, :create] + prepend_before_action :require_no_authentication_without_flash, only: [:new, :create] + before_action :auto_sign_in_with_provider, only: [:new] before_action :load_recaptcha @@ -54,6 +58,14 @@ class SessionsController < Devise::SessionsController private + def require_no_authentication_without_flash + require_no_authentication + + if flash[:alert] == I18n.t('devise.failure.already_authenticated') + flash[:alert] = nil + end + end + def captcha_enabled? request.headers[CAPTCHA_HEADER] && Gitlab::Recaptcha.enabled? end diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index b7f693f723f..76300e791e6 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -242,6 +242,10 @@ module LabelsHelper klass.new(hash.slice(:color, :description, :title, :group_id, :project_id)) end + def issuable_types + ['issues', 'merge requests'] + end + # Required for Banzai::Filter::LabelReferenceFilter module_function :render_colored_label, :text_color_for_bg, :escape_once, :label_tooltip_title end diff --git a/app/helpers/markup_helper.rb b/app/helpers/markup_helper.rb index be4fc2531ae..ad77f99fe44 100644 --- a/app/helpers/markup_helper.rb +++ b/app/helpers/markup_helper.rb @@ -83,7 +83,8 @@ module MarkupHelper text = sanitize( text, tags: tags, - attributes: Rails::Html::WhiteListSanitizer.allowed_attributes + ['style', 'data-src', 'data-name', 'data-unicode-version'] + attributes: Rails::Html::WhiteListSanitizer.allowed_attributes + + %w(style data-src data-name data-unicode-version data-iid data-project-path data-mr-title) ) # since <img> tags are stripped, this can leave empty <a> tags hanging around diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index b8edaf82c3d..bbd21eb0e78 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -642,6 +642,7 @@ module Ci variables.append(key: 'CI_COMMIT_MESSAGE', value: git_commit_message.to_s) variables.append(key: 'CI_COMMIT_TITLE', value: git_commit_full_title.to_s) variables.append(key: 'CI_COMMIT_DESCRIPTION', value: git_commit_description.to_s) + variables.append(key: 'CI_COMMIT_REF_PROTECTED', value: (!!protected_ref?).to_s) if merge_request_event? && merge_request variables.append(key: 'CI_MERGE_REQUEST_SOURCE_BRANCH_SHA', value: source_sha.to_s) diff --git a/app/models/concerns/cache_markdown_field.rb b/app/models/concerns/cache_markdown_field.rb index 28ea51d6769..f90cd1ea690 100644 --- a/app/models/concerns/cache_markdown_field.rb +++ b/app/models/concerns/cache_markdown_field.rb @@ -15,7 +15,7 @@ module CacheMarkdownField # Increment this number every time the renderer changes its output CACHE_COMMONMARK_VERSION_START = 10 - CACHE_COMMONMARK_VERSION = 15 + CACHE_COMMONMARK_VERSION = 16 # changes to these attributes cause the cache to be invalidates INVALIDATED_BY = %w[author project].freeze diff --git a/app/models/internal_id.rb b/app/models/internal_id.rb index 401b94d36e5..237401899db 100644 --- a/app/models/internal_id.rb +++ b/app/models/internal_id.rb @@ -87,12 +87,16 @@ class InternalId < ApplicationRecord end def available? - @available_flag ||= ActiveRecord::Migrator.current_version >= REQUIRED_SCHEMA_VERSION # rubocop:disable Gitlab/PredicateMemoization + return true unless Rails.env.test? + + Gitlab::SafeRequestStore.fetch(:internal_ids_available_flag) do + ActiveRecord::Migrator.current_version >= REQUIRED_SCHEMA_VERSION + end end # Flushes cached information about schema def reset_column_information - @available_flag = nil + Gitlab::SafeRequestStore[:internal_ids_available_flag] = nil super end end diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb index 23ddd708396..c91add6439f 100644 --- a/app/models/project_wiki.rb +++ b/app/models/project_wiki.rb @@ -82,17 +82,25 @@ class ProjectWiki end def empty? - pages(limit: 1).empty? + list_pages(limit: 1).empty? end + # Lists wiki pages of the repository. + # + # limit - max number of pages returned by the method. + # sort - criterion by which the pages are sorted. + # direction - order of the sorted pages. + # load_content - option, which specifies whether the content inside the page + # will be loaded. + # # Returns an Array of GitLab WikiPage instances or an # empty Array if this Wiki has no pages. - def pages(limit: 0, sort: nil, direction: DIRECTION_ASC) - sort ||= TITLE_ORDER - direction_desc = direction == DIRECTION_DESC - - wiki.pages( - limit: limit, sort: sort, direction_desc: direction_desc + def list_pages(limit: 0, sort: nil, direction: DIRECTION_ASC, load_content: false) + wiki.list_pages( + limit: limit, + sort: sort, + direction_desc: direction == DIRECTION_DESC, + load_content: load_content ).map do |page| WikiPage.new(self, page, true) end diff --git a/app/services/test_hooks/project_service.rb b/app/services/test_hooks/project_service.rb index 6607f5b2418..a71278e8b8b 100644 --- a/app/services/test_hooks/project_service.rb +++ b/app/services/test_hooks/project_service.rb @@ -56,7 +56,7 @@ module TestHooks end def wiki_page_events_data - page = project.wiki.pages.first + page = project.wiki.list_pages(limit: 1).first if !project.wiki_enabled? || page.blank? throw(:validation_error, s_('TestHooks|Ensure the wiki is enabled and has pages.')) end diff --git a/app/views/groups/labels/index.html.haml b/app/views/groups/labels/index.html.haml index a9a73be4045..a8358704b03 100644 --- a/app/views/groups/labels/index.html.haml +++ b/app/views/groups/labels/index.html.haml @@ -1,7 +1,6 @@ - @no_container = true -- page_title "Labels" +- page_title 'Labels' - can_admin_label = can?(current_user, :admin_label, @group) -- issuables = ['issues', 'merge requests'] - search = params[:search] - subscribed = params[:subscribed] - labels_or_filters = @labels.exists? || search.present? || subscribed.present? @@ -14,7 +13,7 @@ .labels-container.prepend-top-5 - if @labels.any? .text-muted - = _('Labels can be applied to %{features}. Group labels are available for any project within the group.') % { features: issuables.to_sentence } + = _('Labels can be applied to %{features}. Group labels are available for any project within the group.') % { features: issuable_types.to_sentence } .other-labels %h5= _('Labels') %ul.content-list.manage-labels-list.js-other-labels diff --git a/app/views/projects/forks/error.html.haml b/app/views/projects/forks/error.html.haml index e8a89b8c6fc..b37dba8b35d 100644 --- a/app/views/projects/forks/error.html.haml +++ b/app/views/projects/forks/error.html.haml @@ -1,24 +1,20 @@ -- page_title "Fork project" +- page_title _("Fork project") - if @forked_project && !@forked_project.saved? .alert.alert-danger.alert-block %h4 = sprite_icon('fork', size: 16) - Fork Error! + = _("Fork Error!") %p - You tried to fork - = link_to_project @project - but it failed for the following reason: - + = _("You tried to fork %{link_to_the_project} but it failed for the following reason:").html_safe % { link_to_the_project: link_to_project(@project) } - if @forked_project && @forked_project.errors.any? %p – - error = @forked_project.errors.full_messages.first - if error.include?("already been taken") - Name has already been taken + = _("Name has already been taken") - else = error %p - = link_to new_project_fork_path(@project), title: "Fork", class: "btn" do - Try to fork again + = link_to _("Try to fork again"), new_project_fork_path(@project), title: _("Fork"), class: "btn" diff --git a/app/views/projects/forks/index.html.haml b/app/views/projects/forks/index.html.haml index c63c34c4ebb..0397a7034c7 100644 --- a/app/views/projects/forks/index.html.haml +++ b/app/views/projects/forks/index.html.haml @@ -5,12 +5,12 @@ .nav-controls = form_tag request.original_url, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f| - = search_field_tag :filter_projects, nil, placeholder: 'Search forks', class: 'projects-list-filter project-filter-form-field form-control input-short', + = search_field_tag :filter_projects, nil, placeholder: _('Search forks'), class: 'projects-list-filter project-filter-form-field form-control input-short', spellcheck: false, data: { 'filter-selector' => 'span.namespace-name' } .dropdown %button.dropdown-menu-toggle{ type: 'button', 'data-toggle' => 'dropdown' } - %span.light sort: + %span.light= _("sort:") - if @sort.present? = sort_options_hash[@sort] - else @@ -30,13 +30,12 @@ - if current_user && can?(current_user, :fork_project, @project) - if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2 - = link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork', class: 'btn btn-success' do + = link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: _('Go to your fork'), class: 'btn btn-success' do = sprite_icon('fork', size: 12) - %span Fork + %span= _('Fork') - else - = link_to new_project_fork_path(@project), title: "Fork project", class: 'btn btn-success' do + = link_to new_project_fork_path(@project), title: _("Fork project"), class: 'btn btn-success' do = sprite_icon('fork', size: 12) - %span Fork - + %span= _('Fork') = render 'projects', projects: @forks diff --git a/app/views/projects/forks/new.html.haml b/app/views/projects/forks/new.html.haml index a603b1024eb..bf03353a565 100644 --- a/app/views/projects/forks/new.html.haml +++ b/app/views/projects/forks/new.html.haml @@ -1,13 +1,11 @@ -- page_title "Fork project" +- page_title _("Fork project") .row.prepend-top-default .col-lg-3 %h4.prepend-top-0 - Fork project + = _("Fork project") %p - A fork is a copy of a project. - %br - Forking a repository allows you to make changes without affecting the original project. + = _("A fork is a copy of a project.<br />Forking a repository allows you to make changes without affecting the original project.").html_safe .col-lg-9 - if @namespaces.present? .fork-thumbnail-container.js-fork-content @@ -17,13 +15,13 @@ = render 'fork_button', namespace: namespace - else %strong - No available namespaces to fork the project. + = _("No available namespaces to fork the project.") %p.prepend-top-default - You must have permission to create a project in a namespace before forking. + = _("You must have permission to create a project in a namespace before forking.") .save-project-loader.hide.js-fork-content %h2.text-center = icon('spinner spin') - Forking repository + = _("Forking repository") %p.text-center - Please wait a moment, this page will automatically refresh when ready. + = _("Please wait a moment, this page will automatically refresh when ready.") diff --git a/changelogs/unreleased/54656-500-error-on-save-of-general-pipeline-settings-timeout.yml b/changelogs/unreleased/54656-500-error-on-save-of-general-pipeline-settings-timeout.yml new file mode 100644 index 00000000000..8b4f4894048 --- /dev/null +++ b/changelogs/unreleased/54656-500-error-on-save-of-general-pipeline-settings-timeout.yml @@ -0,0 +1,5 @@ +--- +title: Fix 500 in general pipeline settings when passing an invalid build timeout. +merge_request: 27416 +author: +type: fixed diff --git a/changelogs/unreleased/60387-use-icons-in-user-popovers.yml b/changelogs/unreleased/60387-use-icons-in-user-popovers.yml new file mode 100644 index 00000000000..100d33690b3 --- /dev/null +++ b/changelogs/unreleased/60387-use-icons-in-user-popovers.yml @@ -0,0 +1,5 @@ +--- +title: Show category icons in user popover +merge_request: +author: +type: added diff --git a/changelogs/unreleased/60540-merge-request-popover-is-not-working-on-the-to-do-page.yml b/changelogs/unreleased/60540-merge-request-popover-is-not-working-on-the-to-do-page.yml new file mode 100644 index 00000000000..7b5fae016bb --- /dev/null +++ b/changelogs/unreleased/60540-merge-request-popover-is-not-working-on-the-to-do-page.yml @@ -0,0 +1,5 @@ +--- +title: Fix MR popover on ToDos page +merge_request: 27382 +author: +type: fixed diff --git a/changelogs/unreleased/60552-period-dropdown.yml b/changelogs/unreleased/60552-period-dropdown.yml new file mode 100644 index 00000000000..e1b4a098ab0 --- /dev/null +++ b/changelogs/unreleased/60552-period-dropdown.yml @@ -0,0 +1,5 @@ +--- +title: Fix autocomplete dropdown for usernames starting with period +merge_request: 27533 +author: Jan Beckmann +type: fixed diff --git a/changelogs/unreleased/60687-enviro-dropdown.yml b/changelogs/unreleased/60687-enviro-dropdown.yml new file mode 100644 index 00000000000..1fc5a7dd6f5 --- /dev/null +++ b/changelogs/unreleased/60687-enviro-dropdown.yml @@ -0,0 +1,5 @@ +--- +title: Fix Metrics Environments dropdown +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/add-ci-variable-protected-ref.yml b/changelogs/unreleased/add-ci-variable-protected-ref.yml new file mode 100644 index 00000000000..150ddcc21ad --- /dev/null +++ b/changelogs/unreleased/add-ci-variable-protected-ref.yml @@ -0,0 +1,5 @@ +--- +title: Add CI_COMMIT_REF_PROTECTED CI variable +merge_request: 26716 +author: Jason van den Hurk +type: added diff --git a/changelogs/unreleased/autodevops_remote_private_helm_repository.yml b/changelogs/unreleased/autodevops_remote_private_helm_repository.yml new file mode 100644 index 00000000000..5341abb1095 --- /dev/null +++ b/changelogs/unreleased/autodevops_remote_private_helm_repository.yml @@ -0,0 +1,6 @@ +--- +title: Allow linking to a private helm repository by providing credentials, and customisation + of repository name +merge_request: 27123 +author: Stuart Moore @stjm-cc +type: added diff --git a/changelogs/unreleased/ce-remove-already-signed-in.yml b/changelogs/unreleased/ce-remove-already-signed-in.yml new file mode 100644 index 00000000000..70bed136ced --- /dev/null +++ b/changelogs/unreleased/ce-remove-already-signed-in.yml @@ -0,0 +1,5 @@ +--- +title: Remove "You are already signed in" banner +merge_request: 27377 +author: +type: other diff --git a/changelogs/unreleased/fix-api-ide-relative-url-root.yml b/changelogs/unreleased/fix-api-ide-relative-url-root.yml new file mode 100644 index 00000000000..8c058645f3e --- /dev/null +++ b/changelogs/unreleased/fix-api-ide-relative-url-root.yml @@ -0,0 +1,5 @@ +--- +title: Fix FE API and IDE handling of '/' relative_url_root +merge_request: 27635 +author: +type: fixed diff --git a/changelogs/unreleased/fj-53523-add-option-avoid-loading-wiki-page-content.yml b/changelogs/unreleased/fj-53523-add-option-avoid-loading-wiki-page-content.yml new file mode 100644 index 00000000000..49eaff52e5a --- /dev/null +++ b/changelogs/unreleased/fj-53523-add-option-avoid-loading-wiki-page-content.yml @@ -0,0 +1,5 @@ +--- +title: Added list_pages method to avoid loading all wiki pages content +merge_request: 22801 +author: +type: performance diff --git a/changelogs/unreleased/fj-60827-fix-web-strategy-error.yml b/changelogs/unreleased/fj-60827-fix-web-strategy-error.yml new file mode 100644 index 00000000000..ad707ca0225 --- /dev/null +++ b/changelogs/unreleased/fj-60827-fix-web-strategy-error.yml @@ -0,0 +1,5 @@ +--- +title: Fix bug when project export to remote url fails +merge_request: 27614 +author: +type: fixed diff --git a/changelogs/unreleased/jc-update-list-last-commits.yml b/changelogs/unreleased/jc-update-list-last-commits.yml new file mode 100644 index 00000000000..0e72c4255ae --- /dev/null +++ b/changelogs/unreleased/jc-update-list-last-commits.yml @@ -0,0 +1,5 @@ +--- +title: Client side changes for ListLastCommitsForTree response update +merge_request: 26880 +author: +type: fixed diff --git a/changelogs/unreleased/sh-disable-internal-ids-available-check.yml b/changelogs/unreleased/sh-disable-internal-ids-available-check.yml new file mode 100644 index 00000000000..069a9ba7d69 --- /dev/null +++ b/changelogs/unreleased/sh-disable-internal-ids-available-check.yml @@ -0,0 +1,5 @@ +--- +title: Always use internal ID tables in development and production +merge_request: 27544 +author: +type: fixed diff --git a/changelogs/unreleased/sh-upgrade-grpc-and-protobuf.yml b/changelogs/unreleased/sh-upgrade-grpc-and-protobuf.yml new file mode 100644 index 00000000000..a43a59a4f8a --- /dev/null +++ b/changelogs/unreleased/sh-upgrade-grpc-and-protobuf.yml @@ -0,0 +1,5 @@ +--- +title: Bump gRPC to 1.19.0 and protobuf to 3.7.1 +merge_request: 27086 +author: +type: other diff --git a/changelogs/unreleased/update-workhorse-master.yml b/changelogs/unreleased/update-workhorse-master.yml new file mode 100644 index 00000000000..97e2e891ab1 --- /dev/null +++ b/changelogs/unreleased/update-workhorse-master.yml @@ -0,0 +1,5 @@ +--- +title: Update Workhorse to v8.7.0 +merge_request: 27630 +author: +type: fixed diff --git a/danger/ce_ee_vue_templates/Dangerfile b/danger/ce_ee_vue_templates/Dangerfile new file mode 100644 index 00000000000..f7715eb2a89 --- /dev/null +++ b/danger/ce_ee_vue_templates/Dangerfile @@ -0,0 +1,56 @@ +# frozen_string_literal: true +require 'cgi' + +def get_vue_files_with_ce_and_ee_versions(files) + files.select do |file| + if file.end_with?('.vue') + counterpart_path = if file.start_with?('ee/') + file.delete_prefix('ee/') + else + "ee/#{file}" + end + + escaped_path = CGI.escape(counterpart_path) + api_endpoint = "https://gitlab.com/api/v4/projects/gitlab-org%2Fgitlab-ee/repository/files/#{escaped_path}?ref=master" + response = HTTParty.get(api_endpoint) # rubocop:disable Gitlab/HTTParty + response.code != 404 + else + false + end + end +end + +vue_candidates = get_vue_files_with_ce_and_ee_versions(helper.all_changed_files) + +return if vue_candidates.empty? + +message 'This merge request includes changes to Vue files that have both CE and EE versions.' + +markdown(<<~MARKDOWN) + ## Vue `<template>` in CE and EE + + Some Vue files in CE have a counterpart in EE. + (For example, `path/to/file.vue` and `ee/path/to/file.vue`.) + + When run in the context of CE, the `<template>` of the CE Vue file is used. + When run in the context of EE, the `<template>` of the EE Vue file is used. + + It's easy to accidentally make a change to a CE `<template>` that _should_ + appear in both CE and EE without making the change in both places. + When this happens, the change only takes effect in CE. + + The following Vue files were changed as part of this merge request that + include both a CE and EE version of the file: + + * #{vue_candidates.map { |path| "`#{path}`" }.join("\n* ")} + + If you made a change to the `<template>` of any of these Vue files that + should be visible in both CE and EE, please ensure you have made your + change to both versions of the file. + + ### A better alternative + + An even _better_ alternative is to refactor this component to only use + a single template for both CE and EE. More info on this approach here: + https://docs.gitlab.com/ee/development/ee_features.html#template-tag +MARKDOWN diff --git a/doc/ci/variables/predefined_variables.md b/doc/ci/variables/predefined_variables.md index 4c562138b98..40458137752 100644 --- a/doc/ci/variables/predefined_variables.md +++ b/doc/ci/variables/predefined_variables.md @@ -84,6 +84,7 @@ future GitLab releases.** | `CI_PROJECT_PATH_SLUG` | 9.3 | all | `$CI_PROJECT_PATH` lowercased and with everything except `0-9` and `a-z` replaced with `-`. Use in URLs and domain names. | | `CI_PROJECT_URL` | 8.10 | 0.5 | The HTTP(S) address to access project | | `CI_PROJECT_VISIBILITY` | 10.3 | all | The project visibility (internal, private, public) | +| `CI_COMMIT_REF_PROTECTED` | 11.11 | all | If the job is running on a protected branch | | `CI_REGISTRY` | 8.10 | 0.5 | If the Container Registry is enabled it returns the address of GitLab's Container Registry | | `CI_REGISTRY_IMAGE` | 8.10 | 0.5 | If the Container Registry is enabled for the project it returns the address of the registry tied to the specific project | | `CI_REGISTRY_PASSWORD` | 9.0 | all | The password to use to push containers to the GitLab Container Registry | diff --git a/doc/development/contributing/issue_workflow.md b/doc/development/contributing/issue_workflow.md index 827b3d7681a..0e1ab8663ed 100644 --- a/doc/development/contributing/issue_workflow.md +++ b/doc/development/contributing/issue_workflow.md @@ -340,13 +340,14 @@ addressed. In order to track things that can be improved in GitLab's codebase, we use the ~"technical debt" label in [GitLab's issue tracker][ce-tracker]. -For user experience improvements, we use the ~"UX debt" label. +For missed user experience requirements, we use the ~"UX debt" label. These labels should be added to issues that describe things that can be improved, shortcuts that have been taken, features that need additional attention, and all other things that have been left behind due to high velocity of development. For example, code that needs refactoring should use the ~"technical debt" label, -user experience refinements should use the ~"UX debt" label. +something that didn't ship according to our Design System guidelines should +use the ~"UX debt" label. Everyone can create an issue, though you may need to ask for adding a specific label, if you do not have permissions to do it by yourself. Additional labels diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md index 0ab9406c681..1c559b5263c 100644 --- a/doc/topics/autodevops/index.md +++ b/doc/topics/autodevops/index.md @@ -737,6 +737,9 @@ also be customized, and you can easily use a [custom buildpack](#custom-buildpac | `AUTO_DEVOPS_DOMAIN` | The [Auto DevOps domain](#auto-devops-base-domain). By default, set automatically by the [Auto DevOps setting](#enablingdisabling-auto-devops). This variable is deprecated and [is scheduled to be removed](https://gitlab.com/gitlab-org/gitlab-ce/issues/56959). Use `KUBE_INGRESS_BASE_DOMAIN` instead. | | `AUTO_DEVOPS_CHART` | The Helm Chart used to deploy your apps; defaults to the one [provided by GitLab](https://gitlab.com/gitlab-org/charts/auto-deploy-app). | | `AUTO_DEVOPS_CHART_REPOSITORY` | The Helm Chart repository used to search for charts; defaults to `https://charts.gitlab.io`. | +| `AUTO_DEVOPS_CHART_REPOSITORY_NAME` | From Gitlab 11.11, this variable can be used to set the name of the helm repository; defaults to "gitlab" | +| `AUTO_DEVOPS_CHART_REPOSITORY_USERNAME` | From Gitlab 11.11, this variable can be used to set a username to connect to the helm repository. Defaults to no credentials. (Also set AUTO_DEVOPS_CHART_REPOSITORY_PASSWORD) | +| `AUTO_DEVOPS_CHART_REPOSITORY_PASSWORD` | From Gitlab 11.11, this variable can be used to set a password to connect to the helm repository. Defaults to no credentials. (Also set AUTO_DEVOPS_CHART_REPOSITORY_USERNAME) | | `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 | diff --git a/doc/user/project/pipelines/job_artifacts.md b/doc/user/project/pipelines/job_artifacts.md index a3f40c20192..629b5e1fde4 100644 --- a/doc/user/project/pipelines/job_artifacts.md +++ b/doc/user/project/pipelines/job_artifacts.md @@ -55,7 +55,8 @@ For more examples on artifacts, follow the [artifacts reference in > **Note:** > With [GitLab 10.1][ce-14399], HTML files in a public project can be previewed > directly in a new tab without the need to download them when -> [GitLab Pages](../../../administration/pages/index.md) is enabled +> [GitLab Pages](../../../administration/pages/index.md) is enabled. +> The same holds for textual formats (currently supported extensions: `.txt`, `.json`, and `.log`). After a job finishes, if you visit the job's specific page, there are three buttons. You can download the artifacts archive or browse its contents, whereas diff --git a/doc/user/project/repository/index.md b/doc/user/project/repository/index.md index 718566a539f..97ecc4c0d65 100644 --- a/doc/user/project/repository/index.md +++ b/doc/user/project/repository/index.md @@ -97,7 +97,7 @@ Some things to note about precedence: > [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/2508) in GitLab 9.1 -[Jupyter][jupyter] Notebook (previously IPython Notebook) files are used for +[Jupyter](https://jupyter.org) Notebook (previously IPython Notebook) files are used for interactive computing in many fields and contain a complete record of the user's sessions and include code, narrative text, equations and rich output. @@ -220,14 +220,10 @@ Select branches to compare using the [branch filter search box](branches/index.m Find it under your project's **Repository > Compare**. -## Locked files +## Locked files **[PREMIUM]** -> Available in [GitLab Premium](https://about.gitlab.com/pricing/). - -Lock your files to prevent any conflicting changes. - -[File Locking](https://docs.gitlab.com/ee/user/project/file_lock.html) is available only in -[GitLab Premium](https://about.gitlab.com/pricing/). +Use [File Locking](https://docs.gitlab.com/ee/user/project/file_lock.html) to +lock your files to prevent any conflicting changes. ## Repository's API @@ -243,22 +239,19 @@ used for cloning your project. The button is only shown on macOS. ## Download Source Code -Source code stored in the repository can be downloaded. +> Support for directory download was [introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/24704) in GitLab 11.11. +The source code stored in a repository can be downloaded from the UI. By clicking the download icon, a dropdown will open with links to download the following: ![Download source code](img/download_source_code.png) -- **Source Code:** - This allows users to download the source code on branch they're currently - viewing. Available zip, tar, tar.gz and tar.bz2. +- **Source code:** + allows users to download the source code on branch they're currently + viewing. Available extensions: `zip`, `tar`, `tar.gz`, and `tar.bz2`. - **Directory:** - > [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/24704) in GitLab 11.10 - - Only shows up when viewing a sub-directory. This allows users to download - the specific directory they're currently viewing. Also available in zip, tar, - tar.gz and tar.bz2. + only shows up when viewing a sub-directory. This allows users to download + the specific directory they're currently viewing. Also available in `zip`, + `tar`, `tar.gz`, and `tar.bz2`. - **Artifacts:** - This allows users to download the artifacts of the latest CI build. - -[jupyter]: https://jupyter.org + allows users to download the artifacts of the latest CI build. diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 8a21d44b4bf..7e4539d0419 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -67,10 +67,6 @@ module API initial_current_user != current_user end - def user_namespace - @user_namespace ||= find_namespace!(params[:id]) - end - def user_group @group ||= find_group!(params[:id]) end diff --git a/lib/api/namespaces.rb b/lib/api/namespaces.rb index 3cc09f6ac3f..77ecb3e7cde 100644 --- a/lib/api/namespaces.rb +++ b/lib/api/namespaces.rb @@ -44,6 +44,8 @@ module API requires :id, type: String, desc: "Namespace's ID or path" end get ':id', requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do + user_namespace = find_namespace!(params[:id]) + present user_namespace, with: Entities::Namespace, current_user: current_user end end diff --git a/lib/api/wikis.rb b/lib/api/wikis.rb index 994074ddc67..5724adb2c40 100644 --- a/lib/api/wikis.rb +++ b/lib/api/wikis.rb @@ -33,7 +33,8 @@ module API authorize! :read_wiki, user_project entity = params[:with_content] ? Entities::WikiPage : Entities::WikiPageBasic - present user_project.wiki.pages, with: entity + + present user_project.wiki.list_pages(load_content: params[:with_content]), with: entity end desc 'Get a wiki page' do diff --git a/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml index d36576fe39f..9d99d04d263 100644 --- a/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml @@ -490,7 +490,7 @@ rollout 100%: fi helm init --client-only - helm repo add gitlab ${AUTO_DEVOPS_CHART_REPOSITORY:-https://charts.gitlab.io} + helm repo add ${AUTO_DEVOPS_CHART_REPOSITORY_NAME:-gitlab} ${AUTO_DEVOPS_CHART_REPOSITORY:-https://charts.gitlab.io} ${AUTO_DEVOPS_CHART_REPOSITORY_USERNAME:+"--username" "$AUTO_DEVOPS_CHART_REPOSITORY_USERNAME"} ${AUTO_DEVOPS_CHART_REPOSITORY_PASSWORD:+"--password" "$AUTO_DEVOPS_CHART_REPOSITORY_PASSWORD"} if [[ ! -d "$auto_chart" ]]; then helm fetch ${auto_chart} --untar fi diff --git a/lib/gitlab/git/wiki.rb b/lib/gitlab/git/wiki.rb index a0dd4a24363..c1bcd8e934a 100644 --- a/lib/gitlab/git/wiki.rb +++ b/lib/gitlab/git/wiki.rb @@ -86,9 +86,14 @@ module Gitlab end end - def pages(limit: 0, sort: nil, direction_desc: false) + def list_pages(limit: 0, sort: nil, direction_desc: false, load_content: false) wrapped_gitaly_errors do - gitaly_get_all_pages(limit: limit, sort: sort, direction_desc: direction_desc) + gitaly_list_pages( + limit: limit, + sort: sort, + direction_desc: direction_desc, + load_content: load_content + ) end end @@ -168,10 +173,17 @@ module Gitlab Gitlab::Git::WikiFile.new(wiki_file) end - def gitaly_get_all_pages(limit: 0, sort: nil, direction_desc: false) - gitaly_wiki_client.get_all_pages( - limit: limit, sort: sort, direction_desc: direction_desc - ).map do |wiki_page, version| + def gitaly_list_pages(limit: 0, sort: nil, direction_desc: false, load_content: false) + params = { limit: limit, sort: sort, direction_desc: direction_desc } + + gitaly_pages = + if load_content + gitaly_wiki_client.load_all_pages(params) + else + gitaly_wiki_client.list_all_pages(params) + end + + gitaly_pages.map do |wiki_page, version| Gitlab::Git::WikiPage.new(wiki_page, version) end end diff --git a/lib/gitlab/gitaly_client/commit_service.rb b/lib/gitlab/gitaly_client/commit_service.rb index 0d5debfcd01..2896b7e1ce0 100644 --- a/lib/gitlab/gitaly_client/commit_service.rb +++ b/lib/gitlab/gitaly_client/commit_service.rb @@ -174,7 +174,7 @@ module Gitlab response.each_with_object({}) do |gitaly_response, hsh| gitaly_response.commits.each do |commit_for_tree| - hsh[commit_for_tree.path] = Gitlab::Git::Commit.new(@repository, commit_for_tree.commit) + hsh[commit_for_tree.path_bytes] = Gitlab::Git::Commit.new(@repository, commit_for_tree.commit) end end end diff --git a/lib/gitlab/gitaly_client/wiki_service.rb b/lib/gitlab/gitaly_client/wiki_service.rb index e036cdcd800..ce9faad825c 100644 --- a/lib/gitlab/gitaly_client/wiki_service.rb +++ b/lib/gitlab/gitaly_client/wiki_service.rb @@ -87,7 +87,27 @@ module Gitlab wiki_page_from_iterator(response) end - def get_all_pages(limit: 0, sort: nil, direction_desc: false) + def list_all_pages(limit: 0, sort: nil, direction_desc: false) + sort_value = Gitaly::WikiListPagesRequest::SortBy.resolve(sort.to_s.upcase.to_sym) + + params = { repository: @gitaly_repo, limit: limit, direction_desc: direction_desc } + params[:sort] = sort_value if sort_value + + request = Gitaly::WikiListPagesRequest.new(params) + stream = GitalyClient.call(@repository.storage, :wiki_service, :wiki_list_pages, request, timeout: GitalyClient.medium_timeout) + stream.each_with_object([]) do |message, pages| + page = message.page + + next unless page + + wiki_page = GitalyClient::WikiPage.new(page.to_h) + version = new_wiki_page_version(page.version) + + pages << [wiki_page, version] + end + end + + def load_all_pages(limit: 0, sort: nil, direction_desc: false) sort_value = Gitaly::WikiGetAllPagesRequest::SortBy.resolve(sort.to_s.upcase.to_sym) params = { repository: @gitaly_repo, limit: limit, direction_desc: direction_desc } @@ -95,6 +115,7 @@ module Gitlab request = Gitaly::WikiGetAllPagesRequest.new(params) response = GitalyClient.call(@repository.storage, :wiki_service, :wiki_get_all_pages, request, timeout: GitalyClient.medium_timeout) + pages = [] loop do diff --git a/lib/gitlab/graphql/authorize/authorize_field_service.rb b/lib/gitlab/graphql/authorize/authorize_field_service.rb index 03d6aabb0e3..619ce100421 100644 --- a/lib/gitlab/graphql/authorize/authorize_field_service.rb +++ b/lib/gitlab/graphql/authorize/authorize_field_service.rb @@ -48,7 +48,7 @@ module Gitlab end def authorize_against(parent_typed_object, resolved_type) - if built_in_type? + if scalar_type? # The field is a built-in/scalar type, or a list of scalars # authorize using the parent's object parent_typed_object.object @@ -108,8 +108,8 @@ module Gitlab type.unwrap end - def built_in_type? - GraphQL::Schema::BUILT_IN_TYPES.has_value?(node_type_for_basic_connection(@field.type)) + def scalar_type? + node_type_for_basic_connection(@field.type).kind.scalar? end end end diff --git a/lib/gitlab/import_export/after_export_strategies/web_upload_strategy.rb b/lib/gitlab/import_export/after_export_strategies/web_upload_strategy.rb index fcf6a25ab00..acb7f225b17 100644 --- a/lib/gitlab/import_export/after_export_strategies/web_upload_strategy.rb +++ b/lib/gitlab/import_export/after_export_strategies/web_upload_strategy.rb @@ -30,10 +30,7 @@ module Gitlab def handle_response_error(response) unless response.success? - error_code = response.dig('Error', 'Code') || response.code - error_message = response.dig('Error', 'Message') || response.message - - raise StrategyError.new("Error uploading the project. Code #{error_code}: #{error_message}") + raise StrategyError.new("Error uploading the project. Code #{response.code}: #{response.message}") end end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 54c40e48084..06f2f848925 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -372,6 +372,9 @@ msgstr "" msgid "A deleted user" msgstr "" +msgid "A fork is a copy of a project.<br />Forking a repository allows you to make changes without affecting the original project." +msgstr "" + msgid "A member of GitLab's abuse team will review your report as soon as possible." msgstr "" @@ -4140,6 +4143,15 @@ msgstr "" msgid "For public projects, anyone can view pipelines and access job details (output logs and artifacts)" msgstr "" +msgid "Fork" +msgstr "" + +msgid "Fork Error!" +msgstr "" + +msgid "Fork project" +msgstr "" + msgid "ForkedFromProjectPath|Forked from" msgstr "" @@ -4149,6 +4161,9 @@ msgstr "" msgid "Forking in progress" msgstr "" +msgid "Forking repository" +msgstr "" + msgid "Forks" msgstr "" @@ -4311,6 +4326,9 @@ msgstr "" msgid "Go to project" msgstr "" +msgid "Go to your fork" +msgstr "" + msgid "Google Code import" msgstr "" @@ -5775,6 +5793,9 @@ msgstr "" msgid "Name" msgstr "" +msgid "Name has already been taken" +msgstr "" + msgid "Name new label" msgstr "" @@ -5927,6 +5948,9 @@ msgstr "" msgid "No activities found" msgstr "" +msgid "No available namespaces to fork the project." +msgstr "" + msgid "No branches found" msgstr "" @@ -7835,6 +7859,9 @@ msgstr "" msgid "Search for projects, issues, etc." msgstr "" +msgid "Search forks" +msgstr "" + msgid "Search groups" msgstr "" @@ -9709,6 +9736,9 @@ msgstr "" msgid "Try again?" msgstr "" +msgid "Try to fork again" +msgstr "" + msgid "Trying to communicate with your device. Plug it in (if you haven't already) and press the button on the device now." msgstr "" @@ -10533,6 +10563,9 @@ msgstr "" msgid "You must have maintainer access to force delete a lock" msgstr "" +msgid "You must have permission to create a project in a namespace before forking." +msgstr "" + msgid "You need permission." msgstr "" @@ -10548,6 +10581,9 @@ msgstr "" msgid "You need to upload a Google Takeout archive." msgstr "" +msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:" +msgstr "" + msgid "You will lose all changes you've made to this file. This action cannot be undone." msgstr "" @@ -11199,6 +11235,9 @@ msgstr "" msgid "sign in" msgstr "" +msgid "sort:" +msgstr "" + msgid "source" msgstr "" diff --git a/spec/controllers/projects/settings/ci_cd_controller_spec.rb b/spec/controllers/projects/settings/ci_cd_controller_spec.rb index fc9a0adeed2..db53e5bc8a4 100644 --- a/spec/controllers/projects/settings/ci_cd_controller_spec.rb +++ b/spec/controllers/projects/settings/ci_cd_controller_spec.rb @@ -191,6 +191,15 @@ describe Projects::Settings::CiCdController do expect(project.build_timeout).to eq(5400) end end + + context 'when build_timeout_human_readable is invalid' do + let(:params) { { build_timeout_human_readable: '5m' } } + + it 'set specified timeout' do + expect(subject).to set_flash[:alert] + expect(response).to redirect_to(namespace_project_settings_ci_cd_path) + end + end end end end diff --git a/spec/controllers/projects/wikis_controller_spec.rb b/spec/controllers/projects/wikis_controller_spec.rb index e0a6fc52ee9..f2e0b5e5c1d 100644 --- a/spec/controllers/projects/wikis_controller_spec.rb +++ b/spec/controllers/projects/wikis_controller_spec.rb @@ -19,6 +19,18 @@ describe Projects::WikisController do destroy_page(wiki_title) end + describe 'GET #pages' do + subject { get :pages, params: { namespace_id: project.namespace, project_id: project, id: wiki_title } } + + it 'does not load the pages content' do + expect(controller).to receive(:load_wiki).and_return(project_wiki) + + expect(project_wiki).to receive(:list_pages).twice.and_call_original + + subject + end + end + describe 'GET #show' do render_views @@ -28,9 +40,9 @@ describe Projects::WikisController do expect(controller).to receive(:load_wiki).and_return(project_wiki) # empty? call - expect(project_wiki).to receive(:pages).with(limit: 1).and_call_original + expect(project_wiki).to receive(:list_pages).with(limit: 1).and_call_original # Sidebar entries - expect(project_wiki).to receive(:pages).with(limit: 15).and_call_original + expect(project_wiki).to receive(:list_pages).with(limit: 15).and_call_original subject @@ -104,7 +116,7 @@ describe Projects::WikisController do subject - expect(response).to redirect_to(project_wiki_path(project, project_wiki.pages.first)) + expect(response).to redirect_to(project_wiki_path(project, project_wiki.list_pages.first)) end end @@ -138,7 +150,7 @@ describe Projects::WikisController do allow(controller).to receive(:valid_encoding?).and_return(false) subject - expect(response).to redirect_to(project_wiki_path(project, project_wiki.pages.first)) + expect(response).to redirect_to(project_wiki_path(project, project_wiki.list_pages.first)) end end @@ -148,7 +160,7 @@ describe Projects::WikisController do it 'updates the page' do subject - wiki_page = project_wiki.pages.first + wiki_page = project_wiki.list_pages(load_content: true).first expect(wiki_page.title).to eq new_title expect(wiki_page.content).to eq new_content diff --git a/spec/features/admin/admin_users_spec.rb b/spec/features/admin/admin_users_spec.rb index b1c6f308bc6..29545779a34 100644 --- a/spec/features/admin/admin_users_spec.rb +++ b/spec/features/admin/admin_users_spec.rb @@ -34,14 +34,11 @@ describe "Admin::Users" do expect(page).to have_button('Delete user and contributions') end - describe "view extra user information", :js do - it 'does not have the user popover open' do + describe "view extra user information" do + it 'shows the user popover on hover', :js, :quarantine do expect(page).not_to have_selector('#__BV_popover_1__') - end - it 'shows the user popover on hover' do first_user_link = page.first('.js-user-link') - first_user_link.hover expect(page).to have_selector('#__BV_popover_1__') diff --git a/spec/features/dashboard/todos/todos_spec.rb b/spec/features/dashboard/todos/todos_spec.rb index fd8677feab5..d58e3b2841e 100644 --- a/spec/features/dashboard/todos/todos_spec.rb +++ b/spec/features/dashboard/todos/todos_spec.rb @@ -17,6 +17,26 @@ describe 'Dashboard Todos' do end end + context 'when the todo references a merge request' do + let(:referenced_mr) { create(:merge_request, source_project: project) } + let(:note) { create(:note, project: project, note: "Check out #{referenced_mr.to_reference}") } + let!(:todo) { create(:todo, :mentioned, user: user, project: project, author: author, note: note) } + + before do + sign_in(user) + visit dashboard_todos_path + end + + it 'renders the mr link with the extra attributes' do + link = page.find_link(referenced_mr.to_reference) + + expect(link).not_to be_nil + expect(link['data-iid']).to eq(referenced_mr.iid.to_s) + expect(link['data-project-path']).to eq(referenced_mr.project.full_path) + expect(link['data-mr-title']).to eq(referenced_mr.title) + end + end + context 'User has a todo', :js do before do create(:todo, :mentioned, user: user, project: project, target: issue, author: author) diff --git a/spec/features/users/login_spec.rb b/spec/features/users/login_spec.rb index 9d5780d29b0..efba303033b 100644 --- a/spec/features/users/login_spec.rb +++ b/spec/features/users/login_spec.rb @@ -137,7 +137,7 @@ describe 'Login' do enter_code(user.current_otp) - expect(page).not_to have_content('You are already signed in.') + expect(page).not_to have_content(I18n.t('devise.failure.already_authenticated')) end context 'using one-time code' do @@ -317,7 +317,17 @@ describe 'Login' do gitlab_sign_in(user) expect(current_path).to eq root_path - expect(page).not_to have_content('You are already signed in.') + expect(page).not_to have_content(I18n.t('devise.failure.already_authenticated')) + end + + it 'does not show already signed in message when opening sign in page after login' do + expect(authentication_metrics) + .to increment(:user_authenticated_counter) + + gitlab_sign_in(user) + visit new_user_session_path + + expect(page).not_to have_content(I18n.t('devise.failure.already_authenticated')) end end @@ -579,7 +589,7 @@ describe 'Login' do click_button 'Accept terms' expect(current_path).to eq(root_path) - expect(page).not_to have_content('You are already signed in.') + expect(page).not_to have_content(I18n.t('devise.failure.already_authenticated')) end it 'does not ask for terms when the user already accepted them' do diff --git a/spec/frontend/gfm_auto_complete_spec.js b/spec/frontend/gfm_auto_complete_spec.js index 841aff0d7ff..3886853f3c1 100644 --- a/spec/frontend/gfm_auto_complete_spec.js +++ b/spec/frontend/gfm_auto_complete_spec.js @@ -1,7 +1,7 @@ /* eslint no-param-reassign: "off" */ import $ from 'jquery'; -import GfmAutoComplete from '~/gfm_auto_complete'; +import GfmAutoComplete from 'ee_else_ce/gfm_auto_complete'; import 'jquery.caret'; import 'at.js'; @@ -209,6 +209,38 @@ describe('GfmAutoComplete', () => { }); }); + describe('DefaultOptions.highlighter', () => { + beforeEach(() => { + atwhoInstance = { setting: {} }; + }); + + it('should return li if no query is given', () => { + const liTag = '<li></li>'; + + const highlightedTag = gfmAutoCompleteCallbacks.highlighter.call(atwhoInstance, liTag); + + expect(highlightedTag).toEqual(liTag); + }); + + it('should highlight search query in li element', () => { + const liTag = '<li><img src="" />string</li>'; + const query = 's'; + + const highlightedTag = gfmAutoCompleteCallbacks.highlighter.call(atwhoInstance, liTag, query); + + expect(highlightedTag).toEqual('<li><img src="" /> <strong>s</strong>tring </li>'); + }); + + it('should highlight search query with special char in li element', () => { + const liTag = '<li><img src="" />te.st</li>'; + const query = '.'; + + const highlightedTag = gfmAutoCompleteCallbacks.highlighter.call(atwhoInstance, liTag, query); + + expect(highlightedTag).toEqual('<li><img src="" /> te<strong>.</strong>st </li>'); + }); + }); + describe('isLoading', () => { it('should be true with loading data object item', () => { expect(GfmAutoComplete.isLoading({ name: 'loading' })).toBe(true); diff --git a/spec/frontend/mr_popover/index_spec.js b/spec/frontend/mr_popover/index_spec.js index 8c33e52a04b..b9db2342687 100644 --- a/spec/frontend/mr_popover/index_spec.js +++ b/spec/frontend/mr_popover/index_spec.js @@ -7,18 +7,28 @@ createDefaultClient.default = jest.fn(); describe('initMRPopovers', () => { let mr1; let mr2; + let mr3; beforeEach(() => { setHTMLFixture(` - <div id="one" class="gfm-merge_request">MR1</div> - <div id="two" class="gfm-merge_request">MR2</div> + <div id="one" class="gfm-merge_request" data-mr-title="title" data-iid="1" data-project-path="group/project"> + MR1 + </div> + <div id="two" class="gfm-merge_request" data-mr-title="title" data-iid="1" data-project-path="group/project"> + MR2 + </div> + <div id="three" class="gfm-merge_request"> + MR3 + </div> `); mr1 = document.querySelector('#one'); mr2 = document.querySelector('#two'); + mr3 = document.querySelector('#three'); mr1.addEventListener = jest.fn(); mr2.addEventListener = jest.fn(); + mr3.addEventListener = jest.fn(); }); it('does not add the same event listener twice', () => { @@ -27,4 +37,10 @@ describe('initMRPopovers', () => { expect(mr1.addEventListener).toHaveBeenCalledTimes(1); expect(mr2.addEventListener).toHaveBeenCalledTimes(1); }); + + it('does not add listener if it does not have the necessary data attributes', () => { + initMRPopovers([mr1, mr2, mr3]); + + expect(mr3.addEventListener).not.toHaveBeenCalled(); + }); }); diff --git a/spec/javascripts/api_spec.js b/spec/javascripts/api_spec.js index e537e0e8afc..494b3b934a8 100644 --- a/spec/javascripts/api_spec.js +++ b/spec/javascripts/api_spec.js @@ -4,7 +4,7 @@ import Api from '~/api'; describe('Api', () => { const dummyApiVersion = 'v3000'; - const dummyUrlRoot = 'http://host.invalid'; + const dummyUrlRoot = '/gitlab'; const dummyGon = { api_version: dummyApiVersion, relative_url_root: dummyUrlRoot, @@ -32,6 +32,18 @@ describe('Api', () => { expect(builtUrl).toEqual(expectedOutput); }); + + [null, '', '/'].forEach(root => { + it(`works when relative_url_root is ${root}`, () => { + window.gon.relative_url_root = root; + const input = '/api/:version/foo/bar'; + const expectedOutput = `/api/${dummyApiVersion}/foo/bar`; + + const builtUrl = Api.buildUrl(input); + + expect(builtUrl).toEqual(expectedOutput); + }); + }); }); describe('group', () => { diff --git a/spec/javascripts/ide/stores/modules/commit/actions_spec.js b/spec/javascripts/ide/stores/modules/commit/actions_spec.js index abc97f3072c..cdeb9b4b896 100644 --- a/spec/javascripts/ide/stores/modules/commit/actions_spec.js +++ b/spec/javascripts/ide/stores/modules/commit/actions_spec.js @@ -4,6 +4,7 @@ import service from '~/ide/services'; import router from '~/ide/ide_router'; import eventHub from '~/ide/eventhub'; import consts from '~/ide/stores/modules/commit/constants'; +import { commitActionTypes } from '~/ide/constants'; import { resetStore, file } from 'spec/ide/helpers'; describe('IDE commit module actions', () => { @@ -294,7 +295,7 @@ describe('IDE commit module actions', () => { commit_message: 'testing 123', actions: [ { - action: 'update', + action: commitActionTypes.update, file_path: jasmine.anything(), content: undefined, encoding: jasmine.anything(), @@ -321,7 +322,7 @@ describe('IDE commit module actions', () => { commit_message: 'testing 123', actions: [ { - action: 'update', + action: commitActionTypes.update, file_path: jasmine.anything(), content: undefined, encoding: jasmine.anything(), diff --git a/spec/javascripts/ide/stores/utils_spec.js b/spec/javascripts/ide/stores/utils_spec.js index c4f122efdda..debe1c4acee 100644 --- a/spec/javascripts/ide/stores/utils_spec.js +++ b/spec/javascripts/ide/stores/utils_spec.js @@ -1,4 +1,5 @@ import * as utils from '~/ide/stores/utils'; +import { commitActionTypes } from '~/ide/constants'; import { file } from '../helpers'; describe('Multi-file store utils', () => { @@ -107,7 +108,7 @@ describe('Multi-file store utils', () => { commit_message: 'commit message', actions: [ { - action: 'update', + action: commitActionTypes.update, file_path: 'staged', content: 'updated file content', encoding: 'text', @@ -115,7 +116,7 @@ describe('Multi-file store utils', () => { previous_path: undefined, }, { - action: 'create', + action: commitActionTypes.create, file_path: 'added', content: 'new file content', encoding: 'base64', @@ -123,7 +124,7 @@ describe('Multi-file store utils', () => { previous_path: undefined, }, { - action: 'delete', + action: commitActionTypes.delete, file_path: 'deletedFile', content: undefined, encoding: 'text', @@ -170,7 +171,7 @@ describe('Multi-file store utils', () => { commit_message: 'prebuilt test commit message', actions: [ { - action: 'update', + action: commitActionTypes.update, file_path: 'staged', content: 'updated file content', encoding: 'text', @@ -178,7 +179,7 @@ describe('Multi-file store utils', () => { previous_path: undefined, }, { - action: 'create', + action: commitActionTypes.create, file_path: 'added', content: 'new file content', encoding: 'base64', @@ -193,19 +194,19 @@ describe('Multi-file store utils', () => { describe('commitActionForFile', () => { it('returns deleted for deleted file', () => { - expect(utils.commitActionForFile({ deleted: true })).toBe('delete'); + expect(utils.commitActionForFile({ deleted: true })).toBe(commitActionTypes.delete); }); it('returns create for tempFile', () => { - expect(utils.commitActionForFile({ tempFile: true })).toBe('create'); + expect(utils.commitActionForFile({ tempFile: true })).toBe(commitActionTypes.create); }); it('returns move for moved file', () => { - expect(utils.commitActionForFile({ prevPath: 'test' })).toBe('move'); + expect(utils.commitActionForFile({ prevPath: 'test' })).toBe(commitActionTypes.move); }); it('returns update by default', () => { - expect(utils.commitActionForFile({})).toBe('update'); + expect(utils.commitActionForFile({})).toBe(commitActionTypes.update); }); }); diff --git a/spec/javascripts/monitoring/dashboard_spec.js b/spec/javascripts/monitoring/dashboard_spec.js index ce2c6c43c0f..16dc0084a10 100644 --- a/spec/javascripts/monitoring/dashboard_spec.js +++ b/spec/javascripts/monitoring/dashboard_spec.js @@ -175,7 +175,7 @@ describe('Dashboard', () => { setTimeout(() => { const dropdownItems = component.$el.querySelectorAll( - '.js-environments-dropdown .dropdown-item[active="true"]', + '.js-environments-dropdown .dropdown-item.is-active', ); expect(dropdownItems.length).toEqual(1); diff --git a/spec/javascripts/vue_shared/components/user_popover/user_popover_spec.js b/spec/javascripts/vue_shared/components/user_popover/user_popover_spec.js index 852558a83bc..c7e0d806d80 100644 --- a/spec/javascripts/vue_shared/components/user_popover/user_popover_spec.js +++ b/spec/javascripts/vue_shared/components/user_popover/user_popover_spec.js @@ -61,6 +61,12 @@ describe('User Popover Component', () => { expect(vm.$el.textContent).toContain(DEFAULT_PROPS.user.username); expect(vm.$el.textContent).toContain(DEFAULT_PROPS.user.location); }); + + it('shows icon for location', () => { + const iconEl = vm.$el.querySelector('.js-location svg'); + + expect(iconEl.querySelector('use').getAttribute('xlink:href')).toContain('location'); + }); }); describe('job data', () => { @@ -117,6 +123,18 @@ describe('User Popover Component', () => { 'Me & my <funky> Company', ); }); + + it('shows icon for bio', () => { + const iconEl = vm.$el.querySelector('.js-bio svg'); + + expect(iconEl.querySelector('use').getAttribute('xlink:href')).toContain('profile'); + }); + + it('shows icon for organization', () => { + const iconEl = vm.$el.querySelector('.js-organization svg'); + + expect(iconEl.querySelector('use').getAttribute('xlink:href')).toContain('work'); + }); }); describe('status data', () => { diff --git a/spec/lib/api/helpers_spec.rb b/spec/lib/api/helpers_spec.rb index 08165f147bb..00916f80784 100644 --- a/spec/lib/api/helpers_spec.rb +++ b/spec/lib/api/helpers_spec.rb @@ -137,18 +137,6 @@ describe API::Helpers do it_behaves_like 'user namespace finder' end - describe '#user_namespace' do - let(:namespace_finder) do - subject.user_namespace - end - - before do - allow(subject).to receive(:params).and_return({ id: namespace.id }) - end - - it_behaves_like 'user namespace finder' - end - describe '#send_git_blob' do let(:repository) { double } let(:blob) { double(name: 'foobar') } diff --git a/spec/lib/gitlab/git/blob_spec.rb b/spec/lib/gitlab/git/blob_spec.rb index 10bc82e24d1..1c24244c3a6 100644 --- a/spec/lib/gitlab/git/blob_spec.rb +++ b/spec/lib/gitlab/git/blob_spec.rb @@ -341,7 +341,7 @@ describe Gitlab::Git::Blob, :seed_helper do it { expect(blob.mode).to eq("100755") } end - context 'file with Chinese text' do + context 'file with Japanese text' do let(:blob) { Gitlab::Git::Blob.find(repository, SeedRepo::Commit::ID, "encoding/テスト.txt") } it { expect(blob.name).to eq("テスト.txt") } diff --git a/spec/lib/gitlab/git/wiki_spec.rb b/spec/lib/gitlab/git/wiki_spec.rb index ded5d7576df..1e577392949 100644 --- a/spec/lib/gitlab/git/wiki_spec.rb +++ b/spec/lib/gitlab/git/wiki_spec.rb @@ -21,13 +21,13 @@ describe Gitlab::Git::Wiki do end it 'returns all the pages' do - expect(subject.pages.count).to eq(2) - expect(subject.pages.first.title).to eq 'page1' - expect(subject.pages.last.title).to eq 'page2' + expect(subject.list_pages.count).to eq(2) + expect(subject.list_pages.first.title).to eq 'page1' + expect(subject.list_pages.last.title).to eq 'page2' end it 'returns only one page' do - pages = subject.pages(limit: 1) + pages = subject.list_pages(limit: 1) expect(pages.count).to eq(1) expect(pages.first.title).to eq 'page1' @@ -62,8 +62,8 @@ describe Gitlab::Git::Wiki do subject.delete_page('*', commit_details('whatever')) - expect(subject.pages.count).to eq 1 - expect(subject.pages.first.title).to eq 'page1' + expect(subject.list_pages.count).to eq 1 + expect(subject.list_pages.first.title).to eq 'page1' end end @@ -87,7 +87,7 @@ describe Gitlab::Git::Wiki do with_them do subject { wiki.preview_slug(title, format) } - let(:gitaly_slug) { wiki.pages.first } + let(:gitaly_slug) { wiki.list_pages.first } it { is_expected.to eq(expected_slug) } @@ -96,7 +96,7 @@ describe Gitlab::Git::Wiki do create_page(title, 'content', format: format) - gitaly_slug = wiki.pages.first.url_path + gitaly_slug = wiki.list_pages.first.url_path is_expected.to eq(gitaly_slug) end diff --git a/spec/lib/gitlab/gitaly_client/wiki_service_spec.rb b/spec/lib/gitlab/gitaly_client/wiki_service_spec.rb index d82c9c28da0..4fa8e97aca0 100644 --- a/spec/lib/gitlab/gitaly_client/wiki_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/wiki_service_spec.rb @@ -46,7 +46,7 @@ describe Gitlab::GitalyClient::WikiService do end end - describe '#get_all_pages' do + describe '#load_all_pages' do let(:page_2_info) { { title: 'My Page 2', raw_data: 'c', version: page_version } } let(:response) do [ @@ -63,7 +63,7 @@ describe Gitlab::GitalyClient::WikiService do let(:wiki_page_2) { subject[1].first } let(:wiki_page_2_version) { subject[1].last } - subject { client.get_all_pages } + subject { client.load_all_pages } it 'sends a wiki_get_all_pages message' do expect_any_instance_of(Gitaly::WikiService::Stub) @@ -99,7 +99,7 @@ describe Gitlab::GitalyClient::WikiService do end context 'with limits' do - subject { client.get_all_pages(limit: 1) } + subject { client.load_all_pages(limit: 1) } it 'sends a request with the limit' do expect_any_instance_of(Gitaly::WikiService::Stub) diff --git a/spec/lib/gitlab/graphql/authorize/authorize_field_service_spec.rb b/spec/lib/gitlab/graphql/authorize/authorize_field_service_spec.rb index 95a4eb296fb..aec9c4baf0a 100644 --- a/spec/lib/gitlab/graphql/authorize/authorize_field_service_spec.rb +++ b/spec/lib/gitlab/graphql/authorize/authorize_field_service_spec.rb @@ -45,7 +45,7 @@ describe Gitlab::Graphql::Authorize::AuthorizeFieldService do end end - context "when the field is a scalar type" do + context "when the field is a built-in scalar type" do let(:field) { type_with_field(GraphQL::STRING_TYPE, :read_field).fields["testField"].to_graphql } let(:expected_permissions) { [:read_field] } @@ -58,6 +58,20 @@ describe Gitlab::Graphql::Authorize::AuthorizeFieldService do it_behaves_like "checking permissions on the presented object" end + + context "when the field is sub-classed scalar type" do + let(:field) { type_with_field(Types::TimeType, :read_field).fields["testField"].to_graphql } + let(:expected_permissions) { [:read_field] } + + it_behaves_like "checking permissions on the presented object" + end + + context "when the field is a list of sub-classed scalar types" do + let(:field) { type_with_field([Types::TimeType], :read_field).fields["testField"].to_graphql } + let(:expected_permissions) { [:read_field] } + + it_behaves_like "checking permissions on the presented object" + end end context "when the field is a specific type" do diff --git a/spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb b/spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb index ec17ad8541f..7c4ac62790e 100644 --- a/spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb +++ b/spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb @@ -32,5 +32,17 @@ describe Gitlab::ImportExport::AfterExportStrategies::WebUploadStrategy do strategy.execute(user, project) end + + context 'when upload fails' do + it 'stores the export error' do + stub_request(:post, example_url).to_return(status: [404, 'Page not found']) + + strategy.execute(user, project) + + errors = project.import_export_shared.errors + expect(errors).not_to be_empty + expect(errors.first).to eq "Error uploading the project. Code 404: Page not found" + end + end end end diff --git a/spec/models/ci/bridge_spec.rb b/spec/models/ci/bridge_spec.rb index aacfbe3f180..44b5af5e5aa 100644 --- a/spec/models/ci/bridge_spec.rb +++ b/spec/models/ci/bridge_spec.rb @@ -33,7 +33,7 @@ describe Ci::Bridge do CI_PROJECT_ID CI_PROJECT_NAME CI_PROJECT_PATH CI_PROJECT_PATH_SLUG CI_PROJECT_NAMESPACE CI_PIPELINE_IID CI_CONFIG_PATH CI_PIPELINE_SOURCE CI_COMMIT_MESSAGE - CI_COMMIT_TITLE CI_COMMIT_DESCRIPTION + CI_COMMIT_TITLE CI_COMMIT_DESCRIPTION CI_COMMIT_REF_PROTECTED ] expect(bridge.scoped_variables_hash.keys).to include(*variables) diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 95b9bd4a4d9..3a7d20a58c8 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -2183,7 +2183,8 @@ describe Ci::Build do { key: 'CI_PIPELINE_SOURCE', value: pipeline.source, public: true, masked: false }, { key: 'CI_COMMIT_MESSAGE', value: pipeline.git_commit_message, public: true, masked: false }, { key: 'CI_COMMIT_TITLE', value: pipeline.git_commit_title, public: true, masked: false }, - { key: 'CI_COMMIT_DESCRIPTION', value: pipeline.git_commit_description, public: true, masked: false } + { key: 'CI_COMMIT_DESCRIPTION', value: pipeline.git_commit_description, public: true, masked: false }, + { key: 'CI_COMMIT_REF_PROTECTED', value: (!!pipeline.protected_ref?).to_s, public: true, masked: false } ] end diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index fd66e344d63..3c823b78be7 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -690,7 +690,8 @@ describe Ci::Pipeline, :mailer do CI_PIPELINE_SOURCE CI_COMMIT_MESSAGE CI_COMMIT_TITLE - CI_COMMIT_DESCRIPTION] + CI_COMMIT_DESCRIPTION + CI_COMMIT_REF_PROTECTED] end context 'when source is merge request' do diff --git a/spec/models/internal_id_spec.rb b/spec/models/internal_id_spec.rb index 0ed4e146caa..806b4f61bd8 100644 --- a/spec/models/internal_id_spec.rb +++ b/spec/models/internal_id_spec.rb @@ -93,7 +93,7 @@ describe InternalId do before do described_class.reset_column_information # Project factory will also call the current_version - expect(ActiveRecord::Migrator).to receive(:current_version).twice.and_return(InternalId::REQUIRED_SCHEMA_VERSION - 1) + expect(ActiveRecord::Migrator).to receive(:current_version).at_least(:once).and_return(InternalId::REQUIRED_SCHEMA_VERSION - 1) end let(:init) { double('block') } @@ -104,6 +104,15 @@ describe InternalId do expect(init).to receive(:call).with(issue).and_return(val) expect(subject).to eq(val + 1) end + + it 'always attempts to generate internal IDs in production mode' do + allow(Rails.env).to receive(:test?).and_return(false) + val = rand(1..100) + generator = double(generate: val) + expect(InternalId::InternalIdGenerator).to receive(:new).and_return(generator) + + expect(subject).to eq(val) + end end end diff --git a/spec/models/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb index 2525a6aebe0..d12dd97bb9e 100644 --- a/spec/models/project_wiki_spec.rb +++ b/spec/models/project_wiki_spec.rb @@ -109,8 +109,7 @@ describe ProjectWiki do subject { super().empty? } it { is_expected.to be_falsey } - # Re-enable this when https://gitlab.com/gitlab-org/gitaly/issues/1204 is fixed - xit 'only instantiates a Wiki page once' do + it 'only instantiates a Wiki page once' do expect(WikiPage).to receive(:new).once.and_call_original subject @@ -119,22 +118,65 @@ describe ProjectWiki do end end - describe "#pages" do + describe "#list_pages" do + let(:wiki_pages) { subject.list_pages } + before do - create_page("index", "This is an awesome new Gollum Wiki") - @pages = subject.pages + create_page("index", "This is an index") + create_page("index2", "This is an index2") + create_page("an index3", "This is an index3") end after do - destroy_page(@pages.first.page) + wiki_pages.each do |wiki_page| + destroy_page(wiki_page.page) + end end it "returns an array of WikiPage instances" do - expect(@pages.first).to be_a WikiPage + expect(wiki_pages.first).to be_a WikiPage + end + + it 'does not load WikiPage content by default' do + wiki_pages.each do |page| + expect(page.content).to be_empty + end + end + + it 'returns all pages by default' do + expect(wiki_pages.count).to eq(3) + end + + context "with limit option" do + it 'returns limited set of pages' do + expect(subject.list_pages(limit: 1).count).to eq(1) + end end - it "returns the correct number of pages" do - expect(@pages.count).to eq(1) + context "with sorting options" do + it 'returns pages sorted by title by default' do + pages = ['an index3', 'index', 'index2'] + + expect(subject.list_pages.map(&:title)).to eq(pages) + expect(subject.list_pages(direction: "desc").map(&:title)).to eq(pages.reverse) + end + + it 'returns pages sorted by created_at' do + pages = ['index', 'index2', 'an index3'] + + expect(subject.list_pages(sort: 'created_at').map(&:title)).to eq(pages) + expect(subject.list_pages(sort: 'created_at', direction: "desc").map(&:title)).to eq(pages.reverse) + end + end + + context "with load_content option" do + let(:pages) { subject.list_pages(load_content: true) } + + it 'loads WikiPage content' do + expect(pages.first.content).to eq("This is an index3") + expect(pages.second.content).to eq("This is an index") + expect(pages.third.content).to eq("This is an index2") + end end end @@ -144,7 +186,7 @@ describe ProjectWiki do end after do - subject.pages.each { |page| destroy_page(page.page) } + subject.list_pages.each { |page| destroy_page(page.page) } end it "returns the latest version of the page if it exists" do @@ -195,7 +237,7 @@ describe ProjectWiki do end after do - subject.pages.each { |page| destroy_page(page.page) } + subject.list_pages.each { |page| destroy_page(page.page) } end it 'finds the page defined as _sidebar' do @@ -242,12 +284,12 @@ describe ProjectWiki do describe "#create_page" do after do - destroy_page(subject.pages.first.page) + destroy_page(subject.list_pages.first.page) end it "creates a new wiki page" do expect(subject.create_page("test page", "this is content")).not_to eq(false) - expect(subject.pages.count).to eq(1) + expect(subject.list_pages.count).to eq(1) end it "returns false when a duplicate page exists" do @@ -262,7 +304,7 @@ describe ProjectWiki do it "sets the correct commit message" do subject.create_page("test page", "some content", :markdown, "commit message") - expect(subject.pages.first.page.version.message).to eq("commit message") + expect(subject.list_pages.first.page.version.message).to eq("commit message") end it 'sets the correct commit email' do @@ -293,7 +335,7 @@ describe ProjectWiki do format: :markdown, message: "updated page" ) - @page = subject.pages.first.page + @page = subject.list_pages(load_content: true).first.page end after do @@ -337,7 +379,7 @@ describe ProjectWiki do it "deletes the page" do subject.delete_page(@page) - expect(subject.pages.count).to eq(0) + expect(subject.list_pages.count).to eq(0) end it 'sets the correct commit email' do diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 3f5d285bc2c..4c354593b57 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -217,6 +217,25 @@ describe Repository do expect(result.size).to eq(0) end + + context 'with a commit with invalid UTF-8 path' do + def create_commit_with_invalid_utf8_path + rugged = rugged_repo(repository) + blob_id = Rugged::Blob.from_buffer(rugged, "some contents") + tree_builder = Rugged::Tree::Builder.new(rugged) + tree_builder.insert({ oid: blob_id, name: "hello\x80world", filemode: 0100644 }) + tree_id = tree_builder.write + user = { email: "jcai@gitlab.com", time: Time.now, name: "John Cai" } + + Rugged::Commit.create(rugged, message: 'some commit message', parents: [rugged.head.target.oid], tree: tree_id, committer: user, author: user) + end + + it 'does not raise an error' do + commit = create_commit_with_invalid_utf8_path + + expect { repository.list_last_commits_for_tree(commit, '.', offset: 0) }.not_to raise_error + end + end end describe '#last_commit_for_path' do diff --git a/spec/models/wiki_page_spec.rb b/spec/models/wiki_page_spec.rb index d5c85c11195..520a06e138e 100644 --- a/spec/models/wiki_page_spec.rb +++ b/spec/models/wiki_page_spec.rb @@ -44,47 +44,49 @@ describe WikiPage do WikiDirectory.new('dir_2', pages) end - context 'sort by title' do - let(:grouped_entries) { described_class.group_by_directory(wiki.pages) } - let(:expected_grouped_entries) { [dir_1_1, dir_1, page_dir_2, dir_2, page_1, page_6] } - - it 'returns an array with pages and directories' do - grouped_entries.each_with_index do |page_or_dir, i| - expected_page_or_dir = expected_grouped_entries[i] - expected_slugs = get_slugs(expected_page_or_dir) - slugs = get_slugs(page_or_dir) - - expect(slugs).to match_array(expected_slugs) + context "#list_pages" do + context 'sort by title' do + let(:grouped_entries) { described_class.group_by_directory(wiki.list_pages) } + let(:expected_grouped_entries) { [dir_1_1, dir_1, page_dir_2, dir_2, page_1, page_6] } + + it 'returns an array with pages and directories' do + grouped_entries.each_with_index do |page_or_dir, i| + expected_page_or_dir = expected_grouped_entries[i] + expected_slugs = get_slugs(expected_page_or_dir) + slugs = get_slugs(page_or_dir) + + expect(slugs).to match_array(expected_slugs) + end end end - end - context 'sort by created_at' do - let(:grouped_entries) { described_class.group_by_directory(wiki.pages(sort: 'created_at')) } - let(:expected_grouped_entries) { [dir_1_1, page_1, dir_1, page_dir_2, dir_2, page_6] } + context 'sort by created_at' do + let(:grouped_entries) { described_class.group_by_directory(wiki.list_pages(sort: 'created_at')) } + let(:expected_grouped_entries) { [dir_1_1, page_1, dir_1, page_dir_2, dir_2, page_6] } - it 'returns an array with pages and directories' do - grouped_entries.each_with_index do |page_or_dir, i| - expected_page_or_dir = expected_grouped_entries[i] - expected_slugs = get_slugs(expected_page_or_dir) - slugs = get_slugs(page_or_dir) + it 'returns an array with pages and directories' do + grouped_entries.each_with_index do |page_or_dir, i| + expected_page_or_dir = expected_grouped_entries[i] + expected_slugs = get_slugs(expected_page_or_dir) + slugs = get_slugs(page_or_dir) - expect(slugs).to match_array(expected_slugs) + expect(slugs).to match_array(expected_slugs) + end end end - end - it 'returns an array with retained order with directories at the top' do - expected_order = ['dir_1/dir_1_1/page_3', 'dir_1/page_2', 'dir_2', 'dir_2/page_4', 'dir_2/page_5', 'page_1', 'page_6'] + it 'returns an array with retained order with directories at the top' do + expected_order = ['dir_1/dir_1_1/page_3', 'dir_1/page_2', 'dir_2', 'dir_2/page_4', 'dir_2/page_5', 'page_1', 'page_6'] - grouped_entries = described_class.group_by_directory(wiki.pages) + grouped_entries = described_class.group_by_directory(wiki.list_pages) - actual_order = - grouped_entries.map do |page_or_dir| - get_slugs(page_or_dir) - end - .flatten - expect(actual_order).to eq(expected_order) + actual_order = + grouped_entries.map do |page_or_dir| + get_slugs(page_or_dir) + end + .flatten + expect(actual_order).to eq(expected_order) + end end end end @@ -386,7 +388,7 @@ describe WikiPage do it "deletes the page" do @page.delete - expect(wiki.pages).to be_empty + expect(wiki.list_pages).to be_empty end it "returns true" do diff --git a/spec/services/merge_requests/merge_to_ref_service_spec.rb b/spec/services/merge_requests/merge_to_ref_service_spec.rb index 24d09c1fd00..0ac23050caf 100644 --- a/spec/services/merge_requests/merge_to_ref_service_spec.rb +++ b/spec/services/merge_requests/merge_to_ref_service_spec.rb @@ -104,7 +104,7 @@ describe MergeRequests::MergeToRefService do it_behaves_like 'MergeService for target ref' end - context 'when merge commit with squash' do + context 'when merge commit with squash', :quarantine do before do merge_request.update!(squash: true, source_branch: 'master', target_branch: 'feature') end |