diff options
252 files changed, 3289 insertions, 1354 deletions
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index e640847f99c..b978278f05f 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -1.42.1 +1.43.0 diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js index e583a8affd4..7cebb88f3a4 100644 --- a/app/assets/javascripts/api.js +++ b/app/assets/javascripts/api.js @@ -12,7 +12,7 @@ const Api = { groupProjectsPath: '/api/:version/groups/:id/projects.json', projectsPath: '/api/:version/projects.json', projectPath: '/api/:version/projects/:id', - projectLabelsPath: '/:namespace_path/:project_path/labels', + projectLabelsPath: '/:namespace_path/:project_path/-/labels', projectMergeRequestsPath: '/api/:version/projects/:id/merge_requests', projectMergeRequestPath: '/api/:version/projects/:id/merge_requests/:mrid', projectMergeRequestChangesPath: '/api/:version/projects/:id/merge_requests/:mrid/changes', diff --git a/app/assets/javascripts/diffs/components/diff_gutter_avatars.vue b/app/assets/javascripts/diffs/components/diff_gutter_avatars.vue index 0c0a0faa59d..7cf3d90d468 100644 --- a/app/assets/javascripts/diffs/components/diff_gutter_avatars.vue +++ b/app/assets/javascripts/diffs/components/diff_gutter_avatars.vue @@ -86,7 +86,6 @@ export default { :key="note.id" :img-src="note.author.avatar_url" :tooltip-text="getTooltipText(note)" - :size="19" class="diff-comment-avatar js-diff-comment-avatar" @click.native="toggleDiscussions" /> diff --git a/app/assets/javascripts/notes/components/note_actions.vue b/app/assets/javascripts/notes/components/note_actions.vue index 78f397ccc12..c9c40cb6acf 100644 --- a/app/assets/javascripts/notes/components/note_actions.vue +++ b/app/assets/javascripts/notes/components/note_actions.vue @@ -147,6 +147,7 @@ export default { class="note-action-button note-emoji-button js-add-award js-note-emoji" href="#" title="Add reaction" + data-position="right" > <icon css-classes="link-highlight award-control-icon-neutral" name="slight-smile" /> <icon css-classes="link-highlight award-control-icon-positive" name="smiley" /> diff --git a/app/assets/javascripts/profile/profile.js b/app/assets/javascripts/profile/profile.js index 6e3800021b4..8dd37aee7e1 100644 --- a/app/assets/javascripts/profile/profile.js +++ b/app/assets/javascripts/profile/profile.js @@ -39,6 +39,7 @@ export default class Profile { bindEvents() { $('.js-preferences-form').on('change.preference', 'input[type=radio]', this.submitForm); + $('.js-group-notification-email').on('change', this.submitForm); $('#user_notification_email').on('change', this.submitForm); $('#user_notified_of_own_activity').on('change', this.submitForm); this.form.on('submit', this.onSubmitForm); diff --git a/app/assets/javascripts/repository/components/table/index.vue b/app/assets/javascripts/repository/components/table/index.vue index f4df98ac2ff..cccde1bb278 100644 --- a/app/assets/javascripts/repository/components/table/index.vue +++ b/app/assets/javascripts/repository/components/table/index.vue @@ -100,7 +100,7 @@ export default { this.fetchFiles(); } }) - .catch(() => createFlash(__('An error occurding while fetching folder content.'))); + .catch(() => createFlash(__('An error occurred while fetching folder content.'))); }, normalizeData(key, data) { return this.entries[key].concat(data.map(({ node }) => node)); diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index b85abfd9c14..97a763671ba 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -1,12 +1,12 @@ @mixin btn-comment-icon { border-radius: 50%; background: $white-light; - padding: 1px 5px; + padding: 1px; font-size: 12px; color: $blue-500; + border: 1px solid $blue-500; width: 24px; height: 24px; - border: 1px solid $blue-500; &:hover, &.inverted { diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss index 18671f7c4d8..df40149f0a6 100644 --- a/app/assets/stylesheets/framework/mixins.scss +++ b/app/assets/stylesheets/framework/mixins.scss @@ -385,3 +385,8 @@ height: $size; margin-right: $margin-right; } + +@mixin code-icon-size() { + width: $gl-font-size * $code-line-height * 0.9; + height: $gl-font-size * $code-line-height * 0.9; +} diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index b3a634e23a3..4a648a5d3b6 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -615,10 +615,9 @@ table.code { .diff-comment-avatar-holders { position: absolute; - height: 19px; - width: 19px; - margin-left: -15px; + margin-left: -$gl-padding; z-index: 100; + @include code-icon-size(); &:hover { .diff-comment-avatar, @@ -652,26 +651,28 @@ table.code { .diff-comments-more-count { position: absolute; left: 0; - width: 19px; - height: 19px; margin-right: 0; border-color: $white-light; cursor: pointer; transition: all 0.1s ease-out; + @include code-icon-size(); @for $i from 1 through 4 { &:nth-child(#{$i}) { z-index: (4 - $i); } } + + .avatar { + @include code-icon-size(); + } } .diff-comments-more-count { - width: 19px; - min-width: 19px; padding-left: 0; padding-right: 0; overflow: hidden; + @include code-icon-size(); } .diff-comments-more-count, @@ -680,12 +681,15 @@ table.code { } .diff-notes-collapse { - width: 24px; - height: 24px; + border: 0; border-radius: 50%; padding: 0; transition: transform 0.1s ease-out; z-index: 100; + display: flex; + justify-content: center; + align-items: center; + @include code-icon-size(); .collapse-icon { height: 50%; diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 04c66006027..4ba74d34664 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -218,7 +218,7 @@ .title { color: $gl-text-color; - margin-bottom: 10px; + margin-bottom: $gl-padding-8; line-height: 1; .avatar { @@ -604,7 +604,6 @@ .participants-list { display: flex; flex-wrap: wrap; - margin: -7px; } .user-list { @@ -614,7 +613,7 @@ .participants-author { display: inline-block; - padding: 7px; + padding: 0 $gl-padding-8 $gl-padding-8 0; &:nth-of-type(7n) { padding-right: 0; @@ -641,7 +640,6 @@ .participants-more, .user-list-more { - margin-top: 5px; margin-left: 5px; a, diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index 170432a9e62..32477c20db6 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -437,7 +437,9 @@ $note-form-margin-left: 72px; .diff-file { .is-over { .add-diff-note { - display: inline-block; + display: inline-flex; + justify-content: center; + align-items: center; } } @@ -741,7 +743,7 @@ $note-form-margin-left: 72px; .add-diff-note { @include btn-comment-icon; opacity: 0; - margin-left: -50px; + margin-left: -52px; position: absolute; top: 50%; transform: translateY(-50%); @@ -900,10 +902,6 @@ $note-form-margin-left: 72px; .diff-comment-form { display: block; } - - .add-diff-note svg { - margin-top: 4px; - } } .discussion-filter-container { diff --git a/app/assets/stylesheets/pages/notifications.scss b/app/assets/stylesheets/pages/notifications.scss index e98cb444f0a..e1cbf0e6654 100644 --- a/app/assets/stylesheets/pages/notifications.scss +++ b/app/assets/stylesheets/pages/notifications.scss @@ -4,6 +4,34 @@ .dropdown-menu { @extend .dropdown-menu-right; } + + @include media-breakpoint-down(sm) { + .notification-dropdown { + width: 100%; + } + + .notification-form { + display: block; + } + + .notifications-btn, + .btn-group { + width: 100%; + } + + .table-section { + border-top: 0; + min-height: unset; + + &:not(:first-child) { + padding-top: 0; + } + } + + .update-notifications { + width: 100%; + } + } } .notification { diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 4cbab6811bc..6e98d66d712 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -42,7 +42,7 @@ class ApplicationController < ActionController::Base :bitbucket_server_import_enabled?, :google_code_import_enabled?, :fogbugz_import_enabled?, :git_import_enabled?, :gitlab_project_import_enabled?, - :manifest_import_enabled? + :manifest_import_enabled?, :phabricator_import_enabled? # Adds `no-store` to the DEFAULT_CACHE_CONTROL, to prevent security # concerns due to caching private data. @@ -424,6 +424,10 @@ class ApplicationController < ActionController::Base Group.supports_nested_objects? && Gitlab::CurrentSettings.import_sources.include?('manifest') end + def phabricator_import_enabled? + Gitlab::PhabricatorImport.available? + end + # U2F (universal 2nd factor) devices need a unique identifier for the application # to perform authentication. # https://developers.yubico.com/U2F/App_ID.html diff --git a/app/controllers/import/phabricator_controller.rb b/app/controllers/import/phabricator_controller.rb new file mode 100644 index 00000000000..d1c04817689 --- /dev/null +++ b/app/controllers/import/phabricator_controller.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +class Import::PhabricatorController < Import::BaseController + include ImportHelper + + before_action :verify_import_enabled + + def new + end + + def create + @project = Gitlab::PhabricatorImport::ProjectCreator + .new(current_user, import_params).execute + + if @project&.persisted? + redirect_to @project + else + @name = params[:name] + @path = params[:path] + @errors = @project&.errors&.full_messages || [_("Invalid import params")] + + render :new + end + end + + def verify_import_enabled + render_404 unless phabricator_import_enabled? + end + + private + + def import_params + params.permit(:path, :phabricator_server_url, :api_token, :name, :namespace_id) + end +end diff --git a/app/controllers/profiles/accounts_controller.rb b/app/controllers/profiles/accounts_controller.rb index 0d2a6145d0e..b03f4b7435f 100644 --- a/app/controllers/profiles/accounts_controller.rb +++ b/app/controllers/profiles/accounts_controller.rb @@ -17,7 +17,7 @@ class Profiles::AccountsController < Profiles::ApplicationController if unlink_provider_allowed?(provider) identity.destroy else - flash[:alert] = "You are not allowed to unlink your primary login account" + flash[:alert] = _("You are not allowed to unlink your primary login account") end redirect_to profile_account_path diff --git a/app/controllers/profiles/groups_controller.rb b/app/controllers/profiles/groups_controller.rb new file mode 100644 index 00000000000..c755bcb718a --- /dev/null +++ b/app/controllers/profiles/groups_controller.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +class Profiles::GroupsController < Profiles::ApplicationController + include RoutableActions + + def update + group = find_routable!(Group, params[:id]) + notification_setting = current_user.notification_settings.find_by(source: group) # rubocop: disable CodeReuse/ActiveRecord + + if notification_setting.update(update_params) + flash[:notice] = "Notification settings for #{group.name} saved" + else + flash[:alert] = "Failed to save new settings for #{group.name}" + end + + redirect_back_or_default(default: profile_notifications_path) + end + + private + + def update_params + params.require(:notification_setting).permit(:notification_email) + end +end diff --git a/app/controllers/profiles/passwords_controller.rb b/app/controllers/profiles/passwords_controller.rb index 7038447581c..d2787c2e450 100644 --- a/app/controllers/profiles/passwords_controller.rb +++ b/app/controllers/profiles/passwords_controller.rb @@ -14,7 +14,7 @@ class Profiles::PasswordsController < Profiles::ApplicationController def create unless @user.password_automatically_set || @user.valid_password?(user_params[:current_password]) - redirect_to new_profile_password_path, alert: 'You must provide a valid current password' + redirect_to new_profile_password_path, alert: _('You must provide a valid current password') return end @@ -29,7 +29,7 @@ class Profiles::PasswordsController < Profiles::ApplicationController if result[:status] == :success Users::UpdateService.new(current_user, user: @user, password_expires_at: nil).execute - redirect_to root_path, notice: 'Password successfully changed' + redirect_to root_path, notice: _('Password successfully changed') else render :new end @@ -45,14 +45,14 @@ class Profiles::PasswordsController < Profiles::ApplicationController password_attributes[:password_automatically_set] = false unless @user.password_automatically_set || @user.valid_password?(user_params[:current_password]) - redirect_to edit_profile_password_path, alert: 'You must provide a valid current password' + redirect_to edit_profile_password_path, alert: _('You must provide a valid current password') return end result = Users::UpdateService.new(current_user, password_attributes.merge(user: @user)).execute if result[:status] == :success - flash[:notice] = "Password was successfully updated. Please login with it" + flash[:notice] = _('Password was successfully updated. Please login with it') redirect_to new_user_session_path else @user.reset @@ -62,7 +62,7 @@ class Profiles::PasswordsController < Profiles::ApplicationController def reset current_user.send_reset_password_instructions - redirect_to edit_profile_password_path, notice: 'We sent you an email with reset password instructions' + redirect_to edit_profile_password_path, notice: _('We sent you an email with reset password instructions') end private diff --git a/app/controllers/profiles/two_factor_auths_controller.rb b/app/controllers/profiles/two_factor_auths_controller.rb index 83e14275a8b..95b9344c551 100644 --- a/app/controllers/profiles/two_factor_auths_controller.rb +++ b/app/controllers/profiles/two_factor_auths_controller.rb @@ -18,7 +18,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController two_factor_authentication_reason( global: lambda do flash.now[:alert] = - s_('The global settings require you to enable Two-Factor Authentication for your account.') + _('The global settings require you to enable Two-Factor Authentication for your account.') end, group: lambda do |groups| flash.now[:alert] = groups_notification(groups) @@ -27,7 +27,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController unless two_factor_grace_period_expired? grace_period_deadline = current_user.otp_grace_period_started_at + two_factor_grace_period.hours - flash.now[:alert] = flash.now[:alert] + s_(" You need to do this before %{grace_period_deadline}.") % { grace_period_deadline: l(grace_period_deadline) } + flash.now[:alert] = flash.now[:alert] + _(" You need to do this before %{grace_period_deadline}.") % { grace_period_deadline: l(grace_period_deadline) } end end @@ -44,7 +44,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController render 'create' else - @error = s_('Invalid pin code') + @error = _('Invalid pin code') @qr_code = build_qr_code setup_u2f_registration render 'show' diff --git a/app/graphql/gitlab_schema.rb b/app/graphql/gitlab_schema.rb index a63f45f231c..f8ad6bee21b 100644 --- a/app/graphql/gitlab_schema.rb +++ b/app/graphql/gitlab_schema.rb @@ -16,7 +16,7 @@ class GitlabSchema < GraphQL::Schema use Gitlab::Graphql::Connections use Gitlab::Graphql::GenericTracing - query_analyzer Gitlab::Graphql::QueryAnalyzers::LogQueryComplexity.analyzer + query_analyzer Gitlab::Graphql::QueryAnalyzers::LoggerAnalyzer.new query(Types::QueryType) diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb index 7af766c8544..a3f53ca8dd6 100644 --- a/app/helpers/groups_helper.rb +++ b/app/helpers/groups_helper.rb @@ -99,7 +99,7 @@ module GroupsHelper end def remove_group_message(group) - _("You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?") % + _("You are going to remove %{group_name}, this will also remove all of its subgroups and projects. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?") % { group_name: group.name } end diff --git a/app/mailers/emails/issues.rb b/app/mailers/emails/issues.rb index 2b046d17122..f3a3203f7ad 100644 --- a/app/mailers/emails/issues.rb +++ b/app/mailers/emails/issues.rb @@ -83,7 +83,7 @@ module Emails @project = Project.find(project_id) @results = results - mail(to: @user.notification_email, subject: subject('Imported issues')) do |format| + mail(to: recipient(@user.id, @project.group), subject: subject('Imported issues')) do |format| format.html { render layout: 'mailer' } format.text { render layout: 'mailer' } end @@ -103,7 +103,7 @@ module Emails def issue_thread_options(sender_id, recipient_id, reason) { from: sender(sender_id), - to: recipient(recipient_id), + to: recipient(recipient_id, @project.group), subject: subject("#{@issue.title} (##{@issue.iid})"), 'X-GitLab-NotificationReason' => reason } diff --git a/app/mailers/emails/members.rb b/app/mailers/emails/members.rb index 91dfdf58982..2bfa59774d7 100644 --- a/app/mailers/emails/members.rb +++ b/app/mailers/emails/members.rb @@ -58,9 +58,8 @@ module Emails @member_source_type = member_source_type @member_source = member_source_class.find(source_id) @invite_email = invite_email - inviter = User.find(created_by_id) - mail(to: inviter.notification_email, + mail(to: recipient(created_by_id, member_source_type == 'Project' ? @member_source.group : @member_source), subject: subject('Invitation declined')) end diff --git a/app/mailers/emails/merge_requests.rb b/app/mailers/emails/merge_requests.rb index fc6ed695675..864f9e2975a 100644 --- a/app/mailers/emails/merge_requests.rb +++ b/app/mailers/emails/merge_requests.rb @@ -110,7 +110,7 @@ module Emails def merge_request_thread_options(sender_id, recipient_id, reason = nil) { from: sender(sender_id), - to: recipient(recipient_id), + to: recipient(recipient_id, @project.group), subject: subject("#{@merge_request.title} (#{@merge_request.to_reference})"), 'X-GitLab-NotificationReason' => reason } diff --git a/app/mailers/emails/notes.rb b/app/mailers/emails/notes.rb index 1b3c1f9a8a9..70d296fe3b8 100644 --- a/app/mailers/emails/notes.rb +++ b/app/mailers/emails/notes.rb @@ -51,7 +51,7 @@ module Emails def note_thread_options(recipient_id) { from: sender(@note.author_id), - to: recipient(recipient_id), + to: recipient(recipient_id, @group), subject: subject("#{@note.noteable.title} (#{@note.noteable.reference_link_text})") } end diff --git a/app/mailers/emails/pages_domains.rb b/app/mailers/emails/pages_domains.rb index ce449237ef6..2d390666f65 100644 --- a/app/mailers/emails/pages_domains.rb +++ b/app/mailers/emails/pages_domains.rb @@ -7,7 +7,7 @@ module Emails @project = domain.project mail( - to: recipient.notification_email, + to: recipient(recipient.id, @project.group), subject: subject("GitLab Pages domain '#{domain.domain}' has been enabled") ) end @@ -17,7 +17,7 @@ module Emails @project = domain.project mail( - to: recipient.notification_email, + to: recipient(recipient.id, @project.group), subject: subject("GitLab Pages domain '#{domain.domain}' has been disabled") ) end @@ -27,7 +27,7 @@ module Emails @project = domain.project mail( - to: recipient.notification_email, + to: recipient(recipient.id, @project.group), subject: subject("Verification succeeded for GitLab Pages domain '#{domain.domain}'") ) end @@ -37,7 +37,7 @@ module Emails @project = domain.project mail( - to: recipient.notification_email, + to: recipient(recipient.id, @project.group), subject: subject("ACTION REQUIRED: Verification failed for GitLab Pages domain '#{domain.domain}'") ) end diff --git a/app/mailers/emails/projects.rb b/app/mailers/emails/projects.rb index 2500622caa7..f81f76f67f7 100644 --- a/app/mailers/emails/projects.rb +++ b/app/mailers/emails/projects.rb @@ -7,20 +7,20 @@ module Emails @project = Project.find project_id @target_url = project_url(@project) @old_path_with_namespace = old_path_with_namespace - mail(to: @user.notification_email, + mail(to: recipient(user_id, @project.group), subject: subject("Project was moved")) end def project_was_exported_email(current_user, project) @project = project - mail(to: current_user.notification_email, + mail(to: recipient(current_user.id, project.group), subject: subject("Project was exported")) end def project_was_not_exported_email(current_user, project, errors) @project = project @errors = errors - mail(to: current_user.notification_email, + mail(to: recipient(current_user.id, @project.group), subject: subject("Project export error")) end @@ -28,7 +28,7 @@ module Emails @project = project @user = user - mail(to: user.notification_email, subject: subject("Project cleanup has completed")) + mail(to: recipient(user.id, project.group), subject: subject("Project cleanup has completed")) end def repository_cleanup_failure_email(project, user, error) @@ -36,7 +36,7 @@ module Emails @user = user @error = error - mail(to: user.notification_email, subject: subject("Project cleanup failure")) + mail(to: recipient(user.id, project.group), subject: subject("Project cleanup failure")) end def repository_push_email(project_id, opts = {}) diff --git a/app/mailers/emails/remote_mirrors.rb b/app/mailers/emails/remote_mirrors.rb index 2018eb7260b..2d8137843ec 100644 --- a/app/mailers/emails/remote_mirrors.rb +++ b/app/mailers/emails/remote_mirrors.rb @@ -6,7 +6,7 @@ module Emails @remote_mirror = RemoteMirrorFinder.new(id: remote_mirror_id).execute @project = @remote_mirror.project - mail(to: recipient(recipient_id), subject: subject('Remote mirror update failed')) + mail(to: recipient(recipient_id, @project.group), subject: subject('Remote mirror update failed')) end end end diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb index 0b740809f30..576caea4c10 100644 --- a/app/mailers/notify.rb +++ b/app/mailers/notify.rb @@ -73,12 +73,22 @@ class Notify < BaseMailer # Look up a User by their ID and return their email address # - # recipient_id - User ID + # recipient_id - User ID + # notification_group - The parent group of the notification # # Returns a String containing the User's email address. - def recipient(recipient_id) + def recipient(recipient_id, notification_group = nil) @current_user = User.find(recipient_id) - @current_user.notification_email + group_notification_email = nil + + if notification_group + notification_settings = notification_group.notification_settings_for(@current_user, hierarchy_order: :asc) + group_notification_email = notification_settings.find { |n| n.notification_email.present? }&.notification_email + end + + # Return group-specific email address if present, otherwise return global + # email address + group_notification_email || @current_user.notification_email end # Formats arguments into a String suitable for use as an email subject diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 56786fae6ea..f743fca423a 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -2,7 +2,6 @@ module Ci class Build < CommitStatus - prepend ArtifactMigratable include Ci::Processable include Ci::Metadatable include Ci::Contextable @@ -21,6 +20,11 @@ module Ci BuildArchivedError = Class.new(StandardError) ignore_column :commands + ignore_column :artifacts_file + ignore_column :artifacts_metadata + ignore_column :artifacts_file_store + ignore_column :artifacts_metadata_store + ignore_column :artifacts_size belongs_to :project, inverse_of: :builds belongs_to :runner @@ -83,13 +87,7 @@ module Ci scope :unstarted, ->() { where(runner_id: nil) } scope :ignore_failures, ->() { where(allow_failure: false) } scope :with_artifacts_archive, ->() do - if Feature.enabled?(:ci_enable_legacy_artifacts) - where('(artifacts_file IS NOT NULL AND artifacts_file <> ?) OR EXISTS (?)', - '', Ci::JobArtifact.select(1).where('ci_builds.id = ci_job_artifacts.job_id').archive) - else - where('EXISTS (?)', - Ci::JobArtifact.select(1).where('ci_builds.id = ci_job_artifacts.job_id').archive) - end + where('EXISTS (?)', Ci::JobArtifact.select(1).where('ci_builds.id = ci_job_artifacts.job_id').archive) end scope :with_existing_job_artifacts, ->(query) do @@ -111,8 +109,8 @@ module Ci scope :eager_load_job_artifacts, -> { includes(:job_artifacts) } - scope :with_artifacts_stored_locally, -> { with_artifacts_archive.where(artifacts_file_store: [nil, LegacyArtifactUploader::Store::LOCAL]) } - scope :with_archived_trace_stored_locally, -> { with_archived_trace.where(artifacts_file_store: [nil, LegacyArtifactUploader::Store::LOCAL]) } + scope :with_artifacts_stored_locally, -> { with_existing_job_artifacts(Ci::JobArtifact.archive.with_files_stored_locally) } + scope :with_archived_trace_stored_locally, -> { with_existing_job_artifacts(Ci::JobArtifact.trace.with_files_stored_locally) } scope :with_artifacts_not_expired, ->() { with_artifacts_archive.where('artifacts_expire_at IS NULL OR artifacts_expire_at > ?', Time.now) } scope :with_expired_artifacts, ->() { with_artifacts_archive.where('artifacts_expire_at < ?', Time.now) } scope :last_month, ->() { where('created_at > ?', Date.today - 1.month) } @@ -142,16 +140,10 @@ module Ci scope :queued_before, ->(time) { where(arel_table[:queued_at].lt(time)) } - ## - # TODO: Remove these mounters when we remove :ci_enable_legacy_artifacts feature flag - mount_uploader :legacy_artifacts_file, LegacyArtifactUploader, mount_on: :artifacts_file - mount_uploader :legacy_artifacts_metadata, LegacyArtifactUploader, mount_on: :artifacts_metadata - acts_as_taggable add_authentication_token_field :token, encrypted: :optional - before_save :update_artifacts_size, if: :artifacts_file_changed? before_save :ensure_token before_destroy { unscoped_project } @@ -159,8 +151,6 @@ module Ci run_after_commit { BuildHooksWorker.perform_async(build.id) } end - update_project_statistics stat: :build_artifacts_size, attribute: :artifacts_size - class << self # This is needed for url_for to work, # as the controller is JobsController @@ -542,6 +532,26 @@ module Ci trace.exist? end + def artifacts_file + job_artifacts_archive&.file + end + + def artifacts_size + job_artifacts_archive&.size + end + + def artifacts_metadata + job_artifacts_metadata&.file + end + + def artifacts? + !artifacts_expired? && artifacts_file&.exists? + end + + def artifacts_metadata? + artifacts? && artifacts_metadata&.exists? + end + def has_job_artifacts? job_artifacts.any? end @@ -610,14 +620,12 @@ module Ci # and use that for `ExpireBuildInstanceArtifactsWorker`? def erase_erasable_artifacts! job_artifacts.erasable.destroy_all # rubocop: disable DestroyAll - erase_old_artifacts! end def erase(opts = {}) return false unless erasable? job_artifacts.destroy_all # rubocop: disable DestroyAll - erase_old_artifacts! erase_trace! update_erased!(opts[:erased_by]) end @@ -655,10 +663,7 @@ module Ci end def artifacts_file_for_type(type) - file = job_artifacts.find_by(file_type: Ci::JobArtifact.file_types[type])&.file - # TODO: to be removed once legacy artifacts is removed - file ||= legacy_artifacts_file if type == :archive - file + job_artifacts.find_by(file_type: Ci::JobArtifact.file_types[type])&.file end def coverage_regex @@ -784,13 +789,6 @@ module Ci private - def erase_old_artifacts! - # TODO: To be removed once we get rid of ci_enable_legacy_artifacts feature flag - remove_artifacts_file! - remove_artifacts_metadata! - save - end - def successful_deployment_status if deployment&.last? :last @@ -812,10 +810,6 @@ module Ci job_artifacts.select { |artifact| artifact.file_type.in?(report_types) } end - def update_artifacts_size - self.artifacts_size = legacy_artifacts_file&.size - end - def erase_trace! trace.erase! end diff --git a/app/models/concerns/artifact_migratable.rb b/app/models/concerns/artifact_migratable.rb deleted file mode 100644 index 7c9f579b480..00000000000 --- a/app/models/concerns/artifact_migratable.rb +++ /dev/null @@ -1,58 +0,0 @@ -# frozen_string_literal: true - -# Adapter class to unify the interface between mounted uploaders and the -# Ci::Artifact model -# Meant to be prepended so the interface can stay the same -module ArtifactMigratable - def artifacts_file - job_artifacts_archive&.file || legacy_artifacts_file - end - - def artifacts_metadata - job_artifacts_metadata&.file || legacy_artifacts_metadata - end - - def artifacts? - !artifacts_expired? && artifacts_file&.exists? - end - - def artifacts_metadata? - artifacts? && artifacts_metadata.exists? - end - - def artifacts_file_changed? - job_artifacts_archive&.file_changed? || attribute_changed?(:artifacts_file) - end - - def remove_artifacts_file! - if job_artifacts_archive - job_artifacts_archive.destroy - else - remove_legacy_artifacts_file! - end - end - - def remove_artifacts_metadata! - if job_artifacts_metadata - job_artifacts_metadata.destroy - else - remove_legacy_artifacts_metadata! - end - end - - def artifacts_size - read_attribute(:artifacts_size).to_i + job_artifacts.sum(:size).to_i - end - - def legacy_artifacts_file - return unless Feature.enabled?(:ci_enable_legacy_artifacts) - - super - end - - def legacy_artifacts_metadata - return unless Feature.enabled?(:ci_enable_legacy_artifacts) - - super - end -end diff --git a/app/models/concerns/referable.rb b/app/models/concerns/referable.rb index 58143a32fdc..4a506146de3 100644 --- a/app/models/concerns/referable.rb +++ b/app/models/concerns/referable.rb @@ -73,6 +73,7 @@ module Referable (?<url> #{Regexp.escape(Gitlab.config.gitlab.url)} \/#{Project.reference_pattern} + (?:\/\-)? \/#{Regexp.escape(route)} \/#{pattern} (?<path> diff --git a/app/models/group.rb b/app/models/group.rb index 53331a19776..cdb4e6e87f6 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -126,10 +126,20 @@ class Group < Namespace # Overrides notification_settings has_many association # This allows to apply notification settings from parent groups # to child groups and projects. - def notification_settings + def notification_settings(hierarchy_order: nil) source_type = self.class.base_class.name + settings = NotificationSetting.where(source_type: source_type, source_id: self_and_ancestors_ids) - NotificationSetting.where(source_type: source_type, source_id: self_and_ancestors_ids) + return settings unless hierarchy_order && self_and_ancestors_ids.length > 1 + + settings + .joins("LEFT JOIN (#{self_and_ancestors(hierarchy_order: hierarchy_order).to_sql}) AS ordered_groups ON notification_settings.source_id = ordered_groups.id") + .select('notification_settings.*, ordered_groups.depth AS depth') + .order("ordered_groups.depth #{hierarchy_order}") + end + + def notification_settings_for(user, hierarchy_order: nil) + notification_settings(hierarchy_order: hierarchy_order).where(user: user) end def to_reference(_from = nil, full: nil) diff --git a/app/models/milestone.rb b/app/models/milestone.rb index 787600569fa..37c129e843a 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -58,6 +58,7 @@ class Milestone < ApplicationRecord validate :uniqueness_of_title, if: :title_changed? validate :milestone_type_check validate :start_date_should_be_less_than_due_date, if: proc { |m| m.start_date.present? && m.due_date.present? } + validate :dates_within_4_digits strip_attributes :title @@ -326,6 +327,16 @@ class Milestone < ApplicationRecord end end + def dates_within_4_digits + if start_date && start_date > Date.new(9999, 12, 31) + errors.add(:start_date, _("date must not be after 9999-12-31")) + end + + if due_date && due_date > Date.new(9999, 12, 31) + errors.add(:due_date, _("date must not be after 9999-12-31")) + end + end + def issues_finder_params { project_id: project_id } end diff --git a/app/models/namespace.rb b/app/models/namespace.rb index f7c31890198..3c270c7396a 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -206,12 +206,12 @@ class Namespace < ApplicationRecord .ancestors(upto: top, hierarchy_order: hierarchy_order) end - def self_and_ancestors + def self_and_ancestors(hierarchy_order: nil) return self.class.where(id: id) unless parent_id Gitlab::ObjectHierarchy .new(self.class.where(id: id)) - .base_and_ancestors + .base_and_ancestors(hierarchy_order: hierarchy_order) end # Returns all the descendants of the current namespace. diff --git a/app/models/notification_recipient.rb b/app/models/notification_recipient.rb index 6889e0d776b..6f057f79ef6 100644 --- a/app/models/notification_recipient.rb +++ b/app/models/notification_recipient.rb @@ -156,23 +156,11 @@ class NotificationRecipient # Returns the notification_setting of the lowest group in hierarchy with non global level def closest_non_global_group_notification_settting return unless @group - return if indexed_group_notification_settings.empty? - notification_setting = nil - - @group.self_and_ancestors_ids.each do |id| - notification_setting = indexed_group_notification_settings[id] - break if notification_setting - end - - notification_setting - end - - def indexed_group_notification_settings - strong_memoize(:indexed_group_notification_settings) do - @group.notification_settings.where(user_id: user.id) - .where.not(level: NotificationSetting.levels[:global]) - .index_by(&:source_id) - end + @group + .notification_settings(hierarchy_order: :asc) + .where(user: user) + .where.not(level: NotificationSetting.levels[:global]) + .first end end diff --git a/app/models/project_statistics.rb b/app/models/project_statistics.rb index 832c8417b5b..dd0654aec0b 100644 --- a/app/models/project_statistics.rb +++ b/app/models/project_statistics.rb @@ -48,7 +48,7 @@ class ProjectStatistics < ApplicationRecord # older migrations fail due to non-existent attribute without this def packages_size - has_attribute?(:packages_size) ? super.to_i : 0 + has_attribute?(:packages_size) ? super : 0 end def update_storage_size diff --git a/app/uploaders/legacy_artifact_uploader.rb b/app/uploaders/legacy_artifact_uploader.rb deleted file mode 100644 index fac3c3dcb8f..00000000000 --- a/app/uploaders/legacy_artifact_uploader.rb +++ /dev/null @@ -1,27 +0,0 @@ -# frozen_string_literal: true - -## -# TODO: Remove this uploader when we remove :ci_enable_legacy_artifacts feature flag -# See https://gitlab.com/gitlab-org/gitlab-ce/issues/58595 -class LegacyArtifactUploader < GitlabUploader - extend Workhorse::UploadPath - include ObjectStorage::Concern - - ObjectNotReadyError = Class.new(StandardError) - - storage_options Gitlab.config.artifacts - - alias_method :upload, :model - - def store_dir - dynamic_segment - end - - private - - def dynamic_segment - raise ObjectNotReadyError, 'Build is not ready' unless model.id - - File.join(model.created_at.utc.strftime('%Y_%m'), model.project_id.to_s, model.id.to_s) - end -end diff --git a/app/views/import/gitlab_projects/new.html.haml b/app/views/import/gitlab_projects/new.html.haml index 5e4595d930b..a19c8911559 100644 --- a/app/views/import/gitlab_projects/new.html.haml +++ b/app/views/import/gitlab_projects/new.html.haml @@ -7,28 +7,7 @@ %hr = form_tag import_gitlab_project_path, class: 'new_project', multipart: true do - .row - .form-group.project-name.col-sm-12 - = label_tag :name, _('Project name'), class: 'label-bold' - = text_field_tag :name, @name, placeholder: "My awesome project", class: "js-project-name form-control input-lg", autofocus: true - .form-group.col-12.col-sm-6 - = label_tag :namespace_id, _('Project URL'), class: 'label-bold' - .form-group - .input-group - - if current_user.can_select_namespace? - .input-group-prepend.has-tooltip{ title: root_url } - .input-group-text - = root_url - = select_tag :namespace_id, namespaces_options(namespace_id_from(params) || :current_user, display_path: true, extra_group: namespace_id_from(params)), class: 'select2 js-select-namespace', tabindex: 1 - - - else - .input-group-prepend.static-namespace.has-tooltip{ title: user_url(current_user.username) + '/' } - .input-group-text.border-0 - #{user_url(current_user.username)}/ - = hidden_field_tag :namespace_id, value: current_user.namespace_id - .form-group.col-12.col-sm-6.project-path - = label_tag :path, _('Project slug'), class: 'label-bold' - = text_field_tag :path, @path, placeholder: "my-awesome-project", class: "js-path-name form-control", tabindex: 2, required: true + = render 'import/shared/new_project_form' .row .form-group.col-md-12 diff --git a/app/views/import/manifest/new.html.haml b/app/views/import/manifest/new.html.haml index 056e4922b9e..df00c4d2179 100644 --- a/app/views/import/manifest/new.html.haml +++ b/app/views/import/manifest/new.html.haml @@ -4,9 +4,5 @@ %h3.page-title = _('Manifest file import') -- if @errors.present? - .alert.alert-danger - - @errors.each do |error| - = error - += render 'import/shared/errors' = render 'form' diff --git a/app/views/import/phabricator/new.html.haml b/app/views/import/phabricator/new.html.haml new file mode 100644 index 00000000000..811e126579e --- /dev/null +++ b/app/views/import/phabricator/new.html.haml @@ -0,0 +1,25 @@ +- title = _('Phabricator Server Import') +- page_title title +- breadcrumb_title title +- header_title _("Projects"), root_path + +%h3.page-title + = icon 'issues', text: _('Import tasks from Phabricator into issues') + += render 'import/shared/errors' + += form_tag import_phabricator_path, class: 'new_project', method: :post do + = render 'import/shared/new_project_form' + + %h4.prepend-top-0= _('Enter in your Phabricator Server URL and personal access token below') + + .form-group.row + = label_tag :phabricator_server_url, _('Phabricator Server URL'), class: 'col-form-label col-md-2' + .col-md-4 + = text_field_tag :phabricator_server_url, params[:phabricator_server_url], class: 'form-control append-right-8', placeholder: 'https://your-phabricator-server', size: 40 + .form-group.row + = label_tag :api_token, _('API Token'), class: 'col-form-label col-md-2' + .col-md-4 + = password_field_tag :api_token, params[:api_token], class: 'form-control append-right-8', placeholder: _('Personal Access Token'), size: 40 + .form-actions + = submit_tag _('Import tasks'), class: 'btn btn-success' diff --git a/app/views/import/shared/_errors.html.haml b/app/views/import/shared/_errors.html.haml new file mode 100644 index 00000000000..de60c15351f --- /dev/null +++ b/app/views/import/shared/_errors.html.haml @@ -0,0 +1,4 @@ +- if @errors.present? + .alert.alert-danger + - @errors.each do |error| + = error diff --git a/app/views/import/shared/_new_project_form.html.haml b/app/views/import/shared/_new_project_form.html.haml new file mode 100644 index 00000000000..4d13d4f2869 --- /dev/null +++ b/app/views/import/shared/_new_project_form.html.haml @@ -0,0 +1,21 @@ +.row + .form-group.project-name.col-sm-12 + = label_tag :name, _('Project name'), class: 'label-bold' + = text_field_tag :name, @name, placeholder: "My awesome project", class: "js-project-name form-control input-lg", autofocus: true + .form-group.col-12.col-sm-6 + = label_tag :namespace_id, _('Project URL'), class: 'label-bold' + .form-group + .input-group.flex-nowrap + - if current_user.can_select_namespace? + .input-group-prepend.flex-shrink-0.has-tooltip{ title: root_url } + .input-group-text + = root_url + = select_tag :namespace_id, namespaces_options(namespace_id_from(params) || :current_user, display_path: true, extra_group: namespace_id_from(params)), class: 'select2 js-select-namespace', tabindex: 1 + - else + .input-group-prepend.static-namespace.has-tooltip{ title: user_url(current_user.username) + '/' } + .input-group-text.border-0 + #{user_url(current_user.username)}/ + = hidden_field_tag :namespace_id, value: current_user.namespace_id + .form-group.col-12.col-sm-6.project-path + = label_tag :path, _('Project slug'), class: 'label-bold' + = text_field_tag :path, @path, placeholder: "my-awesome-project", class: "js-path-name form-control", tabindex: 2, required: true diff --git a/app/views/profiles/emails/index.html.haml b/app/views/profiles/emails/index.html.haml index 1823f191fb3..c90a0b3e329 100644 --- a/app/views/profiles/emails/index.html.haml +++ b/app/views/profiles/emails/index.html.haml @@ -26,7 +26,9 @@ %li Your Commit Email will be used for web based operations, such as edits and merges. %li - Your Notification Email will be used for account notifications. + Your Default Notification Email will be used for account notifications if a + = link_to 'group-specific email address', profile_notifications_path + is not set. %li Your Public Email will be displayed on your public profile. %li @@ -41,7 +43,7 @@ - if @primary_email === current_user.public_email %span.badge.badge-info Public email - if @primary_email === current_user.notification_email - %span.badge.badge-info Notification email + %span.badge.badge-info Default notification email - @emails.each do |email| %li = render partial: 'shared/email_with_badge', locals: { email: email.email, verified: email.confirmed? } diff --git a/app/views/profiles/notifications/_group_settings.html.haml b/app/views/profiles/notifications/_group_settings.html.haml index a12246bcdcc..cf17ee44145 100644 --- a/app/views/profiles/notifications/_group_settings.html.haml +++ b/app/views/profiles/notifications/_group_settings.html.haml @@ -1,12 +1,17 @@ -%li.notification-list-item - %span.notification.fa.fa-holder.append-right-5 - - if setting.global? - = notification_icon(current_user.global_notification_setting.level) - - else - = notification_icon(setting.level) +.gl-responsive-table-row.notification-list-item + .table-section.section-40 + %span.notification.fa.fa-holder.append-right-5 + - if setting.global? + = notification_icon(current_user.global_notification_setting.level) + - else + = notification_icon(setting.level) - %span.str-truncated - = link_to group.name, group_path(group) + %span.str-truncated + = link_to group.name, group_path(group) - .float-right + .table-section.section-30.text-right = render 'shared/notifications/button', notification_setting: setting + + .table-section.section-30 + = form_for @user.notification_settings.find { |ns| ns.source == group }, url: profile_notifications_group_path(group), method: :put, html: { class: 'update-notifications' } do |f| + = f.select :notification_email, @user.all_emails, { include_blank: 'Global notification email' }, class: 'select2 js-group-notification-email' diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml index fa35fbd3961..1f311e9a4a4 100644 --- a/app/views/profiles/notifications/show.html.haml +++ b/app/views/profiles/notifications/show.html.haml @@ -41,9 +41,8 @@ %h5 = _('Groups (%{count})') % { count: @group_notifications.count } %div - %ul.bordered-list - - @group_notifications.each do |setting| - = render 'group_settings', setting: setting, group: setting.source + - @group_notifications.each do |setting| + = render 'group_settings', setting: setting, group: setting.source %h5 = _('Projects (%{count})') % { count: @project_notifications.count } %p.account-well diff --git a/app/views/profiles/passwords/edit.html.haml b/app/views/profiles/passwords/edit.html.haml index 0b4b9841ea1..ac8c31189d0 100644 --- a/app/views/profiles/passwords/edit.html.haml +++ b/app/views/profiles/passwords/edit.html.haml @@ -1,5 +1,5 @@ -- breadcrumb_title "Edit Password" -- page_title "Password" +- breadcrumb_title _('Edit Password') +- page_title _('Password') - @content_class = "limit-container-width" unless fluid_layout .row.prepend-top-default @@ -7,28 +7,29 @@ %h4.prepend-top-0 = page_title %p - After a successful password update, you will be redirected to the login page where you can log in with your new password. + = _('After a successful password update, you will be redirected to the login page where you can log in with your new password.') .col-lg-8 %h5.prepend-top-0 - Change your password - - unless @user.password_automatically_set? - or recover your current one + - if @user.password_automatically_set + = _('Change your password') + - else + = _('Change your password or recover your current one') = form_for @user, url: profile_password_path, method: :put, html: {class: "update-password"} do |f| = form_errors(@user) - unless @user.password_automatically_set? .form-group - = f.label :current_password, class: 'label-bold' + = f.label :current_password, _('Current password'), class: 'label-bold' = f.password_field :current_password, required: true, class: 'form-control' %p.form-text.text-muted - You must provide your current password in order to change it. + = _('You must provide your current password in order to change it.') .form-group - = f.label :password, 'New password', class: 'label-bold' + = f.label :password, _('New password'), class: 'label-bold' = f.password_field :password, required: true, class: 'form-control' .form-group - = f.label :password_confirmation, class: 'label-bold' + = f.label :password_confirmation, _('Password confirmation'), class: 'label-bold' = f.password_field :password_confirmation, required: true, class: 'form-control' .prepend-top-default.append-bottom-default - = f.submit 'Save password', class: "btn btn-success append-right-10" + = f.submit _('Save password'), class: "btn btn-success append-right-10" - unless @user.password_automatically_set? - = link_to "I forgot my password", reset_profile_password_path, method: :put, class: "account-btn-link" + = link_to _('I forgot my password'), reset_profile_password_path, method: :put, class: "account-btn-link" diff --git a/app/views/profiles/passwords/new.html.haml b/app/views/profiles/passwords/new.html.haml index 081166270ab..ce60455ab89 100644 --- a/app/views/profiles/passwords/new.html.haml +++ b/app/views/profiles/passwords/new.html.haml @@ -14,17 +14,17 @@ - unless @user.password_automatically_set? .form-group.row .col-sm-2.col-form-label - = f.label :current_password + = f.label :current_password, _('Current password') .col-sm-10 = f.password_field :current_password, required: true, class: 'form-control' .form-group.row .col-sm-2.col-form-label - = f.label :password + = f.label :password, _('New password') .col-sm-10 = f.password_field :password, required: true, class: 'form-control' .form-group.row .col-sm-2.col-form-label - = f.label :password_confirmation + = f.label :password_confirmation, _('Password confirmation') .col-sm-10 = f.password_field :password_confirmation, required: true, class: 'form-control' .form-actions diff --git a/app/views/profiles/two_factor_auths/_codes.html.haml b/app/views/profiles/two_factor_auths/_codes.html.haml index 759d39cf5f5..be0af977011 100644 --- a/app/views/profiles/two_factor_auths/_codes.html.haml +++ b/app/views/profiles/two_factor_auths/_codes.html.haml @@ -1,8 +1,6 @@ %p.slead - Should you ever lose your phone or access to your one time password secret, each of these recovery codes can be used one - time each to regain access to your account. Please save them in a safe place, or you - %b will - lose access to your account. + - lose_2fa_message = _('Should you ever lose your phone or access to your one time password secret, each of these recovery codes can be used one time each to regain access to your account. Please save them in a safe place, or you %{b_start}will%{b_end} lose access to your account.') % { b_start:'<b>', b_end:'</b>' } + = lose_2fa_message.html_safe .codes.card %ul @@ -11,5 +9,5 @@ %span.monospace= code .d-flex - = link_to 'Proceed', profile_account_path, class: 'btn btn-success append-right-10' - = link_to 'Download codes', "data:text/plain;charset=utf-8,#{CGI.escape(@codes.join("\n"))}", download: "gitlab-recovery-codes.txt", class: 'btn btn-default' + = link_to _('Proceed'), profile_account_path, class: 'btn btn-success append-right-10' + = link_to _('Download codes'), "data:text/plain;charset=utf-8,#{CGI.escape(@codes.join("\n"))}", download: "gitlab-recovery-codes.txt", class: 'btn btn-default' diff --git a/app/views/profiles/two_factor_auths/codes.html.haml b/app/views/profiles/two_factor_auths/codes.html.haml index addf356697a..53907ebffab 100644 --- a/app/views/profiles/two_factor_auths/codes.html.haml +++ b/app/views/profiles/two_factor_auths/codes.html.haml @@ -1,5 +1,6 @@ -- page_title 'Recovery Codes', 'Two-factor Authentication' +- page_title _('Recovery Codes'), _('Two-factor Authentication') -%h3.page-title Two-factor Authentication Recovery codes +%h3.page-title + = _('Two-factor Authentication Recovery codes') %hr = render 'codes' diff --git a/app/views/profiles/two_factor_auths/create.html.haml b/app/views/profiles/two_factor_auths/create.html.haml index e330aadac13..973eb8136c4 100644 --- a/app/views/profiles/two_factor_auths/create.html.haml +++ b/app/views/profiles/two_factor_auths/create.html.haml @@ -1,6 +1,6 @@ -- page_title 'Two-factor Authentication', 'Account' +- page_title _('Two-factor Authentication'), _('Account') .alert.alert-success - Congratulations! You have enabled Two-factor Authentication! + = _('Congratulations! You have enabled Two-factor Authentication!') = render 'codes' diff --git a/app/views/profiles/two_factor_auths/show.html.haml b/app/views/profiles/two_factor_auths/show.html.haml index d986c566928..5501e63e027 100644 --- a/app/views/profiles/two_factor_auths/show.html.haml +++ b/app/views/profiles/two_factor_auths/show.html.haml @@ -1,72 +1,68 @@ -- page_title 'Two-Factor Authentication', 'Account' -- add_to_breadcrumbs("Two-Factor Authentication", profile_account_path) +- page_title _('Two-Factor Authentication'), _('Account') +- add_to_breadcrumbs(_('Two-Factor Authentication'), profile_account_path) - @content_class = "limit-container-width" unless fluid_layout .js-two-factor-auth{ 'data-two-factor-skippable' => "#{two_factor_skippable?}", 'data-two_factor_skip_url' => skip_profile_two_factor_auth_path } .row.prepend-top-default .col-lg-4 %h4.prepend-top-0 - Register Two-Factor Authenticator + = _('Register Two-Factor Authenticator') %p - Use an one time password authenticator on your mobile device or computer to enable two-factor authentication (2FA). + = _('Use an one time password authenticator on your mobile device or computer to enable two-factor authentication (2FA).') .col-lg-8 - if current_user.two_factor_otp_enabled? %p - You've already enabled two-factor authentication using one time password authenticators. In order to register a different device, you must first disable two-factor authentication. + = _("You've already enabled two-factor authentication using one time password authenticators. In order to register a different device, you must first disable two-factor authentication.") %p - If you lose your recovery codes you can generate new ones, invalidating all previous codes. + = _('If you lose your recovery codes you can generate new ones, invalidating all previous codes.') %div - = link_to 'Disable two-factor authentication', profile_two_factor_auth_path, + = link_to _('Disable two-factor authentication'), profile_two_factor_auth_path, method: :delete, - data: { confirm: "Are you sure? This will invalidate your registered applications and U2F devices." }, + data: { confirm: _('Are you sure? This will invalidate your registered applications and U2F devices.') }, class: 'btn btn-danger append-right-10' = form_tag codes_profile_two_factor_auth_path, {style: 'display: inline-block', method: :post} do |f| - = submit_tag 'Regenerate recovery codes', class: 'btn' + = submit_tag _('Regenerate recovery codes'), class: 'btn' - else %p - Install a soft token authenticator like <a href="https://freeotp.github.io/">FreeOTP</a> - or Google Authenticator from your application repository and scan this QR code. - More information is available in the #{link_to('documentation', help_page_path('user/profile/account/two_factor_authentication'))}. + - help_link_start = '<a href="%{url}" target="_blank">' % { url: help_page_path('user/profile/account/two_factor_authentication') } + - register_2fa_token = _('Install a soft token authenticator like %{free_otp_link} or Google Authenticator from your application repository and scan this QR code. More information is available in the %{help_link_start}documentation%{help_link_end}.') % { free_otp_link:'<a href="https://freeotp.github.io/">FreeOTP</a>', help_link_start:help_link_start, help_link_end:'</a>' } + = register_2fa_token.html_safe .row.append-bottom-10 .col-md-4 = raw @qr_code .col-md-8 .account-well %p.prepend-top-0.append-bottom-0 - Can't scan the code? + = _("Can't scan the code?") %p.prepend-top-0.append-bottom-0 - To add the entry manually, provide the following details to the application on your phone. + = _('To add the entry manually, provide the following details to the application on your phone.') %p.prepend-top-0.append-bottom-0 - Account: - = @account_string + = _('Account: %{account}') % { account: @account_string } %p.prepend-top-0.append-bottom-0 - Key: - = current_user.otp_secret.scan(/.{4}/).join(' ') + = _('Key: %{key}') %{ key: current_user.otp_secret.scan(/.{4}/).join(' ') } %p.two-factor-new-manual-content - Time based: Yes + = _('Time based: Yes') = form_tag profile_two_factor_auth_path, method: :post do |f| - if @error .alert.alert-danger = @error .form-group - = label_tag :pin_code, nil, class: "label-bold" + = label_tag :pin_code, _('Pin code'), class: "label-bold" = text_field_tag :pin_code, nil, class: "form-control", required: true .prepend-top-default - = submit_tag 'Register with two-factor app', class: 'btn btn-success' + = submit_tag _('Register with two-factor app'), class: 'btn btn-success' %hr .row.prepend-top-default .col-lg-4 %h4.prepend-top-0 - Register Universal Two-Factor (U2F) Device + = _('Register Universal Two-Factor (U2F) Device') %p - Use a hardware device to add the second factor of authentication. + = _('Use a hardware device to add the second factor of authentication.') %p - As U2F devices are only supported by a few browsers, we require that you set up a - two-factor authentication app before a U2F device. That way you'll always be able to - log in - even when you're using an unsupported browser. + = _("As U2F devices are only supported by a few browsers, we require that you set up a two-factor authentication app before a U2F device. That way you'll always be able to log in - even when you're using an unsupported browser.") .col-lg-8 - if @u2f_registration.errors.present? = form_errors(@u2f_registration) @@ -74,7 +70,8 @@ %hr - %h5 U2F Devices (#{@u2f_registrations.length}) + %h5 + = _('U2F Devices (%{length})') % { length: @u2f_registrations.length } - if @u2f_registrations.present? .table-responsive @@ -85,16 +82,16 @@ %col{ width: "20%" } %thead %tr - %th Name - %th Registered On + %th= _('Name') + %th= s_('2FADevice|Registered On') %th %tbody - @u2f_registrations.each do |registration| %tr - %td= registration.name.presence || "<no name set>" + %td= registration.name.presence || _("<no name set>") %td= registration.created_at.to_date.to_s(:medium) - %td= link_to "Delete", profile_u2f_registration_path(registration), method: :delete, class: "btn btn-danger float-right", data: { confirm: "Are you sure you want to delete this device? This action cannot be undone." } + %td= link_to _('Delete'), profile_u2f_registration_path(registration), method: :delete, class: "btn btn-danger float-right", data: { confirm: _('Are you sure you want to delete this device? This action cannot be undone.') } - else .settings-message.text-center - You don't have any U2F devices registered yet. + = _("You don't have any U2F devices registered yet.") diff --git a/app/views/projects/_import_project_pane.html.haml b/app/views/projects/_import_project_pane.html.haml index 9c854369c93..b5678b56ca6 100644 --- a/app/views/projects/_import_project_pane.html.haml +++ b/app/views/projects/_import_project_pane.html.haml @@ -63,6 +63,13 @@ = link_to new_import_manifest_path, class: 'btn import_manifest', data: { track_label: "#{track_label}", track_event: "click_button", track_property: "manifest_file" } do = icon('file-text-o', text: 'Manifest file') + - if phabricator_import_enabled? + %div + = link_to new_import_phabricator_path, class: 'btn import_phabricator', data: { track_label: "#{track_label}", track_event: "click_button", track_property: "phabricator" } do + = custom_icon('issues') + = _("Phabricator Tasks") + + .js-toggle-content.toggle-import-form{ class: ('hide' if active_tab != 'import') } = form_for @project, html: { class: 'new_project' } do |f| %hr diff --git a/app/views/shared/_old_visibility_level.html.haml b/app/views/shared/_old_visibility_level.html.haml index fd576e4fbea..e8f3d888cce 100644 --- a/app/views/shared/_old_visibility_level.html.haml +++ b/app/views/shared/_old_visibility_level.html.haml @@ -1,6 +1,6 @@ .form-group.row .col-sm-2.col-form-label = _('Visibility level') - = link_to icon('question-circle'), help_page_path("public_access/public_access") + = link_to icon('question-circle'), help_page_path("public_access/public_access"), target: '_blank' .col-sm-10 = render 'shared/visibility_level', f: f, visibility_level: visibility_level, can_change_visibility_level: can_change_visibility_level, form_model: form_model, with_label: with_label diff --git a/app/views/shared/notifications/_button.html.haml b/app/views/shared/notifications/_button.html.haml index 31cc0c091dd..749aa258af6 100644 --- a/app/views/shared/notifications/_button.html.haml +++ b/app/views/shared/notifications/_button.html.haml @@ -1,14 +1,14 @@ - btn_class = local_assigns.fetch(:btn_class, nil) - if notification_setting - .js-notification-dropdown.notification-dropdown.home-panel-action-button.dropdown.inline + .js-notification-dropdown.notification-dropdown.mr-md-2.home-panel-action-button.dropdown.inline = form_for notification_setting, remote: true, html: { class: "inline notification-form" } do |f| = hidden_setting_source_input(notification_setting) = f.hidden_field :level, class: "notification_setting_level" .js-notification-toggle-btns %div{ class: ("btn-group" if notification_setting.custom?) } - if notification_setting.custom? - %button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn#notifications-button{ type: "button", title: _("Notification setting"), class: "#{btn_class}", "aria-label" => _("Notification setting - %{notification_title}") % { notification_title: notification_title(notification_setting.level) }, data: { container: "body", toggle: "modal", target: "#" + notifications_menu_identifier("modal", notification_setting), display: 'static' } } + %button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn.text-left#notifications-button{ type: "button", title: _("Notification setting"), class: "#{btn_class}", "aria-label" => _("Notification setting - %{notification_title}") % { notification_title: notification_title(notification_setting.level) }, data: { container: "body", toggle: "modal", target: "#" + notifications_menu_identifier("modal", notification_setting), display: 'static' } } = icon("bell", class: "js-notification-loading") = notification_title(notification_setting.level) %button.btn.dropdown-toggle{ data: { toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } } @@ -16,9 +16,11 @@ .sr-only Toggle dropdown - else %button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn#notifications-button{ type: "button", title: _("Notification setting"), class: "#{btn_class}", "aria-label" => _("Notification setting - %{notification_title}") % { notification_title: notification_title(notification_setting.level) }, data: { container: "body", toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } } - = icon("bell", class: "js-notification-loading") - = notification_title(notification_setting.level) - = icon("caret-down") + .float-left + = icon("bell", class: "js-notification-loading") + = notification_title(notification_setting.level) + .float-right + = icon("caret-down") = render "shared/notifications/notification_dropdown", notification_setting: notification_setting diff --git a/changelogs/unreleased/50850-kerrizor-extend-api-to-accept-start_project-option.yml b/changelogs/unreleased/50850-kerrizor-extend-api-to-accept-start_project-option.yml new file mode 100644 index 00000000000..45770e1012c --- /dev/null +++ b/changelogs/unreleased/50850-kerrizor-extend-api-to-accept-start_project-option.yml @@ -0,0 +1,5 @@ +--- +title: Add API support for committing changes to different projects in same fork network +merge_request: 27915 +author: +type: added diff --git a/changelogs/unreleased/59587-add-graphql-logging.yml b/changelogs/unreleased/59587-add-graphql-logging.yml new file mode 100644 index 00000000000..74c2a734f37 --- /dev/null +++ b/changelogs/unreleased/59587-add-graphql-logging.yml @@ -0,0 +1,5 @@ +--- +title: Add dedicated logging for GraphQL queries +merge_request: 27885 +author: +type: other diff --git a/changelogs/unreleased/60987-emoji-picker-popup.yml b/changelogs/unreleased/60987-emoji-picker-popup.yml new file mode 100644 index 00000000000..3bccec8e164 --- /dev/null +++ b/changelogs/unreleased/60987-emoji-picker-popup.yml @@ -0,0 +1,5 @@ +--- +title: Fix emoji picker visibility issue +merge_request: 28984 +author: +type: fixed diff --git a/changelogs/unreleased/61988-collapse-icon-on-merge-request-diff-larger-than-profile-picture.yml b/changelogs/unreleased/61988-collapse-icon-on-merge-request-diff-larger-than-profile-picture.yml new file mode 100644 index 00000000000..46d3f439a44 --- /dev/null +++ b/changelogs/unreleased/61988-collapse-icon-on-merge-request-diff-larger-than-profile-picture.yml @@ -0,0 +1,5 @@ +--- +title: Change collapse icon size to size of profile picture +merge_request: 28512 +author: Marcel van Remmerden +type: other diff --git a/changelogs/unreleased/62432-fix-participants-wrapping.yml b/changelogs/unreleased/62432-fix-participants-wrapping.yml new file mode 100644 index 00000000000..a7e4bd372de --- /dev/null +++ b/changelogs/unreleased/62432-fix-participants-wrapping.yml @@ -0,0 +1,5 @@ +--- +title: Fix participants list wrapping +merge_request: 28873 +author: +type: fixed diff --git a/changelogs/unreleased/ac-namespaces-stats-no-coalesce.yml b/changelogs/unreleased/ac-namespaces-stats-no-coalesce.yml new file mode 100644 index 00000000000..bd005206d4e --- /dev/null +++ b/changelogs/unreleased/ac-namespaces-stats-no-coalesce.yml @@ -0,0 +1,5 @@ +--- +title: Forbid NULL in project_statistics.packages_size +merge_request: 28400 +author: +type: other diff --git a/changelogs/unreleased/add-constraint-for-milestone-dates.yml b/changelogs/unreleased/add-constraint-for-milestone-dates.yml new file mode 100644 index 00000000000..485149cf62e --- /dev/null +++ b/changelogs/unreleased/add-constraint-for-milestone-dates.yml @@ -0,0 +1,5 @@ +--- +title: Limit milestone dates to before year 9999 +merge_request: 28742 +author: Luke Picciau +type: fixed diff --git a/changelogs/unreleased/gitaly-version-v1.43.0.yml b/changelogs/unreleased/gitaly-version-v1.43.0.yml new file mode 100644 index 00000000000..67acd2725e1 --- /dev/null +++ b/changelogs/unreleased/gitaly-version-v1.43.0.yml @@ -0,0 +1,5 @@ +--- +title: Upgrade to Gitaly v1.43.0 +merge_request: 28867 +author: +type: changed diff --git a/changelogs/unreleased/gt-open-visibility-help-link-in-a-new-tab.yml b/changelogs/unreleased/gt-open-visibility-help-link-in-a-new-tab.yml new file mode 100644 index 00000000000..35515c9d639 --- /dev/null +++ b/changelogs/unreleased/gt-open-visibility-help-link-in-a-new-tab.yml @@ -0,0 +1,5 @@ +--- +title: Open visibility help link in a new tab +merge_request: 28603 +author: George Tsiolis +type: fixed diff --git a/changelogs/unreleased/remove-legacy-artifacts-related-code.yml b/changelogs/unreleased/remove-legacy-artifacts-related-code.yml new file mode 100644 index 00000000000..acde65af2d4 --- /dev/null +++ b/changelogs/unreleased/remove-legacy-artifacts-related-code.yml @@ -0,0 +1,5 @@ +--- +title: Remove legacy artifact related code +merge_request: 26475 +author: +type: other diff --git a/changelogs/unreleased/weimeng-email-routing.yml b/changelogs/unreleased/weimeng-email-routing.yml new file mode 100644 index 00000000000..6536433bd03 --- /dev/null +++ b/changelogs/unreleased/weimeng-email-routing.yml @@ -0,0 +1,5 @@ +--- +title: Add ability to define notification email addresses for groups you belong to. +merge_request: 25299 +author: +type: added diff --git a/config/routes/import.rb b/config/routes/import.rb index 24013eb2c88..9fe2688de1e 100644 --- a/config/routes/import.rb +++ b/config/routes/import.rb @@ -67,4 +67,6 @@ namespace :import do get :jobs post :upload end + + resource :phabricator, only: [:create, :new], controller: :phabricator end diff --git a/config/routes/profile.rb b/config/routes/profile.rb index c1cac3905f1..0e213b0b989 100644 --- a/config/routes/profile.rb +++ b/config/routes/profile.rb @@ -17,7 +17,11 @@ resource :profile, only: [:show, :update] do delete :unlink end end - resource :notifications, only: [:show, :update] + + resource :notifications, only: [:show, :update] do + resources :groups, only: :update + end + resource :password, only: [:new, :create, :edit, :update] do member do put :reset diff --git a/config/routes/project.rb b/config/routes/project.rb index 1e94bdc245f..840dc4c7844 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -124,6 +124,44 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do put :revoke end end + + resources :milestones, constraints: { id: /\d+/ } do + member do + post :promote + put :sort_issues + put :sort_merge_requests + get :merge_requests + get :participants + get :labels + end + end + + resources :labels, except: [:show], constraints: { id: /\d+/ } do + collection do + post :generate + post :set_priorities + end + + member do + post :promote + post :toggle_subscription + delete :remove_priority + end + end + + resources :services, constraints: { id: %r{[^/]+} }, only: [:edit, :update] do + member do + put :test + end + end + + resources :boards, only: [:index, :show], constraints: { id: /\d+/ } + resources :releases, only: [:index] + resources :forks, only: [:index, :new, :create] + resources :group_links, only: [:index, :create, :update, :destroy], constraints: { id: /\d+/ } + + resource :import, only: [:new, :create, :show] + resource :avatar, only: [:show, :destroy] end # End of the /-/ scope. @@ -132,7 +170,6 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do # get '/templates/:template_type/:key' => 'templates#show', as: :template, constraints: { key: %r{[^/]+} } - resource :avatar, only: [:show, :destroy] resources :commit, only: [:show], constraints: { id: /\h{7,40}/ } do member do get :branches @@ -159,12 +196,6 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do end end - resources :services, constraints: { id: %r{[^/]+} }, only: [:edit, :update] do - member do - put :test - end - end - resource :mattermost, only: [:new, :create] namespace :prometheus do @@ -173,10 +204,6 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do end end - resources :releases, only: [:index] - resources :forks, only: [:index, :new, :create] - resource :import, only: [:new, :create, :show] - resources :merge_requests, concerns: :awardable, except: [:new, :create], constraints: { id: /\d+/ } do member do get :commit_change_content @@ -372,31 +399,8 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do end end - resources :milestones, constraints: { id: /\d+/ } do - member do - post :promote - put :sort_issues - put :sort_merge_requests - get :merge_requests - get :participants - get :labels - end - end - - resources :labels, except: [:show], constraints: { id: /\d+/ } do - collection do - post :generate - post :set_priorities - end - - member do - post :promote - post :toggle_subscription - delete :remove_priority - end - end - get :issues, to: 'issues#calendar', constraints: lambda { |req| req.format == :ics } + resources :issues, concerns: :awardable, constraints: { id: /\d+/ } do member do post :toggle_subscription @@ -408,14 +412,13 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do post :create_merge_request get :discussions, format: :json end + collection do post :bulk_update post :import_csv end end - resources :group_links, only: [:index, :create, :update, :destroy], constraints: { id: /\d+/ } - resources :notes, only: [:create, :destroy, :update], concerns: :awardable, constraints: { id: /\d+/ } do member do delete :delete_attachment @@ -426,8 +429,6 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do get 'noteable/:target_type/:target_id/notes' => 'notes#index', as: 'noteable_notes' - resources :boards, only: [:index, :show], constraints: { id: /\d+/ } - resources :todos, only: [:create] resources :uploads, only: [:create] do @@ -510,7 +511,9 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do as: :project) do Gitlab::Routing.redirect_legacy_paths(self, :settings, :branches, :tags, :network, :graphs, :autocomplete_sources, - :project_members, :deploy_keys, :deploy_tokens) + :project_members, :deploy_keys, :deploy_tokens, + :labels, :milestones, :services, :boards, :releases, + :forks, :group_links, :import, :avatar) end end end diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml index 8bc2426ec4c..0615da2d241 100644 --- a/config/sidekiq_queues.yml +++ b/config/sidekiq_queues.yml @@ -91,3 +91,4 @@ - [chat_notification, 2] - [migrate_external_diffs, 1] - [update_project_statistics, 1] + - [phabricator_import_import_tasks, 1] diff --git a/db/migrate/20190327163904_add_notification_email_to_notification_settings.rb b/db/migrate/20190327163904_add_notification_email_to_notification_settings.rb new file mode 100644 index 00000000000..2f3069032a1 --- /dev/null +++ b/db/migrate/20190327163904_add_notification_email_to_notification_settings.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class AddNotificationEmailToNotificationSettings < ActiveRecord::Migration[5.0] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def change + add_column :notification_settings, :notification_email, :string + end +end diff --git a/db/migrate/20190516155724_change_packages_size_defaults_in_project_statistics.rb b/db/migrate/20190516155724_change_packages_size_defaults_in_project_statistics.rb new file mode 100644 index 00000000000..eba154df496 --- /dev/null +++ b/db/migrate/20190516155724_change_packages_size_defaults_in_project_statistics.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +class ChangePackagesSizeDefaultsInProjectStatistics < ActiveRecord::Migration[5.1] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + change_column_default :project_statistics, :packages_size, 0 + + update_column_in_batches(:project_statistics, :packages_size, 0) do |table, query| + query.where(table[:packages_size].eq(nil)) + end + + change_column_null :project_statistics, :packages_size, false + end + + def down + change_column_null :project_statistics, :packages_size, true + change_column_default :project_statistics, :packages_size, nil + end +end diff --git a/db/migrate/20190523112344_limit_milestone_date_years_to_4_digits.rb b/db/migrate/20190523112344_limit_milestone_date_years_to_4_digits.rb new file mode 100644 index 00000000000..86fe09d7573 --- /dev/null +++ b/db/migrate/20190523112344_limit_milestone_date_years_to_4_digits.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class LimitMilestoneDateYearsTo4Digits < ActiveRecord::Migration[5.1] + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + # When a migration requires downtime you **must** uncomment the following + # constant and define a short and easy to understand explanation as to why the + # migration requires downtime. + # DOWNTIME_REASON = '' + + # When using the methods "add_concurrent_index", "remove_concurrent_index" or + # "add_column_with_default" you must disable the use of transactions + # as these methods can not run in an existing transaction. + # When using "add_concurrent_index" or "remove_concurrent_index" methods make sure + # that either of them is the _only_ method called in the migration, + # any other changes should go in a separate migration. + # This ensures that upon failure _only_ the index creation or removing fails + # and can be retried or reverted easily. + # + # To disable transactions uncomment the following line and remove these + # comments: + # disable_ddl_transaction! + + def change + Milestone.where("start_date > '9999-12-31'").update_all( + "start_date = '9999-12-31'" + ) + Milestone.where("due_date > '9999-12-31'").update_all( + "due_date = '9999-12-31'" + ) + end +end diff --git a/db/migrate/20190524062810_generate_lets_encrypt_private_key.rb b/db/migrate/20190524062810_generate_lets_encrypt_private_key.rb index 21d7049b998..ae93a76575a 100644 --- a/db/migrate/20190524062810_generate_lets_encrypt_private_key.rb +++ b/db/migrate/20190524062810_generate_lets_encrypt_private_key.rb @@ -9,23 +9,8 @@ class GenerateLetsEncryptPrivateKey < ActiveRecord::Migration[5.1] # Set this constant to true if this migration requires downtime. DOWNTIME = false - class ApplicationSetting < ActiveRecord::Base - self.table_name = 'application_settings' - - attr_encrypted :lets_encrypt_private_key, - mode: :per_attribute_iv, - key: Settings.attr_encrypted_db_key_base_truncated, - algorithm: 'aes-256-gcm', - encode: true - end - + # we now generate this key on the fly, but since this migration was merged to master, we don't remove it def up - ApplicationSetting.reset_column_information - - private_key = OpenSSL::PKey::RSA.new(4096).to_pem - ApplicationSetting.find_each do |setting| - setting.update!(lets_encrypt_private_key: private_key) - end end def down diff --git a/db/schema.rb b/db/schema.rb index ef272be22ee..378cb9c65d0 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -1516,6 +1516,7 @@ ActiveRecord::Schema.define(version: 20190527194900) do t.boolean "success_pipeline" t.boolean "push_to_merge_request" t.boolean "issue_due" + t.string "notification_email" t.index ["source_id", "source_type"], name: "index_notification_settings_on_source_id_and_source_type", using: :btree t.index ["user_id", "source_id", "source_type"], name: "index_notifications_on_user_id_and_source_id_and_source_type", unique: true, using: :btree t.index ["user_id"], name: "index_notification_settings_on_user_id", using: :btree @@ -1743,7 +1744,7 @@ ActiveRecord::Schema.define(version: 20190527194900) do t.bigint "repository_size", default: 0, null: false t.bigint "lfs_objects_size", default: 0, null: false t.bigint "build_artifacts_size", default: 0, null: false - t.bigint "packages_size" + t.bigint "packages_size", default: 0, null: false t.bigint "wiki_size" t.index ["namespace_id"], name: "index_project_statistics_on_namespace_id", using: :btree t.index ["project_id"], name: "index_project_statistics_on_project_id", unique: true, using: :btree diff --git a/doc/administration/logs.md b/doc/administration/logs.md index a7e57e44e86..ac41f9177dd 100644 --- a/doc/administration/logs.md +++ b/doc/administration/logs.md @@ -288,6 +288,20 @@ installations from source. It logs information whenever [Rack Attack] registers an abusive request. +## `graphql_json.log` + +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/59587) in GitLab 12.0. + +This file lives in `/var/log/gitlab/gitlab-rails/graphql_json.log` for +Omnibus GitLab packages or in `/home/git/gitlab/log/graphql_json.log` for +installations from source. + +GraphQL queries are recorded in that file. For example: + +```json +{"query_string":"query IntrospectionQuery{__schema {queryType { name },mutationType { name }}}...(etc)","variables":{"a":1,"b":2},"complexity":181,"depth":1,"duration":7} +``` + ## Reconfigure Logs Reconfigure log files live in `/var/log/gitlab/reconfigure` for Omnibus GitLab diff --git a/doc/administration/raketasks/maintenance.md b/doc/administration/raketasks/maintenance.md index b295b7d5dc4..0b4c1ae15d6 100644 --- a/doc/administration/raketasks/maintenance.md +++ b/doc/administration/raketasks/maintenance.md @@ -205,25 +205,6 @@ cd /home/git/gitlab sudo -u git -H bundle exec rake gitlab:track_deployment RAILS_ENV=production ``` -## Create or repair repository hooks symlink - -If the GitLab shell hooks directory location changes or another circumstance -leads to the hooks symlink becoming missing or invalid, run this Rake task -to create or repair the symlinks. - -**Omnibus Installation** - -``` -sudo gitlab-rake gitlab:shell:create_hooks -``` - -**Source Installation** - -``` -cd /home/git/gitlab -sudo -u git -H bundle exec rake gitlab:shell:create_hooks RAILS_ENV=production -``` - ## Check TCP connectivity to a remote site Sometimes you need to know if your GitLab installation can connect to a TCP diff --git a/doc/analytics/README.md b/doc/analytics/README.md index 6b63edb5174..bfb15f6c4f3 100644 --- a/doc/analytics/README.md +++ b/doc/analytics/README.md @@ -1,5 +1,5 @@ --- -redirect_to: 'https://docs.gitlab.com/ee/user/group/index.html#user-contribution-analysis-starter' +redirect_to: '../user/group/index.md#user-contribution-analysis-starter' --- -This document was moved to [another location](https://docs.gitlab.com/ee/user/group/index.html#user-contribution-analysis-starter) +This document was moved to [another location](../user/group/index.md#user-contribution-analysis-starter) diff --git a/doc/analytics/contribution_analytics.md b/doc/analytics/contribution_analytics.md index 38d71263bc1..e36f55071a4 100644 --- a/doc/analytics/contribution_analytics.md +++ b/doc/analytics/contribution_analytics.md @@ -1,5 +1,5 @@ --- -redirect_to: 'https://docs.gitlab.com/ee/user/group/contribution_analytics/index.html' +redirect_to: '../user/group/contribution_analytics/index.md' --- -This document was moved to [another location](https://docs.gitlab.com/ee/user/group/contribution_analytics/index.html). +This document was moved to [another location](../user/group/contribution_analytics/index.md). diff --git a/doc/api/commits.md b/doc/api/commits.md index 92f53c7b5e6..25015fad9e3 100644 --- a/doc/api/commits.md +++ b/doc/api/commits.md @@ -75,6 +75,7 @@ POST /projects/:id/repository/commits | `branch` | string | yes | Name of the branch to commit into. To create a new branch, also provide `start_branch`. | | `commit_message` | string | yes | Commit message | | `start_branch` | string | no | Name of the branch to start the new commit from | +| `start_project` | integer/string | no | The project ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) to start the commit from. Defaults to the value of `id`. | | `actions[]` | array | yes | An array of action hashes to commit as a batch. See the next table for what attributes it can take. | | `author_email` | string | no | Specify the commit author's email address | | `author_name` | string | no | Specify the commit author's name | diff --git a/doc/articles/how_to_configure_ldap_gitlab_ee/index.md b/doc/articles/how_to_configure_ldap_gitlab_ee/index.md index 4ce96fcb230..3e6f3130437 100644 --- a/doc/articles/how_to_configure_ldap_gitlab_ee/index.md +++ b/doc/articles/how_to_configure_ldap_gitlab_ee/index.md @@ -1,5 +1,5 @@ --- -redirect_to: 'https://docs.gitlab.com/ee/administration/auth/how_to_configure_ldap_gitlab_ee/index.html' +redirect_to: '../../administration/auth/how_to_configure_ldap_gitlab_ee/index.md' --- -This document was moved to [another location](https://docs.gitlab.com/ee/administration/auth/how_to_configure_ldap_gitlab_ee/index.html). +This document was moved to [another location](../../administration/auth/how_to_configure_ldap_gitlab_ee/index.md). diff --git a/doc/customization/issue_and_merge_request_template.md b/doc/customization/issue_and_merge_request_template.md index 01c31728c21..adaa120a37e 100644 --- a/doc/customization/issue_and_merge_request_template.md +++ b/doc/customization/issue_and_merge_request_template.md @@ -1,5 +1,5 @@ --- -redirect_to: 'https://docs.gitlab.com/ee/user/project/description_templates.html#setting-a-default-template-for-issues-and-merge-requests--starter' +redirect_to: '../user/project/description_templates.md#setting-a-default-template-for-issues-and-merge-requests--starter' --- -This document was moved to [description_templates](https://docs.gitlab.com/ee/user/project/description_templates.html#setting-a-default-template-for-issues-and-merge-requests--starter). +This document was moved to [description_templates](../user/project/description_templates.md#setting-a-default-template-for-issues-and-merge-requests--starter). diff --git a/doc/development/ee_features.md b/doc/development/ee_features.md index 857595330aa..d0db1a61935 100644 --- a/doc/development/ee_features.md +++ b/doc/development/ee_features.md @@ -459,15 +459,6 @@ Resolving an EE template path that is relative to the CE view path will not work = render_if_exists 'projects/button' # Will render `ee/app/views/projects/_button` ``` -You should not explicitly set render options like `partial` or provide a `locals` hash. -The first argument should be a path string and the second can be a hash replacing `locals`. - -```ruby -render partial: 'projects/button', locals: { project: project } -# becomes -render_if_exists 'projects/button', project: project -``` - #### Using `render_ce` For `render` and `render_if_exists`, they search for the EE partial first, diff --git a/qa/docs/best_practices.md b/doc/development/testing_guide/end_to_end/best_practices.md index 62a90563bf0..89500ef9a90 100644 --- a/qa/docs/best_practices.md +++ b/doc/development/testing_guide/end_to_end/best_practices.md @@ -35,4 +35,4 @@ Finally, interacting with the application only by its GUI generates a higher rat - Building state through the GUI is time consuming and it's not sustainable as the test suite grows. - When depending only on the GUI to create the application's state and tests fail due to front-end issues, we can't rely on the test failures rate, and we generate a higher rate of test flakiness. -Now that we are aware of all of it, [let's go create some tests](writing_tests_from_scratch.md#this-document-covers-the-following-items). +Now that we are aware of all of it, [let's go create some tests](quick_start_guide.md). diff --git a/doc/development/testing_guide/end_to_end_tests.md b/doc/development/testing_guide/end_to_end/index.md index fc7aaedca29..afd81ff00b2 100644 --- a/doc/development/testing_guide/end_to_end_tests.md +++ b/doc/development/testing_guide/end_to_end/index.md @@ -65,7 +65,7 @@ Below you can read more about how to use it and how does it work. Currently, we are using _multi-project pipeline_-like approach to run QA pipelines. -![QA on merge requests CI/CD architecture](img/qa_on_merge_requests_cicd_architecture.png) +![QA on merge requests CI/CD architecture](../img/qa_on_merge_requests_cicd_architecture.png) <details> <summary>Show mermaid source</summary> @@ -136,6 +136,12 @@ Once you decided where to put [test environment orchestration scenarios] and the [GitLab QA orchestrator README][gitlab-qa-readme], and [the already existing instance-level scenarios][instance-level scenarios]. +Continued reading: + +- [Quick Start Guide](quick_start_guide.md) +- [Style Guide](style_guide.md) +- [Best Practices](best_practices.md) + ## Where can I ask for help? You can ask question in the `#quality` channel on Slack (GitLab internal) or @@ -149,7 +155,7 @@ you can find an issue you would like to work on in [gitlab-qa-readme]: https://gitlab.com/gitlab-org/gitlab-qa/tree/master/README.md [quality-nightly-pipelines]: https://gitlab.com/gitlab-org/quality/nightly/pipelines [quality-staging-pipelines]: https://gitlab.com/gitlab-org/quality/staging/pipelines -[review-apps]: ./review_apps.md +[review-apps]: ../review_apps.md [gitlab-qa-architecture]: https://gitlab.com/gitlab-org/gitlab-qa/blob/master/docs/architecture.md [gitlab-qa-issues]: https://gitlab.com/gitlab-org/gitlab-qa/issues?label_name%5B%5D=new+scenario [gitlab-ce-issues]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name[]=QA&label_name[]=test diff --git a/qa/qa/page/README.md b/doc/development/testing_guide/end_to_end/page_objects.md index d0de33892c4..d0de33892c4 100644 --- a/qa/qa/page/README.md +++ b/doc/development/testing_guide/end_to_end/page_objects.md diff --git a/qa/docs/writing_tests_from_scratch.md b/doc/development/testing_guide/end_to_end/quick_start_guide.md index 06b60a1ef45..afe76acf9c9 100644 --- a/qa/docs/writing_tests_from_scratch.md +++ b/doc/development/testing_guide/end_to_end/quick_start_guide.md @@ -17,10 +17,10 @@ If you don't exactly understand what we mean by **not everything needs to happen - [2.](#2-test-skeleton) Creating the skeleton of the test file (`*_spec.rb`) - [3.](#3-test-cases-mvc) The [MVC](https://about.gitlab.com/handbook/values/#minimum-viable-change-mvc) of the test cases' logic - [4.](#4-extracting-duplicated-code) Extracting duplicated code into methods -- [5.](#5-tests-pre-conditions-using-resources-and-page-objects) Tests' pre-conditions (`before :context` and `before`) using resources and [Page Objects](./qa/qa/page/README.md) +- [5.](#5-tests-pre-conditions-using-resources-and-page-objects) Tests' pre-conditions (`before :context` and `before`) using resources and [Page Objects] - [6.](#6-optimization) Optimizing the test suite - [7.](#7-resources) Using and implementing resources -- [8.](#8-page-objects) Moving element definitions and methods to [Page Objects](./qa/qa/page/README.md) +- [8.](#8-page-objects) Moving element definitions and methods to [Page Objects] ### 0. Are end-to-end tests needed? @@ -111,7 +111,7 @@ end > Notice that the test itself is simple. The most challenging part is the creation of the application state, which will be covered later. -> The exemplified test case's MVC is not enough for the change to be merged, but it helps to build up the test logic. The reason is that we do not want to use locators directly in the tests, and tests **must** use [Page Objects](./qa/qa/page/README.md) before they can be merged. This way we better separate the responsibilities, where the Page Objects encapsulate elements and methods that allow us to interact with pages, while the spec files describe the test cases in more business-related language. +> The exemplified test case's MVC is not enough for the change to be merged, but it helps to build up the test logic. The reason is that we do not want to use locators directly in the tests, and tests **must** use [Page Objects] before they can be merged. This way we better separate the responsibilities, where the Page Objects encapsulate elements and methods that allow us to interact with pages, while the spec files describe the test cases in more business-related language. Below are the steps that the test covers: @@ -275,7 +275,7 @@ In the `before` block we create all the application state needed for the tests t > A project is created in the background by creating the `issue` resource. -> When [creating the resources](./qa/qa/resource/README.md), notice that when calling the `fabricate_via_api` method, we pass some attribute:values, like `title`, and `labels` for the `issue` resource; and `project` and `title` for the `label` resource. +> When creating the [Resources], notice that when calling the `fabricate_via_api` method, we pass some attribute:values, like `title`, and `labels` for the `issue` resource; and `project` and `title` for the `label` resource. > What's important to understand here is that by creating the application state mostly using the public APIs we save a lot of time in the test suite setup stage. @@ -283,7 +283,7 @@ In the `before` block we create all the application state needed for the tests t ### 6. Optimization -As already mentioned in the [best practices](./BEST_PRACTICES.md) document, end-to-end tests are very costly in terms of execution time, and it's our responsibility as software engineers to ensure that we optimize them as much as possible. +As already mentioned in the [best practices](best_practices.md) document, end-to-end tests are very costly in terms of execution time, and it's our responsibility as software engineers to ensure that we optimize them as much as possible. > Note that end-to-end tests are slow to run and so they can have several actions and assertions in a single test, which helps us get feedback from the tests sooner. In comparison, unit tests are much faster to run and can exercise every little piece of the application in isolation, and so they usually have only one assertion per test. @@ -339,7 +339,7 @@ To address point 1, we changed the test implementation from two `it` blocks into **Note:** When writing this document, some code that is now merged to master was not implemented yet, but we left them here for the readers to understand the whole process of end-to-end test creation. -You can think of [resources](qa/qa/resource/README.md) as anything that can be created on GitLab CE or EE, either through the GUI, the API, or the CLI. +You can think of [Resources] as anything that can be created on GitLab CE or EE, either through the GUI, the API, or the CLI. With that in mind, resources can be a project, an epic, an issue, a label, a commit, etc. @@ -439,7 +439,7 @@ Page Objects are used in end-to-end tests for maintenance reasons, where a page' > Page Objects are auto-loaded in the `qa/qa.rb` file and available in all the test files (`*_spec.rb`). -Take a look at [this document for a detailed explanation about Page Objects](./qa/page/README.md). +Take a look at the [Page Objects] documentation. Now, let's go back to our example. @@ -580,3 +580,6 @@ As you might remember, in the Issue Page Object we call this method like this: ` ___ With that, you should be able to start writing end-to-end tests yourself. *Congratulations!* + +[Page Objects]: page_objects.md +[Resources]: resources.md diff --git a/qa/qa/resource/README.md b/doc/development/testing_guide/end_to_end/resources.md index 2c8859b6599..1e32db4f633 100644 --- a/qa/qa/resource/README.md +++ b/doc/development/testing_guide/end_to_end/resources.md @@ -5,11 +5,11 @@ be created via the API or the CLI. ## How to properly implement a resource class? -All resource classes should inherit from [`Resource::Base`](./base.rb). +All resource classes should inherit from `Resource::Base`. There is only one mandatory method to implement to define a resource class. This is the `#fabricate!` method, which is used to build the resource via the -browser UI. Note that you should only use [Page objects](../page/README.md) to +browser UI. Note that you should only use [Page objects](page_objects.md) to interact with a Web page in this method. Here is an imaginary example: @@ -74,7 +74,7 @@ module QA end ``` -The [`Project` resource](./project.rb) is a good real example of Browser +The `Project` resource is a good real example of Browser UI and API implementations. #### Resource attributes diff --git a/qa/docs/guidelines.md b/doc/development/testing_guide/end_to_end/style_guide.md index cd4b939fd71..0272e1810f2 100644 --- a/qa/docs/guidelines.md +++ b/doc/development/testing_guide/end_to_end/style_guide.md @@ -1,6 +1,6 @@ -# Style guide for writing E2E tests +# Style guide for writing end-to-end tests -This document describes the conventions used at GitLab for writing E2E tests using the GitLab QA project. +This document describes the conventions used at GitLab for writing End-to-end (E2E) tests using the GitLab QA project. ## `click_` versus `go_to_` @@ -45,7 +45,7 @@ Notice that in the above example, before clicking the `:operations_environments_ > We can create these methods as helpers to abstract multi-step navigation. -### Element Naming Convention +### Element naming convention When adding new elements to a page, it's important that we have a uniform element naming convention. @@ -69,7 +69,8 @@ We follow a simple formula roughly based on hungarian notation. #### Examples -**Good** +**Good** + ```ruby view '...' do element :edit_button @@ -80,7 +81,8 @@ view '...' do end ``` -**Bad** +**Bad** + ```ruby view '...' do # `_confirmation` should be `_field`. what sort of confirmation? a checkbox confirmation? no real way to disambiguate. diff --git a/doc/development/testing_guide/index.md b/doc/development/testing_guide/index.md index ecad9ba48a3..93ee2a6371a 100644 --- a/doc/development/testing_guide/index.md +++ b/doc/development/testing_guide/index.md @@ -71,7 +71,7 @@ Everything you should know about how to test Rake tasks. --- -## [End-to-end tests](end_to_end_tests.md) +## [End-to-end tests](end_to_end/index.md) Everything you should know about how to run end-to-end tests using [GitLab QA][gitlab-qa] testing framework. diff --git a/doc/development/testing_guide/smoke.md b/doc/development/testing_guide/smoke.md index 30d861d7d68..c9d3238fbe9 100644 --- a/doc/development/testing_guide/smoke.md +++ b/doc/development/testing_guide/smoke.md @@ -17,7 +17,7 @@ Currently, our suite consists of this basic functionality coverage: Smoke tests have the `:smoke` RSpec metadata. -See [End-to-end Testing](./end_to_end_tests.md) for more details about +See [End-to-end Testing](end_to_end/index.md) for more details about end-to-end tests. --- diff --git a/doc/development/testing_guide/testing_levels.md b/doc/development/testing_guide/testing_levels.md index b5155b6b7fa..e1ce4d3b7d1 100644 --- a/doc/development/testing_guide/testing_levels.md +++ b/doc/development/testing_guide/testing_levels.md @@ -178,7 +178,7 @@ Every new feature should come with a [test plan]. | ---------- | -------------- | ----- | | `qa/qa/specs/features/` | [Capybara] + [RSpec] + Custom QA framework | Tests should be placed under their corresponding [Product category] | -> See [end-to-end tests](end_to_end_tests.md) for more information. +> See [end-to-end tests](end_to_end/index.md) for more information. Note that `qa/spec` contains unit tests of the QA framework itself, not to be confused with the application's [unit tests](#unit-tests) or diff --git a/doc/git_hooks/git_hooks.md b/doc/git_hooks/git_hooks.md index 9b8ad1578a0..b251e58410a 100644 --- a/doc/git_hooks/git_hooks.md +++ b/doc/git_hooks/git_hooks.md @@ -1,5 +1,5 @@ --- -redirect_to: 'https://docs.gitlab.com/ee/push_rules/push_rules.html' +redirect_to: '../push_rules/push_rules.md' --- -This document was moved to [another location](https://docs.gitlab.com/ee/push_rules/push_rules.html) +This document was moved to [another location](../push_rules/push_rules.md) diff --git a/doc/gitlab-basics/create-project.md b/doc/gitlab-basics/create-project.md index 785e2ffb650..a9ae4fb23f9 100644 --- a/doc/gitlab-basics/create-project.md +++ b/doc/gitlab-basics/create-project.md @@ -16,7 +16,7 @@ To create a project in GitLab: - [Import a project](../user/project/import/index.md) from a different repository, if enabled on your GitLab instance. Contact your GitLab admin if this is unavailable. - - Run [CI/CD pipelines for external repositories](https://docs.gitlab.com/ee/ci/ci_cd_for_external_repos/index.html). **[PREMIUM]** + - Run [CI/CD pipelines for external repositories](../ci/ci_cd_for_external_repos/index.md). **[PREMIUM]** ## Blank projects diff --git a/doc/install/aws/index.md b/doc/install/aws/index.md index 0000e03f1d7..c9fed36f258 100644 --- a/doc/install/aws/index.md +++ b/doc/install/aws/index.md @@ -383,7 +383,7 @@ after the instance is created. CAUTION: **Caution:** We **do not** recommend using the AWS Elastic File System (EFS), as it can result -in [significantly degraded performance](../../administration/high_availability/nfs.html#avoid-using-awss-elastic-file-system-efs). +in [significantly degraded performance](../../administration/high_availability/nfs.md#avoid-using-awss-elastic-file-system-efs). ### Configure security group @@ -649,12 +649,12 @@ Have a read through these other resources and feel free to [open an issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/new) to request additional material: -- [GitLab High Availability](https://docs.gitlab.com/ee/administration/high_availability/): +- [GitLab High Availability](../../administration/high_availability/README.md): GitLab supports several different types of clustering and high-availability. -- [Geo replication](https://docs.gitlab.com/ee/administration/geo/replication/): +- [Geo replication](../../administration/geo/replication/index.md): Geo is the solution for widely distributed development teams. - [Omnibus GitLab](https://docs.gitlab.com/omnibus/) - Everything you need to know about administering your GitLab instance. -- [Upload a license](https://docs.gitlab.com/ee/user/admin_area/license.html): +- [Upload a license](../../user/admin_area/license.md): Activate all GitLab Enterprise Edition functionality with a license. - [Pricing](https://about.gitlab.com/pricing): Pricing for the different tiers. diff --git a/doc/install/kubernetes/index.md b/doc/install/kubernetes/index.md index 7312bf2d4f7..fd0f7b0d328 100644 --- a/doc/install/kubernetes/index.md +++ b/doc/install/kubernetes/index.md @@ -10,7 +10,7 @@ If you're not sure if Kubernetes is for you, our [Omnibus GitLab packages](../README.md#installing-gitlab-using-the-omnibus-gitlab-package-recommended) are mature, scalable, support [high availability](../../administration/high_availability/README.md) and are used today on GitLab.com. -It is not necessary to have GitLab installed on Kubernetes in order to use [GitLab Kubernetes integration](https://docs.gitlab.com/ee/user/project/clusters/index.html). +It is not necessary to have GitLab installed on Kubernetes in order to use [GitLab Kubernetes integration](../../user/project/clusters/index.md). The easiest method to deploy GitLab on [Kubernetes](https://kubernetes.io/) is to take advantage of GitLab's Helm charts. [Helm](https://github.com/kubernetes/helm/blob/master/README.md) diff --git a/doc/install/requirements.md b/doc/install/requirements.md index f6a52205a0e..4931a69f2a3 100644 --- a/doc/install/requirements.md +++ b/doc/install/requirements.md @@ -105,9 +105,9 @@ features of GitLab work with MySQL/MariaDB: 1. MySQL support for subgroups was [dropped with GitLab 9.3][post]. See [issue #30472][30472] for more information. -1. Geo does [not support MySQL](https://docs.gitlab.com/ee/administration/geo/replication/database.html). This means no supported Disaster Recovery solution if using MySQL. **[PREMIUM ONLY]** +1. Geo does [not support MySQL](../administration/geo/replication/database.md). This means no supported Disaster Recovery solution if using MySQL. **[PREMIUM ONLY]** 1. [Zero downtime migrations](../update/README.md#upgrading-without-downtime) do not work with MySQL. -1. [Database load balancing](https://docs.gitlab.com/ee/administration/database_load_balancing.html) is +1. [Database load balancing](../administration/database_load_balancing.md) is supported only for PostgreSQL. **[PREMIUM ONLY]** 1. GitLab [optimizes the loading of dashboard events](https://gitlab.com/gitlab-org/gitlab-ce/issues/31806) using [PostgreSQL LATERAL JOINs](https://blog.heapanalytics.com/postgresqls-powerful-new-join-type-lateral/). 1. In general, SQL optimized for PostgreSQL may run much slower in MySQL due to @@ -143,14 +143,14 @@ On some systems you may need to install an additional package (e.g. #### Additional requirements for GitLab Geo -If you are using [GitLab Geo](https://docs.gitlab.com/ee/development/geo.html): +If you are using [GitLab Geo](../development/geo.md): - We strongly recommend running Omnibus-managed instances as they are actively developed and tested. We aim to be compatible with most external (not managed by Omnibus) databases (for example, AWS RDS) but we do not guarantee compatibility. - The - [tracking database](https://docs.gitlab.com/ee/development/geo.html#geo-tracking-database) + [tracking database](../development/geo.md#using-the-tracking-database) requires the [postgres_fdw](https://www.postgresql.org/docs/9.6/static/postgres-fdw.html) extension. diff --git a/doc/integration/elasticsearch.md b/doc/integration/elasticsearch.md index 15176ede733..0a037b3876b 100644 --- a/doc/integration/elasticsearch.md +++ b/doc/integration/elasticsearch.md @@ -8,8 +8,8 @@ This document describes how to set up Elasticsearch with GitLab. Once enabled, you'll have the benefit of fast search response times and the advantage of two special searches: -- [Advanced Global Search](https://docs.gitlab.com/ee/user/search/advanced_global_search.html) -- [Advanced Syntax Search](https://docs.gitlab.com/ee/user/search/advanced_search_syntax.html) +- [Advanced Global Search](../user/search/advanced_global_search.md) +- [Advanced Syntax Search](../user/search/advanced_search_syntax.md) ## Version Requirements <!-- Please remember to update ee/lib/system_check/app/elasticsearch_check.rb if this changes --> diff --git a/doc/integration/saml.md b/doc/integration/saml.md index 15d9d8c9c74..22e07594d6f 100644 --- a/doc/integration/saml.md +++ b/doc/integration/saml.md @@ -1,6 +1,6 @@ # SAML OmniAuth Provider -> This topic is for SAML on self-managed GitLab instances. For SAML on GitLab.com, see [SAML SSO for GitLab.com Groups](https://docs.gitlab.com/ee/user/group/saml_sso/index.html). +> This topic is for SAML on self-managed GitLab instances. For SAML on GitLab.com, see [SAML SSO for GitLab.com Groups](../user/group/saml_sso/index.md). NOTE: **Note:** You need to [enable OmniAuth](omniauth.md) in order to use this. diff --git a/doc/integration/slash_commands.md b/doc/integration/slash_commands.md index cd755089be8..71ea2e25533 100644 --- a/doc/integration/slash_commands.md +++ b/doc/integration/slash_commands.md @@ -20,8 +20,8 @@ Taking the trigger term as `project-name`, the commands are: | `/project-name deploy <from> to <to>` | Deploy from the `<from>` environment to the `<to>` environment | | `/project-name run <job name> <arguments>` | Execute [ChatOps](../ci/chatops/README.md) job `<job name>` on `master` | -Note that if you are using the [GitLab Slack application](https://docs.gitlab.com/ee/user/project/integrations/gitlab_slack_application.html) for -your GitLab.com projects, you need to [add the `gitlab` keyword at the beginning of the command](https://docs.gitlab.com/ee/user/project/integrations/gitlab_slack_application.html#usage). +Note that if you are using the [GitLab Slack application](../user/project/integrations/gitlab_slack_application.md) for +your GitLab.com projects, you need to [add the `gitlab` keyword at the beginning of the command](../user/project/integrations/gitlab_slack_application.md#usage). ## Issue commands diff --git a/doc/license/README.md b/doc/license/README.md index b9281fd5299..fd110a39b61 100644 --- a/doc/license/README.md +++ b/doc/license/README.md @@ -1,5 +1,5 @@ --- -redirect_to: 'https://docs.gitlab.com/ee/user/admin_area/license.html' +redirect_to: '../user/admin_area/license.md' --- -This document was moved to [another location](https://docs.gitlab.com/ee/user/admin_area/license.html). +This document was moved to [another location](../user/admin_area/license.md). diff --git a/doc/push_rules/push_rules.md b/doc/push_rules/push_rules.md index 7654023f266..e44eab2556e 100644 --- a/doc/push_rules/push_rules.md +++ b/doc/push_rules/push_rules.md @@ -61,7 +61,7 @@ The following options are available. | --------- | :------------: | ----------- | | Removal of tags with `git push` | **Starter** 7.10 | Forbid users to remove git tags with `git push`. Tags will still be able to be deleted through the web UI. | | Check whether author is a GitLab user | **Starter** 7.10 | Restrict commits by author (email) to existing GitLab users. | -| Check whether committer is the current authenticated user | **Premium** 10.2 | GitLab will reject any commit that was not committed by the current authenticated user | +| Committer restriction | **Premium** 10.2 | GitLab will reject any commit that was not committed by the current authenticated user | | Check whether commit is signed through GPG | **Premium** 10.1 | Reject commit when it is not signed through GPG. Read [signing commits with GPG][signing-commits]. | | Prevent committing secrets to Git | **Starter** 8.12 | GitLab will reject any files that are likely to contain secrets. Read [what files are forbidden](#prevent-pushing-secrets-to-the-repository). | | Restrict by commit message | **Starter** 7.10 | Only commit messages that match this regular expression are allowed to be pushed. Leave empty to allow any commit message. Uses multiline mode, which can be disabled using `(?-m)`. | diff --git a/doc/subscriptions/index.md b/doc/subscriptions/index.md index b6d16536fee..dfd80f8882e 100644 --- a/doc/subscriptions/index.md +++ b/doc/subscriptions/index.md @@ -38,7 +38,7 @@ Future purchases will use the information in this section. The email listed in t ### Self-managed: Apply your license file -After purchase, the license file is sent to the email address tied to the Customers portal account, which needs to be [uploaded to the GitLab instance](https://docs.gitlab.com/ee/user/admin_area/license.html#uploading-your-license). +After purchase, the license file is sent to the email address tied to the Customers portal account, which needs to be [uploaded to the GitLab instance](../user/admin_area/license.md#uploading-your-license). ### Link your GitLab.com account with your Customers Portal account diff --git a/doc/update/patch_versions.md b/doc/update/patch_versions.md index c2dff21b028..f2df4277ca8 100644 --- a/doc/update/patch_versions.md +++ b/doc/update/patch_versions.md @@ -108,7 +108,7 @@ sudo -u git -H make ### 8. Install/Update `gitlab-elasticsearch-indexer` (optional) **[STARTER ONLY]** -If you're interested in using GitLab's new [elasticsearch repository indexer](https://docs.gitlab.com/ee/integration/elasticsearch.html#elasticsearch-repository-indexer-beta) (currently in beta) +If you're interested in using GitLab's new [elasticsearch repository indexer](../integration/elasticsearch.md#elasticsearch-repository-indexer-beta) (currently in beta) please follow the instructions on the document linked above and enable the indexer usage in the GitLab admin settings. diff --git a/doc/update/upgrading_from_ce_to_ee.md b/doc/update/upgrading_from_ce_to_ee.md index e74a5c00f7e..428377adb19 100644 --- a/doc/update/upgrading_from_ce_to_ee.md +++ b/doc/update/upgrading_from_ce_to_ee.md @@ -75,7 +75,7 @@ sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS ### 4. Install `gitlab-elasticsearch-indexer` (optional) **[STARTER ONLY]** If you're interested in using GitLab's new [elasticsearch repository -indexer][indexer-beta] (currently in beta) please follow the instructions on the +indexer](../integration/elasticsearch.md) (currently in beta) please follow the instructions on the document linked above and enable the indexer usage in the GitLab admin settings. ### 5. Start application @@ -133,4 +133,3 @@ Additional instructions here. [support@gitlab.com]: mailto:support@gitlab.com [old-ee-upgrade-docs]: https://gitlab.com/gitlab-org/gitlab-ee/tree/11-8-stable-ee/doc/update -[indexer-beta]: https://docs.gitlab.com/ee/integration/elasticsearch.html diff --git a/doc/user/admin_area/geo_nodes.md b/doc/user/admin_area/geo_nodes.md index fb0f9a3285d..d99b87cbc5c 100644 --- a/doc/user/admin_area/geo_nodes.md +++ b/doc/user/admin_area/geo_nodes.md @@ -5,7 +5,7 @@ type: howto # Geo nodes admin area **[PREMIUM ONLY]** You can configure various settings for GitLab Geo nodes. For more information, see -[Geo documentation](https://docs.gitlab.com/ee/administration/geo/replication/index.md). +[Geo documentation](../../administration/geo/replication/index.md). On the primary node, go to **Admin area > Geo**. On secondary nodes, go to **Admin area > Geo > Nodes**. @@ -29,7 +29,7 @@ changes on the **primary** node! | Setting | Description | |---------------------------|-------------| -| Selective synchronization | Enable Geo [selective sync](https://docs.gitlab.com/ee/administration/geo/replication/configuration.html#selective-synchronization) for this **secondary** node. | +| Selective synchronization | Enable Geo [selective sync](../../administration/geo/replication/configuration.md#selective-synchronization) for this **secondary** node. | | Repository sync capacity | Number of concurrent requests this **secondary** node will make to the **primary** node when backfilling repositories. | | File sync capacity | Number of concurrent requests this **secondary** node will make to the **primary** node when backfilling files. | diff --git a/doc/user/admin_area/index.md b/doc/user/admin_area/index.md index db5da26ca0a..0fc6ed349ba 100644 --- a/doc/user/admin_area/index.md +++ b/doc/user/admin_area/index.md @@ -27,7 +27,7 @@ The Admin Area is made up of the following sections: | Applications | Create system [OAuth applications](../../integration/oauth_provider.md) for integrations with other services. | | Abuse Reports | Manage [abuse reports](abuse_reports.md) submitted by your users. | | License **[STARTER ONLY]** | Upload, display, and remove [licenses](license.md). | -| Push Rules **[STARTER]** | Configure pre-defined git [push rules](https://docs.gitlab.com/ee/push_rules/push_rules.html) for projects. | +| Push Rules **[STARTER]** | Configure pre-defined git [push rules](../../push_rules/push_rules.md) for projects. | | Geo **[PREMIUM ONLY]** | Configure and maintain [Geo nodes](geo_nodes.md). | | Deploy Keys | Create instance-wide [SSH deploy keys](../../ssh/README.md#deploy-keys). | | Service Templates | Create [service templates](../project/integrations/services_templates.md) for projects. | diff --git a/doc/user/admin_area/settings/email.md b/doc/user/admin_area/settings/email.md index 912c2cff481..9555a695b13 100644 --- a/doc/user/admin_area/settings/email.md +++ b/doc/user/admin_area/settings/email.md @@ -40,7 +40,7 @@ In order to change this option: 1. Hit **Save** for the changes to take effect. NOTE: **Note**: Once the hostname gets configured, every private commit email using the previous hostname, will not get -recognized by GitLab. This can directly conflict with certain [Push rules](https://docs.gitlab.com/ee/push_rules/push_rules.html) such as +recognized by GitLab. This can directly conflict with certain [Push rules](../../../push_rules/push_rules.md) such as `Check whether author is a GitLab user` and `Check whether committer is the current authenticated user`. <!-- ## Troubleshooting diff --git a/doc/user/application_security/container_scanning/index.md b/doc/user/application_security/container_scanning/index.md index 5c635b09503..adb6868516e 100644 --- a/doc/user/application_security/container_scanning/index.md +++ b/doc/user/application_security/container_scanning/index.md @@ -119,7 +119,7 @@ container_scanning: variables: DOCKER_DRIVER: overlay2 ## Define two new variables based on GitLab's CI/CD predefined variables - ## https://docs.gitlab.com/ee/ci/variables/#predefined-environment-variables + ## https://docs.gitlab.com/ee/ci/variables/README.html#predefined-environment-variables CI_APPLICATION_REPOSITORY: $CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG CI_APPLICATION_TAG: $CI_COMMIT_SHA CLAIR_LOCAL_SCAN_VERSION: v2.0.8_fe9b059d930314b54c78f75afe265955faf4fdc1 @@ -162,7 +162,7 @@ container_scanning: variables: DOCKER_DRIVER: overlay2 ## Define two new variables based on GitLab's CI/CD predefined variables - ## https://docs.gitlab.com/ee/ci/variables/#predefined-environment-variables + ## https://docs.gitlab.com/ee/ci/variables/README.html#predefined-environment-variables CI_APPLICATION_REPOSITORY: $CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG CI_APPLICATION_TAG: $CI_COMMIT_SHA CLAIR_LOCAL_SCAN_VERSION: v2.0.8_fe9b059d930314b54c78f75afe265955faf4fdc1 diff --git a/doc/user/group/clusters/index.md b/doc/user/group/clusters/index.md index 8458b4f5de3..9c7b83252b0 100644 --- a/doc/user/group/clusters/index.md +++ b/doc/user/group/clusters/index.md @@ -87,7 +87,7 @@ The domain should have a wildcard DNS configured to the Ingress IP address. When adding more than one Kubernetes cluster to your project, you need to differentiate them with an environment scope. The environment scope associates clusters with [environments](../../../ci/environments.md) similar to how the -[environment-specific variables](https://docs.gitlab.com/ee/ci/variables/#limiting-environment-scopes-of-environment-variables-premium) +[environment-specific variables](../../../ci/variables/README.md#limiting-environment-scopes-of-environment-variables-premium) work. While evaluating which environment matches the environment scope of a diff --git a/doc/user/group/index.md b/doc/user/group/index.md index 7493e65e237..853b00f1f67 100644 --- a/doc/user/group/index.md +++ b/doc/user/group/index.md @@ -296,7 +296,7 @@ To enable this feature, navigate to the group settings page. Select Member lock lets a group owner prevent any new project membership to all of the projects within a group, allowing tighter control over project membership. -For example, if you want to lock the group for an [Audit Event](https://docs.gitlab.com/ee/administration/audit_events.html), +For example, if you want to lock the group for an [Audit Event](../../administration/audit_events.md), enable Member lock to guarantee that project membership cannot be modified during that audit. To enable this feature: @@ -315,7 +315,7 @@ request to add a new user to a project through API will not be possible. Group file templates allow you to share a set of templates for common file types with every project in a group. It is analogous to the -[instance template repository](https://docs.gitlab.com/ee/user/admin_area/settings/instance_template_repository.html) +[instance template repository](../admin_area/settings/instance_template_repository.md) feature, and the selected project should follow the same naming conventions as are documented on that page. @@ -346,7 +346,7 @@ Define project templates at a group level by setting a group as the template sou access each project's settings, and remove any project, all from the same screen. - **Webhooks**: Configure [webhooks](../project/integrations/webhooks.md) for your group. - **Kubernetes cluster integration**: Connect your GitLab group with [Kubernetes clusters](clusters/index.md). -- **Audit Events**: View [Audit Events](https://docs.gitlab.com/ee/administration/audit_events.html) +- **Audit Events**: View [Audit Events](../../administration/audit_events.md) for the group. **[STARTER ONLY]** - **Pipelines quota**: Keep track of the [pipeline quota](../admin_area/settings/continuous_integration.md) for the group. diff --git a/doc/user/group/insights/index.md b/doc/user/group/insights/index.md index 20f76c54ae7..427b474ca39 100644 --- a/doc/user/group/insights/index.md +++ b/doc/user/group/insights/index.md @@ -31,7 +31,7 @@ If no configuration was set, a [default configuration file]( https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/fixtures/insights/ee/fixtures/insights/default.yml) will be used. -See the [Project's Insights documentation](https://docs.gitlab.com/ee/user/project/insights/index.html) for +See the [Project's Insights documentation](../../project/insights/index.md) for more details about the `.gitlab/insights.yml` configuration file. ## Permissions diff --git a/doc/user/group/saml_sso/scim_setup.md b/doc/user/group/saml_sso/scim_setup.md index ec27ec3e336..c00628bf909 100644 --- a/doc/user/group/saml_sso/scim_setup.md +++ b/doc/user/group/saml_sso/scim_setup.md @@ -2,7 +2,7 @@ > [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/9388) in [GitLab.com Silver](https://about.gitlab.com/pricing/) 11.10. -GitLab's [SCIM API](https://docs.gitlab.com/ee/api/scim.html) implements part of [the RFC7644 protocol](https://tools.ietf.org/html/rfc7644). +GitLab's [SCIM API](../../../api/scim.md) implements part of [the RFC7644 protocol](https://tools.ietf.org/html/rfc7644). Currently, the following actions are available: diff --git a/doc/user/group/security_dashboard/index.md b/doc/user/group/security_dashboard/index.md index 43e910b29fe..c59198df081 100644 --- a/doc/user/group/security_dashboard/index.md +++ b/doc/user/group/security_dashboard/index.md @@ -1,5 +1,5 @@ --- -redirect_to: 'https://docs.gitlab.com/ee/user/application_security/security_dashboard/index.html' +redirect_to: '../../application_security/security_dashboard/index.md' --- -This document was moved to [another location](https://docs.gitlab.com/ee/user/application_security/security_dashboard/index.html). +This document was moved to [another location](../../application_security/security_dashboard/index.md). diff --git a/doc/user/index.md b/doc/user/index.md index 67d5cbf06ec..1fc4e4c43cf 100644 --- a/doc/user/index.md +++ b/doc/user/index.md @@ -49,22 +49,22 @@ GitLab is a Git-based platform that integrates a great number of essential tools With GitLab Enterprise Edition, you can also: -- Provide support with [Service Desk](https://docs.gitlab.com/ee/user/project/service_desk.html). +- Provide support with [Service Desk](project/service_desk.md). - Improve collaboration with - [Merge Request Approvals](https://docs.gitlab.com/ee/user/project/merge_requests/index.html#merge-request-approvals-starter), - [Multiple Assignees for Issues](https://docs.gitlab.com/ee/user/project/issues/multiple_assignees_for_issues.html), + [Merge Request Approvals](project/merge_requests/index.md#merge-request-approvals-starter), + [Multiple Assignees for Issues](project/issues/multiple_assignees_for_issues.md), and [Multiple Issue Boards](project/issue_board.md#multiple-issue-boards-starter). -- Create formal relationships between issues with [Related Issues](https://docs.gitlab.com/ee/user/project/issues/related_issues.html). -- Use [Burndown Charts](https://docs.gitlab.com/ee/user/project/milestones/burndown_charts.html) to track progress during a sprint or while working on a new version of their software. -- Leverage [Elasticsearch](https://docs.gitlab.com/ee/integration/elasticsearch.html) with [Advanced Global Search](search/advanced_global_search.md) and [Advanced Syntax Search](search/advanced_search_syntax.md) for faster, more advanced code search across your entire GitLab instance. -- [Authenticate users with Kerberos](https://docs.gitlab.com/ee/integration/kerberos.html). +- Create formal relationships between issues with [Related Issues](project/issues/related_issues.md). +- Use [Burndown Charts](project/milestones/burndown_charts.md) to track progress during a sprint or while working on a new version of their software. +- Leverage [Elasticsearch](../integration/elasticsearch.md) with [Advanced Global Search](search/advanced_global_search.md) and [Advanced Syntax Search](search/advanced_search_syntax.md) for faster, more advanced code search across your entire GitLab instance. +- [Authenticate users with Kerberos](../integration/kerberos.md). - [Mirror a repository](../workflow/repository_mirroring.md) from elsewhere on your local server. -- [Export issues as CSV](https://docs.gitlab.com/ee/user/project/issues/csv_export.html). -- View your entire CI/CD pipeline involving more than one project with [Multiple-Project Pipelines](https://docs.gitlab.com/ee/ci/multi_project_pipeline_graphs.html). -- [Lock files](https://docs.gitlab.com/ee/user/project/file_lock.html) to prevent conflicts. -- View the current health and status of each CI environment running on Kubernetes with [Deploy Boards](https://docs.gitlab.com/ee/user/project/deploy_boards.html). -- Leverage continuous delivery method with [Canary Deployments](https://docs.gitlab.com/ee/user/project/canary_deployments.html). -- Scan your code for vulnerabilities and [display them in merge requests](https://docs.gitlab.com/ee/user/application_security/sast/index.html). +- [Export issues as CSV](project/issues/csv_export.md). +- View your entire CI/CD pipeline involving more than one project with [Multiple-Project Pipelines](../ci/multi_project_pipeline_graphs.md). +- [Lock files](project/file_lock.md) to prevent conflicts. +- View the current health and status of each CI environment running on Kubernetes with [Deploy Boards](project/deploy_boards.md). +- Leverage continuous delivery method with [Canary Deployments](project/canary_deployments.md). +- Scan your code for vulnerabilities and [display them in merge requests](application_security/sast/index.md). You can also [integrate](project/integrations/project_services.md) GitLab with numerous third-party applications, such as Mattermost, Microsoft Teams, HipChat, Trello, Slack, Bamboo CI, JIRA, and a lot more. diff --git a/doc/user/permissions.md b/doc/user/permissions.md index 318053fdabb..a6e2f187090 100644 --- a/doc/user/permissions.md +++ b/doc/user/permissions.md @@ -36,91 +36,96 @@ In GitLab 11.0, the Master role was renamed to Maintainer. The following table depicts the various user permission levels in a project. -| Action | Guest | Reporter | Developer |Maintainer| Owner | -|---------------------------------------|---------|------------|-------------|----------|--------| -| Create new issue | ✓ [^1] | ✓ | ✓ | ✓ | ✓ | -| Create confidential issue | ✓ [^1] | ✓ | ✓ | ✓ | ✓ | -| View confidential issues | (✓) [^2] | ✓ | ✓ | ✓ | ✓ | -| Leave comments | ✓ [^1] | ✓ | ✓ | ✓ | ✓ | -| See related issues | ✓ | ✓ | ✓ | ✓ | ✓ | -| See a list of jobs | ✓ [^3] | ✓ | ✓ | ✓ | ✓ | -| See a job log | ✓ [^3] | ✓ | ✓ | ✓ | ✓ | -| Download and browse job artifacts | ✓ [^3] | ✓ | ✓ | ✓ | ✓ | -| View wiki pages | ✓ [^1] | ✓ | ✓ | ✓ | ✓ | -| Create and edit wiki pages | | | ✓ | ✓ | ✓ | -| Delete wiki pages | | | | ✓ | ✓ | -| View license management reports **[ULTIMATE]** | ✓ [^1] | ✓ | ✓ | ✓ | ✓ | -| View Security reports **[ULTIMATE]** | ✓ [^1] | ✓ | ✓ | ✓ | ✓ | -| View project code | [^1] | ✓ | ✓ | ✓ | ✓ | -| Pull project code | [^1] | ✓ | ✓ | ✓ | ✓ | -| Download project | [^1] | ✓ | ✓ | ✓ | ✓ | -| Assign issues | | ✓ | ✓ | ✓ | ✓ | -| Assign merge requests | | | ✓ | ✓ | ✓ | -| Label issues | | ✓ | ✓ | ✓ | ✓ | -| Label merge requests | | | ✓ | ✓ | ✓ | -| Create code snippets | | ✓ | ✓ | ✓ | ✓ | -| Manage issue tracker | | ✓ | ✓ | ✓ | ✓ | -| Manage labels | | ✓ | ✓ | ✓ | ✓ | -| See a commit status | | ✓ | ✓ | ✓ | ✓ | -| See a container registry | | ✓ | ✓ | ✓ | ✓ | -| See environments | | ✓ | ✓ | ✓ | ✓ | -| See a list of merge requests | | ✓ | ✓ | ✓ | ✓ | -| Manage related issues **[STARTER]** | | ✓ | ✓ | ✓ | ✓ | -| Lock issue discussions | | ✓ | ✓ | ✓ | ✓ | -| Create issue from vulnerability **[ULTIMATE]** | | ✓ | ✓ | ✓ | ✓ | -| View Error Tracking list | | ✓ | ✓ | ✓ | ✓ | -| Pull from [Maven repository](https://docs.gitlab.com/ee/user/project/packages/maven_repository.html) or [NPM registry](https://docs.gitlab.com/ee/user/project/packages/npm_registry.html) **[PREMIUM]** | | ✓ | ✓ | ✓ | ✓ | -| Publish to [Maven repository](https://docs.gitlab.com/ee/user/project/packages/maven_repository.html) or [NPM registry](https://docs.gitlab.com/ee/user/project/packages/npm_registry.html) **[PREMIUM]** | | | ✓ | ✓ | ✓ | -| Lock merge request discussions | | | ✓ | ✓ | ✓ | -| Create new environments | | | ✓ | ✓ | ✓ | -| Stop environments | | | ✓ | ✓ | ✓ | -| Manage/Accept merge requests | | | ✓ | ✓ | ✓ | -| Create new merge request | | | ✓ | ✓ | ✓ | -| Create new branches | | | ✓ | ✓ | ✓ | -| Push to non-protected branches | | | ✓ | ✓ | ✓ | -| Force push to non-protected branches | | | ✓ | ✓ | ✓ | -| Remove non-protected branches | | | ✓ | ✓ | ✓ | -| Add tags | | | ✓ | ✓ | ✓ | -| Cancel and retry jobs | | | ✓ | ✓ | ✓ | -| Create or update commit status | | | ✓ | ✓ | ✓ | -| Update a container registry | | | ✓ | ✓ | ✓ | -| Remove a container registry image | | | ✓ | ✓ | ✓ | -| Create/edit/delete project milestones | | | ✓ | ✓ | ✓ | +| Action | Guest | Reporter | Developer |Maintainer| Owner | +|---------------------------------------------------|---------|------------|-------------|----------|--------| +| Download project | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ | +| Leave comments | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ | +| View Insights charts **[ULTIMATE]** | ✓ | ✓ | ✓ | ✓ | ✓ | | View approved/blacklisted licenses **[ULTIMATE]** | ✓ | ✓ | ✓ | ✓ | ✓ | -| Use security dashboard **[ULTIMATE]** | | | ✓ | ✓ | ✓ | -| Dismiss vulnerability **[ULTIMATE]** | | | ✓ | ✓ | ✓ | -| Apply code change suggestions | | | ✓ | ✓ | ✓ | -| Use environment terminals | | | | ✓ | ✓ | -| Run Web IDE's Interactive Web Terminals **[ULTIMATE ONLY]** | | | | ✓ | ✓ | -| Add new team members | | | | ✓ | ✓ | -| Push to protected branches | | | | ✓ | ✓ | -| Enable/disable branch protection | | | | ✓ | ✓ | -| Turn on/off protected branch push for devs| | | | ✓ | ✓ | -| Enable/disable tag protections | | | | ✓ | ✓ | -| Rewrite/remove Git tags | | | | ✓ | ✓ | -| Edit project | | | | ✓ | ✓ | -| Add deploy keys to project | | | | ✓ | ✓ | -| Configure project hooks | | | | ✓ | ✓ | -| Manage Runners | | | | ✓ | ✓ | -| Manage job triggers | | | | ✓ | ✓ | -| Manage variables | | | | ✓ | ✓ | -| Manage GitLab Pages | | | | ✓ | ✓ | -| Manage GitLab Pages domains and certificates | | | | ✓ | ✓ | -| Remove GitLab Pages | | | | ✓ | ✓ | +| View license management reports **[ULTIMATE]** | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ | +| View Security reports **[ULTIMATE]** | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ | +| View project code | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ | +| Pull project code | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ | | View GitLab Pages protected by [access control](project/pages/introduction.md#gitlab-pages-access-control-core-only) | ✓ | ✓ | ✓ | ✓ | ✓ | -| Manage clusters | | | | ✓ | ✓ | -| Manage license policy **[ULTIMATE]** | | | | ✓ | ✓ | -| Edit comments (posted by any user) | | | | ✓ | ✓ | -| Manage Error Tracking | | | | ✓ | ✓ | -| Switch visibility level | | | | | ✓ | -| Transfer project to another namespace | | | | | ✓ | -| Remove project | | | | | ✓ | -| Delete issues | | | | | ✓ | -| Force push to protected branches [^4] | | | | | | -| Remove protected branches [^4] | | | | | | -| View project Audit Events | | | | ✓ | ✓ | -| View project statistics | | ✓ | ✓ | ✓ | ✓ | -| View Insights charts **[ULTIMATE]** | ✓ | ✓ | ✓ | ✓ | ✓ | +| View wiki pages | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ | +| See a list of jobs | ✓ (*3*) | ✓ | ✓ | ✓ | ✓ | +| See a job log | ✓ (*3*) | ✓ | ✓ | ✓ | ✓ | +| Download and browse job artifacts | ✓ (*3*) | ✓ | ✓ | ✓ | ✓ | +| Create new issue | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ | +| See related issues | ✓ | ✓ | ✓ | ✓ | ✓ | +| Create confidential issue | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ | +| View confidential issues | (*2*) | ✓ | ✓ | ✓ | ✓ | +| Assign issues | | ✓ | ✓ | ✓ | ✓ | +| Label issues | | ✓ | ✓ | ✓ | ✓ | +| Lock issue discussions | | ✓ | ✓ | ✓ | ✓ | +| Manage issue tracker | | ✓ | ✓ | ✓ | ✓ | +| Manage related issues **[STARTER]** | | ✓ | ✓ | ✓ | ✓ | +| Create issue from vulnerability **[ULTIMATE]** | | ✓ | ✓ | ✓ | ✓ | +| Manage labels | | ✓ | ✓ | ✓ | ✓ | +| Create code snippets | | ✓ | ✓ | ✓ | ✓ | +| See a commit status | | ✓ | ✓ | ✓ | ✓ | +| See a container registry | | ✓ | ✓ | ✓ | ✓ | +| See environments | | ✓ | ✓ | ✓ | ✓ | +| See a list of merge requests | | ✓ | ✓ | ✓ | ✓ | +| View project statistics | | ✓ | ✓ | ✓ | ✓ | +| View Error Tracking list | | ✓ | ✓ | ✓ | ✓ | +| Pull from [Maven repository](project/packages/maven_repository.md) or [NPM registry](project/packages/npm_registry.md) **[PREMIUM]** | | ✓ | ✓ | ✓ | ✓ | +| Publish to [Maven repository](project/packages/maven_repository.md) or [NPM registry](project/packages/npm_registry.md) **[PREMIUM]** | | | ✓ | ✓ | ✓ || +| Create new branches | | | ✓ | ✓ | ✓ | +| Push to non-protected branches | | | ✓ | ✓ | ✓ | +| Force push to non-protected branches | | | ✓ | ✓ | ✓ | +| Remove non-protected branches | | | ✓ | ✓ | ✓ | +| Create new merge request | | | ✓ | ✓ | ✓ | +| Assign merge requests | | | ✓ | ✓ | ✓ | +| Label merge requests | | | ✓ | ✓ | ✓ | +| Lock merge request discussions | | | ✓ | ✓ | ✓ | +| Manage/Accept merge requests | | | ✓ | ✓ | ✓ | +| Create new environments | | | ✓ | ✓ | ✓ | +| Stop environments | | | ✓ | ✓ | ✓ | +| Add tags | | | ✓ | ✓ | ✓ | +| Cancel and retry jobs | | | ✓ | ✓ | ✓ | +| Create or update commit status | | | ✓ | ✓ | ✓ | +| Update a container registry | | | ✓ | ✓ | ✓ | +| Remove a container registry image | | | ✓ | ✓ | ✓ | +| Create/edit/delete project milestones | | | ✓ | ✓ | ✓ | +| Use security dashboard **[ULTIMATE]** | | | ✓ | ✓ | ✓ | +| Dismiss vulnerability **[ULTIMATE]** | | | ✓ | ✓ | ✓ | +| Apply code change suggestions | | | ✓ | ✓ | ✓ | +| Create and edit wiki pages | | | ✓ | ✓ | ✓ | +| Use environment terminals | | | | ✓ | ✓ | +| Run Web IDE's Interactive Web Terminals **[ULTIMATE ONLY]** | | | | ✓ | ✓ | +| Add new team members | | | | ✓ | ✓ | +| Enable/disable branch protection | | | | ✓ | ✓ | +| Push to protected branches | | | | ✓ | ✓ | +| Turn on/off protected branch push for devs | | | | ✓ | ✓ | +| Enable/disable tag protections | | | | ✓ | ✓ | +| Rewrite/remove Git tags | | | | ✓ | ✓ | +| Edit project | | | | ✓ | ✓ | +| Add deploy keys to project | | | | ✓ | ✓ | +| Configure project hooks | | | | ✓ | ✓ | +| Manage Runners | | | | ✓ | ✓ | +| Manage job triggers | | | | ✓ | ✓ | +| Manage variables | | | | ✓ | ✓ | +| Manage GitLab Pages | | | | ✓ | ✓ | +| Manage GitLab Pages domains and certificates | | | | ✓ | ✓ | +| Remove GitLab Pages | | | | ✓ | ✓ | +| Manage clusters | | | | ✓ | ✓ | +| Manage license policy **[ULTIMATE]** | | | | ✓ | ✓ | +| Edit comments (posted by any user) | | | | ✓ | ✓ | +| Manage Error Tracking | | | | ✓ | ✓ | +| Delete wiki pages | | | | ✓ | ✓ | +| View project Audit Events | | | | ✓ | ✓ | +| Switch visibility level | | | | | ✓ | +| Transfer project to another namespace | | | | | ✓ | +| Remove project | | | | | ✓ | +| Delete issues | | | | | ✓ | +| Force push to protected branches [^4] | | | | | | +| Remove protected branches [^4] | | | | | | + +- (*1*): All users are able to perform this action on public and internal projects, but not private projects. +- (*2*): Guest users can only view the confidential issues they created themselves +- (*3*): If **Public pipelines** is enabled in **Project Settings > CI/CD** +- (*4*): Not allowed for Guest, Reporter, Developer, Maintainer, or Owner ## Project features permissions @@ -163,7 +168,7 @@ to learn more. The user that locks a file or directory is the only one that can edit and push their changes back to the repository where the locked objects are located. -Read through the documentation on [permissions for File Locking](https://docs.gitlab.com/ee/user/project/file_lock.html#permissions-on-file-locking) to learn more. +Read through the documentation on [permissions for File Locking](project/file_lock.md#permissions-on-file-locking) to learn more. ### Confidential Issues permissions @@ -191,21 +196,21 @@ Any user can remove themselves from a group, unless they are the last Owner of the group. The following table depicts the various user permission levels in a group. -| Action | Guest | Reporter | Developer | Maintainer | Owner | -|-------------------------|-------|----------|-----------|--------|-------| -| Browse group | ✓ | ✓ | ✓ | ✓ | ✓ | -| Edit group | | | | | ✓ | -| Create subgroup | | | | | ✓ | -| Create project in group | | | ✓ | ✓ | ✓ | -| Manage group members | | | | | ✓ | -| Remove group | | | | | ✓ | -| Manage group labels | | ✓ | ✓ | ✓ | ✓ | -| Create/edit/delete group milestones | | | ✓ | ✓ | ✓ | -| View group epic **[ULTIMATE]** | ✓ | ✓ | ✓ | ✓ | ✓ | -| Create/edit group epic **[ULTIMATE]** | | ✓ | ✓ | ✓ | ✓ | -| Delete group epic **[ULTIMATE]** | | | | | ✓ | -| View group Audit Events | | | | | ✓ | -| View Insights charts **[ULTIMATE]** | ✓ | ✓ | ✓ | ✓ | ✓ | +| Action | Guest | Reporter | Developer | Maintainer | Owner | +|---------------------------------------|-------|----------|-----------|------------|-------| +| Browse group | ✓ | ✓ | ✓ | ✓ | ✓ | +| View Insights charts **[ULTIMATE]** | ✓ | ✓ | ✓ | ✓ | ✓ | +| View group epic **[ULTIMATE]** | ✓ | ✓ | ✓ | ✓ | ✓ | +| Create/edit group epic **[ULTIMATE]** | | ✓ | ✓ | ✓ | ✓ | +| Manage group labels | | ✓ | ✓ | ✓ | ✓ | +| Create project in group | | | ✓ | ✓ | ✓ | +| Create/edit/delete group milestones | | | ✓ | ✓ | ✓ | +| Edit group | | | | | ✓ | +| Create subgroup | | | | | ✓ | +| Manage group members | | | | | ✓ | +| Remove group | | | | | ✓ | +| Delete group epic **[ULTIMATE]** | | | | | ✓ | +| View group Audit Events | | | | | ✓ | ### Subgroup permissions @@ -257,15 +262,15 @@ Please be aware that this regex could lead to a DOS attack, [see](https://en.wik ## Auditor users **[PREMIUM ONLY]** ->[Introduced][ee-998] in [GitLab Premium][eep] 8.17. +>[Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/998) in [GitLab Premium](https://about.gitlab.com/pricing/) 8.17. Auditor users are given read-only access to all projects, groups, and other resources on the GitLab instance. An Auditor user should be able to access all projects and groups of a GitLab instance -with the permissions described on the documentation on [auditor users permissions](https://docs.gitlab.com/ee/administration/auditor_users.html#permissions-and-restrictions-of-an-auditor-user). +with the permissions described on the documentation on [auditor users permissions](../administration/auditor_users.md#permissions-and-restrictions-of-an-auditor-user). -[Read more about Auditor users.](https://docs.gitlab.com/ee/administration/auditor_users.html) +[Read more about Auditor users.](../administration/auditor_users.md) ## Project features @@ -298,7 +303,7 @@ instance and project. In addition, all admins can use the admin interface under |---------------------------------------|-----------------|-------------|----------|--------| | See commits and jobs | ✓ | ✓ | ✓ | ✓ | | Retry or cancel job | | ✓ | ✓ | ✓ | -| Erase job artifacts and trace | | ✓ [^5] | ✓ | ✓ | +| Erase job artifacts and trace | | ✓ (*1*) | ✓ | ✓ | | Remove project | | | ✓ | ✓ | | Create project | | | ✓ | ✓ | | Change project configuration | | | ✓ | ✓ | @@ -307,6 +312,8 @@ instance and project. In addition, all admins can use the admin interface under | See events in the system | | | | ✓ | | Admin interface | | | | ✓ | +- *1*: Only if the job was triggered by the user + ### Job permissions NOTE: **Note:** @@ -314,25 +321,28 @@ In GitLab 11.0, the Master role was renamed to Maintainer. >**Note:** GitLab 8.12 has a completely redesigned job permissions system. -Read all about the [new model and its implications][new-mod]. +Read all about the [new model and its implications](project/new_ci_build_permissions_model.md). This table shows granted privileges for jobs triggered by specific types of users: -| Action | Guest, Reporter | Developer |Maintainer| Admin | -|---------------------------------------------|-----------------|-------------|----------|--------| -| Run CI job | | ✓ | ✓ | ✓ | -| Clone source and LFS from current project | | ✓ | ✓ | ✓ | -| Clone source and LFS from public projects | | ✓ | ✓ | ✓ | -| Clone source and LFS from internal projects | | ✓ [^6] | ✓ [^6] | ✓ | -| Clone source and LFS from private projects | | ✓ [^7] | ✓ [^7] | ✓ [^7] | -| Push source and LFS | | | | | -| Pull container images from current project | | ✓ | ✓ | ✓ | -| Pull container images from public projects | | ✓ | ✓ | ✓ | -| Pull container images from internal projects| | ✓ [^6] | ✓ [^6] | ✓ | -| Pull container images from private projects | | ✓ [^7] | ✓ [^7] | ✓ [^7] | -| Push container images to current project | | ✓ | ✓ | ✓ | -| Push container images to other projects | | | | | +| Action | Guest, Reporter | Developer |Maintainer| Admin | +|---------------------------------------------|-----------------|-------------|----------|---------| +| Run CI job | | ✓ | ✓ | ✓ | +| Clone source and LFS from current project | | ✓ | ✓ | ✓ | +| Clone source and LFS from public projects | | ✓ | ✓ | ✓ | +| Clone source and LFS from internal projects | | ✓ (*1*) | ✓ (*1*) | ✓ | +| Clone source and LFS from private projects | | ✓ (*2*) | ✓ (*2*) | ✓ (*2*) | +| Pull container images from current project | | ✓ | ✓ | ✓ | +| Pull container images from public projects | | ✓ | ✓ | ✓ | +| Pull container images from internal projects| | ✓ (*1*) | ✓ (*1*) | ✓ | +| Pull container images from private projects | | ✓ (*2*) | ✓ (*2*) | ✓ (*2*) | +| Push container images to current project | | ✓ | ✓ | ✓ | +| Push container images to other projects | | | | | +| Push source and LFS | | | | | + +- *1*: Only if the user is not an external one +- *2*: Only if the user is a member of the project ### New CI job permissions model @@ -350,17 +360,4 @@ for details about the pipelines security model. ## LDAP users permissions Since GitLab 8.15, LDAP user permissions can now be manually overridden by an admin user. -Read through the documentation on [LDAP users permissions](https://docs.gitlab.com/ee/administration/auth/how_to_configure_ldap_gitlab_ee/index.html) to learn more. - -[^1]: On public and internal projects, all users are able to perform this action -[^2]: Guest users can only view the confidential issues they created themselves -[^3]: If **Public pipelines** is enabled in **Project Settings > CI/CD** -[^4]: Not allowed for Guest, Reporter, Developer, Maintainer, or Owner -[^5]: Only if the job was triggered by the user -[^6]: Only if user is not external one -[^7]: Only if user is a member of the project - -[ce-18994]: https://gitlab.com/gitlab-org/gitlab-ce/issues/18994 -[new-mod]: project/new_ci_build_permissions_model.md -[ee-998]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/998 -[eep]: https://about.gitlab.com/pricing/ +Read through the documentation on [LDAP users permissions](../administration/auth/how_to_configure_ldap_gitlab_ee/index.html) to learn more. diff --git a/doc/user/profile/personal_access_tokens.md b/doc/user/profile/personal_access_tokens.md index 5166524d13b..0b224fc7e01 100644 --- a/doc/user/profile/personal_access_tokens.md +++ b/doc/user/profile/personal_access_tokens.md @@ -37,20 +37,18 @@ Personal access tokens can be created with one or more scopes that allow various actions that a given token can perform. The available scopes are depicted in the following table. -| Scope | Description | -| ----- | ----------- | -|`read_user` | Allows access to the read-only endpoints under `/users`. Essentially, any of the `GET` requests in the [Users API][users] are allowed ([introduced][ce-5951] in GitLab 8.15). | -| `api` | Grants complete access to the API and Container Registry (read/write) ([introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5951) in GitLab 8.15). | -| `read_registry` | Allows to read (pull) [container registry] images if a project is private and authorization is required ([introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11845) in GitLab 9.3). | -| `sudo` | Allows performing API actions as any user in the system (if the authenticated user is an admin) ([introduced][ce-14838] in GitLab 10.2). | -| `read_repository` | Allows read-only access (pull) to the repository through git clone. | -| `write_repository` | Allows read-write access (pull, push) to the repository through git clone. Required for accessing Git repositories over HTTP when 2FA is enabled. | +| Scope | Introduced in | Description | +| ------------------ | ------------- | ----------- | +| `read_user` | [GitLab 8.15](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5951) | Allows access to the read-only endpoints under `/users`. Essentially, any of the `GET` requests in the [Users API][users] are allowed. | +| `api` | [GitLab 8.15](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5951) | Grants complete access to the API and Container Registry (read/write). | +| `read_registry` | [GitLab 9.3](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11845) | Allows to read (pull) [container registry] images if a project is private and authorization is required. | +| `sudo` | [GitLab 10.2](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14838) | Allows performing API actions as any user in the system (if the authenticated user is an admin). | +| `read_repository` | [GitLab 10.7](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17894) | Allows read-only access (pull) to the repository through git clone. | +| `write_repository` | [GitLab 11.11](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/26021) | Allows read-write access (pull, push) to the repository through git clone. Required for accessing Git repositories over HTTP when 2FA is enabled. | [2fa]: ../account/two_factor_authentication.md [api]: ../../api/README.md [ce-3749]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3749 -[ce-5951]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5951 -[ce-14838]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14838 [container registry]: ../project/container_registry.md [users]: ../../api/users.md [usage]: ../../api/README.md#personal-access-tokens diff --git a/doc/user/project/ci_cd_for_external_repo.md b/doc/user/project/ci_cd_for_external_repo.md index 51b86a68c7b..a92d3a2c308 100644 --- a/doc/user/project/ci_cd_for_external_repo.md +++ b/doc/user/project/ci_cd_for_external_repo.md @@ -1,5 +1,5 @@ --- -redirect_to: 'https://docs.gitlab.com/ee/ci/ci_cd_for_external_repos/index.html' +redirect_to: '../../ci/ci_cd_for_external_repos/index.md' --- -This document was moved to [another location](https://docs.gitlab.com/ee/ci/ci_cd_for_external_repos/index.html). +This document was moved to [another location](../../ci/ci_cd_for_external_repos/index.md). diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md index e38e4059117..dc21db603d6 100644 --- a/doc/user/project/clusters/index.md +++ b/doc/user/project/clusters/index.md @@ -450,7 +450,7 @@ differentiate the new cluster with the rest. When adding more than one Kubernetes cluster to your project, you need to differentiate them with an environment scope. The environment scope associates clusters with [environments](../../../ci/environments.md) similar to how the -[environment-specific variables](https://docs.gitlab.com/ee/ci/variables/index.html#limiting-environment-scopes-of-environment-variables-premium) work. +[environment-specific variables](../../../ci/variables/README.md#limiting-environment-scopes-of-environment-variables-premium) work. The default environment scope is `*`, which means all jobs, regardless of their environment, will use that cluster. Each scope can only be used by a single @@ -588,7 +588,7 @@ displaying the status of the pods in the deployment. Developers and other teammates can view the progress and status of a rollout, pod by pod, in the workflow they already use without any need to access Kubernetes. -[Read more about Deploy Boards](https://docs.gitlab.com/ee/user/project/deploy_boards.html) +[Read more about Deploy Boards](../deploy_boards.md) ### Canary Deployments **[PREMIUM]** @@ -596,7 +596,7 @@ Leverage [Kubernetes' Canary deployments](https://kubernetes.io/docs/concepts/cl and visualize your canary deployments right inside the Deploy Board, without the need to leave GitLab. -[Read more about Canary Deployments](https://docs.gitlab.com/ee/user/project/canary_deployments.html) +[Read more about Canary Deployments](../canary_deployments.md) ### Pod logs **[ULTIMATE]** diff --git a/doc/user/project/clusters/kubernetes_pod_logs.md b/doc/user/project/clusters/kubernetes_pod_logs.md index d5b60250860..368031070c1 100644 --- a/doc/user/project/clusters/kubernetes_pod_logs.md +++ b/doc/user/project/clusters/kubernetes_pod_logs.md @@ -7,10 +7,10 @@ By displaying the logs directly in GitLab, developers can avoid having to manage ## Overview -[Kubernetes](https://kubernetes.io) pod logs can be viewed directly within GitLab. Logs can be displayed by clicking on a specific pod from [Deploy Boards](https://docs.gitlab.com/ee/user/project/deploy_boards.html): +[Kubernetes](https://kubernetes.io) pod logs can be viewed directly within GitLab. Logs can be displayed by clicking on a specific pod from [Deploy Boards](../deploy_boards.md): 1. Go to **Operations > Environments** and find the environment which contains the desired pod, like `production`. -1. On the **Environments** page, you should see the status of the environment's pods with [Deploy Boards](https://docs.gitlab.com/ee/user/project/deploy_boards.html). +1. On the **Environments** page, you should see the status of the environment's pods with [Deploy Boards](../deploy_boards.md). 1. When mousing over the list of pods, a tooltip will appear with the exact pod name and status. ![Deploy Boards pod list](img/pod_logs_deploy_board.png) 1. Click on the desired pod to bring up the logs view, which will contain the last 500 lines for that pod. Support for pods with multiple containers is coming [in a future release](https://gitlab.com/gitlab-org/gitlab-ee/issues/6502). @@ -18,4 +18,4 @@ By displaying the logs directly in GitLab, developers can avoid having to manage ## Requirements -[Enabling Deploy Boards](https://docs.gitlab.com/ee/user/project/deploy_boards.html#enabling-deploy-boards) is required in order to be able to use Pod Logs. +[Enabling Deploy Boards](../deploy_boards.md#enabling-deploy-boards) is required in order to be able to use Pod Logs. diff --git a/doc/user/project/clusters/runbooks/index.md b/doc/user/project/clusters/runbooks/index.md index 54c475a1762..6360a01a0ad 100644 --- a/doc/user/project/clusters/runbooks/index.md +++ b/doc/user/project/clusters/runbooks/index.md @@ -34,7 +34,7 @@ for an overview of how this is accomplished in GitLab!** To create an executable runbook, you will need: 1. **Kubernetes** - A Kubernetes cluster is required to deploy the rest of the applications. - The simplest way to get started is to add a cluster using [GitLab's GKE integration](https://docs.gitlab.com/ee/user/project/clusters/#adding-and-creating-a-new-gke-cluster-via-gitlab). + The simplest way to get started is to add a cluster using [GitLab's GKE integration](../index.md#adding-and-creating-a-new-gke-cluster-via-gitlab). 1. **Helm Tiller** - Helm is a package manager for Kubernetes and is required to install all the other applications. It is installed in its own pod inside the cluster which can run the helm CLI in a safe environment. @@ -59,7 +59,7 @@ the components outlined above and the preloaded demo runbook. ### 1. Add a Kubernetes cluster -Follow the steps outlined in [Adding and creating a new GKE cluster via GitLab](https://docs.gitlab.com/ee/user/project/clusters/#adding-and-creating-a-new-gke-cluster-via-gitlab) +Follow the steps outlined in [Adding and creating a new GKE cluster via GitLab](../index.md#adding-and-creating-a-new-gke-cluster-via-gitlab) to add a Kubernetes cluster to your project. ### 2. Install Helm Tiller, Ingress, and JupyterHub @@ -90,7 +90,7 @@ The server will take a couple of seconds to start. ### 4. Configure access In order for the runbook to access your GitLab project, you will need to enter a -[GitLab Access Token](https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html) +[GitLab Access Token](../../../profile/personal_access_tokens.md) as well as your Project ID in the **Setup** section of the demo runbook. Double-click the **DevOps-Runbook-Demo** folder located on the left panel. diff --git a/doc/user/project/deploy_tokens/index.md b/doc/user/project/deploy_tokens/index.md index 7688508c6ac..92a29b68a22 100644 --- a/doc/user/project/deploy_tokens/index.md +++ b/doc/user/project/deploy_tokens/index.md @@ -5,7 +5,7 @@ Deploy tokens allow to download (through `git clone`), or read the container registry images of a project without the need of having a user and a password. Please note, that the expiration of deploy tokens happens on the date you define, -at midnight UTC and that they can be only managed by [maintainers](https://docs.gitlab.com/ee/user/permissions.html). +at midnight UTC and that they can be only managed by [maintainers](../../permissions.md). ## Creating a Deploy Token diff --git a/doc/user/project/import/gemnasium.md b/doc/user/project/import/gemnasium.md index dc5b3fcd0bb..7f79ebf6353 100644 --- a/doc/user/project/import/gemnasium.md +++ b/doc/user/project/import/gemnasium.md @@ -9,9 +9,9 @@ Gemnasium has been [acquired by GitLab](https://about.gitlab.com/press/releases/ in January 2018. Since May 15, 2018, the services provided by Gemnasium are no longer available. The team behind Gemnasium has joined GitLab as the new Security Products team and is working on a wider range of tools than just Dependency Scanning: -[SAST](https://docs.gitlab.com/ee/user/application_security/sast/index.html), -[DAST](https://docs.gitlab.com/ee/user/application_security/dast/index.html), -[Container Scanning](https://docs.gitlab.com/ee/user/application_security/container_scanning/index.html) and more. +[SAST](../../application_security/sast/index.md), +[DAST](../../application_security/dast/index.md), +[Container Scanning](../../application_security/container_scanning/index.md) and more. If you want to continue monitoring your dependencies, see the [Migrating to GitLab](#migrating-to-gitlab) section below. @@ -45,7 +45,7 @@ Security features are free for public (open-source) projects hosted on GitLab.co You're almost set! If you're already using [Auto DevOps](../../../topics/autodevops/), you are already covered. Otherwise, you must configure your `.gitlab-ci.yml` according to the -[dependency scanning page](https://docs.gitlab.com/ee/user/application_security/dependency_scanning/index.html). +[dependency scanning page](../../application_security/dependency_scanning/index.md). ### If your project is hosted on GitHub (https://github.com / GitHub Enterprise) @@ -81,7 +81,7 @@ back to both GitLab and GitHub when completed. 1. To set up the dependency scanning job, corresponding to what Gemnasium was doing, you must create a `.gitlab-ci.yml` file, or update it according to - the [dependency scanning docs](https://docs.gitlab.com/ee/user/application_security/dependency_scanning/index.html). + the [dependency scanning docs](../../application_security/dependency_scanning/index.md). The mirroring is pull-only by default, so you may create or update the file on GitHub: diff --git a/doc/user/project/import/github.md b/doc/user/project/import/github.md index 63b90dd76fd..8fba892594b 100644 --- a/doc/user/project/import/github.md +++ b/doc/user/project/import/github.md @@ -119,9 +119,9 @@ Depending your GitLab tier, [project mirroring](../../../workflow/repository_mir your imported project in sync with its GitHub copy. Additionally, you can configure GitLab to send pipeline status updates back GitHub with the -[GitHub Project Integration](https://docs.gitlab.com/ee/user/project/integrations/github.html). **[PREMIUM]** +[GitHub Project Integration](../integrations/github.md). **[PREMIUM]** -If you import your project using [CI/CD for external repo](https://docs.gitlab.com/ee/ci/ci_cd_for_external_repos/), then both +If you import your project using [CI/CD for external repo](../../../ci/ci_cd_for_external_repos/index.md), then both of the above are automatically configured. **[PREMIUM]** ## Improving the speed of imports on self-hosted instances diff --git a/doc/user/project/import/index.md b/doc/user/project/import/index.md index ebbc5ca133b..2b6927bd780 100644 --- a/doc/user/project/import/index.md +++ b/doc/user/project/import/index.md @@ -14,12 +14,13 @@ 1. [From repo by URL](repo_by_url.md) 1. [By uploading a manifest file (AOSP)](manifest.md) 1. [From Gemnasium](gemnasium.md) +1. [From Phabricator](phabricator.md) In addition to the specific migration documentation above, you can import any Git repository via HTTP from the New Project page. Be aware that if the repository is too large the import can timeout. -There is also the option of [connecting your external repository to get CI/CD benefits](https://docs.gitlab.com/ee/ci/ci_cd_for_external_repos/index.html). **[PREMIUM]** +There is also the option of [connecting your external repository to get CI/CD benefits](../../../ci/ci_cd_for_external_repos/index.md). **[PREMIUM]** ## Migrating from self-hosted GitLab to GitLab.com diff --git a/doc/user/project/import/phabricator.md b/doc/user/project/import/phabricator.md new file mode 100644 index 00000000000..4d1d99fd35b --- /dev/null +++ b/doc/user/project/import/phabricator.md @@ -0,0 +1,32 @@ +# Import Phabricator tasks into a GitLab project + +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/60562) in +GitLab 12.0. + +GitLab allows you to import all tasks from a Phabricator instance into +GitLab issues. The import creates a single project with the +repository disabled. + +Currently, only the following basic fields are imported: + +- Title +- Description +- State (open or closed) +- Created at +- Closed at + + +## Enabling this feature + +While this feature is incomplete, a feature flag is required to enable it so that +we can gain early feedback before releasing it for everyone. To enable it: + +1. Enable Phabricator as an [import source](../../admin_area/settings/visibility_and_access_controls.md#import-sources) in the Admin area. + + ``` {.ruby} + Feature.enable(:phabricator_import) + ``` + +The [import +source](../../admin_area/settings/visibility_and_access_controls.md#import-sources) +also needs to be activated by an admin in the admin interface. diff --git a/doc/user/project/index.md b/doc/user/project/index.md index 6b3b40bf9f8..a24f525253d 100644 --- a/doc/user/project/index.md +++ b/doc/user/project/index.md @@ -37,7 +37,7 @@ When you create a project in GitLab, you'll have access to a large number of - [Multiple Issue Boards](issue_board.md#multiple-issue-boards-starter): Allow your teams to create their own workflows (Issue Boards) for the same project **[STARTER]** - [Merge Requests](merge_requests/index.md): Apply your branching strategy and get reviewed by your team - - [Merge Request Approvals](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html): Ask for approval before + - [Merge Request Approvals](merge_requests/merge_request_approvals.md): Ask for approval before implementing a change **[STARTER]** - [Fix merge conflicts from the UI](merge_requests/resolve_conflicts.md): Your Git diff tool right from GitLab's UI @@ -74,7 +74,7 @@ When you create a project in GitLab, you'll have access to a large number of timeout (defines the maximum amount of time in minutes that a job is able run), custom path for `.gitlab-ci.yml`, test coverage parsing, pipeline's visibility, and much more - [Kubernetes cluster integration](clusters/index.md): Connecting your GitLab project with a Kubernetes cluster - - [Feature Flags](https://docs.gitlab.com/ee/user/project/operations/feature_flags.html): Feature flags allow you to ship a project in + - [Feature Flags](operations/feature_flags.md): Feature flags allow you to ship a project in different flavors by dynamically toggling certain functionality **[PREMIUM]** - [GitLab Pages](pages/index.md): Build, test, and deploy your static website with GitLab Pages @@ -91,10 +91,10 @@ When you create a project in GitLab, you'll have access to a large number of - [Releases](releases/index.md): a way to track deliverables in your project as snapshot in time of the source, build output, and other metadata or artifacts associated with a released version of your code. -- [Maven packages](https://docs.gitlab.com/ee/user/project/packages/maven_repository.html): your private Maven repository in GitLab. **[PREMIUM]** -- [NPM packages](https://docs.gitlab.com/ee/user/project/packages/npm_registry.html): your private NPM package registry in GitLab. **[PREMIUM]** +- [Maven packages](packages/maven_repository.md): your private Maven repository in GitLab. **[PREMIUM]** +- [NPM packages](packages/npm_registry.md): your private NPM package registry in GitLab. **[PREMIUM]** - [Code owners](code_owners.md): specify code owners for certain files **[STARTER]** -- [License Management](https://docs.gitlab.com/ee/user/application_security/license_management/index.html): approve and blacklist licenses for projects. **[ULTIMATE]** +- [License Management](../application_security/license_management/index.md): approve and blacklist licenses for projects. **[ULTIMATE]** ### Project integrations @@ -135,7 +135,7 @@ Read through the documentation on [project settings](settings/index.md). Instead of importing a repository directly to GitLab, you can connect your repository as a CI/CD project. -Read through the documentation on [CI/CD for external repositories](https://docs.gitlab.com/ee/ci/ci_cd_for_external_repos/index.html). +Read through the documentation on [CI/CD for external repositories](../../ci/ci_cd_for_external_repos/index.md). ## Project members diff --git a/doc/user/project/insights/index.md b/doc/user/project/insights/index.md index b6cc1862cc2..5154ff38154 100644 --- a/doc/user/project/insights/index.md +++ b/doc/user/project/insights/index.md @@ -14,7 +14,7 @@ requests to be merged and much more. ![Insights example bar chart](img/project_insights.png) NOTE: **Note:** -This feature is [also available at the group level](https://docs.gitlab.com/ee/user/group/insights/index.html). +This feature is [also available at the group level](../../group/insights/index.md). ## View your project's Insights @@ -33,7 +33,7 @@ for details about the content of this file. NOTE: **Note:** Once the configuration file is created, you can also -[use it for your project's group](https://docs.gitlab.com/ee/user/group/insights/index.html#configure-your-insights). +[use it for your project's group](../../group/insights/index.md#configure-your-insights). NOTE: **Note:** If the project doesn't have any configuration file, it'll try to use diff --git a/doc/user/project/integrations/project_services.md b/doc/user/project/integrations/project_services.md index f560de427c5..0bfee3bac99 100644 --- a/doc/user/project/integrations/project_services.md +++ b/doc/user/project/integrations/project_services.md @@ -39,7 +39,7 @@ Click on the service links to see further configuration instructions and details | [HipChat](hipchat.md) | Private group chat and IM | | [Irker (IRC gateway)](irker.md) | Send IRC messages, on update, to a list of recipients through an Irker gateway | | [JIRA](jira.md) | JIRA issue tracker | -| [Jenkins](https://docs.gitlab.com/ee/integration/jenkins.html) **[STARTER]** | An extendable open source continuous integration server | +| [Jenkins](../../../integration/jenkins.md) **[STARTER]** | An extendable open source continuous integration server | | JetBrains TeamCity CI | A continuous integration and build server | | [Mattermost slash commands](mattermost_slash_commands.md) | Mattermost chat and ChatOps slash commands | | [Mattermost Notifications](mattermost.md) | Receive event notifications in Mattermost | diff --git a/doc/user/project/integrations/prometheus_library/kubernetes.md b/doc/user/project/integrations/prometheus_library/kubernetes.md index 436129f1dbc..8b1cf1a251a 100644 --- a/doc/user/project/integrations/prometheus_library/kubernetes.md +++ b/doc/user/project/integrations/prometheus_library/kubernetes.md @@ -27,7 +27,7 @@ integration services must be enabled. Prometheus needs to be deployed into the cluster and configured properly in order to gather Kubernetes metrics. GitLab supports two methods for doing so: -- GitLab [integrates with Kubernetes](../../clusters/index.md), and can [deploy Prometheus into a connected cluster](../prometheus.html#managed-prometheus-on-kubernetes). It is automatically configured to collect Kubernetes metrics. +- GitLab [integrates with Kubernetes](../../clusters/index.md), and can [deploy Prometheus into a connected cluster](../prometheus.md#managed-prometheus-on-kubernetes). It is automatically configured to collect Kubernetes metrics. - To configure your own Prometheus server, you can follow the [Prometheus documentation](https://prometheus.io/docs/introduction/overview/). ## Specifying the Environment @@ -40,7 +40,7 @@ Instead, the [Deployment](https://kubernetes.io/docs/concepts/workloads/controll > Introduced in [GitLab 10.2](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/15201). -GitLab also gathers Kubernetes metrics for [canary deployments](https://docs.gitlab.com/ee/user/project/canary_deployments.html), allowing easy comparison between the current deployed version and the canary. +GitLab also gathers Kubernetes metrics for [canary deployments](../../canary_deployments.md), allowing easy comparison between the current deployed version and the canary. These metrics expect the [Deployment](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/) or [DaemonSet](https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/) name to begin with `$CI_ENVIRONMENT_SLUG-canary`, to isolate the canary metrics. diff --git a/doc/user/project/issues/create_new_issue.md b/doc/user/project/issues/create_new_issue.md index 5e05846b77f..c2916c79876 100644 --- a/doc/user/project/issues/create_new_issue.md +++ b/doc/user/project/issues/create_new_issue.md @@ -67,7 +67,7 @@ or contacts to continue working._ ## New issue via Service Desk **[PREMIUM]** -Enable [Service Desk](https://docs.gitlab.com/ee/user/project/service_desk.html) to your project and offer email support. +Enable [Service Desk](../service_desk.md) to your project and offer email support. By doing so, when your customer sends a new email, a new issue can be created in the appropriate project and followed up from there. diff --git a/doc/user/project/issues/index.md b/doc/user/project/issues/index.md index c82b7f100d2..94865ad46ee 100644 --- a/doc/user/project/issues/index.md +++ b/doc/user/project/issues/index.md @@ -102,13 +102,13 @@ For more information, see the [Issue Boards](../issue_board.md) page. Epics let you manage your portfolio of projects more efficiently and with less effort by tracking groups of issues that share a theme, across projects and milestones. -For more information, see the [Epics](https://docs.gitlab.com/ee/user/group/epics/) page. +For more information, see the [Epics](../../group/epics/index.md) page. ### Related issues **[STARTER]** You can mark two issues as related, so that when viewing each one, the other is always listed in its Related Issues section. This can help display important context, such as past work, dependencies, or duplicates. -For more information, see [Related Issues](https://docs.gitlab.com/ee/user/project/issues/related_issues.html). +For more information, see [Related Issues](related_issues.md). ### Crosslinking issues @@ -129,7 +129,7 @@ For more information, see [Crosslinking issues](crosslinking_issues.md). - [Bulk edit issues](../bulk_editing.md) - From the Issues List, select multiple issues in order to change their status, assignee, milestone, or labels in bulk. - [Import issues](csv_import.md) -- [Export issues](https://docs.gitlab.com/ee/user/project/issues/csv_export.html) **[STARTER]** +- [Export issues](csv_export.md) **[STARTER]** - [Issues API](../../../api/issues.md) - Configure an [external issue tracker](../../../integration/external-issue-tracker.md) such as Jira, Redmine, or Bugzilla. diff --git a/doc/user/project/issues/issue_data_and_actions.md b/doc/user/project/issues/issue_data_and_actions.md index ef9fcaec3e6..fc11c0251e0 100644 --- a/doc/user/project/issues/issue_data_and_actions.md +++ b/doc/user/project/issues/issue_data_and_actions.md @@ -90,7 +90,7 @@ If a label doesn't exist yet, you can click **Edit**, and it opens a dropdown me - Assign a weight. Larger values are used to indicate more effort is required to complete the issue. Only positive values or zero are allowed. -Learn more in the [Issue Weight documentation](https://docs.gitlab.com/ee/workflow/issue_weight.html). +Learn more in the [Issue Weight documentation](../../../workflow/issue_weight.md). #### 9. Participants @@ -103,7 +103,7 @@ Learn more in the [Issue Weight documentation](https://docs.gitlab.com/ee/workfl - Unsubscribe: if you are receiving notifications on that issue but no longer want to receive them, unsubscribe from it. -Read more in the [notifications documentation](https://docs.gitlab.com/ee/workflow/notifications.html#issue--epics--merge-request-events). +Read more in the [notifications documentation](../../../workflow/notifications.md#issue--epics--merge-request-events). #### 11. Reference diff --git a/doc/user/project/issues/related_issues.md b/doc/user/project/issues/related_issues.md index db0ab65b442..e679ebf86e6 100644 --- a/doc/user/project/issues/related_issues.md +++ b/doc/user/project/issues/related_issues.md @@ -1,6 +1,6 @@ # Related issues **[STARTER]** -> [Introduced][ee-1797] in [GitLab Starter][ee] 9.4. +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/1797) in [GitLab Starter](https://about.gitlab.com/pricing/) 9.4. Related issues are a bi-directional relationship between any two issues and appear in a block below the issue description. Issues can be across groups @@ -35,11 +35,6 @@ will no longer appear in either issue. ![Removing a related issue](img/related_issues_remove.png) -Please access our [permissions] page for more information. +Please access our [permissions](../../permissions.md) page for more information. -Additionally, you are also able to manage related issues through [our API]. - -[ee]: https://about.gitlab.com/pricing/ -[ee-1797]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/1797 -[permissions]: ../../permissions.md -[Our API]: https://docs.gitlab.com/ee/api/issue_links.html +Additionally, you are also able to manage related issues through [our API](../../../api/issue_links.md). diff --git a/doc/user/project/maven_packages.md b/doc/user/project/maven_packages.md index d32d6084b38..48835a2dac7 100644 --- a/doc/user/project/maven_packages.md +++ b/doc/user/project/maven_packages.md @@ -1,5 +1,5 @@ --- -redirect_to: 'https://docs.gitlab.com/ee/user/project/packages/maven_repository.html' +redirect_to: 'packages/maven_repository.md' --- -This document was moved to [another location](https://docs.gitlab.com/ee/user/project/packages/maven_repository.html). +This document was moved to [another location](packages/maven_repository.md). diff --git a/doc/user/project/merge_requests/code_quality_diff.md b/doc/user/project/merge_requests/code_quality_diff.md index 890058eec6f..ccc694672a6 100644 --- a/doc/user/project/merge_requests/code_quality_diff.md +++ b/doc/user/project/merge_requests/code_quality_diff.md @@ -1,5 +1,5 @@ --- -redirect_from: 'https://docs.gitlab.com/ee/user/project/merge_requests/code_quality_diff.html' +redirect_from: 'code_quality_diff.md' redirect_to: 'code_quality.md' --- diff --git a/doc/user/project/merge_requests/container_scanning.md b/doc/user/project/merge_requests/container_scanning.md index 4d41e424f4a..a062731ea35 100644 --- a/doc/user/project/merge_requests/container_scanning.md +++ b/doc/user/project/merge_requests/container_scanning.md @@ -1,5 +1,5 @@ --- -redirect_to: 'https://docs.gitlab.com/ee/user/application_security/container_scanning/index.html' +redirect_to: '../../application_security/container_scanning/index.md' --- -This document was moved to [another location](https://docs.gitlab.com/ee/user/application_security/container_scanning/index.html). +This document was moved to [another location](../../application_security/container_scanning/index.md). diff --git a/doc/user/project/merge_requests/dast.md b/doc/user/project/merge_requests/dast.md index b676c661267..98a2906e560 100644 --- a/doc/user/project/merge_requests/dast.md +++ b/doc/user/project/merge_requests/dast.md @@ -1,5 +1,5 @@ --- -redirect_to: 'https://docs.gitlab.com/ee/user/application_security/dast/index.html' +redirect_to: '../../application_security/dast/index.md' --- -This document was moved to [another location](https://docs.gitlab.com/ee/user/application_security/dast/index.html). +This document was moved to [another location](../../application_security/dast/index.md). diff --git a/doc/user/project/merge_requests/dependency_scanning.md b/doc/user/project/merge_requests/dependency_scanning.md index 3a8b53b425c..bdc1c355016 100644 --- a/doc/user/project/merge_requests/dependency_scanning.md +++ b/doc/user/project/merge_requests/dependency_scanning.md @@ -1,5 +1,5 @@ --- -redirect_to: 'https://docs.gitlab.com/ee/user/application_security/dependency_scanning/index.html' +redirect_to: '../../application_security/dependency_scanning/index.md' --- -This document was moved to [another location](https://docs.gitlab.com/ee/user/application_security/dependency_scanning/index.html). +This document was moved to [another location](../../application_security/dependency_scanning/index.md). diff --git a/doc/user/project/merge_requests/index.md b/doc/user/project/merge_requests/index.md index 09736c7fc7e..4cfe59b808a 100644 --- a/doc/user/project/merge_requests/index.md +++ b/doc/user/project/merge_requests/index.md @@ -33,15 +33,15 @@ With GitLab merge requests, you can: With **[GitLab Enterprise Edition][ee]**, you can also: -- Prepare a full review and submit it once it's ready with [Merge Request Reviews](https://docs.gitlab.com/ee/user/discussions/index.md#merge-request-reviews-premium) **[PREMIUM]** -- View the deployment process across projects with [Multi-Project Pipelines](https://docs.gitlab.com/ee/ci/multi_project_pipelines.md) **[PREMIUM]** +- Prepare a full review and submit it once it's ready with [Merge Request Reviews](../../discussions/index.md#merge-request-reviews-premium) **[PREMIUM]** +- View the deployment process across projects with [Multi-Project Pipelines](../../../ci/multi_project_pipelines.md) **[PREMIUM]** - Request [approvals](merge_request_approvals.md) from your managers **[STARTER]** - Analyze the impact of your changes with [Code Quality reports](code_quality.md) **[STARTER]** -- Manage the licenses of your dependencies with [License Management](https://docs.gitlab.com/ee/user/application_security/license_management/index.md) **[ULTIMATE]** -- Analyze your source code for vulnerabilities with [Static Application Security Testing](https://docs.gitlab.com/ee/user/application_security/sast/index.md) **[ULTIMATE]** -- Analyze your running web applications for vulnerabilities with [Dynamic Application Security Testing](https://docs.gitlab.com/ee/user/application_security/dast/index.md) **[ULTIMATE]** -- Analyze your dependencies for vulnerabilities with [Dependency Scanning](https://docs.gitlab.com/ee/user/application_security/dependency_scanning/index.md) **[ULTIMATE]** -- Analyze your Docker images for vulnerabilities with [Container Scanning](https://docs.gitlab.com/ee/user/application_security/container_scanning/index.md) **[ULTIMATE]** +- Manage the licenses of your dependencies with [License Management](../../application_security/license_management/index.md) **[ULTIMATE]** +- Analyze your source code for vulnerabilities with [Static Application Security Testing](../../application_security/sast/index.md) **[ULTIMATE]** +- Analyze your running web applications for vulnerabilities with [Dynamic Application Security Testing](../../application_security/dast/index.md) **[ULTIMATE]** +- Analyze your dependencies for vulnerabilities with [Dependency Scanning](../../application_security/dependency_scanning/index.md) **[ULTIMATE]** +- Analyze your Docker images for vulnerabilities with [Container Scanning](../../application_security/container_scanning/index.md) **[ULTIMATE]** - Determine the performance impact of changes with [Browser Performance Testing](#browser-performance-testing-premium) **[PREMIUM]** ## Use cases @@ -174,7 +174,7 @@ Start a review in order to create multiple comments on a diff and publish them o Starting a review allows you to get all your thoughts in order and ensure you haven't missed anything before submitting all your comments. -[Learn more about Merge Request Reviews](https://docs.gitlab.com/ee/user/discussions/index.html#merge-request-reviews-premium) +[Learn more about Merge Request Reviews](../../discussions/index.md#merge-request-reviews-premium) ## Squash and merge @@ -395,7 +395,7 @@ GitLab runs the [Sitespeed.io container][sitespeed-container] and displays the d GitLab can scan and report any vulnerabilities found in your project. -[Read more about security reports.](https://docs.gitlab.com/ee/user/application_security/index.html) +[Read more about security reports.](../../application_security/index.md) ## JUnit test reports diff --git a/doc/user/project/merge_requests/license_management.md b/doc/user/project/merge_requests/license_management.md index 08704425a75..93116ebd7c6 100644 --- a/doc/user/project/merge_requests/license_management.md +++ b/doc/user/project/merge_requests/license_management.md @@ -1,5 +1,5 @@ --- -redirect_to: 'https://docs.gitlab.com/ee/user/application_security/license_management/index.html' +redirect_to: '../../application_security/license_management/index.md' --- -This document was moved to [another location](https://docs.gitlab.com/ee/user/application_security/license_management/index.html). +This document was moved to [another location](../../application_security/license_management/index.md). diff --git a/doc/user/project/merge_requests/merge_request_approvals.md b/doc/user/project/merge_requests/merge_request_approvals.md index d0291c4cef5..52b6b56af84 100644 --- a/doc/user/project/merge_requests/merge_request_approvals.md +++ b/doc/user/project/merge_requests/merge_request_approvals.md @@ -105,7 +105,7 @@ any [eligible approver](#eligible-approvers) may approve. The following can approve merge requests: - Users being added as approvers at project or merge request level. -- [Code owners](https://docs.gitlab.com/ee/user/project/code_owners.html) related to the merge request ([introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/7933) in [GitLab Starter](https://about.gitlab.com/pricing/) 11.5). +- [Code owners](../code_owners.md) related to the merge request ([introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/7933) in [GitLab Starter](https://about.gitlab.com/pricing/) 11.5). An individual user can be added as an approver for a project if they are a member of: @@ -168,7 +168,7 @@ or a [failed CI/CD pipeline](merge_when_pipeline_succeeds.md). > [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/4418) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.9. It is possible to require at least one approval for each entry in the -[`CODEOWNERS` file](https://docs.gitlab.com/ee/user/project/code_owners.html) that matches a file changed in +[`CODEOWNERS` file](../code_owners.md) that matches a file changed in the merge request. To enable this feature: 1. Navigate to your project's **Settings > General** and expand diff --git a/doc/user/project/merge_requests/sast.md b/doc/user/project/merge_requests/sast.md index 688cc79d0f6..165290eb114 100644 --- a/doc/user/project/merge_requests/sast.md +++ b/doc/user/project/merge_requests/sast.md @@ -1,5 +1,5 @@ --- -redirect_to: 'https://docs.gitlab.com/ee/user/application_security/sast/index.html' +redirect_to: '../../application_security/sast/index.md' --- -This document was moved to [another location](https://docs.gitlab.com/ee/user/application_security/sast/index.html). +This document was moved to [another location](../../application_security/sast/index.md). diff --git a/doc/user/project/merge_requests/sast_docker.md b/doc/user/project/merge_requests/sast_docker.md index 4d41e424f4a..a062731ea35 100644 --- a/doc/user/project/merge_requests/sast_docker.md +++ b/doc/user/project/merge_requests/sast_docker.md @@ -1,5 +1,5 @@ --- -redirect_to: 'https://docs.gitlab.com/ee/user/application_security/container_scanning/index.html' +redirect_to: '../../application_security/container_scanning/index.md' --- -This document was moved to [another location](https://docs.gitlab.com/ee/user/application_security/container_scanning/index.html). +This document was moved to [another location](../../application_security/container_scanning/index.md). diff --git a/doc/user/project/milestones/burndown_charts.md b/doc/user/project/milestones/burndown_charts.md index 0ad08da8ff5..7ffeb032d7f 100644 --- a/doc/user/project/milestones/burndown_charts.md +++ b/doc/user/project/milestones/burndown_charts.md @@ -52,7 +52,7 @@ and select a milestone from your current ones, while for group's, access the **G select a group, and go through **Issues > Milestones** on the sidebar. NOTE: **Note:** -You're able to [promote project](https://docs.gitlab.com/ee/user/project/milestones/#promoting-project-milestones-to-group-milestones) to group milestones and still see the **Burndown Chart** for them, respecting license limitations. +You're able to [promote project](index.md#promoting-project-milestones-to-group-milestones) to group milestones and still see the **Burndown Chart** for them, respecting license limitations. The chart indicates the project's progress throughout that milestone (for issues assigned to it). diff --git a/doc/user/project/packages/maven_repository.md b/doc/user/project/packages/maven_repository.md index 94785eb6aec..9b7af738696 100644 --- a/doc/user/project/packages/maven_repository.md +++ b/doc/user/project/packages/maven_repository.md @@ -12,7 +12,7 @@ project can have its own space to store its Maven artifacts. NOTE: **Note:** This option is available only if your GitLab administrator has -[enabled support for the Maven repository](https://docs.gitlab.com/ee/administration/packages.html).**[PREMIUM ONLY]** +[enabled support for the Maven repository](../../../administration/packages.md).**[PREMIUM ONLY]** After the Packages feature is enabled, the Maven Repository will be available for all new projects by default. To enable it for existing projects, or if you want diff --git a/doc/user/project/packages/npm_registry.md b/doc/user/project/packages/npm_registry.md index 9f4c01c9a0a..2e274573434 100644 --- a/doc/user/project/packages/npm_registry.md +++ b/doc/user/project/packages/npm_registry.md @@ -20,7 +20,7 @@ within a subgroup is not supported yet. NOTE: **Note:** This option is available only if your GitLab administrator has -[enabled support for the NPM registry](https://docs.gitlab.com/ee/administration/packages.html).**[PREMIUM ONLY]** +[enabled support for the NPM registry](../../../administration/packages.md).**[PREMIUM ONLY]** After the NPM registry is enabled, it will be available for all new projects by default. To enable it for existing projects, or if you want to disable it: diff --git a/doc/user/project/repository/index.md b/doc/user/project/repository/index.md index cb514b76a4e..6fccfd40987 100644 --- a/doc/user/project/repository/index.md +++ b/doc/user/project/repository/index.md @@ -123,7 +123,7 @@ You can live preview changes submitted to a new branch with [Review Apps](../../../ci/review_apps/index.md). With [GitLab Starter](https://about.gitlab.com/pricing/), you can also request -[approval](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html) from your managers. +[approval](../merge_requests/merge_request_approvals.md) from your managers. To create, delete, and view [branches](branches/index.md) via GitLab's UI: @@ -154,7 +154,7 @@ Via command line, you can commit multiple times before pushing. you will trigger a pipeline per push, not per commit. - **Skip pipelines:** You can add to you commit message the keyword - [`[ci skip]`](../../../ci/yaml/README.html#skipping-jobs) + [`[ci skip]`](../../../ci/yaml/README.md#skipping-jobs) and GitLab CI will skip that pipeline. - **Cross-link issues and merge requests:** [Cross-linking](../issues/crosslinking_issues.md#from-commit-messages) @@ -226,7 +226,7 @@ Find it under your project's **Repository > Compare**. ## Locked files **[PREMIUM]** -Use [File Locking](https://docs.gitlab.com/ee/user/project/file_lock.html) to +Use [File Locking](../file_lock.md) to lock your files to prevent any conflicting changes. ## Repository's API diff --git a/doc/user/project/repository/reducing_the_repo_size_using_git.md b/doc/user/project/repository/reducing_the_repo_size_using_git.md index 2339759ecc8..e3d771524ce 100644 --- a/doc/user/project/repository/reducing_the_repo_size_using_git.md +++ b/doc/user/project/repository/reducing_the_repo_size_using_git.md @@ -1,6 +1,6 @@ # Reducing the repository size using Git -A GitLab Enterprise Edition administrator can set a [repository size limit][admin-repo-size] +A GitLab Enterprise Edition administrator can set a [repository size limit](../../admin_area/settings/account_and_limit_settings.md) which will prevent you from exceeding it. When a project has reached its size limit, you will not be able to push to it, @@ -14,7 +14,8 @@ move some blobs to LFS, or remove some old dependency updates from history. Unfortunately, it's not so easy and that workflow won't work. Deleting files in a commit doesn't actually reduce the size of the repo since the earlier commits and blobs are still around. What you need to do is rewrite history with Git's -[`filter-branch` option][gitscm], or a tool like the [BFG Repo-Cleaner][bfg]. +[`filter-branch` option](https://git-scm.com/book/en/v2/Git-Tools-Rewriting-History#The-Nuclear-Option:-filter-branch), +or a tool like the [BFG Repo-Cleaner](https://rtyley.github.io/bfg-repo-cleaner/). Note that even with that method, until `git gc` runs on the GitLab side, the "removed" commits and blobs will still be around. You also need to be able to @@ -137,7 +138,3 @@ remove some of them, but it should not be depended on for security purposes! ``` Your repository should now be below the size limit. - -[admin-repo-size]: https://docs.gitlab.com/ee/user/admin_area/settings/account_and_limit_settings.html#repository-size-limit -[bfg]: https://rtyley.github.io/bfg-repo-cleaner/ -[gitscm]: https://git-scm.com/book/en/v2/Git-Tools-Rewriting-History#The-Nuclear-Option:-filter-branch diff --git a/doc/user/project/security_dashboard.md b/doc/user/project/security_dashboard.md index 43e910b29fe..a3da1ec97d3 100644 --- a/doc/user/project/security_dashboard.md +++ b/doc/user/project/security_dashboard.md @@ -1,5 +1,5 @@ --- -redirect_to: 'https://docs.gitlab.com/ee/user/application_security/security_dashboard/index.html' +redirect_to: '../application_security/security_dashboard/index.md' --- -This document was moved to [another location](https://docs.gitlab.com/ee/user/application_security/security_dashboard/index.html). +This document was moved to [another location](../application_security/security_dashboard/index.md). diff --git a/doc/user/project/settings/index.md b/doc/user/project/settings/index.md index 737dea1de6c..e3502a632d9 100644 --- a/doc/user/project/settings/index.md +++ b/doc/user/project/settings/index.md @@ -36,7 +36,7 @@ Set up your project's merge request settings: - Set up the merge request method (merge commit, [fast-forward merge](../merge_requests/fast_forward_merge.html)). - Merge request [description templates](../description_templates.md#description-templates). -- Enable [merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html#merge-request-approvals). **[STARTER]** +- Enable [merge request approvals](../merge_requests/merge_request_approvals.md). **[STARTER]** - Enable [merge only of pipeline succeeds](../merge_requests/merge_when_pipeline_succeeds.md). - Enable [merge only when all discussions are resolved](../../discussions/index.md#only-allow-merge-requests-to-be-merged-if-all-discussions-are-resolved). @@ -44,7 +44,7 @@ Set up your project's merge request settings: ### Service Desk **[PREMIUM]** -Enable [Service Desk](https://docs.gitlab.com/ee/user/project/service_desk.html) for your project to offer customer support. +Enable [Service Desk](../service_desk.md) for your project to offer customer support. ### Export project diff --git a/doc/user/search/advanced_global_search.md b/doc/user/search/advanced_global_search.md index 38b26f31417..f80f4183802 100644 --- a/doc/user/search/advanced_global_search.md +++ b/doc/user/search/advanced_global_search.md @@ -2,7 +2,7 @@ > - [Introduced][ee-109] in GitLab [Starter][ee] 8.4. > - This is the user documentation. To install and configure Elasticsearch, -> visit the [administrator documentation](https://docs.gitlab.com/ee/integration/elasticsearch.html). +> visit the [administrator documentation](../../integration/elasticsearch.md). NOTE: **Note** Advanced Global Search (powered by Elasticsearch) is not yet available on GitLab.com. We are working on adding it. [Follow this epic for the latest updates](https://gitlab.com/groups/gitlab-org/-/epics/153). diff --git a/doc/user/search/advanced_search_syntax.md b/doc/user/search/advanced_search_syntax.md index 8d4aac5f502..d302cb7a809 100644 --- a/doc/user/search/advanced_search_syntax.md +++ b/doc/user/search/advanced_search_syntax.md @@ -3,7 +3,7 @@ > **Notes:** > - Introduced in [GitLab Enterprise Starter][ee] 9.2 > - This is the user documentation. To install and configure Elasticsearch, -> visit the [administrator documentation](https://docs.gitlab.com/ee/integration/elasticsearch.html). +> visit the [administrator documentation](../../integration/elasticsearch.md). NOTE: **Note** Advanced Global Search (powered by Elasticsearch) is not yet available on GitLab.com. We are working on adding it. [Follow this epic for the latest updates](https://gitlab.com/groups/gitlab-org/-/epics/153). diff --git a/doc/workflow/img/notification_global_settings.png b/doc/workflow/img/notification_global_settings.png Binary files differindex 8a5494d16a8..72f7418f1f8 100644 --- a/doc/workflow/img/notification_global_settings.png +++ b/doc/workflow/img/notification_global_settings.png diff --git a/doc/workflow/notifications.md b/doc/workflow/notifications.md index 0eb4f85e0ec..5d560f2e000 100644 --- a/doc/workflow/notifications.md +++ b/doc/workflow/notifications.md @@ -16,12 +16,16 @@ Notification settings are divided into three groups: Each of these settings have levels of notification: +- Global: For groups and projects, notifications as per global settings. - Watch: Receive notifications for any activity. -- On Mention: Receive notifications when `@mentioned` in comments. - Participate: Receive notifications for threads you have participated in. +- On Mention: Receive notifications when `@mentioned` in comments. - Disabled: Turns off notifications. - Custom: Receive notifications for custom selected events. -- Global: For groups and projects, notifications as per global settings. + +> Introduced in GitLab 12.0 + +You can also select an email address to receive notifications for each group you belong to. ### Global Settings diff --git a/lib/api/commits.rb b/lib/api/commits.rb index 65eb9bfb87e..80913f4ca07 100644 --- a/lib/api/commits.rb +++ b/lib/api/commits.rb @@ -96,17 +96,27 @@ module API end end optional :start_branch, type: String, desc: 'Name of the branch to start the new commit from' + optional :start_project, types: [Integer, String], desc: 'The ID or path of the project to start the commit from' optional :author_email, type: String, desc: 'Author email for commit' optional :author_name, type: String, desc: 'Author name for commit' optional :stats, type: Boolean, default: true, desc: 'Include commit stats' optional :force, type: Boolean, default: false, desc: 'When `true` overwrites the target branch with a new commit based on the `start_branch`' end post ':id/repository/commits' do + if params[:start_project] + start_project = find_project!(params[:start_project]) + + unless user_project.forked_from?(start_project) + forbidden!("Project is not included in the fork network for #{start_project.full_name}") + end + end + authorize_push_to_branch!(params[:branch]) attrs = declared_params attrs[:branch_name] = attrs.delete(:branch) attrs[:start_branch] ||= attrs[:branch_name] + attrs[:start_project] = start_project if start_project result = ::Files::MultiService.new(user_project, current_user, attrs).execute diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 7e4539d0419..00bcf6b055b 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -445,7 +445,7 @@ module API end def present_carrierwave_file!(file, supports_direct_download: true) - return not_found! unless file.exists? + return not_found! unless file&.exists? if file.file_storage? present_disk_file!(file.path, file.filename) diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index 455588f3c66..dcdd3581d92 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -34,34 +34,6 @@ module Gitlab TagExistsError = Class.new(StandardError) ChecksumError = Class.new(StandardError) - class << self - def create_hooks(repo_path, global_hooks_path) - local_hooks_path = File.join(repo_path, 'hooks') - real_local_hooks_path = :not_found - - begin - real_local_hooks_path = File.realpath(local_hooks_path) - rescue Errno::ENOENT - # real_local_hooks_path == :not_found - end - - # Do nothing if hooks already exist - unless real_local_hooks_path == File.realpath(global_hooks_path) - if File.exist?(local_hooks_path) - # Move the existing hooks somewhere safe - FileUtils.mv( - local_hooks_path, - "#{local_hooks_path}.old.#{Time.now.to_i}") - end - - # Create the hooks symlink - FileUtils.ln_sf(global_hooks_path, local_hooks_path) - end - - true - end - end - # Directory name of repo attr_reader :name diff --git a/lib/gitlab/github_import/parallel_importer.rb b/lib/gitlab/github_import/parallel_importer.rb index 9d81441d96e..1d3541b80c7 100644 --- a/lib/gitlab/github_import/parallel_importer.rb +++ b/lib/gitlab/github_import/parallel_importer.rb @@ -29,29 +29,13 @@ module Gitlab end def execute - jid = generate_jid - - # The original import JID is the JID of the RepositoryImportWorker job, - # which will be removed once that job completes. Reusing that JID could - # result in StuckImportJobsWorker marking the job as stuck before we get - # to running Stage::ImportRepositoryWorker. - # - # We work around this by setting the JID to a custom generated one, then - # refreshing it in the various stages whenever necessary. - Gitlab::SidekiqStatus - .set(jid, StuckImportJobsWorker::IMPORT_JOBS_EXPIRATION) - - project.import_state.update_column(:jid, jid) + Gitlab::Import::SetAsyncJid.set_jid(project) Stage::ImportRepositoryWorker .perform_async(project.id) true end - - def generate_jid - "github-importer/#{project.id}" - end end end end diff --git a/lib/gitlab/graphql/query_analyzers/log_query_complexity.rb b/lib/gitlab/graphql/query_analyzers/log_query_complexity.rb deleted file mode 100644 index 95db130dbfc..00000000000 --- a/lib/gitlab/graphql/query_analyzers/log_query_complexity.rb +++ /dev/null @@ -1,18 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module Graphql - module QueryAnalyzers - class LogQueryComplexity - class << self - def analyzer - GraphQL::Analysis::QueryComplexity.new do |query, complexity| - # temporary until https://gitlab.com/gitlab-org/gitlab-ce/issues/59587 - Rails.logger.info("[GraphQL Query Complexity] #{complexity} | admin? #{query.context[:current_user]&.admin?}") - end - end - end - end - end - end -end diff --git a/lib/gitlab/graphql/query_analyzers/logger_analyzer.rb b/lib/gitlab/graphql/query_analyzers/logger_analyzer.rb new file mode 100644 index 00000000000..01b55a1667f --- /dev/null +++ b/lib/gitlab/graphql/query_analyzers/logger_analyzer.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +module Gitlab + module Graphql + module QueryAnalyzers + class LoggerAnalyzer + COMPLEXITY_ANALYZER = GraphQL::Analysis::QueryComplexity.new { |query, complexity_value| complexity_value } + DEPTH_ANALYZER = GraphQL::Analysis::QueryDepth.new { |query, depth_value| depth_value } + + def analyze?(query) + Feature.enabled?(:graphql_logging, default_enabled: true) + end + + def initial_value(query) + variables = process_variables(query.provided_variables) + default_initial_values(query).merge({ + query_string: query.query_string, + variables: variables + }) + rescue => e + Gitlab::Sentry.track_exception(e) + default_initial_values(query) + end + + def call(memo, visit_type, irep_node) + memo + end + + def final_value(memo) + return if memo.nil? + + analyzers = [COMPLEXITY_ANALYZER, DEPTH_ANALYZER] + complexity, depth = GraphQL::Analysis.analyze_query(memo[:query], analyzers) + + memo[:depth] = depth + memo[:complexity] = complexity + memo[:duration] = duration(memo[:time_started]).round(1) + + GraphqlLogger.info(memo.except!(:time_started, :query)) + rescue => e + Gitlab::Sentry.track_exception(e) + end + + private + + def process_variables(variables) + if variables.respond_to?(:to_s) + variables.to_s + else + variables + end + end + + def duration(time_started) + nanoseconds = Gitlab::Metrics::System.monotonic_time - time_started + nanoseconds * 1000000 + end + + def default_initial_values(query) + { + time_started: Gitlab::Metrics::System.monotonic_time, + query_string: nil, + query: query, + variables: nil, + duration: nil + } + end + end + end + end +end diff --git a/lib/gitlab/graphql_logger.rb b/lib/gitlab/graphql_logger.rb new file mode 100644 index 00000000000..43d917908b6 --- /dev/null +++ b/lib/gitlab/graphql_logger.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module Gitlab + class GraphqlLogger < Gitlab::JsonLogger + def self.file_name_noext + 'graphql_json' + end + end +end diff --git a/lib/gitlab/http.rb b/lib/gitlab/http.rb index bcd9e2be35f..313b5df51d4 100644 --- a/lib/gitlab/http.rb +++ b/lib/gitlab/http.rb @@ -9,6 +9,13 @@ module Gitlab BlockedUrlError = Class.new(StandardError) RedirectionTooDeep = Class.new(StandardError) + HTTP_ERRORS = [ + SocketError, OpenSSL::SSL::SSLError, Errno::ECONNRESET, + Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Net::OpenTimeout, + Net::ReadTimeout, Gitlab::HTTP::BlockedUrlError, + Gitlab::HTTP::RedirectionTooDeep + ].freeze + include HTTParty # rubocop:disable Gitlab/HTTParty connection_adapter ProxyHTTPConnectionAdapter diff --git a/lib/gitlab/import/set_async_jid.rb b/lib/gitlab/import/set_async_jid.rb new file mode 100644 index 00000000000..584d24045ee --- /dev/null +++ b/lib/gitlab/import/set_async_jid.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +# The original import JID is the JID of the RepositoryImportWorker job, +# which will be removed once that job completes. Reusing that JID could +# result in StuckImportJobsWorker marking the job as stuck before we get +# to running Stage::ImportRepositoryWorker. +# +# We work around this by setting the JID to a custom generated one, then +# refreshing it in the various stages whenever necessary. +module Gitlab + module Import + module SetAsyncJid + def self.set_jid(project) + jid = generate_jid(project) + + Gitlab::SidekiqStatus + .set(jid, StuckImportJobsWorker::IMPORT_JOBS_EXPIRATION) + + project.import_state.update_column(:jid, jid) + end + + def self.generate_jid(project) + "async-import/#{project.id}" + end + end + end +end diff --git a/lib/gitlab/import_sources.rb b/lib/gitlab/import_sources.rb index 67952ca0f2d..e4d625b5738 100644 --- a/lib/gitlab/import_sources.rb +++ b/lib/gitlab/import_sources.rb @@ -20,7 +20,8 @@ module Gitlab ImportSource.new('git', 'Repo by URL', nil), ImportSource.new('gitlab_project', 'GitLab export', Gitlab::ImportExport::Importer), ImportSource.new('gitea', 'Gitea', Gitlab::LegacyGithubImport::Importer), - ImportSource.new('manifest', 'Manifest file', nil) + ImportSource.new('manifest', 'Manifest file', nil), + ImportSource.new('phabricator', 'Phabricator', Gitlab::PhabricatorImport::Importer) ].freeze class << self diff --git a/lib/gitlab/lets_encrypt/client.rb b/lib/gitlab/lets_encrypt/client.rb index 5501f7981ec..66aea137012 100644 --- a/lib/gitlab/lets_encrypt/client.rb +++ b/lib/gitlab/lets_encrypt/client.rb @@ -3,6 +3,8 @@ module Gitlab module LetsEncrypt class Client + include Gitlab::Utils::StrongMemoize + PRODUCTION_DIRECTORY_URL = 'https://acme-v02.api.letsencrypt.org/directory' STAGING_DIRECTORY_URL = 'https://acme-staging-v02.api.letsencrypt.org/directory' @@ -35,6 +37,8 @@ module Gitlab def enabled? return false unless Feature.enabled?(:pages_auto_ssl) + return false unless private_key + Gitlab::CurrentSettings.lets_encrypt_terms_of_service_accepted end @@ -45,7 +49,11 @@ module Gitlab end def private_key - @private_key ||= OpenSSL::PKey.read(Gitlab::CurrentSettings.lets_encrypt_private_key) + strong_memoize(:private_key) do + private_key_string = Gitlab::CurrentSettings.lets_encrypt_private_key + private_key_string ||= generate_private_key + OpenSSL::PKey.read(private_key_string) if private_key_string + end end def admin_email @@ -69,6 +77,19 @@ module Gitlab STAGING_DIRECTORY_URL end end + + def generate_private_key + return if Gitlab::Database.read_only? + + application_settings = Gitlab::CurrentSettings.current_application_settings + application_settings.with_lock do + unless application_settings.lets_encrypt_private_key + application_settings.update(lets_encrypt_private_key: OpenSSL::PKey::RSA.new(4096).to_pem) + end + + application_settings.lets_encrypt_private_key + end + end end end end diff --git a/lib/gitlab/phabricator_import.rb b/lib/gitlab/phabricator_import.rb new file mode 100644 index 00000000000..3885a9934d5 --- /dev/null +++ b/lib/gitlab/phabricator_import.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module Gitlab + module PhabricatorImport + BaseError = Class.new(StandardError) + + def self.available? + Feature.enabled?(:phabricator_import) && + Gitlab::CurrentSettings.import_sources.include?('phabricator') + end + end +end diff --git a/lib/gitlab/phabricator_import/base_worker.rb b/lib/gitlab/phabricator_import/base_worker.rb new file mode 100644 index 00000000000..b69c65e78f8 --- /dev/null +++ b/lib/gitlab/phabricator_import/base_worker.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true + +# All workers within a Phabricator import should inherit from this worker and +# implement the `#import` method. The jobs should then be scheduled using the +# `.schedule` class method instead of `.perform_async` +# +# Doing this makes sure that only one job of that type is running at the same time +# for a certain project. This will avoid deadlocks. When a job is already running +# we'll wait for it for 10 times 5 seconds to restart. If the running job hasn't +# finished, by then, we'll retry in 30 seconds. +# +# It also makes sure that we keep the import state of the project up to date: +# - It keeps track of the jobs so we know how many jobs are running for the +# project +# - It refreshes the import jid, so it doesn't get cleaned up by the +# `StuckImportJobsWorker` +# - It marks the import as failed if a job failed to many times +# - It marks the import as finished when all remaining jobs are done +module Gitlab + module PhabricatorImport + class BaseWorker + include ApplicationWorker + include ProjectImportOptions # This marks the project as failed after too many tries + include Gitlab::ExclusiveLeaseHelpers + + class << self + def schedule(project_id, *args) + perform_async(project_id, *args) + add_job(project_id) + end + + def add_job(project_id) + worker_state(project_id).add_job + end + + def remove_job(project_id) + worker_state(project_id).remove_job + end + + def worker_state(project_id) + Gitlab::PhabricatorImport::WorkerState.new(project_id) + end + end + + def perform(project_id, *args) + in_lock("#{self.class.name.underscore}/#{project_id}/#{args}", ttl: 2.hours, sleep_sec: 5.seconds) do + project = Project.find_by_id(project_id) + next unless project + + # Bail if the import job already failed + next unless project.import_state&.in_progress? + + project.import_state.refresh_jid_expiration + + import(project, *args) + + # If this is the last running job, finish the import + project.after_import if self.class.worker_state(project_id).running_count < 2 + + self.class.remove_job(project_id) + end + rescue Gitlab::ExclusiveLeaseHelpers::FailedToObtainLockError + # Reschedule a job if there was already a running one + # Running them at the same time could cause a deadlock updating the same + # resource + self.class.perform_in(30.seconds, project_id, *args) + end + + private + + def import(project, *args) + importer_class.new(project, *args).execute + end + + def importer_class + raise NotImplementedError, "Implement `#{__method__}` on #{self.class}" + end + end + end +end diff --git a/lib/gitlab/phabricator_import/cache/map.rb b/lib/gitlab/phabricator_import/cache/map.rb new file mode 100644 index 00000000000..fa8b37b20ca --- /dev/null +++ b/lib/gitlab/phabricator_import/cache/map.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true +module Gitlab + module PhabricatorImport + module Cache + class Map + def initialize(project) + @project = project + end + + def get_gitlab_model(phabricator_id) + cached_info = get(phabricator_id) + return unless cached_info[:classname] && cached_info[:database_id] + + cached_info[:classname].constantize.find_by_id(cached_info[:database_id]) + end + + def set_gitlab_model(object, phabricator_id) + set(object.class, object.id, phabricator_id) + end + + private + + attr_reader :project + + def set(klass_name, object_id, phabricator_id) + key = cache_key_for_phabricator_id(phabricator_id) + + redis.with do |r| + r.multi do |multi| + multi.mapped_hmset(key, + { classname: klass_name, database_id: object_id }) + multi.expire(key, timeout) + end + end + end + + def get(phabricator_id) + key = cache_key_for_phabricator_id(phabricator_id) + + redis.with do |r| + r.pipelined do |pipe| + # Extend the TTL when a key was + pipe.expire(key, timeout) + pipe.mapped_hmget(key, :classname, :database_id) + end.last + end + end + + def cache_key_for_phabricator_id(phabricator_id) + "#{Redis::Cache::CACHE_NAMESPACE}/phabricator-import/#{project.id}/#{phabricator_id}" + end + + def redis + Gitlab::Redis::Cache + end + + def timeout + # Setting the timeout to the same one as we do for clearing stuck jobs + # this makes sure all cache is available while the import is running. + StuckImportJobsWorker::IMPORT_JOBS_EXPIRATION + end + end + end + end +end diff --git a/lib/gitlab/phabricator_import/conduit.rb b/lib/gitlab/phabricator_import/conduit.rb new file mode 100644 index 00000000000..4c64d737389 --- /dev/null +++ b/lib/gitlab/phabricator_import/conduit.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true +module Gitlab + module PhabricatorImport + module Conduit + ApiError = Class.new(Gitlab::PhabricatorImport::BaseError) + ResponseError = Class.new(ApiError) + end + end +end diff --git a/lib/gitlab/phabricator_import/conduit/client.rb b/lib/gitlab/phabricator_import/conduit/client.rb new file mode 100644 index 00000000000..4469a3f5849 --- /dev/null +++ b/lib/gitlab/phabricator_import/conduit/client.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true +module Gitlab + module PhabricatorImport + module Conduit + class Client + def initialize(phabricator_url, api_token) + @phabricator_url = phabricator_url + @api_token = api_token + end + + def get(path, params: {}) + response = Gitlab::HTTP.get(build_url(path), body: build_params(params), headers: headers) + Response.parse!(response) + rescue *Gitlab::HTTP::HTTP_ERRORS => e + # Wrap all errors from the API into an API-error. + raise ApiError.new(e) + end + + private + + attr_reader :phabricator_url, :api_token + + def headers + { "Accept" => 'application/json' } + end + + def build_url(path) + URI.join(phabricator_url, '/api/', path).to_s + end + + def build_params(params) + params = params.dup + params.compact! + params.reverse_merge!("api.token" => api_token) + + CGI.unescape(params.to_query) + end + end + end + end +end diff --git a/lib/gitlab/phabricator_import/conduit/maniphest.rb b/lib/gitlab/phabricator_import/conduit/maniphest.rb new file mode 100644 index 00000000000..848b71e49e7 --- /dev/null +++ b/lib/gitlab/phabricator_import/conduit/maniphest.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true +module Gitlab + module PhabricatorImport + module Conduit + class Maniphest + def initialize(phabricator_url:, api_token:) + @client = Client.new(phabricator_url, api_token) + end + + def tasks(after: nil) + TasksResponse.new(get_tasks(after)) + end + + private + + def get_tasks(after) + client.get('maniphest.search', + params: { + after: after, + attachments: { projects: 1, subscribers: 1, columns: 1 } + }) + end + + attr_reader :client + end + end + end +end diff --git a/lib/gitlab/phabricator_import/conduit/pagination.rb b/lib/gitlab/phabricator_import/conduit/pagination.rb new file mode 100644 index 00000000000..5f54cccdbc8 --- /dev/null +++ b/lib/gitlab/phabricator_import/conduit/pagination.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true +module Gitlab + module PhabricatorImport + module Conduit + class Pagination + def initialize(cursor_json) + @cursor_json = cursor_json + end + + def has_next_page? + next_page.present? + end + + def next_page + cursor_json["after"] + end + + private + + attr_reader :cursor_json + end + end + end +end diff --git a/lib/gitlab/phabricator_import/conduit/response.rb b/lib/gitlab/phabricator_import/conduit/response.rb new file mode 100644 index 00000000000..6053ecfbd5e --- /dev/null +++ b/lib/gitlab/phabricator_import/conduit/response.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true +module Gitlab + module PhabricatorImport + module Conduit + class Response + def self.parse!(http_response) + unless http_response.success? + raise Gitlab::PhabricatorImport::Conduit::ResponseError, + "Phabricator responded with #{http_response.status}" + end + + response = new(JSON.parse(http_response.body)) + + unless response.success? + raise ResponseError, + "Phabricator Error: #{response.error_code}: #{response.error_info}" + end + + response + rescue JSON::JSONError => e + raise ResponseError.new(e) + end + + def initialize(json) + @json = json + end + + def success? + error_code.nil? + end + + def error_code + json['error_code'] + end + + def error_info + json['error_info'] + end + + def data + json_result&.fetch('data') + end + + def pagination + return unless cursor_info = json_result&.fetch('cursor') + + @pagination ||= Pagination.new(cursor_info) + end + + private + + attr_reader :json + + def json_result + json['result'] + end + end + end + end +end diff --git a/lib/gitlab/phabricator_import/conduit/tasks_response.rb b/lib/gitlab/phabricator_import/conduit/tasks_response.rb new file mode 100644 index 00000000000..cbcf7259fb2 --- /dev/null +++ b/lib/gitlab/phabricator_import/conduit/tasks_response.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true +module Gitlab + module PhabricatorImport + module Conduit + class TasksResponse + def initialize(conduit_response) + @conduit_response = conduit_response + end + + delegate :pagination, to: :conduit_response + + def tasks + @tasks ||= conduit_response.data.map do |task_json| + Gitlab::PhabricatorImport::Representation::Task.new(task_json) + end + end + + private + + attr_reader :conduit_response + end + end + end +end diff --git a/lib/gitlab/phabricator_import/import_tasks_worker.rb b/lib/gitlab/phabricator_import/import_tasks_worker.rb new file mode 100644 index 00000000000..c36954a8d41 --- /dev/null +++ b/lib/gitlab/phabricator_import/import_tasks_worker.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true +module Gitlab + module PhabricatorImport + class ImportTasksWorker < BaseWorker + def importer_class + Gitlab::PhabricatorImport::Issues::Importer + end + end + end +end diff --git a/lib/gitlab/phabricator_import/importer.rb b/lib/gitlab/phabricator_import/importer.rb new file mode 100644 index 00000000000..c1797f4027e --- /dev/null +++ b/lib/gitlab/phabricator_import/importer.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +module Gitlab + module PhabricatorImport + class Importer + def self.async? + true + end + + def self.imports_repository? + # This does not really import a repository, but we want to skip all + # repository related tasks in the `Projects::ImportService` + true + end + + def initialize(project) + @project = project + end + + def execute + Gitlab::Import::SetAsyncJid.set_jid(project) + schedule_first_tasks_page + + true + rescue => e + fail_import(e.message) + + false + end + + private + + attr_reader :project + + def schedule_first_tasks_page + ImportTasksWorker.schedule(project.id) + end + + def fail_import(message) + project.import_state.mark_as_failed(message) + end + end + end +end diff --git a/lib/gitlab/phabricator_import/issues/importer.rb b/lib/gitlab/phabricator_import/issues/importer.rb new file mode 100644 index 00000000000..a58438452ff --- /dev/null +++ b/lib/gitlab/phabricator_import/issues/importer.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true +module Gitlab + module PhabricatorImport + module Issues + class Importer + def initialize(project, after = nil) + @project, @after = project, after + end + + def execute + schedule_next_batch + + tasks_response.tasks.each do |task| + TaskImporter.new(project, task).execute + end + end + + private + + attr_reader :project, :after + + def schedule_next_batch + return unless tasks_response.pagination.has_next_page? + + Gitlab::PhabricatorImport::ImportTasksWorker + .schedule(project.id, tasks_response.pagination.next_page) + end + + def tasks_response + @tasks_response ||= client.tasks(after: after) + end + + def client + @client ||= + Gitlab::PhabricatorImport::Conduit::Maniphest + .new(phabricator_url: project.import_data.data['phabricator_url'], + api_token: project.import_data.credentials[:api_token]) + end + end + end + end +end diff --git a/lib/gitlab/phabricator_import/issues/task_importer.rb b/lib/gitlab/phabricator_import/issues/task_importer.rb new file mode 100644 index 00000000000..40d4392cbc1 --- /dev/null +++ b/lib/gitlab/phabricator_import/issues/task_importer.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true +module Gitlab + module PhabricatorImport + module Issues + class TaskImporter + def initialize(project, task) + @project, @task = project, task + end + + def execute + # TODO: get the user from the project namespace from the username loaded by Phab-id + # https://gitlab.com/gitlab-org/gitlab-ce/issues/60565 + issue.author = User.ghost + + # TODO: Reformat the description with attachments, escaping accidental + # links and add attachments + # https://gitlab.com/gitlab-org/gitlab-ce/issues/60603 + issue.assign_attributes(task.issue_attributes) + + save! + + issue + end + + private + + attr_reader :project, :task + + def save! + # Just avoiding an extra redis call, we've already updated the expiry + # when reading the id from the map + was_persisted = issue.persisted? + + issue.save! if issue.changed? + + object_map.set_gitlab_model(issue, task.phabricator_id) unless was_persisted + end + + def issue + @issue ||= find_issue_by_phabricator_id(task.phabricator_id) || + project.issues.new + end + + def find_issue_by_phabricator_id(phabricator_id) + object_map.get_gitlab_model(phabricator_id) + end + + def object_map + Gitlab::PhabricatorImport::Cache::Map.new(project) + end + end + end + end +end diff --git a/lib/gitlab/phabricator_import/project_creator.rb b/lib/gitlab/phabricator_import/project_creator.rb new file mode 100644 index 00000000000..b37a5b44980 --- /dev/null +++ b/lib/gitlab/phabricator_import/project_creator.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +module Gitlab + module PhabricatorImport + class ProjectCreator + def initialize(current_user, params) + @current_user = current_user + @params = params.dup + end + + def execute + return unless import_url.present? && api_token.present? + + project = Projects::CreateService.new(current_user, create_params).execute + return project unless project.persisted? + + project.project_feature.update!(project_feature_attributes) + + project + end + + private + + attr_reader :current_user, :params + + def create_params + { + name: project_name, + path: project_path, + namespace_id: namespace_id, + import_type: 'phabricator', + import_url: Project::UNKNOWN_IMPORT_URL, + import_data: import_data + } + end + + def project_name + params[:name] + end + + def project_path + params[:path] + end + + def namespace_id + params[:namespace_id] || current_user.namespace_id + end + + def import_url + params[:phabricator_server_url] + end + + def api_token + params[:api_token] + end + + def project_feature_attributes + @project_features_attributes ||= begin + # everything disabled except for issues + ProjectFeature::FEATURES.map do |feature| + [ProjectFeature.access_level_attribute(feature), ProjectFeature::DISABLED] + end.to_h.merge(ProjectFeature.access_level_attribute(:issues) => ProjectFeature::ENABLED) + end + end + + def import_data + { + data: { + phabricator_url: import_url + }, + credentials: { + api_token: params.fetch(:api_token) + } + } + end + end + end +end diff --git a/lib/gitlab/phabricator_import/representation/task.rb b/lib/gitlab/phabricator_import/representation/task.rb new file mode 100644 index 00000000000..6aedc71b626 --- /dev/null +++ b/lib/gitlab/phabricator_import/representation/task.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true +module Gitlab + module PhabricatorImport + module Representation + class Task + def initialize(json) + @json = json + end + + def phabricator_id + json['phid'] + end + + def issue_attributes + @issue_attributes ||= { + title: issue_title, + description: issue_description, + state: issue_state, + created_at: issue_created_at, + closed_at: issue_closed_at + } + end + + private + + attr_reader :json + + def issue_title + # The 255 limit is the validation we impose on the Issue title in + # Issuable + @issue_title ||= json['fields']['name'].truncate(255) + end + + def issue_description + json['fields']['description']['raw'] + end + + def issue_state + issue_closed_at.present? ? :closed : :opened + end + + def issue_created_at + return unless json['fields']['dateCreated'] + + @issue_created_at ||= cast_datetime(json['fields']['dateCreated']) + end + + def issue_closed_at + return unless json['fields']['dateClosed'] + + @issue_closed_at ||= cast_datetime(json['fields']['dateClosed']) + end + + def cast_datetime(value) + Time.at(value.to_i) + end + end + end + end +end diff --git a/lib/gitlab/phabricator_import/worker_state.rb b/lib/gitlab/phabricator_import/worker_state.rb new file mode 100644 index 00000000000..38829e34509 --- /dev/null +++ b/lib/gitlab/phabricator_import/worker_state.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true +module Gitlab + module PhabricatorImport + class WorkerState + def initialize(project_id) + @project_id = project_id + end + + def add_job + redis.with do |r| + r.pipelined do |pipe| + pipe.incr(all_jobs_key) + pipe.expire(all_jobs_key, timeout) + end + end + end + + def remove_job + redis.with do |r| + r.decr(all_jobs_key) + end + end + + def running_count + redis.with { |r| r.get(all_jobs_key) }.to_i + end + + private + + attr_reader :project_id + + def redis + Gitlab::Redis::SharedState + end + + def all_jobs_key + @all_jobs_key ||= "phabricator-import/jobs/project-#{project_id}/job-count" + end + + def timeout + # Make sure we get rid of all the information after a job is marked + # as failed/succeeded + StuckImportJobsWorker::IMPORT_JOBS_EXPIRATION + end + end + end +end diff --git a/lib/tasks/gitlab/shell.rake b/lib/tasks/gitlab/shell.rake index ee3ef9dad6e..487808a7baa 100644 --- a/lib/tasks/gitlab/shell.rake +++ b/lib/tasks/gitlab/shell.rake @@ -51,9 +51,6 @@ namespace :gitlab do end end - # (Re)create hooks - Rake::Task['gitlab:shell:create_hooks'].invoke - Gitlab::Shell.ensure_secret_token! end @@ -78,15 +75,6 @@ namespace :gitlab do end end end - - desc 'Create or repair repository hooks symlink' - task create_hooks: :gitlab_environment do - warn_user_is_not_gitlab - - puts 'Creating/Repairing hooks symlinks for all repositories' - system(*%W(#{Gitlab.config.gitlab_shell.path}/bin/create-hooks) + repository_storage_paths_args) - puts 'done'.color(:green) - end end def setup diff --git a/locale/gitlab.pot b/locale/gitlab.pot index a02c5e41721..f737d75ca95 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -340,6 +340,9 @@ msgstr "" msgid "2FA enabled" msgstr "" +msgid "2FADevice|Registered On" +msgstr "" + msgid "3 days" msgstr "" @@ -382,6 +385,9 @@ msgstr "" msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address." msgstr "" +msgid "<no name set>" +msgstr "" + msgid "<strong>%{changedFilesLength} unstaged</strong> and <strong>%{stagedFilesLength} staged</strong> changes" msgstr "" @@ -451,6 +457,9 @@ msgstr "" msgid "API Help" msgstr "" +msgid "API Token" +msgstr "" + msgid "About GitLab" msgstr "" @@ -496,6 +505,9 @@ msgstr "" msgid "Account and limit" msgstr "" +msgid "Account: %{account}" +msgstr "" + msgid "Active" msgstr "" @@ -754,6 +766,9 @@ msgstr "" msgid "After a successful password update you will be redirected to login screen." msgstr "" +msgid "After a successful password update, you will be redirected to the login page where you can log in with your new password." +msgstr "" + msgid "All" msgstr "" @@ -838,9 +853,6 @@ msgstr "" msgid "An error has occurred" msgstr "" -msgid "An error occurding while fetching folder content." -msgstr "" - msgid "An error occurred creating the new branch." msgstr "" @@ -868,6 +880,9 @@ msgstr "" msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again." msgstr "" +msgid "An error occurred while fetching folder content." +msgstr "" + msgid "An error occurred while fetching label colors." msgstr "" @@ -1099,6 +1114,9 @@ msgstr "" msgid "Are you sure you want to cancel editing this comment?" msgstr "" +msgid "Are you sure you want to delete this device? This action cannot be undone." +msgstr "" + msgid "Are you sure you want to delete this list?" msgstr "" @@ -1150,9 +1168,15 @@ msgstr "" msgid "Are you sure? Removing this GPG key does not affect already signed commits." msgstr "" +msgid "Are you sure? This will invalidate your registered applications and U2F devices." +msgstr "" + msgid "Artifacts" msgstr "" +msgid "As U2F devices are only supported by a few browsers, we require that you set up a two-factor authentication app before a U2F device. That way you'll always be able to log in - even when you're using an unsupported browser." +msgstr "" + msgid "AsanaService|%{user} pushed to branch %{branch} of %{project_name} ( %{commit_url} ):" msgstr "" @@ -1734,6 +1758,9 @@ msgstr "" msgid "Can't find variable: ZiteReader" msgstr "" +msgid "Can't scan the code?" +msgstr "" + msgid "Cancel" msgstr "" @@ -1785,6 +1812,12 @@ msgstr "" msgid "Change title" msgstr "" +msgid "Change your password" +msgstr "" + +msgid "Change your password or recover your current one" +msgstr "" + msgid "ChangeTypeActionLabel|Pick into branch" msgstr "" @@ -2798,6 +2831,9 @@ msgstr "" msgid "Confirmation required" msgstr "" +msgid "Congratulations! You have enabled Two-factor Authentication!" +msgstr "" + msgid "Connect" msgstr "" @@ -3119,6 +3155,9 @@ msgstr "" msgid "Current Branch" msgstr "" +msgid "Current password" +msgstr "" + msgid "CurrentUser|Profile" msgstr "" @@ -3508,6 +3547,9 @@ msgstr "" msgid "Disable shared Runners" msgstr "" +msgid "Disable two-factor authentication" +msgstr "" + msgid "Disabled" msgstr "" @@ -3589,6 +3631,9 @@ msgstr "" msgid "Download asset" msgstr "" +msgid "Download codes" +msgstr "" + msgid "Download export" msgstr "" @@ -3628,6 +3673,9 @@ msgstr "" msgid "Edit Milestone" msgstr "" +msgid "Edit Password" +msgstr "" + msgid "Edit Pipeline Schedule %{id}" msgstr "" @@ -3808,6 +3856,9 @@ msgstr "" msgid "Enter in your Bitbucket Server URL and personal access token below" msgstr "" +msgid "Enter in your Phabricator Server URL and personal access token below" +msgstr "" + msgid "Enter the issue description" msgstr "" @@ -5018,6 +5069,9 @@ msgstr "" msgid "I accept the|Terms of Service and Privacy Policy" msgstr "" +msgid "I forgot my password" +msgstr "" + msgid "I have read and agree to the Let's Encrypt %{link_start}Terms of Service%{link_end}" msgstr "" @@ -5090,6 +5144,9 @@ msgstr "" msgid "If this was a mistake you can leave the %{source_type}." msgstr "" +msgid "If you lose your recovery codes you can generate new ones, invalidating all previous codes." +msgstr "" + msgid "If your HTTP repository is not publicly accessible, add authentication information to the URL: <code>https://username:password@gitlab.company.com/group/project.git</code>." msgstr "" @@ -5171,6 +5228,12 @@ msgstr "" msgid "Import repository" msgstr "" +msgid "Import tasks" +msgstr "" + +msgid "Import tasks from Phabricator into issues" +msgstr "" + msgid "Import timed out. Import took longer than %{import_jobs_expiration} seconds" msgstr "" @@ -5270,6 +5333,9 @@ msgstr "" msgid "Install Runner on Kubernetes" msgstr "" +msgid "Install a soft token authenticator like %{free_otp_link} or Google Authenticator from your application repository and scan this QR code. More information is available in the %{help_link_start}documentation%{help_link_end}." +msgstr "" + msgid "Instance Statistics" msgstr "" @@ -5321,6 +5387,9 @@ msgstr "" msgid "Invalid file." msgstr "" +msgid "Invalid import params" +msgstr "" + msgid "Invalid input, please avoid emojis" msgstr "" @@ -5549,6 +5618,9 @@ msgstr "" msgid "Key (PEM)" msgstr "" +msgid "Key: %{key}" +msgstr "" + msgid "Kubernetes" msgstr "" @@ -6398,6 +6470,9 @@ msgstr "" msgid "New milestone" msgstr "" +msgid "New password" +msgstr "" + msgid "New pipelines will cancel older, pending pipelines on the same branch" msgstr "" @@ -6856,6 +6931,15 @@ msgstr "" msgid "Password authentication is unavailable." msgstr "" +msgid "Password confirmation" +msgstr "" + +msgid "Password successfully changed" +msgstr "" + +msgid "Password was successfully updated. Please login with it" +msgstr "" + msgid "Past due" msgstr "" @@ -6907,9 +6991,21 @@ msgstr "" msgid "Personal project creation is not allowed. Please contact your administrator with questions" msgstr "" +msgid "Phabricator Server Import" +msgstr "" + +msgid "Phabricator Server URL" +msgstr "" + +msgid "Phabricator Tasks" +msgstr "" + msgid "Pick a name" msgstr "" +msgid "Pin code" +msgstr "" + msgid "Pipeline" msgstr "" @@ -7285,6 +7381,9 @@ msgstr "" msgid "Private projects can be created in your personal namespace with:" msgstr "" +msgid "Proceed" +msgstr "" + msgid "Profile" msgstr "" @@ -8089,6 +8188,9 @@ msgstr "" msgid "Recent searches" msgstr "" +msgid "Recovery Codes" +msgstr "" + msgid "Reference:" msgstr "" @@ -8100,6 +8202,9 @@ msgstr[1] "" msgid "Regenerate key" msgstr "" +msgid "Regenerate recovery codes" +msgstr "" + msgid "Regex pattern" msgstr "" @@ -8109,15 +8214,24 @@ msgstr "" msgid "Register / Sign In" msgstr "" +msgid "Register Two-Factor Authenticator" +msgstr "" + msgid "Register U2F device" msgstr "" +msgid "Register Universal Two-Factor (U2F) Device" +msgstr "" + msgid "Register and see your runners for this group." msgstr "" msgid "Register and see your runners for this project." msgstr "" +msgid "Register with two-factor app" +msgstr "" + msgid "Registry" msgstr "" @@ -8534,6 +8648,9 @@ msgstr "" msgid "Save comment" msgstr "" +msgid "Save password" +msgstr "" + msgid "Save pipeline schedule" msgstr "" @@ -8924,6 +9041,9 @@ msgstr "" msgid "Sherlock Transactions" msgstr "" +msgid "Should you ever lose your phone or access to your one time password secret, each of these recovery codes can be used one time each to regain access to your account. Please save them in a safe place, or you %{b_start}will%{b_end} lose access to your account." +msgstr "" + msgid "Show all activity" msgstr "" @@ -10269,6 +10389,9 @@ msgstr "" msgid "Thursday" msgstr "" +msgid "Time based: Yes" +msgstr "" + msgid "Time before an issue gets scheduled" msgstr "" @@ -10471,6 +10594,9 @@ msgstr "" msgid "To add an SSH key you need to %{generate_link_start}generate one%{link_end} or use an %{existing_link_start}existing key%{link_end}." msgstr "" +msgid "To add the entry manually, provide the following details to the application on your phone." +msgstr "" + msgid "To define internal users, first enable new users set to external" msgstr "" @@ -10684,6 +10810,15 @@ msgstr "" msgid "Twitter" msgstr "" +msgid "Two-Factor Authentication" +msgstr "" + +msgid "Two-factor Authentication" +msgstr "" + +msgid "Two-factor Authentication Recovery codes" +msgstr "" + msgid "Two-factor Authentication has been disabled for this user" msgstr "" @@ -10693,6 +10828,9 @@ msgstr "" msgid "Type" msgstr "" +msgid "U2F Devices (%{length})" +msgstr "" + msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details." msgstr "" @@ -10873,6 +11011,12 @@ msgstr "" msgid "Use <code>%{native_redirect_uri}</code> for local tests" msgstr "" +msgid "Use a hardware device to add the second factor of authentication." +msgstr "" + +msgid "Use an one time password authenticator on your mobile device or computer to enable two-factor authentication (2FA)." +msgstr "" + msgid "Use group milestones to manage issues from multiple projects in the same milestone." msgstr "" @@ -11179,6 +11323,9 @@ msgstr "" msgid "We heard back from your U2F device. You have been authenticated." msgstr "" +msgid "We sent you an email with reset password instructions" +msgstr "" + msgid "We want to be sure it is you, please confirm you are not a robot." msgstr "" @@ -11418,7 +11565,7 @@ msgstr "" msgid "You are attempting to update a file that has changed since you started editing it." msgstr "" -msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?" +msgid "You are going to remove %{group_name}, this will also remove all of its subgroups and projects. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?" msgstr "" msgid "You are going to remove %{project_full_name}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?" @@ -11430,6 +11577,9 @@ msgstr "" msgid "You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?" msgstr "" +msgid "You are not allowed to unlink your primary login account" +msgstr "" + msgid "You are now impersonating %{username}" msgstr "" @@ -11541,6 +11691,9 @@ msgstr "" msgid "You do not have permission to leave this %{namespaceType}." msgstr "" +msgid "You don't have any U2F devices registered yet." +msgstr "" + msgid "You don't have any active chat names." msgstr "" @@ -11589,6 +11742,12 @@ msgstr "" msgid "You must have permission to create a project in a namespace before forking." msgstr "" +msgid "You must provide a valid current password" +msgstr "" + +msgid "You must provide your current password in order to change it." +msgstr "" + msgid "You need permission." msgstr "" @@ -11664,6 +11823,9 @@ msgstr "" msgid "You're receiving this email because of your account on %{host}. %{manage_notifications_link} · %{help_link}" msgstr "" +msgid "You've already enabled two-factor authentication using one time password authenticators. In order to register a different device, you must first disable two-factor authentication." +msgstr "" + msgid "YouTube" msgstr "" @@ -11829,6 +11991,9 @@ msgstr "" msgid "customize" msgstr "" +msgid "date must not be after 9999-12-31" +msgstr "" + msgid "day" msgid_plural "days" msgstr[0] "" diff --git a/qa/README.md b/qa/README.md index f75205133e6..ef6f202464d 100644 --- a/qa/README.md +++ b/qa/README.md @@ -1,6 +1,6 @@ # GitLab QA - End-to-end tests for GitLab -This directory contains [end-to-end tests](doc/development/testing_guide/end_to_end_tests.md) +This directory contains [end-to-end tests](../../../doc/development/testing_guide/end_to_end/index.md) for GitLab. It includes the test framework and the tests themselves. The tests can be found in `qa/specs/features` (not to be confused with the unit @@ -29,7 +29,7 @@ verify coupling between page objects implemented as a part of GitLab QA and corresponding views / partials / selectors in CE / EE. Whenever `qa:selectors` job fails in your merge request, you are supposed to -fix [page objects](qa/page/README.md). You should also trigger end-to-end tests +fix [page objects](../doc/development/testing_guide/end_to_end/page_objects.md). You should also trigger end-to-end tests using `package-and-qa` manual action, to test if everything works fine. ## How can I use it? @@ -49,10 +49,10 @@ will need to [modify your GDK setup](https://gitlab.com/gitlab-org/gitlab-qa/blo ### Writing tests -- [Writing tests from scratch tutorial](docs/writing_tests_from_scratch.md) - - [Best practices](docs/best_practices.md) - - [Using page objects](qa/page/README.md) - - [Guidelines](docs/guidelines.md) +- [Writing tests from scratch tutorial](../doc/development/testing_guide/end_to_end/quick_start_guide.md) + - [Best practices](../doc/development/testing_guide/best_practices.md) + - [Using page objects](../doc/development/testing_guide/end_to_end/page_objects.md) + - [Guidelines](../doc/development/testing_guide/index.md) ### Running specific tests diff --git a/qa/qa/fixtures/auto_devops_rack/Dockerfile b/qa/qa/fixtures/auto_devops_rack/Dockerfile new file mode 100644 index 00000000000..1f59c23ea88 --- /dev/null +++ b/qa/qa/fixtures/auto_devops_rack/Dockerfile @@ -0,0 +1,9 @@ +FROM ruby:2.6.3-alpine +ADD ./ /app/ +WORKDIR /app +ENV RACK_ENV production +ENV PORT 5000 +EXPOSE 5000 + +RUN bundle install +CMD ["bundle","exec", "rackup", "-p", "5000"] diff --git a/qa/qa/runtime/api/client.rb b/qa/qa/runtime/api/client.rb index 58de01705d7..40a3bc85195 100644 --- a/qa/qa/runtime/api/client.rb +++ b/qa/qa/runtime/api/client.rb @@ -25,15 +25,12 @@ module QA private def create_personal_access_token - if @is_new_session - Runtime::Browser.visit(@address, Page::Main::Login) { do_create_personal_access_token } - else - do_create_personal_access_token - end + Runtime::Browser.visit(@address, Page::Main::Login) if @is_new_session + do_create_personal_access_token end def do_create_personal_access_token - Page::Main::Login.act { sign_in_using_credentials } + Page::Main::Login.perform(&:sign_in_using_credentials) Resource::PersonalAccessToken.fabricate!.access_token end end diff --git a/spec/controllers/import/phabricator_controller_spec.rb b/spec/controllers/import/phabricator_controller_spec.rb new file mode 100644 index 00000000000..85085a8e996 --- /dev/null +++ b/spec/controllers/import/phabricator_controller_spec.rb @@ -0,0 +1,92 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Import::PhabricatorController do + let(:current_user) { create(:user) } + + before do + sign_in current_user + end + + describe 'GET #new' do + subject { get :new } + + context 'when the import source is not available' do + before do + stub_feature_flags(phabricator_import: true) + stub_application_setting(import_sources: []) + end + + it { is_expected.to have_gitlab_http_status(404) } + end + + context 'when the feature is disabled' do + before do + stub_feature_flags(phabricator_import: false) + stub_application_setting(import_sources: ['phabricator']) + end + + it { is_expected.to have_gitlab_http_status(404) } + end + + context 'when the import is available' do + before do + stub_feature_flags(phabricator_import: true) + stub_application_setting(import_sources: ['phabricator']) + end + + it { is_expected.to have_gitlab_http_status(200) } + end + end + + describe 'POST #create' do + subject(:post_create) { post :create, params: params } + + context 'with valid params' do + let(:params) do + { path: 'phab-import', + name: 'Phab import', + phabricator_server_url: 'https://phabricator.example.com', + api_token: 'hazaah', + namespace_id: current_user.namespace_id } + end + + it 'creates a project to import' do + expect_next_instance_of(Gitlab::PhabricatorImport::Importer) do |importer| + expect(importer).to receive(:execute) + end + + expect { post_create }.to change { current_user.namespace.projects.reload.size }.from(0).to(1) + + expect(current_user.namespace.projects.last).to be_import + end + end + + context 'when an import param is missing' do + let(:params) do + { path: 'phab-import', + name: 'Phab import', + phabricator_server_url: nil, + api_token: 'hazaah', + namespace_id: current_user.namespace_id } + end + + it 'does not create the project' do + expect { post_create }.not_to change { current_user.namespace.projects.reload.size } + end + end + + context 'when a project param is missing' do + let(:params) do + { phabricator_server_url: 'https://phabricator.example.com', + api_token: 'hazaah', + namespace_id: current_user.namespace_id } + end + + it 'does not create the project' do + expect { post_create }.not_to change { current_user.namespace.projects.reload.size } + end + end + end +end diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb index 9ef00fff3b2..490e9841492 100644 --- a/spec/controllers/projects/jobs_controller_spec.rb +++ b/spec/controllers/projects/jobs_controller_spec.rb @@ -841,8 +841,8 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do end it 'erases artifacts' do - expect(job.artifacts_file.exists?).to be_falsey - expect(job.artifacts_metadata.exists?).to be_falsey + expect(job.artifacts_file.present?).to be_falsey + expect(job.artifacts_metadata.present?).to be_falsey end it 'erases trace' do diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb index f8c494c159e..a473136b57b 100644 --- a/spec/factories/ci/builds.rb +++ b/spec/factories/ci/builds.rb @@ -248,17 +248,6 @@ FactoryBot.define do runner factory: :ci_runner end - trait :legacy_artifacts do - after(:create) do |build, _| - build.update!( - legacy_artifacts_file: fixture_file_upload( - Rails.root.join('spec/fixtures/ci_build_artifacts.zip'), 'application/zip'), - legacy_artifacts_metadata: fixture_file_upload( - Rails.root.join('spec/fixtures/ci_build_artifacts_metadata.gz'), 'application/x-gzip') - ) - end - end - trait :artifacts do after(:create) do |build| create(:ci_job_artifact, :archive, job: build, expire_at: build.artifacts_expire_at) diff --git a/spec/factories/ci/job_artifacts.rb b/spec/factories/ci/job_artifacts.rb index 2c76c22ba69..542fa9775cd 100644 --- a/spec/factories/ci/job_artifacts.rb +++ b/spec/factories/ci/job_artifacts.rb @@ -45,9 +45,12 @@ FactoryBot.define do file_type :archive file_format :zip - after(:build) do |artifact, _| - artifact.file = fixture_file_upload( - Rails.root.join('spec/fixtures/ci_build_artifacts.zip'), 'application/zip') + transient do + file { fixture_file_upload(Rails.root.join('spec/fixtures/ci_build_artifacts.zip'), 'application/zip') } + end + + after(:build) do |artifact, evaluator| + artifact.file = evaluator.file end end @@ -61,9 +64,12 @@ FactoryBot.define do file_type :metadata file_format :gzip - after(:build) do |artifact, _| - artifact.file = fixture_file_upload( - Rails.root.join('spec/fixtures/ci_build_artifacts_metadata.gz'), 'application/x-gzip') + transient do + file { fixture_file_upload(Rails.root.join('spec/fixtures/ci_build_artifacts_metadata.gz'), 'application/x-gzip') } + end + + after(:build) do |artifact, evaluator| + artifact.file = evaluator.file end end diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb index 5c6c1c4fd15..2adeb37c98a 100644 --- a/spec/features/commits_spec.rb +++ b/spec/features/commits_spec.rb @@ -89,7 +89,7 @@ describe 'Commits' do context 'Download artifacts' do before do - build.update(legacy_artifacts_file: artifacts_file) + create(:ci_job_artifact, :archive, file: artifacts_file, job: build) end it do @@ -119,7 +119,7 @@ describe 'Commits' do context "when logged as reporter" do before do project.add_reporter(user) - build.update(legacy_artifacts_file: artifacts_file) + create(:ci_job_artifact, :archive, file: artifacts_file, job: build) visit pipeline_path(pipeline) end @@ -141,7 +141,7 @@ describe 'Commits' do project.update( visibility_level: Gitlab::VisibilityLevel::INTERNAL, public_builds: false) - build.update(legacy_artifacts_file: artifacts_file) + create(:ci_job_artifact, :archive, file: artifacts_file, job: build) visit pipeline_path(pipeline) end diff --git a/spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb b/spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb index 5188dc3625f..dd8900a3698 100644 --- a/spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb +++ b/spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb @@ -27,7 +27,8 @@ describe 'Merge request < User sees mini pipeline graph', :js do let(:artifacts_file2) { fixture_file_upload(File.join('spec/fixtures/dk.png'), 'image/png') } before do - create(:ci_build, :success, :trace_artifact, pipeline: pipeline, legacy_artifacts_file: artifacts_file1) + job = create(:ci_build, :success, :trace_artifact, pipeline: pipeline) + create(:ci_job_artifact, :archive, file: artifacts_file1, job: job) create(:ci_build, :manual, pipeline: pipeline, when: 'manual') end @@ -35,7 +36,8 @@ describe 'Merge request < User sees mini pipeline graph', :js do xit 'avoids repeated database queries' do before = ActiveRecord::QueryRecorder.new { visit_merge_request(format: :json, serializer: 'widget') } - create(:ci_build, :success, :trace_artifact, pipeline: pipeline, legacy_artifacts_file: artifacts_file2) + job = create(:ci_build, :success, :trace_artifact, pipeline: pipeline) + create(:ci_job_artifact, :archive, file: artifacts_file2, job: job) create(:ci_build, :manual, pipeline: pipeline, when: 'manual') after = ActiveRecord::QueryRecorder.new { visit_merge_request(format: :json, serializer: 'widget') } diff --git a/spec/features/projects/import_export/import_file_spec.rb b/spec/features/projects/import_export/import_file_spec.rb index 28ae90bc0de..8d2b1fc7e30 100644 --- a/spec/features/projects/import_export/import_file_spec.rb +++ b/spec/features/projects/import_export/import_file_spec.rb @@ -47,7 +47,6 @@ describe 'Import/Export - project import integration test', :js do expect(project.description).to eq("Foo Bar") expect(project.issues).not_to be_empty expect(project.merge_requests).not_to be_empty - expect(project_hook_exists?(project)).to be true expect(wiki_exists?(project)).to be true expect(project.import_state.status).to eq('finished') end diff --git a/spec/features/projects/jobs/permissions_spec.rb b/spec/features/projects/jobs/permissions_spec.rb index 6ce37297a7e..b5e711997a0 100644 --- a/spec/features/projects/jobs/permissions_spec.rb +++ b/spec/features/projects/jobs/permissions_spec.rb @@ -90,7 +90,7 @@ describe 'Project Jobs Permissions' do before do archive = fixture_file_upload('spec/fixtures/ci_build_artifacts.zip') - job.update(legacy_artifacts_file: archive) + create(:ci_job_artifact, :archive, file: archive, job: job) end context 'when public access for jobs is disabled' do diff --git a/spec/features/projects/jobs/user_browses_job_spec.rb b/spec/features/projects/jobs/user_browses_job_spec.rb index 908c616f2fc..54b462da87a 100644 --- a/spec/features/projects/jobs/user_browses_job_spec.rb +++ b/spec/features/projects/jobs/user_browses_job_spec.rb @@ -28,8 +28,8 @@ describe 'User browses a job', :js do expect(page).to have_no_css('.artifacts') expect(build).not_to have_trace - expect(build.artifacts_file.exists?).to be_falsy - expect(build.artifacts_metadata.exists?).to be_falsy + expect(build.artifacts_file.present?).to be_falsy + expect(build.artifacts_metadata.present?).to be_falsy expect(page).to have_content('Job has been erased') end diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb index 77ea613b282..d0878c4088a 100644 --- a/spec/features/projects/jobs_spec.rb +++ b/spec/features/projects/jobs_spec.rb @@ -314,7 +314,7 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do context "Download artifacts", :js do before do - job.update(legacy_artifacts_file: artifacts_file) + create(:ci_job_artifact, :archive, file: artifacts_file, job: job) visit project_job_path(project, job) end @@ -338,8 +338,8 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do context 'Artifacts expire date', :js do before do - job.update(legacy_artifacts_file: artifacts_file, - artifacts_expire_at: expire_at) + create(:ci_job_artifact, :archive, file: artifacts_file, expire_at: expire_at, job: job) + job.update!(artifacts_expire_at: expire_at) visit project_job_path(project, job) end @@ -981,7 +981,7 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do describe "GET /:project/jobs/:id/download", :js do before do - job.update(legacy_artifacts_file: artifacts_file) + create(:ci_job_artifact, :archive, file: artifacts_file, job: job) visit project_job_path(project, job) click_link 'Download' @@ -989,7 +989,7 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do context "Build from other project" do before do - job2.update(legacy_artifacts_file: artifacts_file) + create(:ci_job_artifact, :archive, file: artifacts_file, job: job2) end it do diff --git a/spec/features/projects/pages_spec.rb b/spec/features/projects/pages_spec.rb index f564ae34f11..be05c74efdb 100644 --- a/spec/features/projects/pages_spec.rb +++ b/spec/features/projects/pages_spec.rb @@ -287,10 +287,17 @@ describe 'Pages' do :ci_build, project: project, pipeline: pipeline, - ref: 'HEAD', - legacy_artifacts_file: fixture_file_upload(File.join('spec/fixtures/pages.zip')), - legacy_artifacts_metadata: fixture_file_upload(File.join('spec/fixtures/pages.zip.meta')) - ) + ref: 'HEAD') + end + + let!(:artifact) do + create(:ci_job_artifact, :archive, + file: fixture_file_upload(File.join('spec/fixtures/pages.zip')), job: ci_build) + end + + let!(:metadata) do + create(:ci_job_artifact, :metadata, + file: fixture_file_upload(File.join('spec/fixtures/pages.zip.meta')), job: ci_build) end before do diff --git a/spec/fixtures/phabricator_responses/auth_failed.json b/spec/fixtures/phabricator_responses/auth_failed.json new file mode 100644 index 00000000000..50e57c0ba49 --- /dev/null +++ b/spec/fixtures/phabricator_responses/auth_failed.json @@ -0,0 +1 @@ +{"result":null,"error_code":"ERR-INVALID-AUTH","error_info":"API token \"api-token\" has the wrong length. API tokens should be 32 characters long."} diff --git a/spec/fixtures/phabricator_responses/maniphest.search.json b/spec/fixtures/phabricator_responses/maniphest.search.json new file mode 100644 index 00000000000..6a965007d0c --- /dev/null +++ b/spec/fixtures/phabricator_responses/maniphest.search.json @@ -0,0 +1,98 @@ +{ + "result": { + "data": [ + { + "id": 283, + "type": "TASK", + "phid": "PHID-TASK-fswfs3wkowjb6cyyxtyx", + "fields": { + "name": "Things are slow", + "description": { + "raw": "Things are slow but should be fast!" + }, + "authorPHID": "PHID-USER-nrtht5wijwbxquens3qr", + "ownerPHID": "PHID-USER-nrtht5wijwbxquens3qr", + "status": { + "value": "resolved", + "name": "Resolved", + "color": null + }, + "priority": { + "value": 100, + "subpriority": 8589934592, + "name": "Super urgent", + "color": "pink" + }, + "points": null, + "subtype": "default", + "closerPHID": "PHID-USER-nrtht5wijwbxquens3qr", + "dateClosed": 1374657042, + "spacePHID": null, + "dateCreated": 1374616241, + "dateModified": 1374657044, + "policy": { + "view": "users", + "interact": "users", + "edit": "users" + }, + "custom.field-1": null, + "custom.field-2": null, + "custom.field-3": null + }, + "attachments": {} + }, + { + "id": 284, + "type": "TASK", + "phid": "PHID-TASK-5f73nyq5sjeh4cbmcsnb", + "fields": { + "name": "Things are broken", + "description": { + "raw": "Things are broken and should be fixed" + }, + "authorPHID": "PHID-USER-nrtht5wijwbxquens3qr", + "ownerPHID": "PHID-USER-h425fsrixz4gjxiyr7ot", + "status": { + "value": "resolved", + "name": "Resolved", + "color": null + }, + "priority": { + "value": 100, + "subpriority": 8589803520, + "name": "Super urgent", + "color": "pink" + }, + "points": null, + "subtype": "default", + "closerPHID": "PHID-USER-h425fsrixz4gjxiyr7ot", + "dateClosed": 1375049556, + "spacePHID": null, + "dateCreated": 1374616578, + "dateModified": 1375049556, + "policy": { + "view": "users", + "interact": "users", + "edit": "users" + }, + "custom.field-1": null, + "custom.field-2": null, + "custom.field-3": null + }, + "attachments": {} + } + ], + "maps": {}, + "query": { + "queryKey": null + }, + "cursor": { + "limit": "2", + "after": "284", + "before": null, + "order": null + } + }, + "error_code": null, + "error_info": null +} diff --git a/spec/helpers/gitlab_routing_helper_spec.rb b/spec/helpers/gitlab_routing_helper_spec.rb index 143b28728a3..027480143bd 100644 --- a/spec/helpers/gitlab_routing_helper_spec.rb +++ b/spec/helpers/gitlab_routing_helper_spec.rb @@ -101,7 +101,7 @@ describe GitlabRoutingHelper do it 'returns project milestone edit path when given entity parent is not a Group' do milestone = create(:milestone, group: nil) - expect(edit_milestone_path(milestone)).to eq("/#{milestone.project.full_path}/milestones/#{milestone.iid}/edit") + expect(edit_milestone_path(milestone)).to eq("/#{milestone.project.full_path}/-/milestones/#{milestone.iid}/edit") end end end diff --git a/spec/javascripts/api_spec.js b/spec/javascripts/api_spec.js index 805bb10bda6..d9dcb08b177 100644 --- a/spec/javascripts/api_spec.js +++ b/spec/javascripts/api_spec.js @@ -264,7 +264,7 @@ describe('Api', () => { const namespace = 'some namespace'; const project = 'some project'; const labelData = { some: 'data' }; - const expectedUrl = `${dummyUrlRoot}/${namespace}/${project}/labels`; + const expectedUrl = `${dummyUrlRoot}/${namespace}/${project}/-/labels`; const expectedData = { label: labelData, }; diff --git a/spec/javascripts/boards/mock_data.js b/spec/javascripts/boards/mock_data.js index 93a0f29af0a..9854cf49e97 100644 --- a/spec/javascripts/boards/mock_data.js +++ b/spec/javascripts/boards/mock_data.js @@ -47,7 +47,7 @@ export const BoardsMockData = { }, ], }, - '/test/issue-boards/milestones.json': [ + '/test/issue-boards/-/milestones.json': [ { id: 1, title: 'test', @@ -58,10 +58,10 @@ export const BoardsMockData = { '/test/-/boards/1/lists': listObj, }, PUT: { - '/test/issue-boards/board/1/lists{/id}': {}, + '/test/issue-boards/-/board/1/lists{/id}': {}, }, DELETE: { - '/test/issue-boards/board/1/lists{/id}': {}, + '/test/issue-boards/-/board/1/lists{/id}': {}, }, }; @@ -71,7 +71,7 @@ export const boardsMockInterceptor = config => { }; export const mockBoardService = (opts = {}) => { - const boardsEndpoint = opts.boardsEndpoint || '/test/issue-boards/boards.json'; + const boardsEndpoint = opts.boardsEndpoint || '/test/issue-boards/-/boards.json'; const listsEndpoint = opts.listsEndpoint || '/test/-/boards/1/lists'; const bulkUpdatePath = opts.bulkUpdatePath || ''; const boardId = opts.boardId || '1'; diff --git a/spec/javascripts/notes/components/note_actions_spec.js b/spec/javascripts/notes/components/note_actions_spec.js index 0cfcc994234..2159e4ddf16 100644 --- a/spec/javascripts/notes/components/note_actions_spec.js +++ b/spec/javascripts/notes/components/note_actions_spec.js @@ -58,6 +58,7 @@ describe('noteActions', () => { it('should render emoji link', () => { expect(wrapper.find('.js-add-award').exists()).toBe(true); + expect(wrapper.find('.js-add-award').attributes('data-position')).toBe('right'); }); describe('actions dropdown', () => { diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index 7644d83992f..cb4701e8edc 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -29,51 +29,6 @@ describe Gitlab::Git::Repository, :seed_helper do let(:storage_path) { TestEnv.repos_path } let(:user) { build(:user) } - describe '.create_hooks' do - let(:repo_path) { File.join(storage_path, 'hook-test.git') } - let(:hooks_dir) { File.join(repo_path, 'hooks') } - let(:target_hooks_dir) { Gitlab::Shell.new.hooks_path } - let(:existing_target) { File.join(repo_path, 'foobar') } - - before do - FileUtils.rm_rf(repo_path) - FileUtils.mkdir_p(repo_path) - end - - context 'hooks is a directory' do - let(:existing_file) { File.join(hooks_dir, 'my-file') } - - before do - FileUtils.mkdir_p(hooks_dir) - FileUtils.touch(existing_file) - described_class.create_hooks(repo_path, target_hooks_dir) - end - - it { expect(File.readlink(hooks_dir)).to eq(target_hooks_dir) } - it { expect(Dir[File.join(repo_path, "hooks.old.*/my-file")].count).to eq(1) } - end - - context 'hooks is a valid symlink' do - before do - FileUtils.mkdir_p existing_target - File.symlink(existing_target, hooks_dir) - described_class.create_hooks(repo_path, target_hooks_dir) - end - - it { expect(File.readlink(hooks_dir)).to eq(target_hooks_dir) } - end - - context 'hooks is a broken symlink' do - before do - FileUtils.rm_f(existing_target) - File.symlink(existing_target, hooks_dir) - described_class.create_hooks(repo_path, target_hooks_dir) - end - - it { expect(File.readlink(hooks_dir)).to eq(target_hooks_dir) } - end - end - describe "Respond to" do subject { repository } @@ -1959,13 +1914,6 @@ describe Gitlab::Git::Repository, :seed_helper do expect { imported_repo.fsck }.not_to raise_exception end - it 'creates a symlink to the global hooks dir' do - imported_repo.create_from_bundle(valid_bundle_path) - hooks_path = Gitlab::GitalyClient::StorageSettings.allow_disk_access { File.join(imported_repo.path, 'hooks') } - - expect(File.readlink(hooks_path)).to eq(Gitlab::Shell.new.hooks_path) - end - it 'raises an error if the bundle is an attempted malicious payload' do expect do imported_repo.create_from_bundle(malicious_bundle_path) diff --git a/spec/lib/gitlab/github_import/parallel_importer_spec.rb b/spec/lib/gitlab/github_import/parallel_importer_spec.rb index f5df38c9aaf..ecab64a372a 100644 --- a/spec/lib/gitlab/github_import/parallel_importer_spec.rb +++ b/spec/lib/gitlab/github_import/parallel_importer_spec.rb @@ -25,18 +25,9 @@ describe Gitlab::GithubImport::ParallelImporter do end it 'sets the JID in Redis' do - expect(Gitlab::SidekiqStatus) - .to receive(:set) - .with("github-importer/#{project.id}", StuckImportJobsWorker::IMPORT_JOBS_EXPIRATION) - .and_call_original + expect(Gitlab::Import::SetAsyncJid).to receive(:set_jid).with(project).and_call_original importer.execute end - - it 'updates the import JID of the project' do - importer.execute - - expect(project.import_state.reload.jid).to eq("github-importer/#{project.id}") - end end end diff --git a/spec/lib/gitlab/graphql/query_analyzers/logger_analyzer_spec.rb b/spec/lib/gitlab/graphql/query_analyzers/logger_analyzer_spec.rb new file mode 100644 index 00000000000..66033736e01 --- /dev/null +++ b/spec/lib/gitlab/graphql/query_analyzers/logger_analyzer_spec.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Graphql::QueryAnalyzers::LoggerAnalyzer do + subject { described_class.new } + + describe '#analyze?' do + context 'feature flag disabled' do + before do + stub_feature_flags(graphql_logging: false) + end + + it 'disables the analyzer' do + expect(subject.analyze?(anything)).to be_falsey + end + end + + context 'feature flag enabled by default' do + it 'enables the analyzer' do + expect(subject.analyze?(anything)).to be_truthy + end + end + end +end diff --git a/spec/lib/gitlab/graphql_logger_spec.rb b/spec/lib/gitlab/graphql_logger_spec.rb new file mode 100644 index 00000000000..4977f98b83e --- /dev/null +++ b/spec/lib/gitlab/graphql_logger_spec.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::GraphqlLogger do + subject { described_class.new('/dev/null') } + + let(:now) { Time.now } + + it 'builds a logger once' do + expect(::Logger).to receive(:new).and_call_original + + subject.info('hello world') + subject.error('hello again') + end + + context 'logging a GraphQL query' do + let(:query) { File.read(Rails.root.join('spec/fixtures/api/graphql/introspection.graphql')) } + + it 'logs a query from JSON' do + analyzer_memo = { + query_string: query, + variables: {}, + complexity: 181, + depth: 0, + duration: 7 + } + + output = subject.format_message('INFO', now, 'test', analyzer_memo) + + data = JSON.parse(output) + expect(data['severity']).to eq('INFO') + expect(data['time']).to eq(now.utc.iso8601(3)) + expect(data['complexity']).to eq(181) + expect(data['variables']).to eq({}) + expect(data['depth']).to eq(0) + expect(data['duration']).to eq(7) + end + end +end diff --git a/spec/lib/gitlab/import/set_async_jid_spec.rb b/spec/lib/gitlab/import/set_async_jid_spec.rb new file mode 100644 index 00000000000..51397280138 --- /dev/null +++ b/spec/lib/gitlab/import/set_async_jid_spec.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe Gitlab::Import::SetAsyncJid do + describe '.set_jid', :clean_gitlab_redis_shared_state do + let(:project) { create(:project, :import_scheduled) } + + it 'sets the JID in Redis' do + expect(Gitlab::SidekiqStatus) + .to receive(:set) + .with("async-import/#{project.id}", StuckImportJobsWorker::IMPORT_JOBS_EXPIRATION) + .and_call_original + + described_class.set_jid(project) + end + + it 'updates the import JID of the project' do + described_class.set_jid(project) + + expect(project.import_state.reload.jid).to eq("async-import/#{project.id}") + end + end +end diff --git a/spec/lib/gitlab/import_export/repo_restorer_spec.rb b/spec/lib/gitlab/import_export/repo_restorer_spec.rb index 8a699eb1461..e2ffb2adb9b 100644 --- a/spec/lib/gitlab/import_export/repo_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/repo_restorer_spec.rb @@ -34,11 +34,5 @@ describe Gitlab::ImportExport::RepoRestorer do it 'restores the repo successfully' do expect(restorer.restore).to be_truthy end - - it 'has the webhooks' do - restorer.restore - - expect(project_hook_exists?(project)).to be true - end end end diff --git a/spec/lib/gitlab/import_sources_spec.rb b/spec/lib/gitlab/import_sources_spec.rb index 94abf9679c4..8060b5d4448 100644 --- a/spec/lib/gitlab/import_sources_spec.rb +++ b/spec/lib/gitlab/import_sources_spec.rb @@ -14,7 +14,8 @@ describe Gitlab::ImportSources do 'Repo by URL' => 'git', 'GitLab export' => 'gitlab_project', 'Gitea' => 'gitea', - 'Manifest file' => 'manifest' + 'Manifest file' => 'manifest', + 'Phabricator' => 'phabricator' } expect(described_class.options).to eq(expected) @@ -35,6 +36,7 @@ describe Gitlab::ImportSources do gitlab_project gitea manifest + phabricator ) expect(described_class.values).to eq(expected) @@ -53,6 +55,7 @@ describe Gitlab::ImportSources do fogbugz gitlab_project gitea + phabricator ) expect(described_class.importer_names).to eq(expected) @@ -70,7 +73,8 @@ describe Gitlab::ImportSources do 'git' => nil, 'gitlab_project' => Gitlab::ImportExport::Importer, 'gitea' => Gitlab::LegacyGithubImport::Importer, - 'manifest' => nil + 'manifest' => nil, + 'phabricator' => Gitlab::PhabricatorImport::Importer } import_sources.each do |name, klass| @@ -91,7 +95,8 @@ describe Gitlab::ImportSources do 'git' => 'Repo by URL', 'gitlab_project' => 'GitLab export', 'gitea' => 'Gitea', - 'manifest' => 'Manifest file' + 'manifest' => 'Manifest file', + 'phabricator' => 'Phabricator' } import_sources.each do |name, title| @@ -102,7 +107,7 @@ describe Gitlab::ImportSources do end describe 'imports_repository? checker' do - let(:allowed_importers) { %w[github gitlab_project bitbucket_server] } + let(:allowed_importers) { %w[github gitlab_project bitbucket_server phabricator] } it 'fails if any importer other than the allowed ones implements this method' do current_importers = described_class.values.select { |kind| described_class.importer(kind).try(:imports_repository?) } diff --git a/spec/lib/gitlab/lets_encrypt/client_spec.rb b/spec/lib/gitlab/lets_encrypt/client_spec.rb index d63a2fbee04..5454d9c1af4 100644 --- a/spec/lib/gitlab/lets_encrypt/client_spec.rb +++ b/spec/lib/gitlab/lets_encrypt/client_spec.rb @@ -5,14 +5,12 @@ require 'spec_helper' describe ::Gitlab::LetsEncrypt::Client do include LetsEncryptHelpers - set(:private_key) { OpenSSL::PKey::RSA.new(4096).to_pem } let(:client) { described_class.new } before do stub_application_setting( lets_encrypt_notification_email: 'myemail@test.example.com', - lets_encrypt_terms_of_service_accepted: true, - lets_encrypt_private_key: private_key + lets_encrypt_terms_of_service_accepted: true ) end @@ -28,6 +26,36 @@ describe ::Gitlab::LetsEncrypt::Client do ) end + it 'generates and stores private key and initialize acme client with it' do + expect(Gitlab::CurrentSettings.lets_encrypt_private_key).to eq(nil) + + subject + + saved_private_key = Gitlab::CurrentSettings.lets_encrypt_private_key + + expect(saved_private_key).to be + expect(Acme::Client).to have_received(:new).with( + hash_including(private_key: eq_pem(saved_private_key)) + ) + end + + context 'when private key is saved in settings' do + let!(:saved_private_key) do + key = OpenSSL::PKey::RSA.new(4096).to_pem + Gitlab::CurrentSettings.current_application_settings.update(lets_encrypt_private_key: key) + key + end + + it 'uses current value of private key' do + subject + + expect(Acme::Client).to have_received(:new).with( + hash_including(private_key: eq_pem(saved_private_key)) + ) + expect(Gitlab::CurrentSettings.lets_encrypt_private_key).to eq(saved_private_key) + end + end + context 'when acme integration is disabled' do before do stub_application_setting(lets_encrypt_terms_of_service_accepted: false) @@ -94,6 +122,18 @@ describe ::Gitlab::LetsEncrypt::Client do context 'when terms of service are accepted' do it { is_expected.to eq(true) } + context "when private_key isn't present and database is read only" do + before do + allow(::Gitlab::Database).to receive(:read_only?).and_return(true) + end + + it 'returns false' do + expect(::Gitlab::CurrentSettings.lets_encrypt_private_key).to eq(nil) + + is_expected.to eq(false) + end + end + context 'when feature flag is disabled' do before do stub_feature_flags(pages_auto_ssl: false) diff --git a/spec/lib/gitlab/phabricator_import/base_worker_spec.rb b/spec/lib/gitlab/phabricator_import/base_worker_spec.rb new file mode 100644 index 00000000000..d46d908a3e3 --- /dev/null +++ b/spec/lib/gitlab/phabricator_import/base_worker_spec.rb @@ -0,0 +1,74 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe Gitlab::PhabricatorImport::BaseWorker do + let(:subclass) do + # Creating an anonymous class for a worker is complicated, as we generate the + # queue name from the class name. + Gitlab::PhabricatorImport::ImportTasksWorker + end + + describe '.schedule' do + let(:arguments) { %w[project_id the_next_page] } + + it 'schedules the job' do + expect(subclass).to receive(:perform_async).with(*arguments) + + subclass.schedule(*arguments) + end + + it 'counts the scheduled job', :clean_gitlab_redis_shared_state do + state = Gitlab::PhabricatorImport::WorkerState.new('project_id') + + allow(subclass).to receive(:remove_job) # otherwise the job is removed before we saw it + + expect { subclass.schedule(*arguments) }.to change { state.running_count }.by(1) + end + end + + describe '#perform' do + let(:project) { create(:project, :import_started, import_url: "https://a.phab.instance") } + let(:worker) { subclass.new } + let(:state) { Gitlab::PhabricatorImport::WorkerState.new(project.id) } + + before do + allow(worker).to receive(:import) + end + + it 'does not break for a non-existing project' do + expect { worker.perform('not a thing') }.not_to raise_error + end + + it 'does not do anything when the import is not in progress' do + project = create(:project, :import_failed) + + expect(worker).not_to receive(:import) + + worker.perform(project.id) + end + + it 'calls import for the project' do + expect(worker).to receive(:import).with(project, 'other_arg') + + worker.perform(project.id, 'other_arg') + end + + it 'marks the project as imported if there was only one job running' do + worker.perform(project.id) + + expect(project.import_state.reload).to be_finished + end + + it 'does not mark the job as finished when there are more scheduled jobs' do + 2.times { state.add_job } + + worker.perform(project.id) + + expect(project.import_state.reload).to be_in_progress + end + + it 'decrements the job counter' do + expect { worker.perform(project.id) }.to change { state.running_count }.by(-1) + end + end +end diff --git a/spec/lib/gitlab/phabricator_import/cache/map_spec.rb b/spec/lib/gitlab/phabricator_import/cache/map_spec.rb new file mode 100644 index 00000000000..52c7a02219f --- /dev/null +++ b/spec/lib/gitlab/phabricator_import/cache/map_spec.rb @@ -0,0 +1,66 @@ +require 'spec_helper' + +describe Gitlab::PhabricatorImport::Cache::Map, :clean_gitlab_redis_cache do + set(:project) { create(:project) } + let(:redis) { Gitlab::Redis::Cache } + subject(:map) { described_class.new(project) } + + describe '#get_gitlab_model' do + it 'returns nil if there was nothing cached for the phabricator id' do + expect(map.get_gitlab_model('does not exist')).to be_nil + end + + it 'returns the object if it was set in redis' do + issue = create(:issue, project: project) + set_in_redis('exists', issue) + + expect(map.get_gitlab_model('exists')).to eq(issue) + end + + it 'extends the TTL for the cache key' do + set_in_redis('extend', create(:issue, project: project)) do |redis| + redis.expire(cache_key('extend'), 10.seconds.to_i) + end + + map.get_gitlab_model('extend') + + ttl = redis.with { |redis| redis.ttl(cache_key('extend')) } + + expect(ttl).to be > 10.seconds + end + end + + describe '#set_gitlab_model' do + around do |example| + Timecop.freeze { example.run } + end + + it 'sets the class and id in redis with a ttl' do + issue = create(:issue, project: project) + + map.set_gitlab_model(issue, 'it is set') + + set_data, ttl = redis.with do |redis| + redis.pipelined do |p| + p.mapped_hmget(cache_key('it is set'), :classname, :database_id) + p.ttl(cache_key('it is set')) + end + end + + expect(set_data).to eq({ classname: 'Issue', database_id: issue.id.to_s }) + expect(ttl).to be_within(1.second).of(StuckImportJobsWorker::IMPORT_JOBS_EXPIRATION) + end + end + + def set_in_redis(key, object) + redis.with do |redis| + redis.mapped_hmset(cache_key(key), + { classname: object.class, database_id: object.id }) + yield(redis) if block_given? + end + end + + def cache_key(phabricator_id) + subject.__send__(:cache_key_for_phabricator_id, phabricator_id) + end +end diff --git a/spec/lib/gitlab/phabricator_import/conduit/client_spec.rb b/spec/lib/gitlab/phabricator_import/conduit/client_spec.rb new file mode 100644 index 00000000000..542b3cd060f --- /dev/null +++ b/spec/lib/gitlab/phabricator_import/conduit/client_spec.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe Gitlab::PhabricatorImport::Conduit::Client do + let(:client) do + described_class.new('https://see-ya-later.phabricator', 'api-token') + end + + describe '#get' do + it 'performs and parses a request' do + params = { some: 'extra', values: %w[are passed] } + stub_valid_request(params) + + response = client.get('test', params: params) + + expect(response).to be_a(Gitlab::PhabricatorImport::Conduit::Response) + expect(response).to be_success + end + + it 'wraps request errors in an `ApiError`' do + stub_timeout + + expect { client.get('test') }.to raise_error(Gitlab::PhabricatorImport::Conduit::ApiError) + end + + it 'raises response error' do + stub_error_response + + expect { client.get('test') } + .to raise_error(Gitlab::PhabricatorImport::Conduit::ResponseError, /has the wrong length/) + end + end + + def stub_valid_request(params = {}) + WebMock.stub_request( + :get, 'https://see-ya-later.phabricator/api/test' + ).with( + body: CGI.unescape(params.reverse_merge('api.token' => 'api-token').to_query) + ).and_return( + status: 200, + body: fixture_file('phabricator_responses/maniphest.search.json') + ) + end + + def stub_timeout + WebMock.stub_request( + :get, 'https://see-ya-later.phabricator/api/test' + ).to_timeout + end + + def stub_error_response + WebMock.stub_request( + :get, 'https://see-ya-later.phabricator/api/test' + ).and_return( + status: 200, + body: fixture_file('phabricator_responses/auth_failed.json') + ) + end +end diff --git a/spec/lib/gitlab/phabricator_import/conduit/maniphest_spec.rb b/spec/lib/gitlab/phabricator_import/conduit/maniphest_spec.rb new file mode 100644 index 00000000000..0d7714649b9 --- /dev/null +++ b/spec/lib/gitlab/phabricator_import/conduit/maniphest_spec.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe Gitlab::PhabricatorImport::Conduit::Maniphest do + let(:maniphest) do + described_class.new(phabricator_url: 'https://see-ya-later.phabricator', api_token: 'api-token') + end + + describe '#tasks' do + let(:fake_client) { double('Phabricator client') } + + before do + allow(maniphest).to receive(:client).and_return(fake_client) + end + + it 'calls the api with the correct params' do + expected_params = { + after: '123', + attachments: { + projects: 1, subscribers: 1, columns: 1 + } + } + + expect(fake_client).to receive(:get).with('maniphest.search', + params: expected_params) + + maniphest.tasks(after: '123') + end + + it 'returns a parsed response' do + response = Gitlab::PhabricatorImport::Conduit::Response + .new(fixture_file('phabricator_responses/maniphest.search.json')) + + allow(fake_client).to receive(:get).and_return(response) + + expect(maniphest.tasks).to be_a(Gitlab::PhabricatorImport::Conduit::TasksResponse) + end + end +end diff --git a/spec/lib/gitlab/phabricator_import/conduit/response_spec.rb b/spec/lib/gitlab/phabricator_import/conduit/response_spec.rb new file mode 100644 index 00000000000..a8596968f14 --- /dev/null +++ b/spec/lib/gitlab/phabricator_import/conduit/response_spec.rb @@ -0,0 +1,79 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe Gitlab::PhabricatorImport::Conduit::Response do + let(:response) { described_class.new(JSON.parse(fixture_file('phabricator_responses/maniphest.search.json')))} + let(:error_response) { described_class.new(JSON.parse(fixture_file('phabricator_responses/auth_failed.json'))) } + + describe '.parse!' do + it 'raises a ResponseError if the http response was not successfull' do + fake_response = double(:http_response, success?: false, status: 401) + + expect { described_class.parse!(fake_response) } + .to raise_error(Gitlab::PhabricatorImport::Conduit::ResponseError, /responded with 401/) + end + + it 'raises a ResponseError if the response contained a Phabricator error' do + fake_response = double(:http_response, + success?: true, + status: 200, + body: fixture_file('phabricator_responses/auth_failed.json')) + + expect { described_class.parse!(fake_response) } + .to raise_error(Gitlab::PhabricatorImport::Conduit::ResponseError, /ERR-INVALID-AUTH: API token/) + end + + it 'raises a ResponseError if JSON parsing failed' do + fake_response = double(:http_response, + success?: true, + status: 200, + body: 'This is no JSON') + + expect { described_class.parse!(fake_response) } + .to raise_error(Gitlab::PhabricatorImport::Conduit::ResponseError, /unexpected token at/) + end + + it 'returns a parsed response for valid input' do + fake_response = double(:http_response, + success?: true, + status: 200, + body: fixture_file('phabricator_responses/maniphest.search.json')) + + expect(described_class.parse!(fake_response)).to be_a(described_class) + end + end + + describe '#success?' do + it { expect(response).to be_success } + it { expect(error_response).not_to be_success } + end + + describe '#error_code' do + it { expect(error_response.error_code).to eq('ERR-INVALID-AUTH') } + it { expect(response.error_code).to be_nil } + end + + describe '#error_info' do + it 'returns the correct error info' do + expected_message = 'API token "api-token" has the wrong length. API tokens should be 32 characters long.' + + expect(error_response.error_info).to eq(expected_message) + end + + it { expect(response.error_info).to be_nil } + end + + describe '#data' do + it { expect(error_response.data).to be_nil } + it { expect(response.data).to be_an(Array) } + end + + describe '#pagination' do + it { expect(error_response.pagination).to be_nil } + + it 'builds the pagination correctly' do + expect(response.pagination).to be_a(Gitlab::PhabricatorImport::Conduit::Pagination) + expect(response.pagination.next_page).to eq('284') + end + end +end diff --git a/spec/lib/gitlab/phabricator_import/conduit/tasks_response_spec.rb b/spec/lib/gitlab/phabricator_import/conduit/tasks_response_spec.rb new file mode 100644 index 00000000000..4b4c2a6276e --- /dev/null +++ b/spec/lib/gitlab/phabricator_import/conduit/tasks_response_spec.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe Gitlab::PhabricatorImport::Conduit::TasksResponse do + let(:conduit_response) do + Gitlab::PhabricatorImport::Conduit::Response + .new(JSON.parse(fixture_file('phabricator_responses/maniphest.search.json'))) + end + + subject(:response) { described_class.new(conduit_response) } + + describe '#pagination' do + it 'delegates to the conduit reponse' do + expect(response.pagination).to eq(conduit_response.pagination) + end + end + + describe '#tasks' do + it 'builds the correct tasks representation' do + tasks = response.tasks + + titles = tasks.map(&:issue_attributes).map { |attrs| attrs[:title] } + + expect(titles).to contain_exactly('Things are slow', 'Things are broken') + end + end +end diff --git a/spec/lib/gitlab/phabricator_import/import_tasks_worker_spec.rb b/spec/lib/gitlab/phabricator_import/import_tasks_worker_spec.rb new file mode 100644 index 00000000000..1e38ef8aaa5 --- /dev/null +++ b/spec/lib/gitlab/phabricator_import/import_tasks_worker_spec.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe Gitlab::PhabricatorImport::ImportTasksWorker do + describe '#perform' do + it 'calls the correct importer' do + project = create(:project, :import_started, import_url: "https://the.phab.ulr") + fake_importer = instance_double(Gitlab::PhabricatorImport::Issues::Importer) + + expect(Gitlab::PhabricatorImport::Issues::Importer).to receive(:new).with(project).and_return(fake_importer) + expect(fake_importer).to receive(:execute) + + described_class.new.perform(project.id) + end + end +end diff --git a/spec/lib/gitlab/phabricator_import/importer_spec.rb b/spec/lib/gitlab/phabricator_import/importer_spec.rb new file mode 100644 index 00000000000..bf14010a187 --- /dev/null +++ b/spec/lib/gitlab/phabricator_import/importer_spec.rb @@ -0,0 +1,32 @@ +require 'spec_helper' + +describe Gitlab::PhabricatorImport::Importer do + it { expect(described_class).to be_async } + + it "acts like it's importing repositories" do + expect(described_class).to be_imports_repository + end + + describe '#execute' do + let(:project) { create(:project, :import_scheduled) } + subject(:importer) { described_class.new(project) } + + it 'sets a custom jid that will be kept up to date' do + expect { importer.execute }.to change { project.import_state.reload.jid } + end + + it 'starts importing tasks' do + expect(Gitlab::PhabricatorImport::ImportTasksWorker).to receive(:schedule).with(project.id) + + importer.execute + end + + it 'marks the import as failed when something goes wrong' do + allow(importer).to receive(:schedule_first_tasks_page).and_raise('Stuff is broken') + + importer.execute + + expect(project.import_state).to be_failed + end + end +end diff --git a/spec/lib/gitlab/phabricator_import/issues/importer_spec.rb b/spec/lib/gitlab/phabricator_import/issues/importer_spec.rb new file mode 100644 index 00000000000..2412cf76f79 --- /dev/null +++ b/spec/lib/gitlab/phabricator_import/issues/importer_spec.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe Gitlab::PhabricatorImport::Issues::Importer do + set(:project) { create(:project) } + + let(:response) do + Gitlab::PhabricatorImport::Conduit::TasksResponse.new( + Gitlab::PhabricatorImport::Conduit::Response + .new(JSON.parse(fixture_file('phabricator_responses/maniphest.search.json'))) + ) + end + + subject(:importer) { described_class.new(project, nil) } + + before do + client = instance_double(Gitlab::PhabricatorImport::Conduit::Maniphest) + + allow(client).to receive(:tasks).and_return(response) + allow(importer).to receive(:client).and_return(client) + end + + describe '#execute' do + it 'imports each task in the response' do + response.tasks.each do |task| + task_importer = instance_double(Gitlab::PhabricatorImport::Issues::TaskImporter) + + expect(task_importer).to receive(:execute) + expect(Gitlab::PhabricatorImport::Issues::TaskImporter) + .to receive(:new).with(project, task) + .and_return(task_importer) + end + + importer.execute + end + + it 'schedules the next batch if there is one' do + expect(Gitlab::PhabricatorImport::ImportTasksWorker) + .to receive(:schedule).with(project.id, response.pagination.next_page) + + importer.execute + end + + it 'does not reschedule when there is no next page' do + allow(response.pagination).to receive(:has_next_page?).and_return(false) + + expect(Gitlab::PhabricatorImport::ImportTasksWorker) + .not_to receive(:schedule) + + importer.execute + end + end +end diff --git a/spec/lib/gitlab/phabricator_import/issues/task_importer_spec.rb b/spec/lib/gitlab/phabricator_import/issues/task_importer_spec.rb new file mode 100644 index 00000000000..1625604e754 --- /dev/null +++ b/spec/lib/gitlab/phabricator_import/issues/task_importer_spec.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe Gitlab::PhabricatorImport::Issues::TaskImporter do + set(:project) { create(:project) } + let(:task) do + Gitlab::PhabricatorImport::Representation::Task.new( + { + 'phid' => 'the-phid', + 'fields' => { + 'name' => 'Title', + 'description' => { + 'raw' => '# This is markdown\n it can contain more text.' + }, + 'dateCreated' => '1518688921', + 'dateClosed' => '1518789995' + } + } + ) + end + + describe '#execute' do + it 'creates the issue with the expected attributes' do + issue = described_class.new(project, task).execute + + expect(issue.project).to eq(project) + expect(issue).to be_persisted + expect(issue.author).to eq(User.ghost) + expect(issue.title).to eq('Title') + expect(issue.description).to eq('# This is markdown\n it can contain more text.') + expect(issue).to be_closed + expect(issue.created_at).to eq(Time.at(1518688921)) + expect(issue.closed_at).to eq(Time.at(1518789995)) + end + + it 'does not recreate the issue when called multiple times' do + expect { described_class.new(project, task).execute } + .to change { project.issues.reload.size }.from(0).to(1) + expect { described_class.new(project, task).execute } + .not_to change { project.issues.reload.size } + end + + it 'does not trigger a save when the object did not change' do + existing_issue = create(:issue, + task.issue_attributes.merge(author: User.ghost)) + importer = described_class.new(project, task) + allow(importer).to receive(:issue).and_return(existing_issue) + + expect(existing_issue).not_to receive(:save!) + + importer.execute + end + end +end diff --git a/spec/lib/gitlab/phabricator_import/project_creator_spec.rb b/spec/lib/gitlab/phabricator_import/project_creator_spec.rb new file mode 100644 index 00000000000..e9455b866ac --- /dev/null +++ b/spec/lib/gitlab/phabricator_import/project_creator_spec.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe Gitlab::PhabricatorImport::ProjectCreator do + let(:user) { create(:user) } + let(:params) do + { path: 'new-phab-import', + phabricator_server_url: 'http://phab.example.com', + api_token: 'the-token' } + end + subject(:creator) { described_class.new(user, params) } + + describe '#execute' do + it 'creates a project correctly and schedule an import' do + expect_next_instance_of(Gitlab::PhabricatorImport::Importer) do |importer| + expect(importer).to receive(:execute) + end + + project = creator.execute + + expect(project).to be_persisted + expect(project).to be_import + expect(project.import_type).to eq('phabricator') + expect(project.import_data.credentials).to match(a_hash_including(api_token: 'the-token')) + expect(project.import_data.data).to match(a_hash_including('phabricator_url' => 'http://phab.example.com')) + expect(project.import_url).to eq(Project::UNKNOWN_IMPORT_URL) + expect(project.namespace).to eq(user.namespace) + end + + context 'when import params are missing' do + let(:params) do + { path: 'new-phab-import', + phabricator_server_url: 'http://phab.example.com', + api_token: '' } + end + + it 'returns nil' do + expect(creator.execute).to be_nil + end + end + + context 'when import params are invalid' do + let(:params) do + { path: 'new-phab-import', + namespace_id: '-1', + phabricator_server_url: 'http://phab.example.com', + api_token: 'the-token' } + end + + it 'returns an unpersisted project' do + project = creator.execute + + expect(project).not_to be_persisted + expect(project).not_to be_valid + end + end + end +end diff --git a/spec/lib/gitlab/phabricator_import/representation/task_spec.rb b/spec/lib/gitlab/phabricator_import/representation/task_spec.rb new file mode 100644 index 00000000000..dfbd8c546eb --- /dev/null +++ b/spec/lib/gitlab/phabricator_import/representation/task_spec.rb @@ -0,0 +1,33 @@ +require 'spec_helper' + +describe Gitlab::PhabricatorImport::Representation::Task do + subject(:task) do + described_class.new( + { + 'phid' => 'the-phid', + 'fields' => { + 'name' => 'Title'.ljust(257, '.'), # A string padded to 257 chars + 'description' => { + 'raw' => '# This is markdown\n it can contain more text.' + }, + 'dateCreated' => '1518688921', + 'dateClosed' => '1518789995' + } + } + ) + end + + describe '#issue_attributes' do + it 'contains the expected values' do + expected_attributes = { + title: 'Title'.ljust(255, '.'), + description: '# This is markdown\n it can contain more text.', + state: :closed, + created_at: Time.at(1518688921), + closed_at: Time.at(1518789995) + } + + expect(task.issue_attributes).to eq(expected_attributes) + end + end +end diff --git a/spec/lib/gitlab/phabricator_import/worker_state_spec.rb b/spec/lib/gitlab/phabricator_import/worker_state_spec.rb new file mode 100644 index 00000000000..a44947445c9 --- /dev/null +++ b/spec/lib/gitlab/phabricator_import/worker_state_spec.rb @@ -0,0 +1,46 @@ +require 'spec_helper' + +describe Gitlab::PhabricatorImport::WorkerState, :clean_gitlab_redis_shared_state do + subject(:state) { described_class.new('weird-project-id') } + let(:key) { 'phabricator-import/jobs/project-weird-project-id/job-count' } + + describe '#add_job' do + it 'increments the counter for jobs' do + set_value(3) + + expect { state.add_job }.to change { get_value }.from('3').to('4') + end + end + + describe '#remove_job' do + it 'decrements the counter for jobs' do + set_value(3) + + expect { state.remove_job }.to change { get_value }.from('3').to('2') + end + end + + describe '#running_count' do + it 'reads the value' do + set_value(9) + + expect(state.running_count).to eq(9) + end + + it 'returns 0 when nothing was set' do + expect(state.running_count).to eq(0) + end + end + + def set_value(value) + redis.with { |r| r.set(key, value) } + end + + def get_value + redis.with { |r| r.get(key) } + end + + def redis + Gitlab::Redis::SharedState + end +end diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb index e2f09de2808..bce2e754176 100644 --- a/spec/lib/gitlab/shell_spec.rb +++ b/spec/lib/gitlab/shell_spec.rb @@ -612,16 +612,6 @@ describe Gitlab::Shell do FileUtils.rm_rf(created_path) end - it 'creates a repository' do - expect(gitlab_shell.create_repository(repository_storage, repo_name, repo_name)).to be_truthy - - expect(File.stat(created_path).mode & 0o777).to eq(0o770) - - hooks_path = File.join(created_path, 'hooks') - expect(File.lstat(hooks_path)).to be_symlink - expect(File.realpath(hooks_path)).to eq(gitlab_shell_hooks_path) - end - it 'returns false when the command fails' do FileUtils.mkdir_p(File.dirname(created_path)) # This file will block the creation of the repo's .git directory. That diff --git a/spec/lib/gitlab/url_builder_spec.rb b/spec/lib/gitlab/url_builder_spec.rb index 9f495a5d50b..bbcb92608d8 100644 --- a/spec/lib/gitlab/url_builder_spec.rb +++ b/spec/lib/gitlab/url_builder_spec.rb @@ -32,7 +32,7 @@ describe Gitlab::UrlBuilder do url = described_class.build(milestone) - expect(url).to eq "#{Settings.gitlab['url']}/#{milestone.project.full_path}/milestones/#{milestone.iid}" + expect(url).to eq "#{Settings.gitlab['url']}/#{milestone.project.full_path}/-/milestones/#{milestone.iid}" end end diff --git a/spec/mailers/emails/pages_domains_spec.rb b/spec/mailers/emails/pages_domains_spec.rb index 050af587061..2f594dbf9d1 100644 --- a/spec/mailers/emails/pages_domains_spec.rb +++ b/spec/mailers/emails/pages_domains_spec.rb @@ -5,11 +5,13 @@ describe Emails::PagesDomains do include EmailSpec::Matchers include_context 'gitlab email notification' - set(:project) { create(:project) } set(:domain) { create(:pages_domain, project: project) } - set(:user) { project.owner } + set(:user) { project.creator } shared_examples 'a pages domain email' do + let(:test_recipient) { user } + + it_behaves_like 'an email sent to a user' it_behaves_like 'an email sent from GitLab' it_behaves_like 'it should not have Gmail Actions links' it_behaves_like 'a user cannot unsubscribe through footer link' diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index 8f348b1b053..cbbb22ad78c 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -45,6 +45,10 @@ describe Notify do context 'for a project' do shared_examples 'an assignee email' do + let(:test_recipient) { assignee } + + it_behaves_like 'an email sent to a user' + it 'is sent to the assignee as the author' do sender = subject.header[:from].addrs.first @@ -618,8 +622,10 @@ describe Notify do end describe 'project was moved' do + let(:test_recipient) { user } subject { described_class.project_was_moved_email(project.id, user.id, "gitlab/gitlab") } + it_behaves_like 'an email sent to a user' it_behaves_like 'an email sent from GitLab' it_behaves_like 'it should not have Gmail Actions links' it_behaves_like "a user cannot unsubscribe through footer link" @@ -773,7 +779,7 @@ describe Notify do invitee end - subject { described_class.member_invite_declined_email('project', project.id, project_member.invite_email, maintainer.id) } + subject { described_class.member_invite_declined_email('Project', project.id, project_member.invite_email, maintainer.id) } it_behaves_like 'an email sent from GitLab' it_behaves_like 'it should not have Gmail Actions links' @@ -1083,8 +1089,6 @@ describe Notify do end context 'for a group' do - set(:group) { create(:group) } - describe 'group access requested' do let(:group) { create(:group, :public, :access_requestable) } let(:group_member) do diff --git a/spec/migrations/change_packages_size_defaults_in_project_statistics_spec.rb b/spec/migrations/change_packages_size_defaults_in_project_statistics_spec.rb new file mode 100644 index 00000000000..93e7e9304b1 --- /dev/null +++ b/spec/migrations/change_packages_size_defaults_in_project_statistics_spec.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require 'spec_helper' +require Rails.root.join('db', 'migrate', '20190516155724_change_packages_size_defaults_in_project_statistics.rb') + +describe ChangePackagesSizeDefaultsInProjectStatistics, :migration do + let(:project_statistics) { table(:project_statistics) } + let(:projects) { table(:projects) } + + it 'removes null packages_size' do + stats_to_migrate = 10 + + stats_to_migrate.times do |i| + p = projects.create!(name: "project #{i}", namespace_id: 1) + project_statistics.create!(project_id: p.id, namespace_id: p.namespace_id) + end + + expect { migrate! } + .to change { ProjectStatistics.where(packages_size: nil).count } + .from(stats_to_migrate) + .to(0) + end + + it 'defaults packages_size to 0' do + project = projects.create!(name: 'a new project', namespace_id: 2) + stat = project_statistics.create!(project_id: project.id, namespace_id: project.namespace_id) + + expect(stat.packages_size).to be_nil + + migrate! + + stat.reload + expect(stat.packages_size).to eq(0) + end +end diff --git a/spec/migrations/generate_lets_encrypt_private_key_spec.rb b/spec/migrations/generate_lets_encrypt_private_key_spec.rb index f47cc0c36ef..773bf5222f0 100644 --- a/spec/migrations/generate_lets_encrypt_private_key_spec.rb +++ b/spec/migrations/generate_lets_encrypt_private_key_spec.rb @@ -3,17 +3,9 @@ require Rails.root.join('db', 'migrate', '20190524062810_generate_lets_encrypt_p describe GenerateLetsEncryptPrivateKey, :migration do describe '#up' do - let(:applications_settings) { table(:applications_settings) } - - it 'generates RSA private key and saves it in application settings' do - application_setting = described_class::ApplicationSetting.create! - - described_class.new.up - application_setting.reload - - expect(application_setting.lets_encrypt_private_key).to be_present + it 'does not fail' do expect do - OpenSSL::PKey::RSA.new(application_setting.lets_encrypt_private_key) + described_class.new.up end.not_to raise_error end end diff --git a/spec/migrations/migrate_old_artifacts_spec.rb b/spec/migrations/migrate_old_artifacts_spec.rb index 79e21514506..bc826d91471 100644 --- a/spec/migrations/migrate_old_artifacts_spec.rb +++ b/spec/migrations/migrate_old_artifacts_spec.rb @@ -45,10 +45,6 @@ describe MigrateOldArtifacts, :migration, schema: 20170918072948 do expect(build_with_legacy_artifacts.artifacts?).to be_falsey end - it "legacy artifacts are set" do - expect(build_with_legacy_artifacts.legacy_artifacts_file_identifier).not_to be_nil - end - describe '#min_id' do subject { migration.send(:min_id) } diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 32eef9e0e01..89d18abee27 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -30,12 +30,6 @@ describe Ci::Build do it { is_expected.to delegate_method(:legacy_detached_merge_request_pipeline?).to(:pipeline) } it { is_expected.to include_module(Ci::PipelineDelegator) } - it { is_expected.to be_a(ArtifactMigratable) } - - it_behaves_like 'UpdateProjectStatistics' do - subject { FactoryBot.build(:ci_build, pipeline: pipeline, artifacts_size: 23) } - end - describe 'associations' do it 'has a bidirectional relationship with projects' do expect(described_class.reflect_on_association(:project).has_inverse?).to eq(:builds) @@ -116,24 +110,6 @@ describe Ci::Build do end end - context 'when job has a legacy archive' do - let!(:job) { create(:ci_build, :legacy_artifacts) } - - it 'returns the job' do - is_expected.to include(job) - end - - context 'when ci_enable_legacy_artifacts feature flag is disabled' do - before do - stub_feature_flags(ci_enable_legacy_artifacts: false) - end - - it 'does not return the job' do - is_expected.not_to include(job) - end - end - end - context 'when job has a job artifact archive' do let!(:job) { create(:ci_build, :artifacts) } @@ -464,51 +440,11 @@ describe Ci::Build do end end end - - context 'when legacy artifacts are used' do - let(:build) { create(:ci_build, :legacy_artifacts) } - - subject { build.artifacts? } - - context 'is expired' do - let(:build) { create(:ci_build, :legacy_artifacts, :expired) } - - it { is_expected.to be_falsy } - end - - context 'artifacts archive does not exist' do - let(:build) { create(:ci_build) } - - it { is_expected.to be_falsy } - end - - context 'artifacts archive exists' do - let(:build) { create(:ci_build, :legacy_artifacts) } - - it { is_expected.to be_truthy } - - context 'when ci_enable_legacy_artifacts feature flag is disabled' do - before do - stub_feature_flags(ci_enable_legacy_artifacts: false) - end - - it { is_expected.to be_falsy } - end - end - end end describe '#browsable_artifacts?' do subject { build.browsable_artifacts? } - context 'artifacts metadata does not exist' do - before do - build.update(legacy_artifacts_metadata: nil) - end - - it { is_expected.to be_falsy } - end - context 'artifacts metadata does exists' do let(:build) { create(:ci_build, :artifacts) } @@ -764,12 +700,6 @@ describe Ci::Build do it { is_expected.to be_truthy } end - - context 'when build does not have job artifacts' do - let(:build) { create(:ci_build, :legacy_artifacts) } - - it { is_expected.to be_falsy } - end end describe '#has_old_trace?' do @@ -1096,11 +1026,11 @@ describe Ci::Build do describe 'erasable build' do shared_examples 'erasable' do it 'removes artifact file' do - expect(build.artifacts_file.exists?).to be_falsy + expect(build.artifacts_file.present?).to be_falsy end it 'removes artifact metadata file' do - expect(build.artifacts_metadata.exists?).to be_falsy + expect(build.artifacts_metadata.present?).to be_falsy end it 'removes all job_artifacts' do @@ -1192,7 +1122,7 @@ describe Ci::Build do let!(:build) { create(:ci_build, :success, :artifacts) } before do - build.remove_artifacts_metadata! + build.erase_erasable_artifacts! end describe '#erase' do @@ -1203,76 +1133,6 @@ describe Ci::Build do end end end - - context 'old artifacts' do - context 'build is erasable' do - context 'new artifacts' do - let!(:build) { create(:ci_build, :trace_artifact, :success, :legacy_artifacts) } - - describe '#erase' do - before do - build.erase(erased_by: erased_by) - end - - context 'erased by user' do - let!(:erased_by) { create(:user, username: 'eraser') } - - include_examples 'erasable' - - it 'records user who erased a build' do - expect(build.erased_by).to eq erased_by - end - end - - context 'erased by system' do - let(:erased_by) { nil } - - include_examples 'erasable' - - it 'does not set user who erased a build' do - expect(build.erased_by).to be_nil - end - end - end - - describe '#erasable?' do - subject { build.erasable? } - it { is_expected.to be_truthy } - end - - describe '#erased?' do - let!(:build) { create(:ci_build, :trace_artifact, :success, :legacy_artifacts) } - subject { build.erased? } - - context 'job has not been erased' do - it { is_expected.to be_falsey } - end - - context 'job has been erased' do - before do - build.erase - end - - it { is_expected.to be_truthy } - end - end - - context 'metadata and build trace are not available' do - let!(:build) { create(:ci_build, :success, :legacy_artifacts) } - - before do - build.remove_artifacts_metadata! - end - - describe '#erase' do - it 'does not raise error' do - expect { build.erase }.not_to raise_error - end - end - end - end - end - end end describe '#erase_erasable_artifacts!' do diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb index b82368318f2..0fa4e470eef 100644 --- a/spec/models/milestone_spec.rb +++ b/spec/models/milestone_spec.rb @@ -31,12 +31,28 @@ describe Milestone do end describe 'start_date' do - it 'adds an error when start_date is greated then due_date' do + it 'adds an error when start_date is greater then due_date' do milestone = build(:milestone, start_date: Date.tomorrow, due_date: Date.yesterday) expect(milestone).not_to be_valid expect(milestone.errors[:due_date]).to include("must be greater than start date") end + + it 'adds an error when start_date is greater than 9999-12-31' do + milestone = build(:milestone, start_date: Date.new(10000, 1, 1)) + + expect(milestone).not_to be_valid + expect(milestone.errors[:start_date]).to include("date must not be after 9999-12-31") + end + end + + describe 'due_date' do + it 'adds an error when due_date is greater than 9999-12-31' do + milestone = build(:milestone, due_date: Date.new(10000, 1, 1)) + + expect(milestone).not_to be_valid + expect(milestone.errors[:due_date]).to include("date must not be after 9999-12-31") + end end end @@ -381,21 +397,6 @@ describe Milestone do expect(milestone_ids).to be_empty end end - - context 'when there is a milestone with a date after 294276 AD', :postgresql do - before do - past_milestone_project_1.update!(due_date: Date.new(294277, 1, 1)) - end - - it 'returns the next upcoming open milestone ID for each project and group' do - expect(milestone_ids).to contain_exactly( - current_milestone_project_1.id, - current_milestone_project_2.id, - current_milestone_group_1.id, - current_milestone_group_2.id - ) - end - end end describe '#to_reference' do @@ -519,4 +520,20 @@ describe Milestone do end end end + + describe '.reference_pattern' do + subject { described_class.reference_pattern } + + it { is_expected.to match('gitlab-org/gitlab-ce%123') } + it { is_expected.to match('gitlab-org/gitlab-ce%"my-milestone"') } + end + + describe '.link_reference_pattern' do + subject { described_class.link_reference_pattern } + + it { is_expected.to match("#{Gitlab.config.gitlab.url}/gitlab-org/gitlab-ce/milestones/123") } + it { is_expected.to match("#{Gitlab.config.gitlab.url}/gitlab-org/gitlab-ce/-/milestones/123") } + it { is_expected.not_to match("#{Gitlab.config.gitlab.url}/gitlab-org/gitlab-ce/issues/123") } + it { is_expected.not_to match("gitlab-org/gitlab-ce/milestones/123") } + end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 08662231fdf..453f9761602 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -1147,7 +1147,7 @@ describe Project do allow(project).to receive(:avatar_in_git) { true } end - let(:avatar_path) { "/#{project.full_path}/avatar" } + let(:avatar_path) { "/#{project.full_path}/-/avatar" } it { is_expected.to eq "http://#{Gitlab.config.gitlab.host}#{avatar_path}" } end diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index a132b85b878..f104da6ebba 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -2,6 +2,8 @@ require 'spec_helper' require 'mime/types' describe API::Commits do + include ProjectForksHelper + let(:user) { create(:user) } let(:guest) { create(:user).tap { |u| project.add_guest(u) } } let(:project) { create(:project, :repository, creator: user, path: 'my.project') } @@ -317,6 +319,96 @@ describe API::Commits do expect(response).to have_gitlab_http_status(201) end end + + context 'when the API user is a guest' do + def last_commit_id(project, branch_name) + project.repository.find_branch(branch_name)&.dereferenced_target&.id + end + + let(:public_project) { create(:project, :public, :repository) } + let!(:url) { "/projects/#{public_project.id}/repository/commits" } + let(:guest) { create(:user).tap { |u| public_project.add_guest(u) } } + + it 'returns a 403' do + post api(url, guest), params: valid_c_params + + expect(response).to have_gitlab_http_status(403) + end + + context 'when start_project is provided' do + context 'when posting to a forked project the user owns' do + let!(:forked_project) { fork_project(public_project, guest, namespace: guest.namespace, repository: true) } + let!(:url) { "/projects/#{forked_project.id}/repository/commits" } + + before do + valid_c_params[:start_branch] = "master" + valid_c_params[:branch] = "patch" + end + + context 'identified by Integer (id)' do + before do + valid_c_params[:start_project] = public_project.id + end + + it 'adds a new commit to forked_project and returns a 201' do + expect { post api(url, guest), params: valid_c_params } + .to change { last_commit_id(forked_project, valid_c_params[:branch]) } + .and not_change { last_commit_id(public_project, valid_c_params[:start_branch]) } + + expect(response).to have_gitlab_http_status(201) + end + end + + context 'identified by String (full_path)' do + before do + valid_c_params[:start_project] = public_project.full_path + end + + it 'adds a new commit to forked_project and returns a 201' do + expect { post api(url, guest), params: valid_c_params } + .to change { last_commit_id(forked_project, valid_c_params[:branch]) } + .and not_change { last_commit_id(public_project, valid_c_params[:start_branch]) } + + expect(response).to have_gitlab_http_status(201) + end + end + end + + context 'when the target project is not part of the fork network of start_project' do + let(:unrelated_project) { create(:project, :public, :repository, creator: guest) } + let!(:url) { "/projects/#{unrelated_project.id}/repository/commits" } + + before do + valid_c_params[:start_branch] = "master" + valid_c_params[:branch] = "patch" + valid_c_params[:start_project] = public_project.id + end + + it 'returns a 403' do + post api(url, guest), params: valid_c_params + + expect(response).to have_gitlab_http_status(403) + end + end + end + + context 'when posting to a forked project the user does not have write access' do + let!(:forked_project) { fork_project(public_project, user, namespace: user.namespace, repository: true) } + let!(:url) { "/projects/#{forked_project.id}/repository/commits" } + + before do + valid_c_params[:start_branch] = "master" + valid_c_params[:branch] = "patch" + valid_c_params[:start_project] = public_project.id + end + + it 'returns a 403' do + post api(url, guest), params: valid_c_params + + expect(response).to have_gitlab_http_status(403) + end + end + end end describe 'delete' do diff --git a/spec/requests/api/graphql/gitlab_schema_spec.rb b/spec/requests/api/graphql/gitlab_schema_spec.rb index 9beea2e2594..b6ca9246399 100644 --- a/spec/requests/api/graphql/gitlab_schema_spec.rb +++ b/spec/requests/api/graphql/gitlab_schema_spec.rb @@ -111,4 +111,29 @@ describe 'GitlabSchema configurations' do expect(graphql_errors).to be_nil end end + + context 'logging' do + let(:query) { File.read(Rails.root.join('spec/fixtures/api/graphql/introspection.graphql')) } + + it 'logs the query complexity and depth' do + analyzer_memo = { + query_string: query, + variables: {}.to_s, + complexity: 181, + depth: 0, + duration: 7 + } + + expect_any_instance_of(Gitlab::Graphql::QueryAnalyzers::LoggerAnalyzer).to receive(:duration).and_return(7) + expect(Gitlab::GraphqlLogger).to receive(:info).with(analyzer_memo) + + post_graphql(query, current_user: nil) + end + + it 'logs using `format_message`' do + expect_any_instance_of(Gitlab::GraphqlLogger).to receive(:format_message) + + post_graphql(query, current_user: nil) + end + end end diff --git a/spec/requests/api/graphql_spec.rb b/spec/requests/api/graphql_spec.rb index cca87c16f27..656d6f8b50b 100644 --- a/spec/requests/api/graphql_spec.rb +++ b/spec/requests/api/graphql_spec.rb @@ -16,6 +16,54 @@ describe 'GraphQL' do end end + context 'logging' do + shared_examples 'logging a graphql query' do + let(:expected_params) do + { query_string: query, variables: variables.to_s, duration: anything, depth: 1, complexity: 1 } + end + + it 'logs a query with the expected params' do + expect(Gitlab::GraphqlLogger).to receive(:info).with(expected_params).once + + post_graphql(query, variables: variables) + end + + it 'does not instantiate any query analyzers' do # they are static and re-used + expect(GraphQL::Analysis::QueryComplexity).not_to receive(:new) + expect(GraphQL::Analysis::QueryDepth).not_to receive(:new) + + 2.times { post_graphql(query, variables: variables) } + end + end + + context 'with no variables' do + let(:variables) { {} } + + it_behaves_like 'logging a graphql query' + end + + context 'with variables' do + let(:variables) do + { "foo" => "bar" } + end + + it_behaves_like 'logging a graphql query' + end + + context 'when there is an error in the logger' do + before do + allow_any_instance_of(Gitlab::Graphql::QueryAnalyzers::LoggerAnalyzer).to receive(:process_variables).and_raise(StandardError.new("oh noes!")) + end + + it 'logs the exception in Sentry and continues with the request' do + expect(Gitlab::Sentry).to receive(:track_exception).at_least(1).times + expect(Gitlab::GraphqlLogger).to receive(:info) + + post_graphql(query, variables: {}) + end + end + end + context 'invalid variables' do it 'returns an error' do post_graphql(query, variables: "This is not JSON") diff --git a/spec/requests/api/jobs_spec.rb b/spec/requests/api/jobs_spec.rb index 43462913497..7208cec357a 100644 --- a/spec/requests/api/jobs_spec.rb +++ b/spec/requests/api/jobs_spec.rb @@ -913,8 +913,8 @@ describe API::Jobs do expect(response).to have_gitlab_http_status(201) expect(job.job_artifacts.count).to eq(0) expect(job.trace.exist?).to be_falsy - expect(job.artifacts_file.exists?).to be_falsy - expect(job.artifacts_metadata.exists?).to be_falsy + expect(job.artifacts_file.present?).to be_falsy + expect(job.artifacts_metadata.present?).to be_falsy expect(job.has_job_artifacts?).to be_falsy end diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb index 4006e697a41..3202050ac20 100644 --- a/spec/requests/api/runner_spec.rb +++ b/spec/requests/api/runner_spec.rb @@ -1632,8 +1632,8 @@ describe API::Runner, :clean_gitlab_redis_shared_state do let!(:metadata) { file_upload2 } let!(:metadata_sha256) { Digest::SHA256.file(metadata.path).hexdigest } - let(:stored_artifacts_file) { job.reload.artifacts_file.file } - let(:stored_metadata_file) { job.reload.artifacts_metadata.file } + let(:stored_artifacts_file) { job.reload.artifacts_file } + let(:stored_metadata_file) { job.reload.artifacts_metadata } let(:stored_artifacts_size) { job.reload.artifacts_size } let(:stored_artifacts_sha256) { job.reload.job_artifacts_archive.file_sha256 } let(:stored_metadata_sha256) { job.reload.job_artifacts_metadata.file_sha256 } @@ -1654,9 +1654,9 @@ describe API::Runner, :clean_gitlab_redis_shared_state do it 'stores artifacts and artifacts metadata' do expect(response).to have_gitlab_http_status(201) - expect(stored_artifacts_file.original_filename).to eq(artifacts.original_filename) - expect(stored_metadata_file.original_filename).to eq(metadata.original_filename) - expect(stored_artifacts_size).to eq(72821) + expect(stored_artifacts_file.filename).to eq(artifacts.original_filename) + expect(stored_metadata_file.filename).to eq(metadata.original_filename) + expect(stored_artifacts_size).to eq(artifacts.size) expect(stored_artifacts_sha256).to eq(artifacts_sha256) expect(stored_metadata_sha256).to eq(metadata_sha256) end diff --git a/spec/routing/import_routing_spec.rb b/spec/routing/import_routing_spec.rb index 106f92082e4..3fdede7914d 100644 --- a/spec/routing/import_routing_spec.rb +++ b/spec/routing/import_routing_spec.rb @@ -174,3 +174,15 @@ describe Import::GitlabProjectsController, 'routing' do expect(get('/import/gitlab_project/new')).to route_to('import/gitlab_projects#new') end end + +# new_import_phabricator GET /import/phabricator/new(.:format) import/phabricator#new +# import_phabricator POST /import/phabricator(.:format) import/phabricator#create +describe Import::PhabricatorController, 'routing' do + it 'to #create' do + expect(post("/import/phabricator")).to route_to("import/phabricator#create") + end + + it 'to #new' do + expect(get("/import/phabricator/new")).to route_to("import/phabricator#new") + end +end diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb index 6f40e88d26f..83775b1040e 100644 --- a/spec/routing/project_routing_spec.rb +++ b/spec/routing/project_routing_spec.rb @@ -470,18 +470,23 @@ describe 'project routing' do it_behaves_like 'RESTful project resources' do let(:controller) { 'milestones' } let(:actions) { [:index, :create, :new, :edit, :show, :update] } + let(:controller_path) { '/-/milestones' } end it 'to #promote' do - expect(post('/gitlab/gitlabhq/milestones/1/promote')).to route_to('projects/milestones#promote', namespace_id: 'gitlab', project_id: 'gitlabhq', id: "1") + expect(post('/gitlab/gitlabhq/-/milestones/1/promote')).to route_to('projects/milestones#promote', namespace_id: 'gitlab', project_id: 'gitlabhq', id: "1") end + + it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/milestones", "/gitlab/gitlabhq/-/milestones" end # project_labels GET /:project_id/labels(.:format) labels#index describe Projects::LabelsController, 'routing' do it 'to #index' do - expect(get('/gitlab/gitlabhq/labels')).to route_to('projects/labels#index', namespace_id: 'gitlab', project_id: 'gitlabhq') + expect(get('/gitlab/gitlabhq/-/labels')).to route_to('projects/labels#index', namespace_id: 'gitlab', project_id: 'gitlabhq') end + + it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/labels", "/gitlab/gitlabhq/-/labels" end # sort_project_issues POST /:project_id/issues/sort(.:format) issues#sort @@ -623,20 +628,24 @@ describe 'project routing' do describe Projects::ForksController, 'routing' do it 'to #new' do - expect(get('/gitlab/gitlabhq/forks/new')).to route_to('projects/forks#new', namespace_id: 'gitlab', project_id: 'gitlabhq') + expect(get('/gitlab/gitlabhq/-/forks/new')).to route_to('projects/forks#new', namespace_id: 'gitlab', project_id: 'gitlabhq') end it 'to #create' do - expect(post('/gitlab/gitlabhq/forks')).to route_to('projects/forks#create', namespace_id: 'gitlab', project_id: 'gitlabhq') + expect(post('/gitlab/gitlabhq/-/forks')).to route_to('projects/forks#create', namespace_id: 'gitlab', project_id: 'gitlabhq') end + + it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/forks", "/gitlab/gitlabhq/-/forks" end # project_avatar DELETE /project/avatar(.:format) projects/avatars#destroy describe Projects::AvatarsController, 'routing' do it 'to #destroy' do - expect(delete('/gitlab/gitlabhq/avatar')).to route_to( + expect(delete('/gitlab/gitlabhq/-/avatar')).to route_to( 'projects/avatars#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq') end + + it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/avatar", "/gitlab/gitlabhq/-/avatar" end describe Projects::PagesDomainsController, 'routing' do diff --git a/spec/services/ci/retry_build_service_spec.rb b/spec/services/ci/retry_build_service_spec.rb index df2376384ca..e9a26400723 100644 --- a/spec/services/ci/retry_build_service_spec.rb +++ b/spec/services/ci/retry_build_service_spec.rb @@ -23,7 +23,7 @@ describe Ci::RetryBuildService do REJECT_ACCESSORS = %i[id status user token token_encrypted coverage trace runner - artifacts_expire_at artifacts_file artifacts_metadata artifacts_size + artifacts_expire_at created_at updated_at started_at finished_at queued_at erased_by erased_at auto_canceled_by job_artifacts job_artifacts_archive job_artifacts_metadata job_artifacts_trace job_artifacts_junit @@ -38,7 +38,8 @@ describe Ci::RetryBuildService do runner_id tag_taggings taggings tags trigger_request_id user_id auto_canceled_by_id retried failure_reason sourced_pipelines artifacts_file_store artifacts_metadata_store - metadata runner_session trace_chunks].freeze + metadata runner_session trace_chunks + artifacts_file artifacts_metadata artifacts_size].freeze shared_examples 'build duplication' do let(:another_pipeline) { create(:ci_empty_pipeline, project: project) } diff --git a/spec/services/projects/update_pages_service_spec.rb b/spec/services/projects/update_pages_service_spec.rb index 7c91f0bbe6e..b597717c347 100644 --- a/spec/services/projects/update_pages_service_spec.rb +++ b/spec/services/projects/update_pages_service_spec.rb @@ -27,59 +27,6 @@ describe Projects::UpdatePagesService do it { is_expected.not_to match(Gitlab::PathRegex.namespace_format_regex) } end - context 'legacy artifacts' do - before do - build.update(legacy_artifacts_file: file) - build.update(legacy_artifacts_metadata: metadata) - end - - describe 'pages artifacts' do - it "doesn't delete artifacts after deploying" do - expect(execute).to eq(:success) - - expect(build.reload.artifacts?).to eq(true) - end - end - - it 'succeeds' do - expect(project.pages_deployed?).to be_falsey - expect(execute).to eq(:success) - expect(project.pages_deployed?).to be_truthy - - # Check that all expected files are extracted - %w[index.html zero .hidden/file].each do |filename| - expect(File.exist?(File.join(project.public_pages_path, filename))).to be_truthy - end - end - - it 'limits pages size' do - stub_application_setting(max_pages_size: 1) - expect(execute).not_to eq(:success) - end - - it 'removes pages after destroy' do - expect(PagesWorker).to receive(:perform_in) - expect(project.pages_deployed?).to be_falsey - expect(execute).to eq(:success) - expect(project.pages_deployed?).to be_truthy - project.destroy - expect(project.pages_deployed?).to be_falsey - end - - it 'fails if sha on branch is not latest' do - build.update(ref: 'feature') - - expect(execute).not_to eq(:success) - end - - it 'fails for empty file fails' do - build.update(legacy_artifacts_file: empty_file) - - expect { execute } - .to raise_error(Projects::UpdatePagesService::FailedToExtractError) - end - end - context 'for new artifacts' do context "for a valid job" do before do @@ -207,7 +154,7 @@ describe Projects::UpdatePagesService do end it 'fails for invalid archive' do - build.update(legacy_artifacts_file: invalid_file) + create(:ci_job_artifact, :archive, file: invalid_file, job: build) expect(execute).not_to eq(:success) end @@ -218,8 +165,8 @@ describe Projects::UpdatePagesService do file = fixture_file_upload('spec/fixtures/pages.zip') metafile = fixture_file_upload('spec/fixtures/pages.zip.meta') - build.update(legacy_artifacts_file: file) - build.update(legacy_artifacts_metadata: metafile) + create(:ci_job_artifact, :archive, file: file, job: build) + create(:ci_job_artifact, :metadata, file: metafile, job: build) allow(build).to receive(:artifacts_metadata_entry) .and_return(metadata) diff --git a/spec/support/helpers/git_helpers.rb b/spec/support/helpers/git_helpers.rb index 99a7c39852e..99c5871ba54 100644 --- a/spec/support/helpers/git_helpers.rb +++ b/spec/support/helpers/git_helpers.rb @@ -6,12 +6,4 @@ module GitHelpers Rugged::Repository.new(path) end - - def project_hook_exists?(project) - Gitlab::GitalyClient::StorageSettings.allow_disk_access do - project_path = project.repository.raw_repository.path - - File.exist?(File.join(project_path, 'hooks', 'post-receive')) - end - end end diff --git a/spec/support/matchers/eq_pem.rb b/spec/support/matchers/eq_pem.rb new file mode 100644 index 00000000000..158281e4a19 --- /dev/null +++ b/spec/support/matchers/eq_pem.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +RSpec::Matchers.define :eq_pem do |expected_pem_string| + match do |actual| + actual.to_pem == expected_pem_string + end + + description do + "contain pem #{expected_pem_string}" + end +end diff --git a/spec/support/shared_examples/notify_shared_examples.rb b/spec/support/shared_examples/notify_shared_examples.rb index 4fff1c4e228..897c9106d77 100644 --- a/spec/support/shared_examples/notify_shared_examples.rb +++ b/spec/support/shared_examples/notify_shared_examples.rb @@ -1,5 +1,7 @@ shared_context 'gitlab email notification' do - set(:project) { create(:project, :repository, name: 'a-known-name') } + set(:group) { create(:group) } + set(:subgroup) { create(:group, parent: group) } + set(:project) { create(:project, :repository, name: 'a-known-name', group: group) } set(:recipient) { create(:user, email: 'recipient@example.com') } let(:gitlab_sender_display_name) { Gitlab.config.gitlab.email_display_name } @@ -39,6 +41,47 @@ shared_examples 'an email sent from GitLab' do end end +shared_examples 'an email sent to a user' do + let(:group_notification_email) { 'user+group@example.com' } + + it 'is sent to user\'s global notification email address' do + expect(subject).to deliver_to(test_recipient.notification_email) + end + + context 'that is part of a project\'s group' do + it 'is sent to user\'s group notification email address when set' do + create(:notification_setting, user: test_recipient, source: project.group, notification_email: group_notification_email) + expect(subject).to deliver_to(group_notification_email) + end + + it 'is sent to user\'s global notification email address when no group email set' do + create(:notification_setting, user: test_recipient, source: project.group, notification_email: '') + expect(subject).to deliver_to(test_recipient.notification_email) + end + end + + context 'when project is in a sub-group', :nested_groups do + before do + project.update!(group: subgroup) + end + + it 'is sent to user\'s subgroup notification email address when set' do + # Set top-level group notification email address to make sure it doesn't get selected + create(:notification_setting, user: test_recipient, source: group, notification_email: group_notification_email) + + subgroup_notification_email = 'user+subgroup@example.com' + create(:notification_setting, user: test_recipient, source: subgroup, notification_email: subgroup_notification_email) + + expect(subject).to deliver_to(subgroup_notification_email) + end + + it 'is sent to user\'s group notification email address when set and subgroup email address not set' do + create(:notification_setting, user: test_recipient, source: subgroup, notification_email: '') + expect(subject).to deliver_to(test_recipient.notification_email) + end + end +end + shared_examples 'an email that contains a header with author username' do it 'has X-GitLab-Author header containing author\'s username' do is_expected.to have_header 'X-GitLab-Author', user.username diff --git a/spec/tasks/gitlab/artifacts/migrate_rake_spec.rb b/spec/tasks/gitlab/artifacts/migrate_rake_spec.rb index 8544fb62b5a..be69c10d7c8 100644 --- a/spec/tasks/gitlab/artifacts/migrate_rake_spec.rb +++ b/spec/tasks/gitlab/artifacts/migrate_rake_spec.rb @@ -13,61 +13,6 @@ describe 'gitlab:artifacts namespace rake task' do subject { run_rake_task('gitlab:artifacts:migrate') } - context 'legacy artifacts' do - describe 'migrate' do - let!(:build) { create(:ci_build, :legacy_artifacts, artifacts_file_store: store, artifacts_metadata_store: store) } - - context 'when local storage is used' do - let(:store) { ObjectStorage::Store::LOCAL } - - context 'and job does not have file store defined' do - let(:object_storage_enabled) { true } - let(:store) { nil } - - it "migrates file to remote storage" do - subject - - expect(build.reload.artifacts_file_store).to eq(ObjectStorage::Store::REMOTE) - expect(build.reload.artifacts_metadata_store).to eq(ObjectStorage::Store::REMOTE) - end - end - - context 'and remote storage is defined' do - let(:object_storage_enabled) { true } - - it "migrates file to remote storage" do - subject - - expect(build.reload.artifacts_file_store).to eq(ObjectStorage::Store::REMOTE) - expect(build.reload.artifacts_metadata_store).to eq(ObjectStorage::Store::REMOTE) - end - end - - context 'and remote storage is not defined' do - it "fails to migrate to remote storage" do - subject - - expect(build.reload.artifacts_file_store).to eq(ObjectStorage::Store::LOCAL) - expect(build.reload.artifacts_metadata_store).to eq(ObjectStorage::Store::LOCAL) - end - end - end - - context 'when remote storage is used' do - let(:object_storage_enabled) { true } - - let(:store) { ObjectStorage::Store::REMOTE } - - it "file stays on remote storage" do - subject - - expect(build.reload.artifacts_file_store).to eq(ObjectStorage::Store::REMOTE) - expect(build.reload.artifacts_metadata_store).to eq(ObjectStorage::Store::REMOTE) - end - end - end - end - context 'job artifacts' do let!(:artifact) { create(:ci_job_artifact, :archive, file_store: store) } diff --git a/spec/tasks/gitlab/shell_rake_spec.rb b/spec/tasks/gitlab/shell_rake_spec.rb index a9d14070177..c3e912b02c5 100644 --- a/spec/tasks/gitlab/shell_rake_spec.rb +++ b/spec/tasks/gitlab/shell_rake_spec.rb @@ -7,14 +7,8 @@ describe 'gitlab:shell rake tasks' do stub_warn_user_is_not_gitlab end - after do - TestEnv.sabotage_gitlab_shell_hooks - end - describe 'install task' do - it 'invokes create_hooks task' do - expect(Rake::Task['gitlab:shell:create_hooks']).to receive(:invoke) - + it 'installs and compiles gitlab-shell' do storages = Gitlab::GitalyClient::StorageSettings.allow_disk_access do Gitlab.config.repositories.storages.values.map(&:legacy_disk_path) end @@ -24,14 +18,4 @@ describe 'gitlab:shell rake tasks' do run_rake_task('gitlab:shell:install') end end - - describe 'create_hooks task' do - it 'calls gitlab-shell bin/create_hooks' do - expect_any_instance_of(Object).to receive(:system) - .with("#{Gitlab.config.gitlab_shell.path}/bin/create-hooks", - *Gitlab::TaskHelpers.repository_storage_paths_args) - - run_rake_task('gitlab:shell:create_hooks') - end - end end diff --git a/spec/tasks/tokens_spec.rb b/spec/tasks/tokens_spec.rb index 555a58e9aa1..4188e7caccb 100644 --- a/spec/tasks/tokens_spec.rb +++ b/spec/tasks/tokens_spec.rb @@ -8,13 +8,13 @@ describe 'tokens rake tasks' do end describe 'reset_all_email task' do - it 'invokes create_hooks task' do + it 'changes the incoming email token' do expect { run_rake_task('tokens:reset_all_email') }.to change { user.reload.incoming_email_token } end end describe 'reset_all_feed task' do - it 'invokes create_hooks task' do + it 'changes the feed token for the user' do expect { run_rake_task('tokens:reset_all_feed') }.to change { user.reload.feed_token } end end diff --git a/spec/uploaders/legacy_artifact_uploader_spec.rb b/spec/uploaders/legacy_artifact_uploader_spec.rb deleted file mode 100644 index 0589563b502..00000000000 --- a/spec/uploaders/legacy_artifact_uploader_spec.rb +++ /dev/null @@ -1,61 +0,0 @@ -require 'rails_helper' - -describe LegacyArtifactUploader do - let(:store) { described_class::Store::LOCAL } - let(:job) { create(:ci_build, artifacts_file_store: store) } - let(:uploader) { described_class.new(job, :legacy_artifacts_file) } - let(:local_path) { described_class.root } - - subject { uploader } - - # TODO: move to Workhorse::UploadPath - describe '.workhorse_upload_path' do - subject { described_class.workhorse_upload_path } - - it { is_expected.to start_with(local_path) } - it { is_expected.to end_with('tmp/uploads') } - end - - it_behaves_like "builds correct paths", - store_dir: %r[\d{4}_\d{1,2}/\d+/\d+\z], - cache_dir: %r[artifacts/tmp/cache], - work_dir: %r[artifacts/tmp/work] - - context 'object store is remote' do - before do - stub_artifacts_object_storage - end - - include_context 'with storage', described_class::Store::REMOTE - - it_behaves_like "builds correct paths", - store_dir: %r[\d{4}_\d{1,2}/\d+/\d+\z] - end - - describe '#filename' do - # we need to use uploader, as this makes to use mounter - # which initialises uploader.file object - let(:uploader) { job.artifacts_file } - - subject { uploader.filename } - - it { is_expected.to be_nil } - end - - context 'file is stored in valid path' do - let(:file) do - fixture_file_upload('spec/fixtures/ci_build_artifacts.zip', 'application/zip') - end - - before do - uploader.store!(file) - end - - subject { uploader.file.path } - - it { is_expected.to start_with("#{uploader.root}") } - it { is_expected.to include("/#{job.created_at.utc.strftime('%Y_%m')}/") } - it { is_expected.to include("/#{job.project_id}/") } - it { is_expected.to end_with("ci_build_artifacts.zip") } - end -end diff --git a/spec/uploaders/workers/object_storage/background_move_worker_spec.rb b/spec/uploaders/workers/object_storage/background_move_worker_spec.rb index 95813d15e52..cc8970d2ba0 100644 --- a/spec/uploaders/workers/object_storage/background_move_worker_spec.rb +++ b/spec/uploaders/workers/object_storage/background_move_worker_spec.rb @@ -48,40 +48,6 @@ describe ObjectStorage::BackgroundMoveWorker do end end - context 'for legacy artifacts' do - let(:build) { create(:ci_build, :legacy_artifacts) } - let(:uploader_class) { LegacyArtifactUploader } - let(:subject_class) { Ci::Build } - let(:file_field) { :artifacts_file } - let(:subject_id) { build.id } - - context 'when local storage is used' do - let(:store) { local } - - context 'and remote storage is defined' do - before do - stub_artifacts_object_storage(background_upload: true) - end - - it "migrates file to remote storage" do - perform - - expect(build.reload.artifacts_file_store).to eq(remote) - end - - context 'for artifacts_metadata' do - let(:file_field) { :artifacts_metadata } - - it 'migrates metadata to remote storage' do - perform - - expect(build.reload.artifacts_metadata_store).to eq(remote) - end - end - end - end - end - context 'for job artifacts' do let(:artifact) { create(:ci_job_artifact, :archive) } let(:uploader_class) { JobArtifactUploader } diff --git a/spec/workers/expire_build_instance_artifacts_worker_spec.rb b/spec/workers/expire_build_instance_artifacts_worker_spec.rb index bdb5a3801d9..39f676f1057 100644 --- a/spec/workers/expire_build_instance_artifacts_worker_spec.rb +++ b/spec/workers/expire_build_instance_artifacts_worker_spec.rb @@ -21,7 +21,7 @@ describe ExpireBuildInstanceArtifactsWorker do end it 'does remove files' do - expect(build.reload.artifacts_file.exists?).to be_falsey + expect(build.reload.artifacts_file.present?).to be_falsey end it 'does remove the job artifact record' do @@ -40,7 +40,7 @@ describe ExpireBuildInstanceArtifactsWorker do end it 'does not remove files' do - expect(build.reload.artifacts_file.exists?).to be_truthy + expect(build.reload.artifacts_file.present?).to be_truthy end it 'does not remove the job artifact record' do @@ -56,7 +56,7 @@ describe ExpireBuildInstanceArtifactsWorker do end it 'does not remove files' do - expect(build.reload.artifacts_file.exists?).to be_truthy + expect(build.reload.artifacts_file.present?).to be_truthy end it 'does not remove the job artifact record' do |