diff options
91 files changed, 911 insertions, 147 deletions
diff --git a/.gitlab/ci/review.gitlab-ci.yml b/.gitlab/ci/review.gitlab-ci.yml index 3a024c44fe2..80356fa1dc2 100644 --- a/.gitlab/ci/review.gitlab-ci.yml +++ b/.gitlab/ci/review.gitlab-ci.yml @@ -78,7 +78,6 @@ schedule:review-build-cng: <<: *review-base stage: review retry: 2 - allow_failure: true variables: HOST_SUFFIX: "${CI_ENVIRONMENT_SLUG}" DOMAIN: "-${CI_ENVIRONMENT_SLUG}.${REVIEW_APPS_DOMAIN}" @@ -130,7 +129,6 @@ review-stop: .review-qa-base: &review-qa-base <<: *review-docker stage: qa - allow_failure: true variables: <<: *review-docker-variables QA_ARTIFACTS_DIR: "${CI_PROJECT_DIR}/qa" @@ -165,13 +163,14 @@ review-qa-smoke: review-qa-all: <<: *review-qa-base + allow_failure: true when: manual script: - gitlab-qa Test::Instance::Any "${QA_IMAGE}" "${CI_ENVIRONMENT_URL}" .review-performance-base: &review-performance-base <<: *review-qa-base - stage: qa + allow_failure: true before_script: - export CI_ENVIRONMENT_URL="$(cat review_app_url.txt)" - echo "${CI_ENVIRONMENT_URL}" diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION index 47da986f86f..deeb3d66ef0 100644 --- a/GITLAB_SHELL_VERSION +++ b/GITLAB_SHELL_VERSION @@ -1 +1 @@ -9.1.0 +9.2.0 @@ -60,6 +60,8 @@ gem 'u2f', '~> 0.2.1' # GitLab Pages gem 'validates_hostname', '~> 1.0.6' gem 'rubyzip', '~> 1.2.2', require: 'zip' +# GitLab Pages letsencrypt support +gem 'acme-client', '~> 2.0.2' # Browser detection gem 'browser', '~> 2.5' @@ -351,9 +353,9 @@ group :development, :test do gem 'spring', '~> 2.0.0' gem 'spring-commands-rspec', '~> 1.0.4' - gem 'gitlab-styles', '~> 2.6', require: false + gem 'gitlab-styles', '~> 2.7', require: false # Pin these dependencies, otherwise a new rule could break the CI pipelines - gem 'rubocop', '~> 0.68.1' + gem 'rubocop', '~> 0.69.0' gem 'rubocop-performance', '~> 1.1.0' gem 'rubocop-rspec', '~> 1.22.1' diff --git a/Gemfile.lock b/Gemfile.lock index 1bd88b65124..8238ed4763e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -4,6 +4,8 @@ GEM RedCloth (4.3.2) abstract_type (0.0.7) ace-rails-ap (4.1.2) + acme-client (2.0.2) + faraday (~> 0.9, >= 0.9.1) actioncable (5.1.7) actionpack (= 5.1.7) nio4r (~> 2.0) @@ -85,7 +87,7 @@ GEM erubi (>= 1.0.0) rack (>= 0.9.0) bindata (2.4.3) - binding_ninja (0.2.2) + binding_ninja (0.2.3) binding_of_caller (0.8.0) debug_inspector (>= 0.0.1) bootsnap (1.4.1) @@ -297,8 +299,8 @@ GEM gitlab-markup (1.7.0) gitlab-sidekiq-fetcher (0.4.0) sidekiq (~> 5) - gitlab-styles (2.6.2) - rubocop (~> 0.68.1) + gitlab-styles (2.7.0) + rubocop (~> 0.69.0) rubocop-gitlab-security (~> 0.1.0) rubocop-performance (~> 1.1.0) rubocop-rspec (~> 1.19) @@ -591,7 +593,7 @@ GEM orm_adapter (0.5.0) os (1.0.0) parallel (1.17.0) - parser (2.5.3.0) + parser (2.6.3.0) ast (~> 2.4.0) parslet (1.8.2) peek (1.0.1) @@ -766,8 +768,8 @@ GEM rspec-mocks (3.7.0) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.7.0) - rspec-parameterized (0.4.1) - binding_ninja (>= 0.2.1) + rspec-parameterized (0.4.2) + binding_ninja (>= 0.2.3) parser proc_to_ast rspec (>= 2.13, < 4) @@ -791,13 +793,13 @@ GEM pg rails sqlite3 - rubocop (0.68.1) + rubocop (0.69.0) jaro_winkler (~> 1.5.1) parallel (~> 1.10) - parser (>= 2.5, != 2.5.1.1) + parser (>= 2.6) rainbow (>= 2.2.2, < 4.0) ruby-progressbar (~> 1.7) - unicode-display_width (>= 1.4.0, < 1.6) + unicode-display_width (>= 1.4.0, < 1.7) rubocop-gitlab-security (0.1.1) rubocop (>= 0.51) rubocop-performance (1.1.0) @@ -937,7 +939,7 @@ GEM unf (0.1.4) unf_ext unf_ext (0.0.7.5) - unicode-display_width (1.5.0) + unicode-display_width (1.6.0) unicorn (5.4.1) kgio (~> 2.6) raindrops (~> 0.7) @@ -945,13 +947,13 @@ GEM get_process_mem (~> 0) unicorn (>= 4, < 6) uniform_notifier (1.10.0) - unparser (0.4.2) + unparser (0.4.5) abstract_type (~> 0.0.7) adamantium (~> 0.2.0) concord (~> 0.1.5) diff-lcs (~> 1.3) equalizer (~> 0.0.9) - parser (>= 2.3.1.2, < 2.6) + parser (~> 2.6.3) procto (~> 0.0.2) validate_email (0.1.6) activemodel (>= 3.0) @@ -998,6 +1000,7 @@ PLATFORMS DEPENDENCIES RedCloth (~> 4.3.2) ace-rails-ap (~> 4.1.0) + acme-client (~> 2.0.2) activerecord_sane_schema_dumper (= 1.0) acts-as-taggable-on (~> 6.0) addressable (~> 2.5.2) @@ -1073,7 +1076,7 @@ DEPENDENCIES gitlab-labkit (~> 0.2.0) gitlab-markup (~> 1.7.0) gitlab-sidekiq-fetcher (~> 0.4.0) - gitlab-styles (~> 2.6) + gitlab-styles (~> 2.7) gitlab_omniauth-ldap (~> 2.1.1) gon (~> 6.2) google-api-client (~> 0.23) @@ -1181,7 +1184,7 @@ DEPENDENCIES rspec-set (~> 0.1.3) rspec_junit_formatter rspec_profiling (~> 0.0.5) - rubocop (~> 0.68.1) + rubocop (~> 0.69.0) rubocop-performance (~> 1.1.0) rubocop-rspec (~> 1.22.1) ruby-fogbugz (~> 0.2.1) diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue index 0ed4dcdcd81..11d6672cacf 100644 --- a/app/assets/javascripts/diffs/components/app.vue +++ b/app/assets/javascripts/diffs/components/app.vue @@ -157,10 +157,12 @@ export default { this.adjustView(); eventHub.$once('fetchedNotesData', this.setDiscussions); eventHub.$once('fetchDiffData', this.fetchData); + eventHub.$on('refetchDiffData', this.refetchDiffData); this.CENTERED_LIMITED_CONTAINER_CLASSES = CENTERED_LIMITED_CONTAINER_CLASSES; }, beforeDestroy() { eventHub.$off('fetchDiffData', this.fetchData); + eventHub.$off('refetchDiffData', this.refetchDiffData); this.removeEventListeners(); }, methods: { @@ -175,10 +177,16 @@ export default { 'scrollToFile', 'toggleShowTreeList', ]), - fetchData() { + refetchDiffData() { + this.assignedDiscussions = false; + this.fetchData(false); + }, + fetchData(toggleTree = true) { this.fetchDiffFiles() .then(() => { - this.hideTreeListIfJustOneFile(); + if (toggleTree) { + this.hideTreeListIfJustOneFile(); + } requestIdleCallback( () => { diff --git a/app/assets/javascripts/diffs/components/tree_list.vue b/app/assets/javascripts/diffs/components/tree_list.vue index 384f33e0983..30be2e68e76 100644 --- a/app/assets/javascripts/diffs/components/tree_list.vue +++ b/app/assets/javascripts/diffs/components/tree_list.vue @@ -1,6 +1,7 @@ <script> import { mapActions, mapGetters, mapState } from 'vuex'; import { GlTooltipDirective } from '@gitlab/ui'; +import { s__, sprintf } from '~/locale'; import Icon from '~/vue_shared/components/icon.vue'; import FileRow from '~/vue_shared/components/file_row.vue'; import FileRowStats from './file_row_stats.vue'; @@ -57,6 +58,9 @@ export default { this.search = ''; }, }, + searchPlaceholder: sprintf(s__('MergeRequest|Filter files or search with %{modifier_key}+p'), { + modifier_key: /Mac/i.test(navigator.userAgent) ? 'cmd' : 'ctrl', + }), }; </script> @@ -65,10 +69,13 @@ export default { <div class="append-bottom-8 position-relative tree-list-search d-flex"> <div class="flex-fill d-flex"> <icon name="search" class="position-absolute tree-list-icon" /> + <label for="diff-tree-search" class="sr-only">{{ $options.searchPlaceholder }}</label> <input + id="diff-tree-search" v-model="search" - :placeholder="s__('MergeRequest|Filter files')" + :placeholder="$options.searchPlaceholder" type="search" + name="diff-tree-search" class="form-control" /> <button diff --git a/app/assets/javascripts/diffs/store/actions.js b/app/assets/javascripts/diffs/store/actions.js index 386d08aed2b..35297b7c264 100644 --- a/app/assets/javascripts/diffs/store/actions.js +++ b/app/assets/javascripts/diffs/store/actions.js @@ -52,7 +52,7 @@ export const fetchDiffFiles = ({ state, commit }) => { }); return axios - .get(state.endpoint, { params: { w: state.showWhitespace ? null : '1' } }) + .get(mergeUrlParams({ w: state.showWhitespace ? '0' : '1' }, state.endpoint)) .then(res => { commit(types.SET_LOADING, false); commit(types.SET_MERGE_REQUEST_DIFFS, res.data.merge_request_diffs || []); @@ -125,7 +125,8 @@ export const startRenderDiffsQueue = ({ state, commit }) => { new Promise(resolve => { const nextFile = state.diffFiles.find( file => - !file.renderIt && (!file.viewer.collapsed || !file.viewer.name === diffViewerModes.text), + !file.renderIt && + (file.viewer && (!file.viewer.collapsed || !file.viewer.name === diffViewerModes.text)), ); if (nextFile) { @@ -315,8 +316,10 @@ export const setShowWhitespace = ({ commit }, { showWhitespace, pushState = fals localStorage.setItem(WHITESPACE_STORAGE_KEY, showWhitespace); if (pushState) { - historyPushState(showWhitespace ? '?w=0' : '?w=1'); + historyPushState(mergeUrlParams({ w: showWhitespace ? '0' : '1' }, window.location.href)); } + + eventHub.$emit('refetchDiffData'); }; export const toggleFileFinder = ({ commit }, visible) => { diff --git a/app/assets/javascripts/issuable_bulk_update_actions.js b/app/assets/javascripts/issuable_bulk_update_actions.js index ccbe591a63e..bc9d7fcf30d 100644 --- a/app/assets/javascripts/issuable_bulk_update_actions.js +++ b/app/assets/javascripts/issuable_bulk_update_actions.js @@ -4,6 +4,7 @@ import $ from 'jquery'; import _ from 'underscore'; import axios from './lib/utils/axios_utils'; import Flash from './flash'; +import { __ } from './locale'; export default { init({ container, form, issues, prefixId } = {}) { @@ -32,7 +33,7 @@ export default { onFormSubmitFailure() { this.form.find('[type="submit"]').enable(); - return new Flash('Issue update failed'); + return new Flash(__('Issue update failed')); }, getSelectedIssues() { diff --git a/app/assets/javascripts/issuable_index.js b/app/assets/javascripts/issuable_index.js index ffcbd7cf28c..f51c7a2f990 100644 --- a/app/assets/javascripts/issuable_index.js +++ b/app/assets/javascripts/issuable_index.js @@ -1,7 +1,7 @@ import $ from 'jquery'; import axios from './lib/utils/axios_utils'; import flash from './flash'; -import { __ } from './locale'; +import { s__, __ } from './locale'; import IssuableBulkUpdateSidebar from './issuable_bulk_update_sidebar'; import IssuableBulkUpdateActions from './issuable_bulk_update_actions'; @@ -29,7 +29,7 @@ export default class IssuableIndex { $resetToken.on('click', e => { e.preventDefault(); - $resetToken.text('resetting...'); + $resetToken.text(s__('EmailToken|resetting...')); axios .put($resetToken.attr('href')) @@ -38,12 +38,12 @@ export default class IssuableIndex { .val(data.new_address) .focus(); - $resetToken.text('reset it'); + $resetToken.text(s__('EmailToken|reset it')); }) .catch(() => { flash(__('There was an error when reseting email token.')); - $resetToken.text('reset it'); + $resetToken.text(s__('EmailToken|reset it')); }); }); } diff --git a/app/assets/javascripts/issue.js b/app/assets/javascripts/issue.js index cd1afb6ba83..db4607ca58d 100644 --- a/app/assets/javascripts/issue.js +++ b/app/assets/javascripts/issue.js @@ -7,6 +7,7 @@ import flash from './flash'; import TaskList from './task_list'; import CreateMergeRequestDropdown from './create_merge_request_dropdown'; import IssuablesHelper from './helpers/issuables_helper'; +import { __ } from './locale'; export default class Issue { constructor() { @@ -44,7 +45,11 @@ export default class Issue { * @param {Array} data * @param {String} issueFailMessage */ - updateTopState(isClosed, data, issueFailMessage = 'Unable to update this issue at this time.') { + updateTopState( + isClosed, + data, + issueFailMessage = __('Unable to update this issue at this time.'), + ) { if ('id' in data) { const isClosedBadge = $('div.status-box-issue-closed'); const isOpenBadge = $('div.status-box-open'); @@ -81,7 +86,7 @@ export default class Issue { } initIssueBtnEventListeners() { - const issueFailMessage = 'Unable to update this issue at this time.'; + const issueFailMessage = __('Unable to update this issue at this time.'); return $(document).on( 'click', @@ -152,6 +157,6 @@ export default class Issue { $container.html(data.html); } }) - .catch(() => flash('Failed to load related branches')); + .catch(() => flash(__('Failed to load related branches'))); } } diff --git a/app/assets/javascripts/issue_status_select.js b/app/assets/javascripts/issue_status_select.js index c14803c80e7..75edff41a89 100644 --- a/app/assets/javascripts/issue_status_select.js +++ b/app/assets/javascripts/issue_status_select.js @@ -1,4 +1,5 @@ import $ from 'jquery'; +import { __ } from './locale'; export default function issueStatusSelect() { $('.js-issue-status').each((i, el) => { @@ -7,7 +8,7 @@ export default function issueStatusSelect() { selectable: true, fieldName, toggleLabel(selected, element, instance) { - let label = 'Author'; + let label = __('Author'); const $item = instance.dropdown.find('.is-active'); if ($item.length) { label = $item.text(); diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss index e6c55252b24..3aabb66f7a6 100644 --- a/app/assets/stylesheets/framework/blocks.scss +++ b/app/assets/stylesheets/framework/blocks.scss @@ -22,6 +22,10 @@ } } +.oneline { + line-height: 35px; +} + .row-content-block { margin-top: 0; background-color: $gray-light; @@ -77,10 +81,6 @@ color: $gl-text-color; } - .oneline { - line-height: 35px; - } - > p:last-child { margin-bottom: 0; } @@ -108,10 +108,6 @@ padding: 11px 0; margin-bottom: 11px; - .oneline { - line-height: 35px; - } - &.no-bottom-space { border-bottom: 0; margin-bottom: 0; @@ -160,8 +156,6 @@ } .cover-desc { - color: $gl-text-color; - &.username:last-child { padding-bottom: $gl-padding; } diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index d445be0eb19..d5bc723aa8c 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -89,6 +89,13 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController ) end + # Getting ToS url requires `directory` api call to Let's Encrypt + # which could result in 500 error/slow rendering on settings page + # Because of that we use separate controller action + def lets_encrypt_terms_of_service + redirect_to ::Gitlab::LetsEncrypt.terms_of_service_url + end + private def set_application_setting diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index 0fa4677ced1..07b38371ab9 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -4,6 +4,7 @@ class RegistrationsController < Devise::RegistrationsController include Recaptcha::Verify include AcceptsPendingInvitations + prepend_before_action :check_captcha, only: :create before_action :whitelist_query_limiting, only: [:destroy] before_action :ensure_terms_accepted, if: -> { Gitlab::CurrentSettings.current_application_settings.enforce_terms? }, @@ -21,15 +22,10 @@ class RegistrationsController < Devise::RegistrationsController params[resource_name] = params.delete(:"new_#{resource_name}") end - if !Gitlab::Recaptcha.load_configurations! || verify_recaptcha - accept_pending_invitations - super do |new_user| - persist_accepted_terms_if_required(new_user) - end - else - flash[:alert] = s_('Profiles|There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.') - flash.delete :recaptcha_error - render action: 'new' + accept_pending_invitations + + super do |new_user| + persist_accepted_terms_if_required(new_user) end rescue Gitlab::Access::AccessDeniedError redirect_to(new_user_session_path) @@ -89,6 +85,17 @@ class RegistrationsController < Devise::RegistrationsController private + def check_captcha + return unless Feature.enabled?(:registrations_recaptcha, default_enabled: true) + return unless Gitlab::Recaptcha.load_configurations! + + return if verify_recaptcha + + flash[:alert] = _('There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.') + flash.delete :recaptcha_error + render action: 'new' + end + def sign_up_params params.require(:user).permit(:username, :email, :email_confirmation, :name, :password) end diff --git a/app/helpers/emails_helper.rb b/app/helpers/emails_helper.rb index 96471d15aac..dc0e5511fcf 100644 --- a/app/helpers/emails_helper.rb +++ b/app/helpers/emails_helper.rb @@ -91,6 +91,28 @@ module EmailsHelper ].join(';') end + def closure_reason_text(closed_via, format: nil) + case closed_via + when MergeRequest + merge_request = MergeRequest.find(closed_via[:id]).present + + case format + when :html + " via merge request #{link_to(merge_request.to_reference, merge_request.web_url)}" + else + # If it's not HTML nor text then assume it's text to be safe + " via merge request #{merge_request.to_reference} (#{merge_request.web_url})" + end + when String + # Technically speaking this should be Commit but per + # https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/15610#note_163812339 + # we can't deserialize Commit without custom serializer for ActiveJob + " via #{closed_via}" + else + "" + end + end + # "You are receiving this email because #{reason}" def notification_reason_text(reason) string = case reason diff --git a/app/helpers/storage_helper.rb b/app/helpers/storage_helper.rb index 15041bd5805..e80b3f2b54a 100644 --- a/app/helpers/storage_helper.rb +++ b/app/helpers/storage_helper.rb @@ -2,6 +2,8 @@ module StorageHelper def storage_counter(size_in_bytes) + return s_('StorageSize|Unknown') unless size_in_bytes + precision = size_in_bytes < 1.megabyte ? 0 : 1 number_to_human_size(size_in_bytes, delimiter: ',', precision: precision, significant: false) diff --git a/app/mailers/emails/issues.rb b/app/mailers/emails/issues.rb index d2e334fb856..2b046d17122 100644 --- a/app/mailers/emails/issues.rb +++ b/app/mailers/emails/issues.rb @@ -30,8 +30,8 @@ module Emails end # rubocop: enable CodeReuse/ActiveRecord - def closed_issue_email(recipient_id, issue_id, updated_by_user_id, reason = nil) - setup_issue_mail(issue_id, recipient_id) + def closed_issue_email(recipient_id, issue_id, updated_by_user_id, reason: nil, closed_via: nil) + setup_issue_mail(issue_id, recipient_id, closed_via: closed_via) @updated_by = User.find(updated_by_user_id) mail_answer_thread(@issue, issue_thread_options(updated_by_user_id, recipient_id, reason)) @@ -91,10 +91,11 @@ module Emails private - def setup_issue_mail(issue_id, recipient_id) + def setup_issue_mail(issue_id, recipient_id, closed_via: nil) @issue = Issue.find(issue_id) @project = @issue.project @target_url = project_issue_url(@project, @issue) + @closed_via = closed_via @sent_notification = SentNotification.record(@issue, recipient_id, reply_key) end diff --git a/app/mailers/emails/merge_requests.rb b/app/mailers/emails/merge_requests.rb index 63148831a24..fc6ed695675 100644 --- a/app/mailers/emails/merge_requests.rb +++ b/app/mailers/emails/merge_requests.rb @@ -58,14 +58,14 @@ module Emails })) end - def closed_merge_request_email(recipient_id, merge_request_id, updated_by_user_id, reason = nil) + def closed_merge_request_email(recipient_id, merge_request_id, updated_by_user_id, reason: nil, closed_via: nil) setup_merge_request_mail(merge_request_id, recipient_id) @updated_by = User.find(updated_by_user_id) mail_answer_thread(@merge_request, merge_request_thread_options(updated_by_user_id, recipient_id, reason)) end - def merged_merge_request_email(recipient_id, merge_request_id, updated_by_user_id, reason = nil) + def merged_merge_request_email(recipient_id, merge_request_id, updated_by_user_id, reason: nil, closed_via: nil) setup_merge_request_mail(merge_request_id, recipient_id) mail_answer_thread(@merge_request, merge_request_thread_options(updated_by_user_id, recipient_id, reason)) diff --git a/app/models/repository.rb b/app/models/repository.rb index 1c02e68f2f6..e05d3dd58ac 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -283,14 +283,19 @@ class Repository end def diverging_commit_counts(branch) + return diverging_commit_counts_without_max(branch) if Feature.enabled?('gitaly_count_diverging_commits_no_max') + + ## TODO: deprecate the below code after 12.0 @root_ref_hash ||= raw_repository.commit(root_ref).id cache.fetch(:"diverging_commit_counts_#{branch.name}") do # Rugged seems to throw a `ReferenceError` when given branch_names rather # than SHA-1 hashes + branch_sha = branch.dereferenced_target.sha + number_commits_behind, number_commits_ahead = raw_repository.diverging_commit_count( @root_ref_hash, - branch.dereferenced_target.sha, + branch_sha, max_count: MAX_DIVERGING_COUNT) if number_commits_behind + number_commits_ahead >= MAX_DIVERGING_COUNT @@ -301,6 +306,22 @@ class Repository end end + def diverging_commit_counts_without_max(branch) + @root_ref_hash ||= raw_repository.commit(root_ref).id + cache.fetch(:"diverging_commit_counts_without_max_#{branch.name}") do + # Rugged seems to throw a `ReferenceError` when given branch_names rather + # than SHA-1 hashes + branch_sha = branch.dereferenced_target.sha + + number_commits_behind, number_commits_ahead = + raw_repository.diverging_commit_count( + @root_ref_hash, + branch_sha) + + { behind: number_commits_behind, ahead: number_commits_ahead } + end + end + def archive_metadata(ref, storage_path, format = "tar.gz", append_sha:, path: nil) raw_repository.archive_metadata( ref, @@ -1050,7 +1071,7 @@ class Repository # To support the full deprecated behaviour, set the # `rebase_commit_sha` for the merge_request here and return the value - merge_request.update(rebase_commit_sha: rebase_sha) + merge_request.update(rebase_commit_sha: rebase_sha, merge_error: nil) rebase_sha end @@ -1069,7 +1090,7 @@ class Repository remote_repository: merge_request.target_project.repository.raw, remote_branch: merge_request.target_branch ) do |commit_id| - merge_request.update!(rebase_commit_sha: commit_id) + merge_request.update!(rebase_commit_sha: commit_id, merge_error: nil) end end end diff --git a/app/services/issues/close_service.rb b/app/services/issues/close_service.rb index e5cc12e6082..2a19e57a94f 100644 --- a/app/services/issues/close_service.rb +++ b/app/services/issues/close_service.rb @@ -7,7 +7,7 @@ module Issues return issue unless can?(current_user, :update_issue, issue) close_issue(issue, - commit: commit, + closed_via: commit, notifications: notifications, system_note: system_note) end @@ -17,9 +17,9 @@ module Issues # # The code calling this method is responsible for ensuring that a user is # allowed to close the given issue. - def close_issue(issue, commit: nil, notifications: true, system_note: true) + def close_issue(issue, closed_via: nil, notifications: true, system_note: true) if project.jira_tracker? && project.jira_service.active && issue.is_a?(ExternalIssue) - project.jira_service.close_issue(commit, issue) + project.jira_service.close_issue(closed_via, issue) todo_service.close_issue(issue, current_user) return issue end @@ -27,8 +27,11 @@ module Issues if project.issues_enabled? && issue.close issue.update(closed_by: current_user) event_service.close_issue(issue, current_user) - create_note(issue, commit) if system_note - notification_service.async.close_issue(issue, current_user) if notifications + create_note(issue, closed_via) if system_note + + closed_via = "commit #{closed_via.id}" if closed_via.is_a?(Commit) + + notification_service.async.close_issue(issue, current_user, closed_via: closed_via) if notifications todo_service.close_issue(issue, current_user) execute_hooks(issue, 'close') invalidate_cache_counts(issue, users: issue.assignees) diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index 8d3b569498f..f797c0f11c6 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -89,8 +89,8 @@ class NotificationService # * project team members with notification level higher then Participating # * users with custom level checked with "close issue" # - def close_issue(issue, current_user) - close_resource_email(issue, current_user, :closed_issue_email) + def close_issue(issue, current_user, closed_via: nil) + close_resource_email(issue, current_user, :closed_issue_email, closed_via: closed_via) end # When we reassign an issue we should send an email to: @@ -504,7 +504,7 @@ class NotificationService end end - def close_resource_email(target, current_user, method, skip_current_user: true) + def close_resource_email(target, current_user, method, skip_current_user: true, closed_via: nil) action = method == :merged_merge_request_email ? "merge" : "close" recipients = NotificationRecipientService.build_recipients( @@ -515,7 +515,7 @@ class NotificationService ) recipients.each do |recipient| - mailer.send(method, recipient.user.id, target.id, current_user.id, recipient.reason).deliver_later + mailer.send(method, recipient.user.id, target.id, current_user.id, reason: recipient.reason, closed_via: closed_via).deliver_later end end diff --git a/app/views/admin/application_settings/_pages.html.haml b/app/views/admin/application_settings/_pages.html.haml index 64e01fa2d00..77795dbf913 100644 --- a/app/views/admin/application_settings/_pages.html.haml +++ b/app/views/admin/application_settings/_pages.html.haml @@ -30,8 +30,7 @@ .form-check = f.check_box :lets_encrypt_terms_of_service_accepted, class: 'form-check-input' = f.label :lets_encrypt_terms_of_service_accepted, class: 'form-check-label' do - // Terms of Service should actually be a link, but the best way to get the url is using API - // So it will be done in later MR - = _("I have read and agree to the Let's Encrypt Terms of Service") + - terms_of_service_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: lets_encrypt_terms_of_service_admin_application_settings_path } + = _("I have read and agree to the Let's Encrypt %{link_start}Terms of Service%{link_end}").html_safe % { link_start: terms_of_service_link_start, link_end: '</a>'.html_safe } = f.submit _('Save changes'), class: "btn btn-success" diff --git a/app/views/admin/projects/_projects.html.haml b/app/views/admin/projects/_projects.html.haml index 5bc695aa7b5..9117f63f939 100644 --- a/app/views/admin/projects/_projects.html.haml +++ b/app/views/admin/projects/_projects.html.haml @@ -13,7 +13,7 @@ .stats %span.badge.badge-pill - = storage_counter(project.statistics.storage_size) + = storage_counter(project.statistics&.storage_size) - if project.archived %span.badge.badge-warning archived .title diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml index f016a157daf..1e1ad9d5e19 100644 --- a/app/views/admin/projects/show.html.haml +++ b/app/views/admin/projects/show.html.haml @@ -74,10 +74,10 @@ %li %span.light= _('Storage:') - %strong= storage_counter(@project.statistics.storage_size) - ( - = storage_counters_details(@project.statistics) - ) + %strong= storage_counter(@project.statistics&.storage_size) + - if @project.statistics + = surround '(', ')' do + = storage_counters_details(@project.statistics) %li %span.light last commit: diff --git a/app/views/admin/users/_access_levels.html.haml b/app/views/admin/users/_access_levels.html.haml index 12e24ddef02..98b6bc7bc46 100644 --- a/app/views/admin/users/_access_levels.html.haml +++ b/app/views/admin/users/_access_levels.html.haml @@ -22,6 +22,8 @@ %p.light Regular users have access to their groups and projects + = render_if_exists 'admin/users/auditor_access_level_radio', f: f, disabled: editing_current_user + = f.radio_button :access_level, :admin, disabled: editing_current_user = label_tag :admin, class: 'font-weight-bold' do Admin diff --git a/app/views/clusters/platforms/kubernetes/_form.html.haml b/app/views/clusters/platforms/kubernetes/_form.html.haml index 8caa25a7b5e..c1727cf9079 100644 --- a/app/views/clusters/platforms/kubernetes/_form.html.haml +++ b/app/views/clusters/platforms/kubernetes/_form.html.haml @@ -1,7 +1,7 @@ = bootstrap_form_for cluster, url: update_cluster_url_path, html: { class: 'gl-show-field-errors' }, as: :cluster do |field| - copy_name_btn = clipboard_button(text: cluster.name, title: s_('ClusterIntegration|Copy Kubernetes cluster name'), - class: 'input-group-text btn-default') unless !cluster.read_only_kubernetes_platform_fields? + class: 'input-group-text btn-default') if cluster.read_only_kubernetes_platform_fields? = field.text_field :name, class: 'js-select-on-focus cluster-name', required: true, title: s_('ClusterIntegration|Cluster name is required.'), readonly: cluster.read_only_kubernetes_platform_fields?, @@ -10,7 +10,7 @@ = field.fields_for :platform_kubernetes, platform do |platform_field| - copy_api_url = clipboard_button(text: platform.api_url, title: s_('ClusterIntegration|Copy API URL'), - class: 'input-group-text btn-default') unless !cluster.read_only_kubernetes_platform_fields? + class: 'input-group-text btn-default') if cluster.read_only_kubernetes_platform_fields? = platform_field.text_field :api_url, class: 'js-select-on-focus', required: true, title: s_('ClusterIntegration|API URL should be a valid http/https url.'), readonly: cluster.read_only_kubernetes_platform_fields?, @@ -18,7 +18,7 @@ input_group_class: 'gl-field-error-anchor', append: copy_api_url - copy_ca_cert_btn = clipboard_button(text: platform.ca_cert, title: s_('ClusterIntegration|Copy CA Certificate'), - class: 'input-group-text btn-default') unless !cluster.read_only_kubernetes_platform_fields? + class: 'input-group-text btn-default') if cluster.read_only_kubernetes_platform_fields? = platform_field.text_area :ca_cert, class: 'js-select-on-focus', rows: '5', readonly: cluster.read_only_kubernetes_platform_fields?, placeholder: s_('ClusterIntegration|Certificate Authority bundle (PEM format)'), @@ -28,7 +28,7 @@ - show_token_btn = (platform_field.button s_('ClusterIntegration|Show'), type: 'button', class: 'js-show-cluster-token btn btn-default') - copy_token_btn = clipboard_button(text: platform.token, title: s_('ClusterIntegration|Copy Service Token'), - class: 'input-group-text btn-default') unless !cluster.read_only_kubernetes_platform_fields? + class: 'input-group-text btn-default') if cluster.read_only_kubernetes_platform_fields? = platform_field.text_field :token, type: 'password', class: 'js-select-on-focus js-cluster-token', required: true, title: s_('ClusterIntegration|Service token is required.'), diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml index 9c7ca6ebbd4..427db070253 100644 --- a/app/views/devise/shared/_signup_box.html.haml +++ b/app/views/devise/shared/_signup_box.html.haml @@ -29,6 +29,7 @@ - terms_link = link_to s_("I accept the|Terms of Service and Privacy Policy"), terms_path, target: "_blank" - accept_terms_label = _("I accept the %{terms_link}") % { terms_link: terms_link } = accept_terms_label.html_safe + = render_if_exists 'devise/shared/email_opted_in', f: f %div - if Gitlab::Recaptcha.enabled? = recaptcha_tags diff --git a/app/views/help/index.html.haml b/app/views/help/index.html.haml index 75e4dc46c9b..50933c7d434 100644 --- a/app/views/help/index.html.haml +++ b/app/views/help/index.html.haml @@ -5,8 +5,7 @@ %hr %h1 - GitLab - Community Edition + = default_brand_title - if user_signed_in? %span= link_to_version = version_status_badge diff --git a/app/views/help/ui.html.haml b/app/views/help/ui.html.haml index 969df69aafb..cdc894ee5a0 100644 --- a/app/views/help/ui.html.haml +++ b/app/views/help/ui.html.haml @@ -70,7 +70,7 @@ .cover-title John Smith - .cover-desc + .cover-desc.cgray = lorem .cover-controls diff --git a/app/views/notify/closed_issue_email.html.haml b/app/views/notify/closed_issue_email.html.haml index eb148d72da1..f21cf1ad34b 100644 --- a/app/views/notify/closed_issue_email.html.haml +++ b/app/views/notify/closed_issue_email.html.haml @@ -1,2 +1,2 @@ %p - Issue was closed by #{sanitize_name(@updated_by.name)} + Issue was closed by #{sanitize_name(@updated_by.name)} #{closure_reason_text(@closed_via, format: formats.first)}. diff --git a/app/views/notify/closed_issue_email.text.haml b/app/views/notify/closed_issue_email.text.haml index b1f0a3f37ec..5567adc9165 100644 --- a/app/views/notify/closed_issue_email.text.haml +++ b/app/views/notify/closed_issue_email.text.haml @@ -1,3 +1,3 @@ -Issue was closed by #{sanitize_name(@updated_by.name)} +Issue was closed by #{sanitize_name(@updated_by.name)} #{closure_reason_text(@closed_via, format: formats.first)}. Issue ##{@issue.iid}: #{project_issue_url(@issue.project, @issue)} diff --git a/app/views/projects/_flash_messages.html.haml b/app/views/projects/_flash_messages.html.haml index b72f0e39b23..b2dab0b5348 100644 --- a/app/views/projects/_flash_messages.html.haml +++ b/app/views/projects/_flash_messages.html.haml @@ -7,3 +7,4 @@ = render 'shared/no_password' - unless project.empty_repo? = render 'shared/auto_devops_implicitly_enabled_banner', project: project + = render_if_exists 'projects/above_size_limit_warning', project: project diff --git a/app/views/projects/mirrors/_mirror_repos.html.haml b/app/views/projects/mirrors/_mirror_repos.html.haml index 73e2a4ffb8b..e68fa5d08c7 100644 --- a/app/views/projects/mirrors/_mirror_repos.html.haml +++ b/app/views/projects/mirrors/_mirror_repos.html.haml @@ -29,7 +29,7 @@ .form-check.append-bottom-10 = check_box_tag :only_protected_branches, '1', false, class: 'js-mirror-protected form-check-input' = label_tag :only_protected_branches, _('Only mirror protected branches'), class: 'form-check-label' - = link_to icon('question-circle'), help_page_path('user/project/protected_branches') + = link_to icon('question-circle'), help_page_path('user/project/protected_branches'), target: '_blank' .panel-footer = f.submit _('Mirror repository'), class: 'btn btn-success js-mirror-submit qa-mirror-repository-button', name: :update_remote_mirror diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index 211e3eafac6..6dc61088e65 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -45,7 +45,7 @@ = emoji_icon(@user.status.emoji) = markdown_field(@user.status, :message) - .cover-desc.member-date + .cover-desc.member-date.cgray %p %span.middle-dot-divider @#{@user.username} @@ -53,7 +53,7 @@ %span.middle-dot-divider = s_('Member since %{date}') % { date: @user.created_at.to_date.to_s(:long) } - .cover-desc + .cover-desc.cgray - unless @user.public_email.blank? .profile-link-holder.middle-dot-divider = link_to @user.public_email, "mailto:#{@user.public_email}", class: 'text-link' @@ -82,7 +82,7 @@ = @user.organization - if @user.bio.present? - .cover-desc + .cover-desc.cgray %p.profile-user-bio = @user.bio diff --git a/app/workers/process_commit_worker.rb b/app/workers/process_commit_worker.rb index 29a7f8e691a..3efb5343a96 100644 --- a/app/workers/process_commit_worker.rb +++ b/app/workers/process_commit_worker.rb @@ -48,7 +48,7 @@ class ProcessCommitWorker # Issues::CloseService#execute. IssueCollection.new(issues).updatable_by_user(user).each do |issue| Issues::CloseService.new(project, author) - .close_issue(issue, commit: commit) + .close_issue(issue, closed_via: commit) end end diff --git a/changelogs/unreleased/19569-include-information-if-issue-was-closed-via-mr.yml b/changelogs/unreleased/19569-include-information-if-issue-was-closed-via-mr.yml new file mode 100644 index 00000000000..bb2fc9af2a1 --- /dev/null +++ b/changelogs/unreleased/19569-include-information-if-issue-was-closed-via-mr.yml @@ -0,0 +1,5 @@ +--- +title: Include information if issue was clossed via merge request or commit +merge_request: 15610 +author: Michał Zając +type: changed diff --git a/changelogs/unreleased/49915-fix-error-500-admin-projects-nil-storage.yml b/changelogs/unreleased/49915-fix-error-500-admin-projects-nil-storage.yml new file mode 100644 index 00000000000..307c2bfb49d --- /dev/null +++ b/changelogs/unreleased/49915-fix-error-500-admin-projects-nil-storage.yml @@ -0,0 +1,5 @@ +--- +title: Fix an error in projects admin when statistics are missing +merge_request: 28355 +author: +type: fixed diff --git a/changelogs/unreleased/61914-fix-emojis-urls.yml b/changelogs/unreleased/61914-fix-emojis-urls.yml new file mode 100644 index 00000000000..578edf4a063 --- /dev/null +++ b/changelogs/unreleased/61914-fix-emojis-urls.yml @@ -0,0 +1,5 @@ +--- +title: Fix emojis URLs +merge_request: 28371 +author: +type: fixed diff --git a/changelogs/unreleased/diff-whitespace-setting-changes.yml b/changelogs/unreleased/diff-whitespace-setting-changes.yml new file mode 100644 index 00000000000..640e9e589df --- /dev/null +++ b/changelogs/unreleased/diff-whitespace-setting-changes.yml @@ -0,0 +1,5 @@ +--- +title: Fixed show whitespace button not refetching diff content +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/fix-db-migrate-is-failed-on-mysql8.yml b/changelogs/unreleased/fix-db-migrate-is-failed-on-mysql8.yml new file mode 100644 index 00000000000..63f134808e3 --- /dev/null +++ b/changelogs/unreleased/fix-db-migrate-is-failed-on-mysql8.yml @@ -0,0 +1,5 @@ +--- +title: Fix. `db:migrate` is failed on MySQL 8 +merge_request: 28351 +author: sue445 +type: fixed diff --git a/changelogs/unreleased/jc-omit-count-diverging-commits-max.yml b/changelogs/unreleased/jc-omit-count-diverging-commits-max.yml new file mode 100644 index 00000000000..23235060a98 --- /dev/null +++ b/changelogs/unreleased/jc-omit-count-diverging-commits-max.yml @@ -0,0 +1,5 @@ +--- +title: Omit max-count for diverging_commit_counts behind feature flag +merge_request: 28157 +author: +type: other diff --git a/changelogs/unreleased/sh-fix-rebase-error-clearing.yml b/changelogs/unreleased/sh-fix-rebase-error-clearing.yml new file mode 100644 index 00000000000..4f5f2779e7f --- /dev/null +++ b/changelogs/unreleased/sh-fix-rebase-error-clearing.yml @@ -0,0 +1,5 @@ +--- +title: Properly clear the merge error upon rebase failure +merge_request: 28319 +author: +type: fixed diff --git a/config/routes/admin.rb b/config/routes/admin.rb index 90d7f4a04d4..bc19219a0b8 100644 --- a/config/routes/admin.rb +++ b/config/routes/admin.rb @@ -111,6 +111,7 @@ namespace :admin do put :reset_health_check_token put :clear_repository_check_states get :integrations, :repository, :templates, :ci_cd, :reporting, :metrics_and_profiling, :network, :geo, :preferences + get :lets_encrypt_terms_of_service end resources :labels diff --git a/db/migrate/20150509180749_convert_legacy_reference_notes.rb b/db/migrate/20150509180749_convert_legacy_reference_notes.rb index a44a908c2f5..84d4eb9e51f 100644 --- a/db/migrate/20150509180749_convert_legacy_reference_notes.rb +++ b/db/migrate/20150509180749_convert_legacy_reference_notes.rb @@ -7,7 +7,8 @@ # mentioned in 54f7727c850972f0401c1312a7c4a6a380de5666 class ConvertLegacyReferenceNotes < ActiveRecord::Migration[4.2] def up - execute %q{UPDATE notes SET note = trim(both '_' from note) WHERE system = true AND note LIKE '\_%\_'} + quoted_column_name = ActiveRecord::Base.connection.quote_column_name('system') + execute %Q{UPDATE notes SET note = trim(both '_' from note) WHERE #{quoted_column_name} = true AND note LIKE '\_%\_'} end def down diff --git a/db/migrate/20190515125613_add_application_settings_elasticsearch_shards.rb b/db/migrate/20190515125613_add_application_settings_elasticsearch_shards.rb new file mode 100644 index 00000000000..9cebc0f8db4 --- /dev/null +++ b/db/migrate/20190515125613_add_application_settings_elasticsearch_shards.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class AddApplicationSettingsElasticsearchShards < ActiveRecord::Migration[5.1] + DOWNTIME = false + + def change + add_column :application_settings, :elasticsearch_shards, :integer, null: false, default: 5 + add_column :application_settings, :elasticsearch_replicas, :integer, null: false, default: 1 + end +end diff --git a/db/schema.rb b/db/schema.rb index 159e7e03cf4..9d367938cec 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20190506135400) do +ActiveRecord::Schema.define(version: 20190515125613) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -189,6 +189,8 @@ ActiveRecord::Schema.define(version: 20190506135400) do t.string "encrypted_external_auth_client_key_pass_iv" t.string "lets_encrypt_notification_email" t.boolean "lets_encrypt_terms_of_service_accepted", default: false, null: false + t.integer "elasticsearch_shards", default: 5, null: false + t.integer "elasticsearch_replicas", default: 1, null: false t.index ["usage_stats_set_by_user_id"], name: "index_application_settings_on_usage_stats_set_by_user_id", using: :btree end diff --git a/doc/development/architecture.md b/doc/development/architecture.md index 13de1f55f23..56c5056fd6d 100644 --- a/doc/development/architecture.md +++ b/doc/development/architecture.md @@ -212,6 +212,8 @@ To serve repositories over SSH there's an add-on application called gitlab-shell ### Components +#### Component Diagram + ```mermaid graph TB @@ -280,15 +282,15 @@ graph TB ``` ---- - -**Legend**: +#### Component Legend * ✅ - Automatically configured * ⚙ - Requires additional configuration * ⤓ - Additional software/service required * ❌ - Not available +#### Component Table + | Component | Description | [Omnibus GitLab](https://docs.gitlab.com/omnibus/README.html) | [GitLab chart](https://docs.gitlab.com/charts/) | [Minikube Minimal](https://docs.gitlab.com/charts/development/minikube/#deploying-gitlab-with-minimal-settings) | [GitLab.com](https://gitlab.com) | CE/EE | | --------- | ----------- |:--------------------:|:------------------:|:-----:|:--------:|:--------:| | NGINX | Routes requests to appropriate components, terminates SSL | [✅](https://docs.gitlab.com/omnibus/settings/nginx.html) | [✅](https://docs.gitlab.com/charts/charts/nginx/index.html) | [⚙](https://docs.gitlab.com/charts/charts/nginx/index.html) | [✅](https://about.gitlab.com/handbook/engineering/infrastructure/production-architecture/#service-architecture) | CE & EE | @@ -326,6 +328,8 @@ graph TB | Jaeger: deployed apps | Distributed tracing for deployed apps | [⤓](https://docs.gitlab.com/ee/user/project/operations/tracing.html) | [⤓](https://docs.gitlab.com/ee/user/project/operations/tracing.html) | [⤓](https://docs.gitlab.com/ee/user/project/operations/tracing.html) | [⤓](https://docs.gitlab.com/ee/user/project/operations/tracing.html) | EE Only | | Kubernetes cluster apps | Deploy [Helm](https://docs.helm.sh/), [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/), [Cert-Manager](https://docs.cert-manager.io/en/latest/), [Prometheus](https://prometheus.io/docs/introduction/overview/), a [Runner](https://docs.gitlab.com/runner/), [JupyterHub](http://jupyter.org/), [Knative](https://cloud.google.com/knative) to a cluster | [✅](https://docs.gitlab.com/ee/user/project/clusters/#installing-applications) | [✅](https://docs.gitlab.com/ee/user/project/clusters/#installing-applications) | [✅](https://docs.gitlab.com/ee/user/project/clusters/#installing-applications) | [✅](https://docs.gitlab.com/ee/user/project/clusters/#installing-applications) | CE & EE | +#### Component Overview + A typical install of GitLab will be on GNU/Linux. It uses Nginx or Apache as a web front end to proxypass the Unicorn web server. By default, communication between Unicorn and the front end is via a Unix domain socket but forwarding requests via TCP is also supported. The web front end accesses `/home/git/gitlab/public` bypassing the Unicorn server to serve static pages, uploads (e.g. avatar images or attachments), and precompiled assets. GitLab serves web pages and a [GitLab API](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/api) using the Unicorn web server. It uses Sidekiq as a job queue which, in turn, uses redis as a non-persistent database backend for job information, meta data, and incoming jobs. We also support deploying GitLab on Kubernetes using our [gitlab Helm chart](https://docs.gitlab.com/charts/). diff --git a/doc/development/ee_features.md b/doc/development/ee_features.md index 9853b38b8e9..9db28bcddf8 100644 --- a/doc/development/ee_features.md +++ b/doc/development/ee_features.md @@ -446,6 +446,28 @@ The disadvantage of this: port `render_if_exists` to CE. - If we have typos in the partial name, it would be silently ignored. + +##### Caveats + +The `render_if_exists` view path argument must be relative to `app/views/` and `ee/app/views`. +Resolving an EE template path that is relative to the CE view path will not work. + +```haml +- # app/views/projects/index.html.haml + += render_if_exists 'button' # Will not render `ee/app/views/projects/_button` and will quietly fail += 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/doc/development/testing_guide/review_apps.md b/doc/development/testing_guide/review_apps.md index 7ad33f05f77..f34793c11f4 100644 --- a/doc/development/testing_guide/review_apps.md +++ b/doc/development/testing_guide/review_apps.md @@ -78,17 +78,27 @@ subgraph CNG-mirror pipeline **Additional notes:** -- The Kubernetes cluster is connected to the `gitlab-{ce,ee}` projects using - [GitLab's Kubernetes integration][gitlab-k8s-integration]. This basically - allows to have a link to the Review App directly from the merge request - widget. -- If the Review App deployment fails, you can simply retry it (there's no need - to run the [`review-stop`][gitlab-ci-yml] job first). +- If the `review-deploy` job keep failing (note that we already retry it twice), + please post a message in the `#quality` channel and/or create a ~Quality ~bug + issue with a link to your merge request. Note that the deployment failure can + reveal an actual problem introduced in your merge request (i.e. this isn't + necessarily a transient failure)! +- If the `review-qa-smoke` job keep failing (note that we already retry it twice), + please check the job's logs: you could discover an actual problem introduced in + your merge request. You can also download the artifacts to see screenshots of + the page at the time the failures occurred. If you don't find the cause of the + failure or if it seems unrelated to your change, please post a message in the + `#quality` channel and/or create a ~Quality ~bug issue with a link to your + merge request. - The manual [`review-stop`][gitlab-ci-yml] in the `test` stage can be used to stop a Review App manually, and is also started by GitLab once a merge request's branch is deleted after being merged. - Review Apps are cleaned up regularly via a pipeline schedule that runs the [`schedule:review-cleanup`][gitlab-ci-yml] job. +- The Kubernetes cluster is connected to the `gitlab-{ce,ee}` projects using + [GitLab's Kubernetes integration][gitlab-k8s-integration]. This basically + allows to have a link to the Review App directly from the merge request + widget. ## QA runs diff --git a/doc/install/ldap.md b/doc/install/ldap.md index a19f0342b65..d8d54864586 100644 --- a/doc/install/ldap.md +++ b/doc/install/ldap.md @@ -2,6 +2,4 @@ redirect_to: '../administration/auth/ldap.md' --- -# GitLab LDAP integration - -This document was moved under [`administration/auth/ldap`](../administration/auth/ldap.md). +This document was moved to [another location](../administration/auth/ldap.md). diff --git a/doc/integration/elasticsearch.md b/doc/integration/elasticsearch.md index b03bb6d98e8..d1d12dfd064 100644 --- a/doc/integration/elasticsearch.md +++ b/doc/integration/elasticsearch.md @@ -131,6 +131,8 @@ The following Elasticsearch settings are available: | `Use the new repository indexer (beta)` | Perform repository indexing using [GitLab Elasticsearch Indexer](https://gitlab.com/gitlab-org/gitlab-elasticsearch-indexer). | | `Search with Elasticsearch enabled` | Enables/disables using Elasticsearch in search. | | `URL` | The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., "http://host1, https://host2:9200"). If your Elasticsearch instance is password protected, pass the `username:password` in the URL (e.g., `http://<username>:<password>@<elastic_host>:9200/`). | +| `Number of Elasticsearch shards` | Elasticsearch indexes are split into multiple shards for performance reasons. In general, larger indexes need to have more shards. Changes to this value do not take effect until the index is recreated. You can read more about tradeoffs in the [Elasticsearch documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-create-index.html#create-index-settings) | +| `Number of Elasticsearch replicas` | Each Elasticsearch shard can have a number of replicas. These are a complete copy of the shard, and can provide increased query performance or resilience against hardware failure. Increasing this value will greatly increase total disk space required by the index. | | `Limit namespaces and projects that can be indexed` | Enabling this will allow you to select namespaces and projects to index. All other namespaces and projects will use database search instead. Please note that if you enable this option but do not select any namespaces or projects, none will be indexed. [Read more below](#limiting-namespaces-and-projects). | `Using AWS hosted Elasticsearch with IAM credentials` | Sign your Elasticsearch requests using [AWS IAM authorization](http://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html) or [AWS EC2 Instance Profile Credentials](http://docs.aws.amazon.com/codedeploy/latest/userguide/getting-started-create-iam-instance-profile.html#getting-started-create-iam-instance-profile-cli). The policies must be configured to allow `es:*` actions. | | `AWS Region` | The AWS region your Elasticsearch service is located in. | diff --git a/doc/license/README.md b/doc/license/README.md index 4cc387ba95f..b9281fd5299 100644 --- a/doc/license/README.md +++ b/doc/license/README.md @@ -2,4 +2,4 @@ redirect_to: 'https://docs.gitlab.com/ee/user/admin_area/license.html' --- -This document was moved to [user/admin_area/license](https://docs.gitlab.com/ee/user/admin_area/license.html). +This document was moved to [another location](https://docs.gitlab.com/ee/user/admin_area/license.html). diff --git a/doc/topics/authentication/index.md b/doc/topics/authentication/index.md index 0531627b7b3..00b6c1dfdc2 100644 --- a/doc/topics/authentication/index.md +++ b/doc/topics/authentication/index.md @@ -17,11 +17,11 @@ This page gathers all the resources for the topic **Authentication** within GitL ## GitLab administrators - [LDAP (Community Edition)](../../administration/auth/ldap.md) -- [LDAP (Enterprise Edition)](https://docs.gitlab.com/ee/administration/auth/ldap-ee.html) +- [LDAP (Enterprise Edition)](https://docs.gitlab.com/ee/administration/auth/ldap-ee.html) **[STARTER]** - [Enforce Two-factor Authentication (2FA)](../../security/two_factor_authentication.md#enforce-two-factor-authentication-2fa) - **Articles:** - [How to Configure LDAP with GitLab CE](../../administration/auth/how_to_configure_ldap_gitlab_ce/index.md) - - [How to Configure LDAP with GitLab EE](https://docs.gitlab.com/ee/administration/auth/how_to_configure_ldap_gitlab_ee/index.html) + - [How to Configure LDAP with GitLab EE](https://docs.gitlab.com/ee/administration/auth/how_to_configure_ldap_gitlab_ee/index.html) **[STARTER]** - [Feature Highlight: LDAP Integration](https://about.gitlab.com/2014/07/10/feature-highlight-ldap-sync/) - [Debugging LDAP](https://about.gitlab.com/handbook/support/workflows/support-engineering/ldap/debugging_ldap.html) - **Integrations:** @@ -30,10 +30,10 @@ This page gathers all the resources for the topic **Authentication** within GitL - [Atlassian Crowd OmniAuth Provider](../../administration/auth/crowd.md) - [CAS OmniAuth Provider](../../integration/cas.md) - [SAML OmniAuth Provider](../../integration/saml.md) - - [SAML for GitLab.com Groups](https://docs.gitlab.com/ee/user/group/saml_sso/index.html) - - [SCIM user provisioning for GitLab.com Groups](https://docs.gitlab.com/ee/user/group/saml_sso/scim_setup.html) + - [SAML for GitLab.com Groups](https://docs.gitlab.com/ee/user/group/saml_sso/index.html) **[SILVER ONLY]** + - [SCIM user provisioning for GitLab.com Groups](https://docs.gitlab.com/ee/user/group/saml_sso/scim_setup.html) **[SILVER ONLY]** - [Okta SSO provider](../../administration/auth/okta.md) - - [Kerberos integration (GitLab EE)](https://docs.gitlab.com/ee/integration/kerberos.html) + - [Kerberos integration (GitLab EE)](https://docs.gitlab.com/ee/integration/kerberos.html) **[STARTER]** ## API diff --git a/doc/user/admin_area/settings/continuous_integration.md b/doc/user/admin_area/settings/continuous_integration.md index 42fc4efa4ce..9dd476656ed 100644 --- a/doc/user/admin_area/settings/continuous_integration.md +++ b/doc/user/admin_area/settings/continuous_integration.md @@ -118,7 +118,15 @@ will be synced to your Group and you can visualize it from the ![Additional minutes](img/additional_minutes.png) -NOTE: **Important note**: If you have some minutes used over your default quota, these minutes will +Be aware that: + +1. If you have purchased extra CI minutes before the purchase of a paid plan, +we will calculate a pro-rated charge for your paid plan. That means you may +be charged for less than one year since your subscription was previously +created with the extra CI minutes. +1. Once the extra CI minutes has been assigned to a Group they cannot be transferred +to a different Group. +1. If you have some minutes used over your default quota, these minutes will be deducted from your Additional Minutes quota immediately after your purchase of additional minutes. diff --git a/doc/user/application_security/container_scanning/index.md b/doc/user/application_security/container_scanning/index.md index ce86ade3c1a..5c635b09503 100644 --- a/doc/user/application_security/container_scanning/index.md +++ b/doc/user/application_security/container_scanning/index.md @@ -122,12 +122,13 @@ container_scanning: ## https://docs.gitlab.com/ee/ci/variables/#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 allow_failure: true services: - docker:stable-dind script: - docker run -d --name db arminc/clair-db:latest - - docker run -p 6060:6060 --link db:postgres -d --name clair --restart on-failure arminc/clair-local-scan:v2.0.6 + - docker run -p 6060:6060 --link db:postgres -d --name clair --restart on-failure arminc/clair-local-scan:${CLAIR_LOCAL_SCAN_VERSION} - apk add -U wget ca-certificates - docker pull ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG} - wget https://github.com/arminc/clair-scanner/releases/download/v8/clair-scanner_linux_amd64 @@ -164,12 +165,13 @@ container_scanning: ## https://docs.gitlab.com/ee/ci/variables/#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 allow_failure: true services: - docker:stable-dind script: - docker run -d --name db arminc/clair-db:latest - - docker run -p 6060:6060 --link db:postgres -d --name clair --restart on-failure arminc/clair-local-scan:v2.0.6 + - docker run -p 6060:6060 --link db:postgres -d --name clair --restart on-failure arminc/clair-local-scan:${CLAIR_LOCAL_SCAN_VERSION} - apk add -U wget ca-certificates - docker pull ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG} - wget https://github.com/arminc/clair-scanner/releases/download/v8/clair-scanner_linux_amd64 diff --git a/doc/user/markdown.md b/doc/user/markdown.md index c4f9fe45211..5dad9621802 100644 --- a/doc/user/markdown.md +++ b/doc/user/markdown.md @@ -286,15 +286,15 @@ On Linux, you can download [Noto Color Emoji](https://www.google.com/get/noto/he Ubuntu 18.04 (like many modern Linux distros) has this font installed by default. ``` -Sometimes you want to <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/monkey.png" width="20px" height="20px" style="display:inline;margin:0"> around a bit and add some <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/star2.png" width="20px" height="20px" style="display:inline;margin:0"> to your <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/speech_balloon.png" width="20px" height="20px" style="display:inline;margin:0">. Well we have a gift for you: +Sometimes you want to <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/monkey.png" width="20px" height="20px" style="display:inline;margin:0"> around a bit and add some <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/star2.png" width="20px" height="20px" style="display:inline;margin:0"> to your <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/speech_balloon.png" width="20px" height="20px" style="display:inline;margin:0">. Well we have a gift for you: -<img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/zap.png" width="20px" height="20px" style="display:inline;margin:0">You can use emoji anywhere GFM is supported. <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/v.png" width="20px" height="20px" style="display:inline;margin:0"> +<img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/zap.png" width="20px" height="20px" style="display:inline;margin:0">You can use emoji anywhere GFM is supported. <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/v.png" width="20px" height="20px" style="display:inline;margin:0"> -You can use it to point out a <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/bug.png" width="20px" height="20px" style="display:inline;margin:0"> or warn about <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/speak_no_evil.png" width="20px" height="20px" style="display:inline;margin:0"> patches. And if someone improves your really <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/snail.png" width="20px" height="20px" style="display:inline;margin:0"> code, send them some <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/birthday.png" width="20px" height="20px" style="display:inline;margin:0">. People will <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/heart.png" width="20px" height="20px" style="display:inline;margin:0"> you for that. +You can use it to point out a <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/bug.png" width="20px" height="20px" style="display:inline;margin:0"> or warn about <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/speak_no_evil.png" width="20px" height="20px" style="display:inline;margin:0"> patches. And if someone improves your really <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/snail.png" width="20px" height="20px" style="display:inline;margin:0"> code, send them some <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/birthday.png" width="20px" height="20px" style="display:inline;margin:0">. People will <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/heart.png" width="20px" height="20px" style="display:inline;margin:0"> you for that. -If you are new to this, don't be <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/fearful.png" width="20px" height="20px" style="display:inline;margin:0">. You can easily join the emoji <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/family.png" width="20px" height="20px" style="display:inline;margin:0">. All you need to do is to look up one of the supported codes. +If you are new to this, don't be <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/fearful.png" width="20px" height="20px" style="display:inline;margin:0">. You can easily join the emoji <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/family.png" width="20px" height="20px" style="display:inline;margin:0">. All you need to do is to look up one of the supported codes. -Consult the [Emoji Cheat Sheet](https://www.webfx.com/tools/emoji-cheat-sheet/) for a list of all supported emoji codes. <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/thumbsup.png" width="20px" height="20px" style="display:inline;margin:0"> +Consult the [Emoji Cheat Sheet](https://www.webfx.com/tools/emoji-cheat-sheet/) for a list of all supported emoji codes. <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/thumbsup.png" width="20px" height="20px" style="display:inline;margin:0"> Most emoji are natively supported on macOS, Windows, iOS, Android and will fallback to image-based emoji where there is lack of support. diff --git a/doc/user/project/merge_requests/img/container_scanning.png b/doc/user/project/merge_requests/img/container_scanning.png Binary files differdeleted file mode 100644 index e47f62acd9d..00000000000 --- a/doc/user/project/merge_requests/img/container_scanning.png +++ /dev/null diff --git a/doc/user/project/merge_requests/img/create-issue-with-list-hover.png b/doc/user/project/merge_requests/img/create-issue-with-list-hover.png Binary files differdeleted file mode 100644 index 7d70e8299f5..00000000000 --- a/doc/user/project/merge_requests/img/create-issue-with-list-hover.png +++ /dev/null diff --git a/doc/user/project/merge_requests/img/dast_all.png b/doc/user/project/merge_requests/img/dast_all.png Binary files differdeleted file mode 100644 index b6edc928dc3..00000000000 --- a/doc/user/project/merge_requests/img/dast_all.png +++ /dev/null diff --git a/doc/user/project/merge_requests/img/dast_single.png b/doc/user/project/merge_requests/img/dast_single.png Binary files differdeleted file mode 100644 index 26ca4bde786..00000000000 --- a/doc/user/project/merge_requests/img/dast_single.png +++ /dev/null diff --git a/doc/user/project/merge_requests/img/dependency_scanning.png b/doc/user/project/merge_requests/img/dependency_scanning.png Binary files differdeleted file mode 100644 index 18df356f846..00000000000 --- a/doc/user/project/merge_requests/img/dependency_scanning.png +++ /dev/null diff --git a/doc/user/project/merge_requests/img/interactive_reports.png b/doc/user/project/merge_requests/img/interactive_reports.png Binary files differdeleted file mode 100644 index 9f9812dc69d..00000000000 --- a/doc/user/project/merge_requests/img/interactive_reports.png +++ /dev/null diff --git a/doc/user/project/merge_requests/img/license_management.png b/doc/user/project/merge_requests/img/license_management.png Binary files differdeleted file mode 100644 index cdce6b5fe38..00000000000 --- a/doc/user/project/merge_requests/img/license_management.png +++ /dev/null diff --git a/doc/user/project/merge_requests/img/license_management_decision.png b/doc/user/project/merge_requests/img/license_management_decision.png Binary files differdeleted file mode 100644 index 0763130c375..00000000000 --- a/doc/user/project/merge_requests/img/license_management_decision.png +++ /dev/null diff --git a/doc/user/project/merge_requests/img/license_management_pipeline_tab.png b/doc/user/project/merge_requests/img/license_management_pipeline_tab.png Binary files differdeleted file mode 100644 index 80ffca815b9..00000000000 --- a/doc/user/project/merge_requests/img/license_management_pipeline_tab.png +++ /dev/null diff --git a/doc/user/project/merge_requests/img/license_management_settings.png b/doc/user/project/merge_requests/img/license_management_settings.png Binary files differdeleted file mode 100644 index b5490e59074..00000000000 --- a/doc/user/project/merge_requests/img/license_management_settings.png +++ /dev/null diff --git a/doc/user/project/merge_requests/img/revert_changes_commit.png b/doc/user/project/merge_requests/img/revert_changes_commit.png Binary files differdeleted file mode 100644 index c9dd0019024..00000000000 --- a/doc/user/project/merge_requests/img/revert_changes_commit.png +++ /dev/null diff --git a/doc/user/project/merge_requests/img/revert_changes_mr.png b/doc/user/project/merge_requests/img/revert_changes_mr.png Binary files differdeleted file mode 100644 index 06b841b3002..00000000000 --- a/doc/user/project/merge_requests/img/revert_changes_mr.png +++ /dev/null diff --git a/doc/user/project/merge_requests/img/sast.png b/doc/user/project/merge_requests/img/sast.png Binary files differdeleted file mode 100644 index 2c75592c32a..00000000000 --- a/doc/user/project/merge_requests/img/sast.png +++ /dev/null diff --git a/doc/user/project/merge_requests/img/security_report.png b/doc/user/project/merge_requests/img/security_report.png Binary files differdeleted file mode 100644 index ba41b707238..00000000000 --- a/doc/user/project/merge_requests/img/security_report.png +++ /dev/null diff --git a/doc/user/project/merge_requests/img/vulnerability_solution.png b/doc/user/project/merge_requests/img/vulnerability_solution.png Binary files differdeleted file mode 100644 index 7443b9b6eea..00000000000 --- a/doc/user/project/merge_requests/img/vulnerability_solution.png +++ /dev/null diff --git a/doc/user/project/merge_requests/merge_request_approvals.md b/doc/user/project/merge_requests/merge_request_approvals.md index 265871a7b4b..ea86633d9e2 100644 --- a/doc/user/project/merge_requests/merge_request_approvals.md +++ b/doc/user/project/merge_requests/merge_request_approvals.md @@ -294,6 +294,18 @@ enabling [**Prevent approval of merge requests by their committers**](#prevent-a 1. Tick the checkbox **Prevent approval of merge requests by their committers**. 1. Click **Save changes**. +## Require authentication when approving a merge request **[STARTER]** + +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/5981) in [GitLab Starter](https://about.gitlab.com/pricing/) 12.0. + +You can force the approver to enter a password in order to authenticate who is approving the merge request by +enabling **Require user password to approve**. This enables an Electronic Signature +for approvals such as the one defined by [CFR Part 11](https://www.accessdata.fda.gov/scripts/cdrh/cfdocs/cfcfr/CFRSearch.cfm?CFRPart=11&showFR=1&subpartNode=21:1.0.1.1.8.3)): + +1. Navigate to your project's **Settings > General** and expand **Merge request approvals**. +1. Tick the checkbox **Require user password to approve**. +1. Click **Save changes**. + ## Merge requests with different source branch and target branch projects If the merge request source branch and target branch belong to different diff --git a/lib/gitlab/danger/helper.rb b/lib/gitlab/danger/helper.rb index 3ef19d801b7..f0ca397609d 100644 --- a/lib/gitlab/danger/helper.rb +++ b/lib/gitlab/danger/helper.rb @@ -121,6 +121,8 @@ module Gitlab \.prettierrc | \.scss-lint.yml | \.stylelintrc | + \.haml-lint.yml | + \.haml-lint_todo.yml | babel\.config\.js | jest\.config\.js | karma\.config\.js | diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index fc9bcbdcca2..455588f3c66 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -500,7 +500,7 @@ module Gitlab end # Return total diverging commits count - def diverging_commit_count(from, to, max_count:) + def diverging_commit_count(from, to, max_count: 0) wrapped_gitaly_errors do gitaly_commit_client.diverging_commit_count(from, to, max_count: max_count) end diff --git a/lib/gitlab/lets_encrypt/challenge.rb b/lib/gitlab/lets_encrypt/challenge.rb new file mode 100644 index 00000000000..6a7f5e965c5 --- /dev/null +++ b/lib/gitlab/lets_encrypt/challenge.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module Gitlab + module LetsEncrypt + class Challenge + def initialize(acme_challenge) + @acme_challenge = acme_challenge + end + + delegate :url, :token, :file_content, :status, :request_validation, to: :acme_challenge + + private + + attr_reader :acme_challenge + end + end +end diff --git a/lib/gitlab/lets_encrypt/client.rb b/lib/gitlab/lets_encrypt/client.rb new file mode 100644 index 00000000000..d7468b06767 --- /dev/null +++ b/lib/gitlab/lets_encrypt/client.rb @@ -0,0 +1,74 @@ +# frozen_string_literal: true + +module Gitlab + module LetsEncrypt + class Client + PRODUCTION_DIRECTORY_URL = 'https://acme-v02.api.letsencrypt.org/directory' + STAGING_DIRECTORY_URL = 'https://acme-staging-v02.api.letsencrypt.org/directory' + + def new_order(domain_name) + ensure_account + + acme_order = acme_client.new_order(identifiers: [domain_name]) + + ::Gitlab::LetsEncrypt::Order.new(acme_order) + end + + def load_order(url) + ensure_account + + # rubocop: disable CodeReuse/ActiveRecord + ::Gitlab::LetsEncrypt::Order.new(acme_client.order(url: url)) + # rubocop: enable CodeReuse/ActiveRecord + end + + def load_challenge(url) + ensure_account + + ::Gitlab::LetsEncrypt::Challenge.new(acme_client.challenge(url: url)) + end + + def terms_of_service_url + acme_client.terms_of_service + end + + def enabled? + return false unless Feature.enabled?(:pages_auto_ssl) + + Gitlab::CurrentSettings.lets_encrypt_terms_of_service_accepted + end + + private + + def acme_client + @acme_client ||= ::Acme::Client.new(private_key: private_key, directory: acme_api_directory_url) + end + + def private_key + @private_key ||= OpenSSL::PKey.read(Gitlab::Application.secrets.lets_encrypt_private_key) + end + + def admin_email + Gitlab::CurrentSettings.lets_encrypt_notification_email + end + + def contact + "mailto:#{admin_email}" + end + + def ensure_account + raise 'Acme integration is disabled' unless enabled? + + @acme_account ||= acme_client.new_account(contact: contact, terms_of_service_agreed: true) + end + + def acme_api_directory_url + if Rails.env.production? + PRODUCTION_DIRECTORY_URL + else + STAGING_DIRECTORY_URL + end + end + end + end +end diff --git a/lib/gitlab/lets_encrypt/order.rb b/lib/gitlab/lets_encrypt/order.rb new file mode 100644 index 00000000000..5109b5e9843 --- /dev/null +++ b/lib/gitlab/lets_encrypt/order.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module Gitlab + module LetsEncrypt + class Order + def initialize(acme_order) + @acme_order = acme_order + end + + def new_challenge + authorization = @acme_order.authorizations.first + challenge = authorization.http + ::Gitlab::LetsEncrypt::Challenge.new(challenge) + end + + delegate :url, :status, to: :acme_order + + private + + attr_reader :acme_order + end + end +end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index f13c3023bab..5800aa9604b 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -3620,6 +3620,12 @@ msgstr "" msgid "EmailError|Your account has been blocked. If you believe this is in error, contact a staff member." msgstr "" +msgid "EmailToken|reset it" +msgstr "" + +msgid "EmailToken|resetting..." +msgstr "" + msgid "Emails" msgstr "" @@ -4217,6 +4223,9 @@ msgstr "" msgid "Failed to load errors from Sentry. Error message: %{errorMessage}" msgstr "" +msgid "Failed to load related branches" +msgstr "" + msgid "Failed to promote label due to internal error. Please contact administrators." msgstr "" @@ -4896,7 +4905,7 @@ msgstr "" msgid "I accept the|Terms of Service and Privacy Policy" msgstr "" -msgid "I have read and agree to the Let's Encrypt Terms of Service" +msgid "I have read and agree to the Let's Encrypt %{link_start}Terms of Service%{link_end}" msgstr "" msgid "ID" @@ -5241,6 +5250,9 @@ msgstr "" msgid "Issue events" msgstr "" +msgid "Issue update failed" +msgstr "" + msgid "IssueBoards|Board" msgstr "" @@ -5923,7 +5935,7 @@ msgstr "" msgid "MergeRequest|Error loading full diff. Please try again." msgstr "" -msgid "MergeRequest|Filter files" +msgid "MergeRequest|Filter files or search with %{modifier_key}+p" msgstr "" msgid "MergeRequest|No files found" @@ -7268,9 +7280,6 @@ msgstr "" msgid "Profiles|The maximum file size allowed is 200KB." msgstr "" -msgid "Profiles|There was an error with the reCAPTCHA. Please solve the reCAPTCHA again." -msgstr "" - msgid "Profiles|This doesn't look like a public SSH key, are you sure you want to add it?" msgstr "" @@ -9134,6 +9143,9 @@ msgstr "" msgid "Storage:" msgstr "" +msgid "StorageSize|Unknown" +msgstr "" + msgid "Subgroups" msgstr "" @@ -10340,6 +10352,9 @@ msgstr "" msgid "Unable to schedule a pipeline to run immediately" msgstr "" +msgid "Unable to update this issue at this time." +msgstr "" + msgid "Unarchive project" msgstr "" diff --git a/spec/controllers/registrations_controller_spec.rb b/spec/controllers/registrations_controller_spec.rb index 088c515c3a6..9a598790ff2 100644 --- a/spec/controllers/registrations_controller_spec.rb +++ b/spec/controllers/registrations_controller_spec.rb @@ -46,13 +46,17 @@ describe RegistrationsController do end context 'when reCAPTCHA is enabled' do + def fail_recaptcha + # Without this, `verify_recaptcha` arbitrarily returns true in test env + Recaptcha.configuration.skip_verify_env.delete('test') + end + before do stub_application_setting(recaptcha_enabled: true) end it 'displays an error when the reCAPTCHA is not solved' do - # Without this, `verify_recaptcha` arbitrarily returns true in test env - Recaptcha.configuration.skip_verify_env.delete('test') + fail_recaptcha post(:create, params: user_params) @@ -70,6 +74,17 @@ describe RegistrationsController do expect(flash[:notice]).to include 'Welcome! You have signed up successfully.' end + + it 'does not require reCAPTCHA if disabled by feature flag' do + stub_feature_flags(registrations_recaptcha: false) + fail_recaptcha + + post(:create, params: user_params) + + expect(controller).not_to receive(:verify_recaptcha) + expect(flash[:alert]).to be_nil + expect(flash[:notice]).to include 'Welcome! You have signed up successfully.' + end end context 'when terms are enforced' do diff --git a/spec/features/admin/admin_sees_project_statistics_spec.rb b/spec/features/admin/admin_sees_project_statistics_spec.rb new file mode 100644 index 00000000000..95d1fc5b57a --- /dev/null +++ b/spec/features/admin/admin_sees_project_statistics_spec.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe "Admin > Admin sees project statistics" do + let(:current_user) { create(:admin) } + + before do + sign_in(current_user) + + visit admin_project_path(project) + end + + context 'when project has statistics' do + let(:project) { create(:project, :repository) } + + it "shows project statistics" do + expect(page).to have_content("Storage: 0 Bytes (0 Bytes repositories, 0 Bytes build artifacts, 0 Bytes LFS)") + end + end + + context 'when project has no statistics' do + let(:project) { create(:project, :repository) { |project| project.statistics.destroy } } + + it "shows 'Storage: Unknown'" do + expect(page).to have_content("Storage: Unknown") + end + end +end diff --git a/spec/features/admin/admin_sees_projects_statistics_spec.rb b/spec/features/admin/admin_sees_projects_statistics_spec.rb new file mode 100644 index 00000000000..6a6f369ac7c --- /dev/null +++ b/spec/features/admin/admin_sees_projects_statistics_spec.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe "Admin > Admin sees projects statistics" do + let(:current_user) { create(:admin) } + + before do + create(:project, :repository) + create(:project, :repository) { |project| project.statistics.destroy } + + sign_in(current_user) + + visit admin_projects_path + end + + it "shows project statistics for projects that have them" do + expect(page.all('.stats').map(&:text)).to contain_exactly("0 Bytes", "Unknown") + end +end diff --git a/spec/frontend/helpers/timeout.js b/spec/frontend/helpers/timeout.js index 318593a48a4..b30b7f1ce1e 100644 --- a/spec/frontend/helpers/timeout.js +++ b/spec/frontend/helpers/timeout.js @@ -1,24 +1,31 @@ -let testTimeoutInMs; +const NS_PER_SEC = 1e9; +const NS_PER_MS = 1e6; -export const setTestTimeout = newTimeoutInMs => { - testTimeoutInMs = newTimeoutInMs; - jest.setTimeout(newTimeoutInMs); +let testTimeoutNS; + +export const setTestTimeout = newTimeoutMS => { + testTimeoutNS = newTimeoutMS * NS_PER_MS; + jest.setTimeout(newTimeoutMS); }; -export const initializeTestTimeout = defaultTimeoutInMs => { - setTestTimeout(defaultTimeoutInMs); +export const initializeTestTimeout = defaultTimeoutMS => { + setTestTimeout(defaultTimeoutMS); let testStartTime; // https://github.com/facebook/jest/issues/6947 beforeEach(() => { - testStartTime = Date.now(); + testStartTime = process.hrtime(); }); afterEach(() => { - const elapsedTimeInMs = Date.now() - testStartTime; - if (elapsedTimeInMs > testTimeoutInMs) { - throw new Error(`Test took too long (${elapsedTimeInMs}ms > ${testTimeoutInMs}ms)!`); + const [seconds, remainingNs] = process.hrtime(testStartTime); + const elapsedNS = seconds * NS_PER_SEC + remainingNs; + + if (elapsedNS > testTimeoutNS) { + throw new Error( + `Test took too long (${elapsedNS / NS_PER_MS}ms > ${testTimeoutNS / NS_PER_MS}ms)!`, + ); } }); }; diff --git a/spec/helpers/emails_helper_spec.rb b/spec/helpers/emails_helper_spec.rb index 03b4c19ec22..0434af25866 100644 --- a/spec/helpers/emails_helper_spec.rb +++ b/spec/helpers/emails_helper_spec.rb @@ -1,6 +1,45 @@ require 'spec_helper' describe EmailsHelper do + describe 'closure_reason_text' do + context 'when given a MergeRequest' do + let(:merge_request) { create(:merge_request) } + let(:merge_request_presenter) { merge_request.present } + + context "and format is text" do + it "returns plain text" do + expect(closure_reason_text(merge_request, format: :text)).to eq(" via merge request #{merge_request.to_reference} (#{merge_request_presenter.web_url})") + end + end + + context "and format is HTML" do + it "returns HTML" do + expect(closure_reason_text(merge_request, format: :html)).to eq(" via merge request #{link_to(merge_request.to_reference, merge_request_presenter.web_url)}") + end + end + + context "and format is unknown" do + it "returns plain text" do + expect(closure_reason_text(merge_request, format: :text)).to eq(" via merge request #{merge_request.to_reference} (#{merge_request_presenter.web_url})") + end + end + end + + context 'when given a String' do + let(:closed_via) { "5a0eb6fd7e0f133044378c662fcbbc0d0c16dbfa" } + + it "returns plain text" do + expect(closure_reason_text(closed_via)).to eq(" via #{closed_via}") + end + end + + context 'when not given anything' do + it "returns empty string" do + expect(closure_reason_text(nil)).to eq("") + end + end + end + describe 'sanitize_name' do context 'when name contains a valid URL string' do it 'returns name with `.` replaced with `_` to prevent mail clients from auto-linking URLs' do diff --git a/spec/javascripts/diffs/store/actions_spec.js b/spec/javascripts/diffs/store/actions_spec.js index c82dcadd2f1..6309a8823d7 100644 --- a/spec/javascripts/diffs/store/actions_spec.js +++ b/spec/javascripts/diffs/store/actions_spec.js @@ -82,7 +82,7 @@ describe('DiffsStoreActions', () => { describe('fetchDiffFiles', () => { it('should fetch diff files', done => { - const endpoint = '/fetch/diff/files'; + const endpoint = '/fetch/diff/files?w=1'; const mock = new MockAdapter(axios); const res = { diff_files: 1, merge_request_diffs: [] }; mock.onGet(endpoint).reply(200, res); @@ -828,6 +828,10 @@ describe('DiffsStoreActions', () => { }); describe('setShowWhitespace', () => { + beforeEach(() => { + spyOn(eventHub, '$emit').and.stub(); + }); + it('commits SET_SHOW_WHITESPACE', done => { testAction( setShowWhitespace, @@ -855,6 +859,30 @@ describe('DiffsStoreActions', () => { expect(window.history.pushState).toHaveBeenCalled(); }); + + it('calls history pushState with merged params', () => { + const originalPushState = window.history; + + originalPushState.pushState({}, '', '?test=1'); + + spyOn(localStorage, 'setItem').and.stub(); + spyOn(window.history, 'pushState').and.stub(); + + setShowWhitespace({ commit() {} }, { showWhitespace: true, pushState: true }); + + expect(window.history.pushState.calls.mostRecent().args[2]).toMatch(/(.*)\?test=1&w=0/); + + originalPushState.pushState({}, '', '?'); + }); + + it('emits eventHub event', () => { + spyOn(localStorage, 'setItem').and.stub(); + spyOn(window.history, 'pushState').and.stub(); + + setShowWhitespace({ commit() {} }, { showWhitespace: true, pushState: true }); + + expect(eventHub.$emit).toHaveBeenCalledWith('refetchDiffData'); + }); }); describe('setRenderIt', () => { diff --git a/spec/lib/gitlab/lets_encrypt/challenge_spec.rb b/spec/lib/gitlab/lets_encrypt/challenge_spec.rb new file mode 100644 index 00000000000..74622f356de --- /dev/null +++ b/spec/lib/gitlab/lets_encrypt/challenge_spec.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe ::Gitlab::LetsEncrypt::Challenge do + delegated_methods = { + url: 'https://example.com/', + status: 'pending', + token: 'tokenvalue', + file_content: 'hereisfilecontent', + request_validation: true + } + + let(:acme_challenge) do + acme_challenge = instance_double('Acme::Client::Resources::Challenge') + allow(acme_challenge).to receive_messages(delegated_methods) + acme_challenge + end + + let(:challenge) { described_class.new(acme_challenge) } + + delegated_methods.each do |method, value| + describe "##{method}" do + it 'delegates to Acme::Client::Resources::Challenge' do + expect(challenge.public_send(method)).to eq(value) + end + end + end +end diff --git a/spec/lib/gitlab/lets_encrypt/client_spec.rb b/spec/lib/gitlab/lets_encrypt/client_spec.rb new file mode 100644 index 00000000000..16a16acfd25 --- /dev/null +++ b/spec/lib/gitlab/lets_encrypt/client_spec.rb @@ -0,0 +1,120 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe ::Gitlab::LetsEncrypt::Client do + include LetsEncryptHelpers + + 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 + ) + end + + let!(:stub_client) { stub_lets_encrypt_client } + + shared_examples 'ensures account registration' do + it 'ensures account registration' do + subject + + expect(stub_client).to have_received(:new_account).with( + contact: 'mailto:myemail@test.example.com', + terms_of_service_agreed: true + ) + end + + context 'when acme integration is disabled' do + before do + stub_application_setting(lets_encrypt_terms_of_service_accepted: false) + end + + it 'raises error' do + expect do + subject + end.to raise_error('Acme integration is disabled') + end + end + end + + describe '#new_order' do + subject(:new_order) { client.new_order('example.com') } + + before do + order_double = instance_double('Acme::Order') + allow(stub_client).to receive(:new_order).and_return(order_double) + end + + include_examples 'ensures account registration' + + it 'returns order' do + is_expected.to be_a(::Gitlab::LetsEncrypt::Order) + end + end + + describe '#load_order' do + let(:url) { 'https://example.com/order' } + subject { client.load_order(url) } + + before do + acme_order = instance_double('Acme::Client::Resources::Order') + allow(stub_client).to receive(:order).with(url: url).and_return(acme_order) + end + + include_examples 'ensures account registration' + + it 'loads order' do + is_expected.to be_a(::Gitlab::LetsEncrypt::Order) + end + end + + describe '#load_challenge' do + let(:url) { 'https://example.com/challenge' } + subject { client.load_challenge(url) } + + before do + acme_challenge = instance_double('Acme::Client::Resources::Challenge') + allow(stub_client).to receive(:challenge).with(url: url).and_return(acme_challenge) + end + + include_examples 'ensures account registration' + + it 'loads challenge' do + is_expected.to be_a(::Gitlab::LetsEncrypt::Challenge) + end + end + + describe '#enabled?' do + subject { client.enabled? } + + context 'when terms of service are accepted' do + it { is_expected.to eq(true) } + + context 'when feature flag is disabled' do + before do + stub_feature_flags(pages_auto_ssl: false) + end + + it { is_expected.to eq(false) } + end + end + + context 'when terms of service are not accepted' do + before do + stub_application_setting(lets_encrypt_terms_of_service_accepted: false) + end + + it { is_expected.to eq(false) } + end + end + + describe '#terms_of_service_url' do + subject { client.terms_of_service_url } + + it 'returns valid url' do + is_expected.to eq("https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf") + end + end +end diff --git a/spec/lib/gitlab/lets_encrypt/order_spec.rb b/spec/lib/gitlab/lets_encrypt/order_spec.rb new file mode 100644 index 00000000000..ee7058baf8d --- /dev/null +++ b/spec/lib/gitlab/lets_encrypt/order_spec.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe ::Gitlab::LetsEncrypt::Order do + delegated_methods = { + url: 'https://example.com/', + status: 'valid' + } + + let(:acme_order) do + acme_order = instance_double('Acme::Client::Resources::Order') + allow(acme_order).to receive_messages(delegated_methods) + acme_order + end + + let(:order) { described_class.new(acme_order) } + + delegated_methods.each do |method, value| + describe "##{method}" do + it 'delegates to Acme::Client::Resources::Order' do + expect(order.public_send(method)).to eq(value) + end + end + end + + describe '#new_challenge' do + before do + challenge = instance_double('Acme::Client::Resources::Challenges::HTTP01') + authorization = instance_double('Acme::Client::Resources::Authorization') + allow(authorization).to receive(:http).and_return(challenge) + allow(acme_order).to receive(:authorizations).and_return([authorization]) + end + + it 'returns challenge' do + expect(order.new_challenge).to be_a(::Gitlab::LetsEncrypt::Challenge) + end + end +end diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 9ff0f355fd4..c5ab7e57272 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -2286,12 +2286,45 @@ describe Repository do end describe '#diverging_commit_counts' do + let(:diverged_branch) { repository.find_branch('fix') } + let(:root_ref_sha) { repository.raw_repository.commit(repository.root_ref).id } + let(:diverged_branch_sha) { diverged_branch.dereferenced_target.sha } + it 'returns the commit counts behind and ahead of default branch' do - result = repository.diverging_commit_counts( - repository.find_branch('fix')) + result = repository.diverging_commit_counts(diverged_branch) expect(result).to eq(behind: 29, ahead: 2) end + + context 'when gitaly_count_diverging_commits_no_max is enabled' do + before do + stub_feature_flags(gitaly_count_diverging_commits_no_max: true) + end + + it 'calls diverging_commit_count without max count' do + expect(repository.raw_repository) + .to receive(:diverging_commit_count) + .with(root_ref_sha, diverged_branch_sha) + .and_return([29, 2]) + + repository.diverging_commit_counts(diverged_branch) + end + end + + context 'when gitaly_count_diverging_commits_no_max is disabled' do + before do + stub_feature_flags(gitaly_count_diverging_commits_no_max: false) + end + + it 'calls diverging_commit_count with max count' do + expect(repository.raw_repository) + .to receive(:diverging_commit_count) + .with(root_ref_sha, diverged_branch_sha, max_count: Repository::MAX_DIVERGING_COUNT) + .and_return([29, 2]) + + repository.diverging_commit_counts(diverged_branch) + end + end end describe '#refresh_method_caches' do diff --git a/spec/services/issues/close_service_spec.rb b/spec/services/issues/close_service_spec.rb index fce9eed8b08..6874a8a0929 100644 --- a/spec/services/issues/close_service_spec.rb +++ b/spec/services/issues/close_service_spec.rb @@ -3,11 +3,13 @@ require 'spec_helper' describe Issues::CloseService do - let(:user) { create(:user) } - let(:user2) { create(:user) } + let(:project) { create(:project, :repository) } + let(:user) { create(:user, email: "user@example.com") } + let(:user2) { create(:user, email: "user2@example.com") } let(:guest) { create(:user) } - let(:issue) { create(:issue, assignees: [user2], author: create(:user)) } - let(:project) { issue.project } + let(:issue) { create(:issue, title: "My issue", project: project, assignees: [user2], author: create(:user)) } + let(:closing_merge_request) { create(:merge_request, source_project: project) } + let(:closing_commit) { create(:commit, project: project) } let!(:todo) { create(:todo, :assigned, user: user, project: project, target: issue, author: user2) } before do @@ -39,7 +41,7 @@ describe Issues::CloseService do .and_return(true) expect(service).to receive(:close_issue) - .with(issue, commit: nil, notifications: true, system_note: true) + .with(issue, closed_via: nil, notifications: true, system_note: true) service.execute(issue) end @@ -57,6 +59,38 @@ describe Issues::CloseService do end describe '#close_issue' do + context "closed by a merge request" do + before do + perform_enqueued_jobs do + described_class.new(project, user).close_issue(issue, closed_via: closing_merge_request) + end + end + + it 'mentions closure via a merge request' do + email = ActionMailer::Base.deliveries.last + + expect(email.to.first).to eq(user2.email) + expect(email.subject).to include(issue.title) + expect(email.body.parts.map(&:body)).to all(include(closing_merge_request.to_reference)) + end + end + + context "closed by a commit" do + before do + perform_enqueued_jobs do + described_class.new(project, user).close_issue(issue, closed_via: closing_commit) + end + end + + it 'mentions closure via a commit' do + email = ActionMailer::Base.deliveries.last + + expect(email.to.first).to eq(user2.email) + expect(email.subject).to include(issue.title) + expect(email.body.parts.map(&:body)).to all(include(closing_commit.id)) + end + end + context "valid params" do before do perform_enqueued_jobs do diff --git a/spec/services/merge_requests/rebase_service_spec.rb b/spec/services/merge_requests/rebase_service_spec.rb index a443e4588d9..7e2f03d1097 100644 --- a/spec/services/merge_requests/rebase_service_spec.rb +++ b/spec/services/merge_requests/rebase_service_spec.rb @@ -38,6 +38,32 @@ describe MergeRequests::RebaseService do end end + shared_examples 'sequence of failure and success' do + it 'properly clears the error message' do + allow(repository).to receive(:gitaly_operation_client).and_raise('Something went wrong') + + service.execute(merge_request) + + expect(merge_request.reload.merge_error).to eq described_class::REBASE_ERROR + + allow(repository).to receive(:gitaly_operation_client).and_call_original + + service.execute(merge_request) + + expect(merge_request.reload.merge_error).to eq nil + end + end + + it_behaves_like 'sequence of failure and success' + + context 'with deprecated step rebase feature' do + before do + stub_feature_flags(two_step_rebase: false) + end + + it_behaves_like 'sequence of failure and success' + end + context 'when unexpected error occurs' do before do allow(repository).to receive(:gitaly_operation_client).and_raise('Something went wrong') diff --git a/spec/support/helpers/lets_encrypt_helpers.rb b/spec/support/helpers/lets_encrypt_helpers.rb new file mode 100644 index 00000000000..7f0886b451c --- /dev/null +++ b/spec/support/helpers/lets_encrypt_helpers.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module LetsEncryptHelpers + def stub_lets_encrypt_client + client = instance_double('Acme::Client') + + allow(client).to receive(:new_account) + allow(client).to receive(:terms_of_service).and_return( + "https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf" + ) + + allow(Acme::Client).to receive(:new).with( + private_key: kind_of(OpenSSL::PKey::RSA), + directory: ::Gitlab::LetsEncrypt::Client::STAGING_DIRECTORY_URL + ).and_return(client) + + client + end +end |