diff options
63 files changed, 678 insertions, 203 deletions
diff --git a/.gitlab/ci/frontend.gitlab-ci.yml b/.gitlab/ci/frontend.gitlab-ci.yml index 8df530c546d..076de55014e 100644 --- a/.gitlab/ci/frontend.gitlab-ci.yml +++ b/.gitlab/ci/frontend.gitlab-ci.yml @@ -1,3 +1,38 @@ +# Make sure to update all the similar conditions in other CI config files if you modify these conditions +.if-default: &if-default + if: '$CI_COMMIT_REF_NAME == "master" || $CI_COMMIT_REF_NAME =~ /^[\d-]+-stable(-ee)?$/ || $CI_COMMIT_REF_NAME =~ /^\d+-\d+-auto-deploy-\d+$/ || $CI_COMMIT_REF_NAME =~ /^security\// || $CI_MERGE_REQUEST_IID || $CI_COMMIT_TAG' + +# Make sure to update all the similar conditions in other CI config files if you modify these conditions +.if-default-ee: &if-default-ee + if: '($CI_COMMIT_REF_NAME == "master" || $CI_COMMIT_REF_NAME =~ /^[\d-]+-stable(-ee)?$/ || $CI_COMMIT_REF_NAME =~ /^\d+-\d+-auto-deploy-\d+$/ || $CI_COMMIT_REF_NAME =~ /^security\// || $CI_MERGE_REQUEST_IID || $CI_COMMIT_TAG) && $CI_PROJECT_NAME =~ /^gitlab(-ee)?$/' + +# Make sure to update all the similar conditions in other CI config files if you modify these conditions +.if-master: &if-master + if: '$CI_COMMIT_REF_NAME == "master"' + +# Make sure to update all the similar patterns in other CI config files if you modify these patterns +.code-backstage-patterns: &code-backstage-patterns + - ".gitlab/ci/**/*" + - ".{eslintignore,gitattributes,nvmrc,prettierrc,stylelintrc,yamllint}" + - ".{codeclimate,eslintrc,gitlab-ci,haml-lint,haml-lint_todo,rubocop,rubocop_todo,scss-lint}.yml" + - ".csscomb.json" + - "Dockerfile.assets" + - "*_VERSION" + - "Gemfile{,.lock}" + - "Rakefile" + - "{babel.config,jest.config}.js" + - "config.ru" + - "{package.json,yarn.lock}" + - "{,ee/}{app,bin,config,db,haml_lint,lib,locale,public,scripts,symbol,vendor}/**/*" + - "doc/api/graphql/reference/*" # Files in this folder are auto-generated + # Backstage changes + - "Dangerfile" + - "danger/**/*" + - "{,ee/}fixtures/**/*" + - "{,ee/}rubocop/**/*" + - "{,ee/}spec/**/*" + - "doc/README.md" # Some RSpec test rely on this file + .assets-compile-cache: cache: paths: @@ -132,7 +167,6 @@ compile-assets pull-cache foss: - .use-pg9 stage: test needs: ["setup-test-env", "compile-assets pull-cache"] - dependencies: ["setup-test-env", "compile-assets pull-cache"] .karma-base: extends: .only-code-frontend-job-base @@ -204,9 +238,10 @@ jest-foss: - .default-tags - .default-retry - .default-cache - - .default-only - - .only:changes-code-backstage stage: test + rules: + - <<: *if-master + when: on_success dependencies: [] cache: key: "$CI_JOB_NAME" @@ -237,11 +272,12 @@ webpack-dev-server: - .default-tags - .default-retry - .default-cache - - .default-only - - .only:changes-code-backstage stage: test + rules: + - <<: *if-default + changes: *code-backstage-patterns + when: on_success needs: ["setup-test-env", "compile-assets pull-cache"] - dependencies: ["setup-test-env", "compile-assets pull-cache"] variables: WEBPACK_MEMORY_TEST: "true" WEBPACK_VENDOR_DLL: "true" diff --git a/.gitlab/issue_templates/Coding style proposal.md b/.gitlab/issue_templates/Coding style proposal.md index a969c9b72ee..95f0fb5f366 100644 --- a/.gitlab/issue_templates/Coding style proposal.md +++ b/.gitlab/issue_templates/Coding style proposal.md @@ -5,7 +5,7 @@ Please describe the proposal and add a link to the source (for example, http://w --> - [ ] Mention the proposal in the next backend weekly call and the #backend channel to encourage contribution -- [ ] Proceed with the proposal once 50% of the maintainers have weighed in, and 80% of the votes are :+1: +- [ ] Proceed with the proposal once 50% of the maintainers have weighed in, and 80% of their votes are :+1: - [ ] Once approved, mention it again in the next backend weekly call and the #backend channel @@ -283,7 +283,7 @@ gem 'rack-proxy', '~> 0.6.0' gem 'sassc-rails', '~> 2.1.0' gem 'uglifier', '~> 2.7.2' -gem 'addressable', '~> 2.5.2' +gem 'addressable', '~> 2.7' gem 'font-awesome-rails', '~> 4.7' gem 'gemojione', '~> 3.3' gem 'gon', '~> 6.2' @@ -419,7 +419,7 @@ group :test do gem 'guard-rspec' end -gem 'octokit', '~> 4.9' +gem 'octokit', '~> 4.15' gem 'mail_room', '~> 0.10.0' diff --git a/Gemfile.lock b/Gemfile.lock index 13e1e4ac828..f55fb554c4a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -55,8 +55,8 @@ GEM adamantium (0.2.0) ice_nine (~> 0.11.0) memoizable (~> 0.4.0) - addressable (2.5.2) - public_suffix (>= 2.0.2, < 4.0) + addressable (2.7.0) + public_suffix (>= 2.0.2, < 5.0) aes_key_wrap (1.0.1) akismet (3.0.0) apollo_upload_server (2.0.0.beta.3) @@ -650,7 +650,8 @@ GEM multi_json (~> 1.3) multi_xml (~> 0.5) rack (>= 1.2, < 3) - octokit (4.9.0) + octokit (4.15.0) + faraday (>= 0.9) sawyer (~> 0.8.0, >= 0.5.3) omniauth (1.9.0) hashie (>= 3.4.6, < 3.7.0) @@ -762,7 +763,7 @@ GEM pry (~> 0.10) pry-rails (0.3.6) pry (>= 0.10.4) - public_suffix (3.1.1) + public_suffix (4.0.3) pyu-ruby-sasl (0.0.3.3) raabro (1.1.6) rack (2.0.7) @@ -961,9 +962,9 @@ GEM sprockets (> 3.0) sprockets-rails tilt - sawyer (0.8.1) - addressable (>= 2.3.5, < 2.6) - faraday (~> 0.8, < 1.0) + sawyer (0.8.2) + addressable (>= 2.3.5) + faraday (> 0.8, < 2.0) scss_lint (0.56.0) rake (>= 0.9, < 13) sass (~> 3.5.3) @@ -1130,7 +1131,7 @@ DEPENDENCIES acme-client (~> 2.0.2) activerecord-explain-analyze (~> 0.1) acts-as-taggable-on (~> 6.0) - addressable (~> 2.5.2) + addressable (~> 2.7) akismet (~> 3.0) apollo_upload_server (~> 2.0.0.beta3) asana (~> 0.9) @@ -1272,7 +1273,7 @@ DEPENDENCIES net-ssh (~> 5.2) nokogiri (~> 1.10.5) oauth2 (~> 1.4) - octokit (~> 4.9) + octokit (~> 4.15) omniauth (~> 1.8) omniauth-auth0 (~> 2.0.0) omniauth-authentiq (~> 0.3.3) diff --git a/app/assets/javascripts/repository/components/last_commit.vue b/app/assets/javascripts/repository/components/last_commit.vue index 70678b0db37..8e7529899c0 100644 --- a/app/assets/javascripts/repository/components/last_commit.vue +++ b/app/assets/javascripts/repository/components/last_commit.vue @@ -90,7 +90,7 @@ export default { <template> <div class="info-well d-none d-sm-flex project-last-commit commit p-3"> - <gl-loading-icon v-if="isLoading" size="md" class="m-auto" /> + <gl-loading-icon v-if="isLoading" size="md" color="dark" class="m-auto" /> <template v-else> <user-avatar-link v-if="commit.author" diff --git a/app/assets/javascripts/repository/components/preview/index.vue b/app/assets/javascripts/repository/components/preview/index.vue index 6b3822151ff..2bc93c3f1c1 100644 --- a/app/assets/javascripts/repository/components/preview/index.vue +++ b/app/assets/javascripts/repository/components/preview/index.vue @@ -44,7 +44,7 @@ export default { </div> </div> <div class="blob-viewer"> - <gl-loading-icon v-if="loading > 0" size="md" class="my-4 mx-auto" /> + <gl-loading-icon v-if="loading > 0" size="md" color="dark" class="my-4 mx-auto" /> <div v-else-if="readme" v-html="readme.html"></div> </div> </article> diff --git a/app/assets/javascripts/repository/components/table/index.vue b/app/assets/javascripts/repository/components/table/index.vue index 8f2e9264bca..29a3340b83d 100644 --- a/app/assets/javascripts/repository/components/table/index.vue +++ b/app/assets/javascripts/repository/components/table/index.vue @@ -34,6 +34,11 @@ export default { type: Boolean, required: true, }, + loadingPath: { + type: String, + required: false, + default: '', + }, }, data() { return { @@ -69,7 +74,12 @@ export default { <table :aria-label="tableCaption" class="table tree-table qa-file-tree" aria-live="polite"> <table-header v-once /> <tbody> - <parent-row v-show="showParentRow" :commit-ref="ref" :path="path" /> + <parent-row + v-show="showParentRow" + :commit-ref="ref" + :path="path" + :loading-path="loadingPath" + /> <template v-for="val in entries"> <table-row v-for="entry in val" @@ -84,6 +94,7 @@ export default { :url="entry.webUrl" :submodule-tree-url="entry.treeUrl" :lfs-oid="entry.lfsOid" + :loading-path="loadingPath" /> </template> <template v-if="isLoading"> diff --git a/app/assets/javascripts/repository/components/table/parent_row.vue b/app/assets/javascripts/repository/components/table/parent_row.vue index 3c39f404226..70a188f98cc 100644 --- a/app/assets/javascripts/repository/components/table/parent_row.vue +++ b/app/assets/javascripts/repository/components/table/parent_row.vue @@ -1,5 +1,10 @@ <script> +import { GlLoadingIcon } from '@gitlab/ui'; + export default { + components: { + GlLoadingIcon, + }, props: { commitRef: { type: String, @@ -9,13 +14,21 @@ export default { type: String, required: true, }, + loadingPath: { + type: String, + required: false, + default: null, + }, }, computed: { - parentRoute() { + parentPath() { const splitArray = this.path.split('/'); splitArray.pop(); - return { path: `/tree/${this.commitRef}/${splitArray.join('/')}` }; + return splitArray.join('/'); + }, + parentRoute() { + return { path: `/tree/${this.commitRef}/${this.parentPath}` }; }, }, methods: { @@ -29,7 +42,13 @@ export default { <template> <tr class="tree-item"> <td colspan="3" class="tree-item-file-name" @click.self="clickRow"> - <router-link :to="parentRoute" :aria-label="__('Go to parent')"> + <gl-loading-icon + v-if="parentPath === loadingPath" + size="sm" + inline + class="d-inline-block align-text-bottom" + /> + <router-link v-else :to="parentRoute" :aria-label="__('Go to parent')"> .. </router-link> </td> diff --git a/app/assets/javascripts/repository/components/table/row.vue b/app/assets/javascripts/repository/components/table/row.vue index cf0457a2abf..a8e13241c37 100644 --- a/app/assets/javascripts/repository/components/table/row.vue +++ b/app/assets/javascripts/repository/components/table/row.vue @@ -1,5 +1,5 @@ <script> -import { GlBadge, GlLink, GlSkeletonLoading, GlTooltipDirective } from '@gitlab/ui'; +import { GlBadge, GlLink, GlSkeletonLoading, GlTooltipDirective, GlLoadingIcon } from '@gitlab/ui'; import { visitUrl } from '~/lib/utils/url_utility'; import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import Icon from '~/vue_shared/components/icon.vue'; @@ -12,6 +12,7 @@ export default { GlBadge, GlLink, GlSkeletonLoading, + GlLoadingIcon, TimeagoTooltip, Icon, }, @@ -76,6 +77,11 @@ export default { required: false, default: null, }, + loadingPath: { + type: String, + required: false, + default: '', + }, }, data() { return { @@ -125,7 +131,13 @@ export default { <template> <tr :class="`file_${id}`" class="tree-item" @click="openRow"> <td class="tree-item-file-name"> - <i :aria-label="type" role="img" :class="iconName" class="fa fa-fw"></i> + <gl-loading-icon + v-if="path === loadingPath" + size="sm" + inline + class="d-inline-block align-text-bottom fa-fw" + /> + <i v-else :aria-label="type" role="img" :class="iconName" class="fa fa-fw"></i> <component :is="linkComponent" :to="routerLinkTo" :href="url" class="str-truncated"> {{ fullPath }} </component> diff --git a/app/assets/javascripts/repository/components/tree_content.vue b/app/assets/javascripts/repository/components/tree_content.vue index 949e653fc8f..c30d6f05c6a 100644 --- a/app/assets/javascripts/repository/components/tree_content.vue +++ b/app/assets/javascripts/repository/components/tree_content.vue @@ -27,6 +27,11 @@ export default { required: false, default: '/', }, + loadingPath: { + type: String, + required: false, + default: '', + }, }, data() { return { @@ -109,7 +114,12 @@ export default { <template> <div> - <file-table :path="path" :entries="entries" :is-loading="isLoadingFiles" /> + <file-table + :path="path" + :entries="entries" + :is-loading="isLoadingFiles" + :loading-path="loadingPath" + /> <file-preview v-if="readme" :blob="readme" /> </div> </template> diff --git a/app/assets/javascripts/repository/mixins/preload.js b/app/assets/javascripts/repository/mixins/preload.js new file mode 100644 index 00000000000..e68996245a8 --- /dev/null +++ b/app/assets/javascripts/repository/mixins/preload.js @@ -0,0 +1,36 @@ +import getFiles from '../queries/getFiles.query.graphql'; +import getRefMixin from './get_ref'; +import getProjectPath from '../queries/getProjectPath.query.graphql'; + +export default { + mixins: [getRefMixin], + apollo: { + projectPath: { + query: getProjectPath, + }, + }, + data() { + return { projectPath: '', loadingPath: null }; + }, + beforeRouteUpdate(to, from, next) { + this.preload(to.params.pathMatch, next); + }, + methods: { + preload(path, next) { + this.loadingPath = path.replace(/^\//, ''); + + return this.$apollo + .query({ + query: getFiles, + variables: { + projectPath: this.projectPath, + ref: this.ref, + path: this.loadingPath, + nextPageCursor: '', + pageSize: 100, + }, + }) + .then(() => next()); + }, + }, +}; diff --git a/app/assets/javascripts/repository/pages/tree.vue b/app/assets/javascripts/repository/pages/tree.vue index dd4d437f4dd..adc332fa370 100644 --- a/app/assets/javascripts/repository/pages/tree.vue +++ b/app/assets/javascripts/repository/pages/tree.vue @@ -1,11 +1,13 @@ <script> import TreeContent from '../components/tree_content.vue'; import { updateElementsVisibility } from '../utils/dom'; +import preloadMixin from '../mixins/preload'; export default { components: { TreeContent, }, + mixins: [preloadMixin], props: { path: { type: String, @@ -34,5 +36,5 @@ export default { </script> <template> - <tree-content :path="path" /> + <tree-content :path="path" :loading-path="loadingPath" /> </template> diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb index fb06299676c..cfc0925d9e1 100644 --- a/app/controllers/projects/wikis_controller.rb +++ b/app/controllers/projects/wikis_controller.rb @@ -39,6 +39,10 @@ class Projects::WikisController < Projects::ApplicationController if @page set_encoding_error unless valid_encoding? + # Assign vars expected by MarkupHelper + @ref = params[:version_id] + @path = @page.path + render 'show' elsif file_blob send_blob(@project_wiki.repository, file_blob) diff --git a/app/helpers/markup_helper.rb b/app/helpers/markup_helper.rb index e1e756c2f4c..657e5accdab 100644 --- a/app/helpers/markup_helper.rb +++ b/app/helpers/markup_helper.rb @@ -132,6 +132,7 @@ module MarkupHelper pipeline: :wiki, project: @project, project_wiki: @project_wiki, + repository: @project_wiki.repository, page_slug: wiki_page.slug, issuable_state_filter_enabled: true ) diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 5ad3a9b4431..663389050d1 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -26,7 +26,7 @@ module Ci belongs_to :merge_request, class_name: 'MergeRequest' belongs_to :external_pull_request - has_internal_id :iid, scope: :project, presence: false, ensure_if: -> { !importing? }, init: ->(s) do + has_internal_id :iid, scope: :project, presence: false, track_if: -> { !importing? }, ensure_if: -> { !importing? }, init: ->(s) do s&.project&.all_pipelines&.maximum(:iid) || s&.project&.all_pipelines&.count end diff --git a/app/models/concerns/atomic_internal_id.rb b/app/models/concerns/atomic_internal_id.rb index 64df265dc25..3e9b084e784 100644 --- a/app/models/concerns/atomic_internal_id.rb +++ b/app/models/concerns/atomic_internal_id.rb @@ -27,13 +27,13 @@ module AtomicInternalId extend ActiveSupport::Concern class_methods do - def has_internal_id(column, scope:, init:, ensure_if: nil, presence: true) # rubocop:disable Naming/PredicateName + def has_internal_id(column, scope:, init:, ensure_if: nil, track_if: nil, presence: true) # rubocop:disable Naming/PredicateName # We require init here to retain the ability to recalculate in the absence of a - # InternaLId record (we may delete records in `internal_ids` for example). + # InternalId record (we may delete records in `internal_ids` for example). raise "has_internal_id requires a init block, none given." unless init raise "has_internal_id needs to be defined on association." unless self.reflect_on_association(scope) - before_validation :"track_#{scope}_#{column}!", on: :create + before_validation :"track_#{scope}_#{column}!", on: :create, if: track_if before_validation :"ensure_#{scope}_#{column}!", on: :create, if: ensure_if validates column, presence: presence diff --git a/app/models/deployment.rb b/app/models/deployment.rb index 5a22a6ada9d..74cc7f93580 100644 --- a/app/models/deployment.rb +++ b/app/models/deployment.rb @@ -5,6 +5,7 @@ class Deployment < ApplicationRecord include IidRoutes include AfterCommitQueue include UpdatedAtFilterable + include Importable include Gitlab::Utils::StrongMemoize belongs_to :project, required: true @@ -17,7 +18,7 @@ class Deployment < ApplicationRecord has_many :merge_requests, through: :deployment_merge_requests - has_internal_id :iid, scope: :project, init: ->(s) do + has_internal_id :iid, scope: :project, track_if: -> { !importing? }, init: ->(s) do Deployment.where(project: s.project).maximum(:iid) if s&.project end diff --git a/app/models/issue.rb b/app/models/issue.rb index da6450c6092..bf600278162 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -31,7 +31,7 @@ class Issue < ApplicationRecord belongs_to :duplicated_to, class_name: 'Issue' belongs_to :closed_by, class_name: 'User' - has_internal_id :iid, scope: :project, init: ->(s) { s&.project&.issues&.maximum(:iid) } + has_internal_id :iid, scope: :project, track_if: -> { !importing? }, init: ->(s) { s&.project&.issues&.maximum(:iid) } has_many :issue_milestones has_many :milestones, through: :issue_milestones @@ -78,8 +78,8 @@ class Issue < ApplicationRecord ignore_column :state, remove_with: '12.7', remove_after: '2019-12-22' - after_commit :expire_etag_cache - after_save :ensure_metrics, unless: :imported? + after_commit :expire_etag_cache, unless: :importing? + after_save :ensure_metrics, unless: :importing? attr_spammable :title, spam_title: true attr_spammable :description, spam_description: true diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 61f10620e39..6be14abe744 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -31,7 +31,7 @@ class MergeRequest < ApplicationRecord belongs_to :source_project, class_name: "Project" belongs_to :merge_user, class_name: "User" - has_internal_id :iid, scope: :target_project, init: ->(s) { s&.target_project&.merge_requests&.maximum(:iid) } + has_internal_id :iid, scope: :target_project, track_if: -> { !importing? }, init: ->(s) { s&.target_project&.merge_requests&.maximum(:iid) } has_many :merge_request_diffs @@ -97,8 +97,8 @@ class MergeRequest < ApplicationRecord after_create :ensure_merge_request_diff after_update :clear_memoized_shas after_update :reload_diff_if_branch_changed - after_save :ensure_metrics - after_commit :expire_etag_cache + after_save :ensure_metrics, unless: :importing? + after_commit :expire_etag_cache, unless: :importing? # When this attribute is true some MR validation is ignored # It allows us to close or modify broken merge requests diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index 71a344e69e3..fa633a1a725 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -138,7 +138,7 @@ 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_commit :set_as_latest_diff + after_create_commit :set_as_latest_diff, unless: :importing? after_save :update_external_diff_store, if: -> { !importing? && saved_change_to_external_diff? } diff --git a/app/models/milestone.rb b/app/models/milestone.rb index 920c28aeceb..5da92fc4bc5 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -17,6 +17,7 @@ class Milestone < ApplicationRecord include StripAttribute include Milestoneish include FromUnion + include Importable include Gitlab::SQL::Pattern prepend_if_ee('::EE::Milestone') # rubocop: disable Cop/InjectEnterpriseEditionModule @@ -30,8 +31,8 @@ class Milestone < ApplicationRecord has_many :milestone_releases has_many :releases, through: :milestone_releases - has_internal_id :iid, scope: :project, init: ->(s) { s&.project&.milestones&.maximum(:iid) } - has_internal_id :iid, scope: :group, init: ->(s) { s&.group&.milestones&.maximum(:iid) } + has_internal_id :iid, scope: :project, track_if: -> { !importing? }, init: ->(s) { s&.project&.milestones&.maximum(:iid) } + has_internal_id :iid, scope: :group, track_if: -> { !importing? }, init: ->(s) { s&.group&.milestones&.maximum(:iid) } has_many :issues has_many :labels, -> { distinct.reorder('labels.title') }, through: :issues diff --git a/app/models/release.rb b/app/models/release.rb index 823fd61eebd..ecfae554fe0 100644 --- a/app/models/release.rb +++ b/app/models/release.rb @@ -3,6 +3,7 @@ class Release < ApplicationRecord include Presentable include CacheMarkdownField + include Importable include Gitlab::Utils::StrongMemoize cache_markdown_field :description @@ -33,8 +34,8 @@ class Release < ApplicationRecord delegate :repository, to: :project - after_commit :create_evidence!, on: :create - after_commit :notify_new_release, on: :create + after_commit :create_evidence!, on: :create, unless: :importing? + after_commit :notify_new_release, on: :create, unless: :importing? MAX_NUMBER_TO_DISPLAY = 3 diff --git a/app/models/user.rb b/app/models/user.rb index b9efbcdf502..6442e74bbe3 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -20,6 +20,7 @@ class User < ApplicationRecord include WithUploads include OptionallySearch include FromUnion + include BatchDestroyDependentAssociations DEFAULT_NOTIFICATION_LEVEL = :participating diff --git a/app/services/users/destroy_service.rb b/app/services/users/destroy_service.rb index e341c7f0537..643ebdc6839 100644 --- a/app/services/users/destroy_service.rb +++ b/app/services/users/destroy_service.rb @@ -56,6 +56,13 @@ module Users MigrateToGhostUserService.new(user).execute unless options[:hard_delete] + if Feature.enabled?(:destroy_user_associations_in_batches) + # Rails attempts to load all related records into memory before + # destroying: https://github.com/rails/rails/issues/22510 + # This ensures we delete records in batches. + user.destroy_dependent_associations_in_batches + end + # Destroy the namespace after destroying the user since certain methods may depend on the namespace existing user_data = user.destroy namespace.destroy diff --git a/changelogs/unreleased/195625-wiki-support-for-asciidoc-include-directive.yml b/changelogs/unreleased/195625-wiki-support-for-asciidoc-include-directive.yml new file mode 100644 index 00000000000..2e9c65f35af --- /dev/null +++ b/changelogs/unreleased/195625-wiki-support-for-asciidoc-include-directive.yml @@ -0,0 +1,5 @@ +--- +title: Fix error in Wiki when rendering the AsciiDoc include directive +merge_request: 22565 +author: +type: fixed diff --git a/changelogs/unreleased/196172-issue-create-iid-conflict.yml b/changelogs/unreleased/196172-issue-create-iid-conflict.yml new file mode 100644 index 00000000000..bb0ea686f91 --- /dev/null +++ b/changelogs/unreleased/196172-issue-create-iid-conflict.yml @@ -0,0 +1,6 @@ +--- +title: 'Fix Issue API: creating with manual IID returns conflict when IID already + in use' +merge_request: 22788 +author: Mara Sophie Grosch +type: fixed diff --git a/changelogs/unreleased/add_comment_on_event_enabled_to_services_api.yml b/changelogs/unreleased/add_comment_on_event_enabled_to_services_api.yml new file mode 100644 index 00000000000..77acad7af37 --- /dev/null +++ b/changelogs/unreleased/add_comment_on_event_enabled_to_services_api.yml @@ -0,0 +1,5 @@ +--- +title: Add comment_on_event_enabled to services API +merge_request: +author: +type: added diff --git a/changelogs/unreleased/mark-some-as-not-required-during-import.yml b/changelogs/unreleased/mark-some-as-not-required-during-import.yml new file mode 100644 index 00000000000..1d8449728ba --- /dev/null +++ b/changelogs/unreleased/mark-some-as-not-required-during-import.yml @@ -0,0 +1,5 @@ +--- +title: Add `importing?` to disable some callbacks +merge_request: +author: +type: performance diff --git a/changelogs/unreleased/sh-update-octokit.yml b/changelogs/unreleased/sh-update-octokit.yml new file mode 100644 index 00000000000..d57a03e03c7 --- /dev/null +++ b/changelogs/unreleased/sh-update-octokit.yml @@ -0,0 +1,5 @@ +--- +title: Upgrade octokit and its dependencies +merge_request: 22946 +author: +type: other diff --git a/changelogs/unreleased/standardize-timestamp-format-in-app-logs.yml b/changelogs/unreleased/standardize-timestamp-format-in-app-logs.yml new file mode 100644 index 00000000000..c4966c2e77f --- /dev/null +++ b/changelogs/unreleased/standardize-timestamp-format-in-app-logs.yml @@ -0,0 +1,5 @@ +--- +title: 'Use IS08601.3 format for app level logging of timestamps' +merge_request: 22793 +author: +type: other diff --git a/doc/api/services.md b/doc/api/services.md index 2b41b532f1d..f11cf7fd1b1 100644 --- a/doc/api/services.md +++ b/doc/api/services.md @@ -32,7 +32,8 @@ Example response: "confidential_note_events": true, "pipeline_events": true, "wiki_page_events": true, - "job_events": true + "job_events": true, + "comment_on_event_enabled": true } { "id": 76, @@ -50,7 +51,8 @@ Example response: "confidential_note_events": true, "pipeline_events": true, "wiki_page_events": true, - "job_events": true + "job_events": true, + "comment_on_event_enabled": true } ] ``` @@ -723,6 +725,7 @@ Parameters: | `jira_issue_transition_id` | string | no | The ID of a transition that moves issues to a closed state. You can find this number under the Jira workflow administration (**Administration > Issues > Workflows**) by selecting **View** under **Operations** of the desired workflow of your project. The ID of each state can be found inside the parenthesis of each transition name under the **Transitions (id)** column. By default, this ID is set to `2`. | | `commit_events` | boolean | false | Enable notifications for commit events | | `merge_requests_events` | boolean | false | Enable notifications for merge request events | +| `comment_on_event_enabled` | boolean | false | Enable comments inside Jira issues on each GitLab event (commit / merge request) | ### Delete Jira service @@ -761,6 +764,7 @@ Example response: "note_events": true, "job_events": true, "pipeline_events": true, + "comment_on_event_enabled": false, "properties": { "token": "<your_access_token>" } diff --git a/doc/development/logging.md b/doc/development/logging.md index 5d6b2d535b8..ea099de21d5 100644 --- a/doc/development/logging.md +++ b/doc/development/logging.md @@ -191,7 +191,7 @@ Here are some examples of how messages would be handled by both the loggers. FancyMultiLogger.info("Information") # UnstructuredLogger -I, [2020-01-13T12:02:41.566219 #6652] INFO -- : Information +I, [2020-01-13T18:48:49.201Z #5647] INFO -- : Information # StructuredLogger {:severity=>"INFO", :time=>"2020-01-13T11:02:41.559Z", :correlation_id=>"b1701f7ecc4be4bcd4c2d123b214e65a", :message=>"Information"} @@ -203,7 +203,7 @@ I, [2020-01-13T12:02:41.566219 #6652] INFO -- : Information FancyMultiLogger.info({:message=>"This is my message", :project_id=>123}) # UnstructuredLogger -I, [2020-01-13T12:06:09.856766 #8049] INFO -- : {:message=>"This is my message", :project_id=>123} +I, [2020-01-13T19:01:17.091Z #11056] INFO -- : {"message"=>"Message", "project_id"=>"123"} # StructuredLogger {:severity=>"INFO", :time=>"2020-01-13T11:06:09.851Z", :correlation_id=>"d7e0886f096db9a8526a4f89da0e45f6", :message=>"This is my message", :project_id=>123} diff --git a/doc/install/requirements.md b/doc/install/requirements.md index 7c5730f9b07..9bc1658d59c 100644 --- a/doc/install/requirements.md +++ b/doc/install/requirements.md @@ -38,14 +38,40 @@ GitLab is developed for Linux-based operating systems. It does **not** run on Microsoft Windows, and we have no plans to support it in the near future. For the latest development status view this [issue](https://gitlab.com/gitlab-org/gitlab/issues/22337). Please consider using a virtual machine to run GitLab. -## Ruby versions +## Software requirements -GitLab requires Ruby (MRI) 2.6. Support for Ruby versions below 2.6 (2.4, 2.5) will stop with GitLab 12.2. +### Ruby versions -You will have to use the standard MRI implementation of Ruby. -We love [JRuby](https://www.jruby.org/) and [Rubinius](https://rubinius.com) but GitLab +GitLab requires Ruby (MRI) 2.6. Beginning in GitLab 12.2, we no longer support Ruby 2.5 and lower. + +You must use the standard MRI implementation of Ruby. +We love [JRuby](https://www.jruby.org/) and [Rubinius](https://rubinius.com), but GitLab needs several Gems that have native extensions. +### Go versions + +The minimum required Go version is 1.12. + +### Git versions + +GitLab 11.11 and higher only supports Git 2.21.x and newer, and +[dropped support for older versions](https://gitlab.com/gitlab-org/gitlab-foss/issues/54255). + +### Node.js versions + +Beginning in GitLab 11.8, we only support Node.js 8.10.0 or higher, and dropped +support for Node.js 6. + +We recommend Node 12.x, as it is faster. + +GitLab uses [webpack](https://webpack.js.org/) to compile frontend assets, which requires a minimum +version of Node.js 8.10.0. + +You can check which version you are running with `node -v`. If you are running +a version older than `v8.10.0`, you need to update to a newer version. You +can find instructions to install from community maintained packages or compile +from source at the [Node.js website](https://nodejs.org/en/download). + ## Hardware requirements ### Storage diff --git a/doc/update/upgrading_from_source.md b/doc/update/upgrading_from_source.md index aeb99261c4b..48f8052cbb8 100644 --- a/doc/update/upgrading_from_source.md +++ b/doc/update/upgrading_from_source.md @@ -82,24 +82,15 @@ Install Bundler: sudo gem install bundler --no-document --version '< 2' ``` -### 4. Update Node +### 4. Update Node.js -NOTE: Beginning in GitLab 11.8, we only support node 8 or higher, and dropped -support for node 6. Be sure to upgrade if necessary. +NOTE: To check the minimum required Node.js version, see [Node.js versions](../install/requirements.md#nodejs-versions). -GitLab utilizes [webpack](https://webpack.js.org/) to compile frontend assets. -This requires a minimum version of node v8.10.0. - -You can check which version you are running with `node -v`. If you are running -a version older than `v8.10.0` you will need to update to a newer version. You -can find instructions to install from community maintained packages or compile -from source at the nodejs.org website. - -<https://nodejs.org/en/download/> - -GitLab also requires the use of yarn `>= v1.10.0` to manage JavaScript +GitLab also requires the use of Yarn `>= v1.10.0` to manage JavaScript dependencies. +In Debian or Ubuntu: + ```bash curl --silent --show-error https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add - echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list @@ -107,34 +98,33 @@ sudo apt-get update sudo apt-get install yarn ``` -More information can be found on the [yarn website](https://yarnpkg.com/en/docs/install). +More information can be found on the [Yarn website](https://yarnpkg.com/en/docs/install). ### 5. Update Go -NOTE: GitLab 11.4 and higher only supports Go 1.10.x and newer, and dropped support for Go -1.9.x. Be sure to upgrade your installation if necessary. +NOTE: To check the minimum required Go version, see [Go versions](../install/requirements.md#go-versions). You can check which version you are running with `go version`. -Download and install Go: +Download and install Go (for Linux, 64-bit): ```bash # Remove former Go installation folder sudo rm -rf /usr/local/go -curl --remote-name --progress https://dl.google.com/go/go1.11.10.linux-amd64.tar.gz -echo 'aefaa228b68641e266d1f23f1d95dba33f17552ba132878b65bb798ffa37e6d0 go1.11.10.linux-amd64.tar.gz' | shasum -a256 -c - && \ - sudo tar -C /usr/local -xzf go1.11.10.linux-amd64.tar.gz +curl --remote-name --progress https://dl.google.com/go/go1.13.5.linux-amd64.tar.gz +echo '512103d7ad296467814a6e3f635631bd35574cab3369a97a323c9a585ccaa569 go1.13.5.linux-amd64.tar.gz' | shasum -a256 -c - && \ + sudo tar -C /usr/local -xzf go1.13.5.linux-amd64.tar.gz sudo ln -sf /usr/local/go/bin/{go,godoc,gofmt} /usr/local/bin/ -rm go1.11.10.linux-amd64.tar.gz +rm go1.13.5.linux-amd64.tar.gz + ``` ### 6. Update Git -NOTE: **Note:** -GitLab 11.11 and higher only supports Git 2.21.x and newer, and -[dropped support for older versions](https://gitlab.com/gitlab-org/gitlab-foss/issues/54255). -Be sure to upgrade your installation if necessary. +NOTE: To check the minimum required Git version, see [Git versions](../install/requirements.md#git-versions). + +In Debian or Ubuntu: ```bash # Make sure Git is version 2.21.0 or higher @@ -254,9 +244,8 @@ sudo -u git -H make #### New configuration options for `gitlab.yml` -There might be configuration options available for [`gitlab.yml`][yaml]. View -them with the command below and apply them manually to your current -`gitlab.yml`: +There might be configuration options available for [`gitlab.yml`](https://gitlab.com/gitlab-org/gitlab/blob/master/config/gitlab.yml.example)). +View them with the command below and apply them manually to your current `gitlab.yml`: ```sh cd /home/git/gitlab @@ -282,7 +271,7 @@ If you are using Strict-Transport-Security in your installation to continue using it you must enable it in your NGINX configuration as GitLab application no longer handles setting it. -If you are using Apache instead of NGINX please see the updated [Apache templates]. +If you are using Apache instead of NGINX see the updated [Apache templates](https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/web-server/apache). Also note that because Apache does not support upstreams behind Unix sockets you will need to let GitLab Workhorse listen on a TCP port. You can do this via [`/etc/default/gitlab`](https://gitlab.com/gitlab-org/gitlab/blob/master/lib/support/init.d/gitlab.default.example#L38). @@ -296,13 +285,13 @@ add the following line to `config/initializers/smtp_settings.rb`: ActionMailer::Base.delivery_method = :smtp ``` -See [smtp_settings.rb.sample] as an example. +See [smtp_settings.rb.sample](https://gitlab.com/gitlab-org/gitlab/blob/master/config/initializers/smtp_settings.rb.sample#L13) as an example. #### Init script There might be new configuration options available for -[`gitlab.default.example`][gl-example]. View them with the command below and -apply them manually to your current `/etc/default/gitlab`: +[`gitlab.default.example`](https://gitlab.com/gitlab-org/gitlab/blob/master/lib/support/init.d/gitlab.default.example). +View them with the command below and apply them manually to your current `/etc/default/gitlab`: ```sh cd /home/git/gitlab @@ -389,17 +378,19 @@ Example: Additional instructions here. --> -## Things went south? Revert to previous version +## Troubleshooting ### 1. Revert the code to the previous version -To revert to a previous version, you'll need to following the upgrading guides -for the previous version. If you upgraded to 11.8 and want to revert back to -11.7, you'll need to follow the guides for upgrading from 11.6 to 11.7. You can +To revert to a previous version, you need to follow the upgrading guides +for the previous version. + +For example, if you have upgraded to GitLab 12.6 and want to revert back to +12.5, you need to follow the guides for upgrading from 12.4 to 12.5. You can use the version dropdown at the top of the page to select the right version. -When reverting, you should _not_ follow the database migration guides, as the -backup is already migrated to the previous version. +When reverting, you should **not** follow the database migration guides, as the +backup has already been migrated to the previous version. ### 2. Restore from the backup @@ -409,9 +400,4 @@ cd /home/git/gitlab sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production ``` -If you have more than one backup `*.tar` file(s) please add `BACKUP=timestamp_of_backup` to the command above. - -[yaml]: https://gitlab.com/gitlab-org/gitlab/blob/master/config/gitlab.yml.example -[gl-example]: https://gitlab.com/gitlab-org/gitlab/blob/master/lib/support/init.d/gitlab.default.example -[smtp_settings.rb.sample]: https://gitlab.com/gitlab-org/gitlab/blob/master/config/initializers/smtp_settings.rb.sample#L13 -[Apache templates]: https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/web-server/apache +If you have more than one backup `*.tar` file, add `BACKUP=timestamp_of_backup` to the above. diff --git a/doc/user/clusters/applications.md b/doc/user/clusters/applications.md index f2be9b2f3ac..f498645466f 100644 --- a/doc/user/clusters/applications.md +++ b/doc/user/clusters/applications.md @@ -464,6 +464,11 @@ chart is used to install this application with a [`values.yaml`](https://gitlab.com/gitlab-org/gitlab/blob/master/vendor/elastic_stack/values.yaml) file. +NOTE: **Note:** +The chart will deploy 4 Elasticsearch nodes: 2 masters, 1 data and 1 client node, +with resource requests totalling 0.1 CPU and 3GB RAM. Each data node requests 1.5GB of memory, +which makes it incompatible with clusters of `f1-micro` and `g1-small` instance types. + ## Install using GitLab CI (alpha) > [Introduced](https://gitlab.com/gitlab-org/gitlab/merge_requests/20822) in GitLab 12.6. diff --git a/lib/api/entities.rb b/lib/api/entities.rb index e8ea4a9fac5..74f8edb0784 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -1132,7 +1132,7 @@ module API expose :commit_events, :push_events, :issues_events, :confidential_issues_events expose :merge_requests_events, :tag_push_events, :note_events expose :confidential_note_events, :pipeline_events, :wiki_page_events - expose :job_events + expose :job_events, :comment_on_event_enabled end class ProjectService < ProjectServiceBasic diff --git a/lib/api/issues.rb b/lib/api/issues.rb index d0772c70ffb..4e21815fa35 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -220,18 +220,22 @@ module API issue_params = convert_parameters_from_legacy_format(issue_params) - issue = ::Issues::CreateService.new(user_project, - current_user, - issue_params.merge(request: request, api: true)).execute - - if issue.spam? - render_api_error!({ error: 'Spam detected' }, 400) - end - - if issue.valid? - present issue, with: Entities::Issue, current_user: current_user, project: user_project - else - render_validation_error!(issue) + begin + issue = ::Issues::CreateService.new(user_project, + current_user, + issue_params.merge(request: request, api: true)).execute + + if issue.spam? + render_api_error!({ error: 'Spam detected' }, 400) + end + + if issue.valid? + present issue, with: Entities::Issue, current_user: current_user, project: user_project + else + render_validation_error!(issue) + end + rescue ::ActiveRecord::RecordNotUnique + render_api_error!('Duplicated issue', 409) end end diff --git a/lib/gitlab/app_text_logger.rb b/lib/gitlab/app_text_logger.rb index 59ac57b2bb4..5b0439f43ad 100644 --- a/lib/gitlab/app_text_logger.rb +++ b/lib/gitlab/app_text_logger.rb @@ -7,7 +7,7 @@ module Gitlab end def format_message(severity, timestamp, progname, msg) - "#{timestamp.to_s(:long)}: #{msg}\n" + "#{timestamp.utc.iso8601(3)}: #{msg}\n" end end end diff --git a/lib/gitlab/asciidoc/include_processor.rb b/lib/gitlab/asciidoc/include_processor.rb index c6fbf540e9c..6e0b7ce60ba 100644 --- a/lib/gitlab/asciidoc/include_processor.rb +++ b/lib/gitlab/asciidoc/include_processor.rb @@ -13,7 +13,7 @@ module Gitlab super(logger: Gitlab::AppLogger) @context = context - @repository = context[:project].try(:repository) + @repository = context[:repository] || context[:project].try(:repository) # Note: Asciidoctor calls #freeze on extensions, so we can't set new # instance variables after initialization. @@ -111,7 +111,7 @@ module Gitlab end def ref - context[:ref] || context[:project].default_branch + context[:ref] || repository&.root_ref end def requested_path diff --git a/lib/gitlab/github_import/client.rb b/lib/gitlab/github_import/client.rb index 826b35d685c..22803c5cd71 100644 --- a/lib/gitlab/github_import/client.rb +++ b/lib/gitlab/github_import/client.rb @@ -189,7 +189,7 @@ module Gitlab end def default_api_endpoint - OmniAuth::Strategies::GitHub.default_options[:client_options][:site] + OmniAuth::Strategies::GitHub.default_options[:client_options][:site] || ::Octokit::Default.api_endpoint end def verify_ssl diff --git a/lib/gitlab/import/merge_request_helpers.rb b/lib/gitlab/import/merge_request_helpers.rb index 4bc39868389..c5694d95aa1 100644 --- a/lib/gitlab/import/merge_request_helpers.rb +++ b/lib/gitlab/import/merge_request_helpers.rb @@ -60,6 +60,7 @@ module Gitlab diff.importing = true diff.save diff.save_git_content + diff.set_as_latest_diff end end end diff --git a/lib/gitlab/legacy_github_import/client.rb b/lib/gitlab/legacy_github_import/client.rb index b23efd64dee..34634d20a16 100644 --- a/lib/gitlab/legacy_github_import/client.rb +++ b/lib/gitlab/legacy_github_import/client.rb @@ -80,7 +80,7 @@ module Gitlab if host.present? && api_version.present? "#{host}/api/#{api_version}" else - github_options[:site] + github_options[:site] || ::Octokit::Default.api_endpoint end end diff --git a/lib/sentry/api_urls.rb b/lib/sentry/api_urls.rb new file mode 100644 index 00000000000..388d0531da1 --- /dev/null +++ b/lib/sentry/api_urls.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +module Sentry + class ApiUrls + def initialize(url_base) + @uri = URI(url_base).freeze + end + + def issues_url + with_path(File.join(@uri.path, '/issues/')) + end + + def issue_url(issue_id) + with_path("/api/0/issues/#{escape(issue_id)}/") + end + + def projects_url + with_path('/api/0/projects/') + end + + def issue_latest_event_url(issue_id) + with_path("/api/0/issues/#{escape(issue_id)}/events/latest/") + end + + private + + def with_path(new_path) + new_uri = @uri.dup + # Sentry API returns 404 if there are extra slashes in the URL + new_uri.path = new_path.squeeze('/') + + new_uri + end + + def escape(param) + CGI.escape(param.to_s) + end + end +end diff --git a/lib/sentry/client.rb b/lib/sentry/client.rb index 29b00a6f8b2..20c919a97ff 100644 --- a/lib/sentry/client.rb +++ b/lib/sentry/client.rb @@ -19,6 +19,10 @@ module Sentry private + def api_urls + @api_urls ||= Sentry::ApiUrls.new(@url) + end + def handle_mapping_exceptions(&block) yield rescue KeyError => e diff --git a/lib/sentry/client/event.rb b/lib/sentry/client/event.rb index 8bcada5a9ce..01dfaa25969 100644 --- a/lib/sentry/client/event.rb +++ b/lib/sentry/client/event.rb @@ -4,20 +4,13 @@ module Sentry class Client module Event def issue_latest_event(issue_id:) - latest_event = http_get(issue_latest_event_api_url(issue_id))[:body] + latest_event = http_get(api_urls.issue_latest_event_url(issue_id))[:body] map_to_event(latest_event) end private - def issue_latest_event_api_url(issue_id) - latest_event_url = URI(url) - latest_event_url.path = "/api/0/issues/#{issue_id}/events/latest/" - - latest_event_url - end - def map_to_event(event) stack_trace = parse_stack_trace(event) diff --git a/lib/sentry/client/issue.rb b/lib/sentry/client/issue.rb index b3b3776624b..1c5d88e8862 100644 --- a/lib/sentry/client/issue.rb +++ b/lib/sentry/client/issue.rb @@ -35,14 +35,14 @@ module Sentry end def update_issue(issue_id:, params:) - http_put(issue_api_url(issue_id), params)[:body] + http_put(api_urls.issue_url(issue_id), params)[:body] end private def get_issues(**keyword_args) response = http_get( - issues_api_url, + api_urls.issues_url, query: list_issue_sentry_query(keyword_args) ) @@ -72,21 +72,7 @@ module Sentry end def get_issue(issue_id:) - http_get(issue_api_url(issue_id))[:body] - end - - def issues_api_url - issues_url = URI("#{url}/issues/") - issues_url.path.squeeze!('/') - - issues_url - end - - def issue_api_url(issue_id) - issue_url = URI(url) - issue_url.path = "/api/0/issues/#{CGI.escape(issue_id.to_s)}/" - - issue_url + http_get(api_urls.issue_url(issue_id))[:body] end def parse_gitlab_issue(plugin_issues) diff --git a/lib/sentry/client/projects.rb b/lib/sentry/client/projects.rb index 68f8fe0f9c9..e686d4ff715 100644 --- a/lib/sentry/client/projects.rb +++ b/lib/sentry/client/projects.rb @@ -14,14 +14,7 @@ module Sentry private def get_projects - http_get(projects_api_url)[:body] - end - - def projects_api_url - projects_url = URI(url) - projects_url.path = '/api/0/projects/' - - projects_url + http_get(api_urls.projects_url)[:body] end def map_to_projects(projects) diff --git a/spec/features/projects/files/user_browses_files_spec.rb b/spec/features/projects/files/user_browses_files_spec.rb index 657513431e5..b8efabb0cab 100644 --- a/spec/features/projects/files/user_browses_files_spec.rb +++ b/spec/features/projects/files/user_browses_files_spec.rb @@ -41,6 +41,11 @@ describe "User browses files" do it "shows the `Browse Directory` link" do click_link("files") + + page.within('.repo-breadcrumb') do + expect(page).to have_link('files') + end + click_link("History") expect(page).to have_link("Browse Directory").and have_no_link("Browse Code") diff --git a/spec/features/projects/files/user_browses_lfs_files_spec.rb b/spec/features/projects/files/user_browses_lfs_files_spec.rb index 618290416bd..dbeec973865 100644 --- a/spec/features/projects/files/user_browses_lfs_files_spec.rb +++ b/spec/features/projects/files/user_browses_lfs_files_spec.rb @@ -19,7 +19,17 @@ describe 'Projects > Files > User browses LFS files' do it 'is possible to see raw content of LFS pointer' do click_link 'files' + + page.within('.repo-breadcrumb') do + expect(page).to have_link('files') + end + click_link 'lfs' + + page.within('.repo-breadcrumb') do + expect(page).to have_link('lfs') + end + click_link 'lfs_object.iso' expect(page).to have_content 'version https://git-lfs.github.com/spec/v1' @@ -38,6 +48,11 @@ describe 'Projects > Files > User browses LFS files' do it 'shows an LFS object' do click_link('files') + + page.within('.repo-breadcrumb') do + expect(page).to have_link('files') + end + click_link('lfs') click_link('lfs_object.iso') diff --git a/spec/features/projects/wiki/users_views_asciidoc_page_with_includes_spec.rb b/spec/features/projects/wiki/users_views_asciidoc_page_with_includes_spec.rb new file mode 100644 index 00000000000..08eea14c438 --- /dev/null +++ b/spec/features/projects/wiki/users_views_asciidoc_page_with_includes_spec.rb @@ -0,0 +1,79 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'User views AsciiDoc page with includes', :js do + let_it_be(:user) { create(:user) } + let_it_be(:wiki_content_selector) { '[data-qa-selector=wiki_page_content]' } + let(:project) { create(:project, :public, :wiki_repo) } + let!(:included_wiki_page) { create_wiki_page('included_page', content: 'Content from the included page')} + let!(:wiki_page) { create_wiki_page('home', content: "Content from the main page.\ninclude::included_page.asciidoc[]") } + + def create_wiki_page(title, content:) + attrs = { + title: title, + content: content, + format: :asciidoc + } + + create(:wiki_page, wiki: project.wiki, attrs: attrs) + end + + before do + sign_in(user) + end + + context 'when the file being included exists' do + it 'includes the file contents' do + visit(project_wiki_path(project, wiki_page)) + + page.within(:css, wiki_content_selector) do + expect(page).to have_content('Content from the main page. Content from the included page') + end + end + + context 'when there are multiple versions of the wiki pages' do + before do + included_wiki_page.update(message: 'updated included file', content: 'Updated content from the included page') + wiki_page.update(message: 'updated wiki page', content: "Updated content from the main page.\ninclude::included_page.asciidoc[]") + end + + let(:latest_version_id) { wiki_page.versions.first.id } + let(:oldest_version_id) { wiki_page.versions.last.id } + + context 'viewing the latest version' do + it 'includes the latest content' do + visit(project_wiki_path(project, wiki_page, version_id: latest_version_id)) + + page.within(:css, wiki_content_selector) do + expect(page).to have_content('Updated content from the main page. Updated content from the included page') + end + end + end + + context 'viewing the original version' do + it 'includes the content from the original version' do + visit(project_wiki_path(project, wiki_page, version_id: oldest_version_id)) + + page.within(:css, wiki_content_selector) do + expect(page).to have_content('Content from the main page. Content from the included page') + end + end + end + end + end + + context 'when the file being included does not exist' do + before do + included_wiki_page.delete + end + + it 'outputs an error' do + visit(project_wiki_path(project, wiki_page)) + + page.within(:css, wiki_content_selector) do + expect(page).to have_content('Content from the main page. [ERROR: include::included_page.asciidoc[] - unresolved directive]') + end + end + end +end diff --git a/spec/fixtures/api/schemas/public_api/v4/service.json b/spec/fixtures/api/schemas/public_api/v4/service.json index 4a91d264961..a024e38acad 100644 --- a/spec/fixtures/api/schemas/public_api/v4/service.json +++ b/spec/fixtures/api/schemas/public_api/v4/service.json @@ -16,7 +16,8 @@ "confidential_note_events": { "type": "boolean" }, "pipeline_events": { "type": "boolean" }, "wiki_page_events": { "type": "boolean" }, - "job_events": { "type": "boolean" } + "job_events": { "type": "boolean" }, + "comment_on_event_enabled": { "type": "boolean" } }, "additionalProperties": false } diff --git a/spec/frontend/repository/components/table/parent_row_spec.js b/spec/frontend/repository/components/table/parent_row_spec.js index 7020055271f..439c7ff080c 100644 --- a/spec/frontend/repository/components/table/parent_row_spec.js +++ b/spec/frontend/repository/components/table/parent_row_spec.js @@ -1,10 +1,11 @@ import { shallowMount, RouterLinkStub } from '@vue/test-utils'; +import { GlLoadingIcon } from '@gitlab/ui'; import ParentRow from '~/repository/components/table/parent_row.vue'; let vm; let $router; -function factory(path) { +function factory(path, loadingPath) { $router = { push: jest.fn(), }; @@ -13,6 +14,7 @@ function factory(path) { propsData: { commitRef: 'master', path, + loadingPath, }, stubs: { RouterLink: RouterLinkStub, @@ -61,4 +63,10 @@ describe('Repository parent row component', () => { path: '/tree/master/app', }); }); + + it('renders loading icon when loading parent', () => { + factory('app/assets', 'app'); + + expect(vm.find(GlLoadingIcon).exists()).toBe(true); + }); }); diff --git a/spec/frontend/repository/components/table/row_spec.js b/spec/frontend/repository/components/table/row_spec.js index 067bad0dab6..b60560366a6 100644 --- a/spec/frontend/repository/components/table/row_spec.js +++ b/spec/frontend/repository/components/table/row_spec.js @@ -1,5 +1,5 @@ import { shallowMount, RouterLinkStub } from '@vue/test-utils'; -import { GlBadge, GlLink } from '@gitlab/ui'; +import { GlBadge, GlLink, GlLoadingIcon } from '@gitlab/ui'; import { visitUrl } from '~/lib/utils/url_utility'; import TableRow from '~/repository/components/table/row.vue'; import Icon from '~/vue_shared/components/icon.vue'; @@ -198,4 +198,17 @@ describe('Repository table row component', () => { expect(vm.find(Icon).exists()).toBe(true); }); }); + + it('renders loading icon when path is loading', () => { + factory({ + id: '1', + sha: '1', + path: 'test', + type: 'tree', + currentPath: '/', + loadingPath: 'test', + }); + + expect(vm.find(GlLoadingIcon).exists()).toBe(true); + }); }); diff --git a/spec/helpers/markup_helper_spec.rb b/spec/helpers/markup_helper_spec.rb index 66d461a871c..f415fb05b5b 100644 --- a/spec/helpers/markup_helper_spec.rb +++ b/spec/helpers/markup_helper_spec.rb @@ -273,16 +273,19 @@ describe MarkupHelper do describe '#render_wiki_content' do let(:wiki) { double('WikiPage', path: "file.#{extension}") } + let(:wiki_repository) { double('Repository') } let(:context) do { pipeline: :wiki, project: project, project_wiki: wiki, - page_slug: 'nested/page', issuable_state_filter_enabled: true + page_slug: 'nested/page', issuable_state_filter_enabled: true, + repository: wiki_repository } end before do expect(wiki).to receive(:content).and_return('wiki content') expect(wiki).to receive(:slug).and_return('nested/page') + expect(wiki).to receive(:repository).and_return(wiki_repository) helper.instance_variable_set(:@project_wiki, wiki) end diff --git a/spec/lib/gitlab/app_text_logger_spec.rb b/spec/lib/gitlab/app_text_logger_spec.rb index 06d3e643608..c84b986ce40 100644 --- a/spec/lib/gitlab/app_text_logger_spec.rb +++ b/spec/lib/gitlab/app_text_logger_spec.rb @@ -15,4 +15,11 @@ describe Gitlab::AppTextLogger do it 'logs a string unchanged' do expect(subject.format_message('INFO', Time.now, nil, string_message)).to include(string_message) end + + it 'logs time in UTC with ISO8601.3 standard' do + Timecop.freeze do + expect(subject.format_message('INFO', Time.now, nil, string_message)) + .to include(Time.now.utc.iso8601(3)) + end + end end diff --git a/spec/lib/gitlab/asciidoc_spec.rb b/spec/lib/gitlab/asciidoc_spec.rb index ba5b70b44de..c8d159d1e84 100644 --- a/spec/lib/gitlab/asciidoc_spec.rb +++ b/spec/lib/gitlab/asciidoc_spec.rb @@ -518,6 +518,28 @@ module Gitlab end end + context 'when repository is passed into the context' do + let(:wiki_repo) { project.wiki.repository } + let(:include_path) { 'wiki_file.adoc' } + + before do + project.create_wiki + context.merge!(repository: wiki_repo) + end + + context 'when the file exists' do + before do + create_file(include_path, 'Content from wiki', repository: wiki_repo) + end + + it { is_expected.to include('<p>Content from wiki</p>') } + end + + context 'when the file does not exist' do + it { is_expected.to include("[ERROR: include::#{include_path}[] - unresolved directive]")} + end + end + context 'recursive includes with relative paths' do let(:input) do <<~ADOC @@ -562,8 +584,8 @@ module Gitlab end end - def create_file(path, content) - project.repository.create_file(project.creator, path, content, + def create_file(path, content, repository: project.repository) + repository.create_file(project.creator, path, content, message: "Add #{path}", branch_name: 'asciidoc') end end diff --git a/spec/lib/sentry/api_urls_spec.rb b/spec/lib/sentry/api_urls_spec.rb new file mode 100644 index 00000000000..78455f8d51f --- /dev/null +++ b/spec/lib/sentry/api_urls_spec.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Sentry::ApiUrls do + let(:sentry_url) { 'https://sentrytest.gitlab.com/api/0/projects/sentry-org/sentry-project/' } + let(:token) { 'test-token' } + let(:issue_id) { '123456' } + let(:issue_id_with_reserved_chars) { '123$%' } + let(:escaped_issue_id) { '123%24%25' } + let(:api_urls) { Sentry::ApiUrls.new(sentry_url) } + + # Sentry API returns 404 if there are extra slashes in the URL! + shared_examples 'correct url with extra slashes' do + let(:sentry_url) { 'https://sentrytest.gitlab.com/api/0/projects//sentry-org/sentry-project/' } + + it_behaves_like 'correct url' + end + + shared_examples 'correctly escapes issue ID' do + context 'with param a string with reserved chars' do + let(:issue_id) { issue_id_with_reserved_chars } + + it { expect(subject.to_s).to include(escaped_issue_id) } + end + + context 'with param a symbol with reserved chars' do + let(:issue_id) { issue_id_with_reserved_chars.to_sym } + + it { expect(subject.to_s).to include(escaped_issue_id) } + end + + context 'with param an integer' do + let(:issue_id) { 12345678 } + + it { expect(subject.to_s).to include(issue_id.to_s) } + end + end + + describe '#issues_url' do + subject { api_urls.issues_url } + + shared_examples 'correct url' do + it { is_expected.to eq_uri('https://sentrytest.gitlab.com/api/0/projects/sentry-org/sentry-project/issues/') } + end + + it_behaves_like 'correct url' + it_behaves_like 'correct url with extra slashes' + end + + describe '#issue_url' do + subject { api_urls.issue_url(issue_id) } + + shared_examples 'correct url' do + it { is_expected.to eq_uri("https://sentrytest.gitlab.com/api/0/issues/#{issue_id}/") } + end + + it_behaves_like 'correct url' + it_behaves_like 'correct url with extra slashes' + it_behaves_like 'correctly escapes issue ID' + end + + describe '#projects_url' do + subject { api_urls.projects_url } + + shared_examples 'correct url' do + it { is_expected.to eq_uri('https://sentrytest.gitlab.com/api/0/projects/') } + end + + it_behaves_like 'correct url' + it_behaves_like 'correct url with extra slashes' + end + + describe '#issue_latest_event_url' do + subject { api_urls.issue_latest_event_url(issue_id) } + + shared_examples 'correct url' do + it { is_expected.to eq_uri("https://sentrytest.gitlab.com/api/0/issues/#{issue_id}/events/latest/") } + end + + it_behaves_like 'correct url' + it_behaves_like 'correct url with extra slashes' + it_behaves_like 'correctly escapes issue ID' + end +end diff --git a/spec/lib/sentry/client/issue_spec.rb b/spec/lib/sentry/client/issue_spec.rb index e10d27e4342..3130bb38cc2 100644 --- a/spec/lib/sentry/client/issue_spec.rb +++ b/spec/lib/sentry/client/issue_spec.rb @@ -109,28 +109,6 @@ describe Sentry::Client::Issue do it_behaves_like 'no Sentry redirects' end - # Sentry API returns 404 if there are extra slashes in the URL! - context 'extra slashes in URL' do - let(:sentry_url) { 'https://sentrytest.gitlab.com/api/0/projects//sentry-org/sentry-project/' } - - let(:sentry_request_url) do - 'https://sentrytest.gitlab.com/api/0/projects/sentry-org/sentry-project/' \ - 'issues/?limit=20&query=is:unresolved' - end - - it 'removes extra slashes in api url' do - expect(client.url).to eq(sentry_url) - expect(Gitlab::HTTP).to receive(:get).with( - URI('https://sentrytest.gitlab.com/api/0/projects/sentry-org/sentry-project/issues/'), - anything - ).and_call_original - - subject - - expect(sentry_api_request).to have_been_requested - end - end - context 'requests with sort parameter in sentry api' do let(:sentry_request_url) do 'https://sentrytest.gitlab.com/api/0/projects/sentry-org/sentry-project/' \ @@ -232,14 +210,6 @@ describe Sentry::Client::Issue do subject { client.issue_details(issue_id: issue_id) } - it 'escapes issue ID' do - allow(CGI).to receive(:escape).and_call_original - - subject - - expect(CGI).to have_received(:escape).with(issue_id.to_s) - end - context 'error object created from sentry response' do using RSpec::Parameterized::TableSyntax diff --git a/spec/lib/sentry/client/projects_spec.rb b/spec/lib/sentry/client/projects_spec.rb index 462f74eaac9..6183d4c5816 100644 --- a/spec/lib/sentry/client/projects_spec.rb +++ b/spec/lib/sentry/client/projects_spec.rb @@ -91,25 +91,6 @@ describe Sentry::Client::Projects do it_behaves_like 'no Sentry redirects' end - # Sentry API returns 404 if there are extra slashes in the URL! - context 'extra slashes in URL' do - let(:sentry_url) { 'https://sentrytest.gitlab.com/api//0/projects//' } - let!(:valid_req_stub) do - stub_sentry_request(sentry_list_projects_url) - end - - it 'removes extra slashes in api url' do - expect(Gitlab::HTTP).to receive(:get).with( - URI(sentry_list_projects_url), - anything - ).and_call_original - - subject - - expect(valid_req_stub).to have_been_requested - end - end - context 'when exception is raised' do let(:sentry_request_url) { sentry_list_projects_url } diff --git a/spec/models/concerns/atomic_internal_id_spec.rb b/spec/models/concerns/atomic_internal_id_spec.rb index 0605392c0aa..93bf7ec10dd 100644 --- a/spec/models/concerns/atomic_internal_id_spec.rb +++ b/spec/models/concerns/atomic_internal_id_spec.rb @@ -9,6 +9,32 @@ describe AtomicInternalId do let(:scope_attrs) { { project: milestone.project } } let(:usage) { :milestones } + describe '#save!' do + context 'when IID is provided' do + before do + milestone.iid = external_iid + end + + it 'tracks the value' do + expect(milestone).to receive(:track_project_iid!) + + milestone.save! + end + + context 'when importing' do + before do + milestone.importing = true + end + + it 'does not track the value' do + expect(milestone).not_to receive(:track_project_iid!) + + milestone.save! + end + end + end + end + describe '#track_project_iid!' do subject { milestone.track_project_iid! } diff --git a/spec/requests/api/issues/post_projects_issues_spec.rb b/spec/requests/api/issues/post_projects_issues_spec.rb index e9f678d164e..67404cf10df 100644 --- a/spec/requests/api/issues/post_projects_issues_spec.rb +++ b/spec/requests/api/issues/post_projects_issues_spec.rb @@ -160,6 +160,16 @@ describe API::Issues do expect(json_response['iid']).not_to eq 9001 end end + + context 'when an issue with the same IID exists on database' do + it 'returns 409' do + post api("/projects/#{project.id}/issues", admin), + params: { title: 'new issue', iid: issue.iid } + + expect(response).to have_gitlab_http_status(409) + expect(json_response['message']).to eq 'Duplicated issue' + end + end end it 'creates a new project issue' do diff --git a/spec/services/users/destroy_service_spec.rb b/spec/services/users/destroy_service_spec.rb index 23a0c71175e..d9335cef5cc 100644 --- a/spec/services/users/destroy_service_spec.rb +++ b/spec/services/users/destroy_service_spec.rb @@ -20,6 +20,22 @@ describe Users::DestroyService do expect { Namespace.find(namespace.id) }.to raise_error(ActiveRecord::RecordNotFound) end + it 'deletes user associations in batches' do + expect(user).to receive(:destroy_dependent_associations_in_batches) + + service.execute(user) + end + + context 'when :destroy_user_associations_in_batches flag is disabled' do + it 'does not delete user associations in batches' do + stub_feature_flags(destroy_user_associations_in_batches: false) + + expect(user).not_to receive(:destroy_dependent_associations_in_batches) + + service.execute(user) + end + end + it 'will delete the project' do expect_next_instance_of(Projects::DestroyService) do |destroy_service| expect(destroy_service).to receive(:execute).once.and_return(true) diff --git a/spec/support/matchers/eq_uri.rb b/spec/support/matchers/eq_uri.rb new file mode 100644 index 00000000000..47b657b3fe1 --- /dev/null +++ b/spec/support/matchers/eq_uri.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +# Assert the result matches a URI object initialized with the expectation variable. +# +# Success: +# ``` +# expect(URI('www.fish.com')).to eq_uri('www.fish.com') +# ``` +# +# Failure: +# ``` +# expect(URI('www.fish.com')).to eq_uri('www.dog.com') +# ``` +# +RSpec::Matchers.define :eq_uri do |expected| + match do |actual| + actual == URI(expected) + end +end |