diff options
Diffstat (limited to 'app')
50 files changed, 525 insertions, 269 deletions
diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcuts.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts.js index 7e3515b1f4b..66cb9fd7672 100644 --- a/app/assets/javascripts/behaviors/shortcuts/shortcuts.js +++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts.js @@ -46,7 +46,6 @@ export default class Shortcuts { $(document).on('click.more_help', '.js-more-help-button', function clickMoreHelp(e) { $(this).remove(); - $('.hidden-shortcut').show(); e.preventDefault(); }); } @@ -104,7 +103,6 @@ export default class Shortcuts { return results; } - $('.hidden-shortcut').show(); return $('.js-more-help-button').remove(); }); } diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js index c8eb96a625c..f7b327b2af1 100644 --- a/app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js +++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js @@ -6,7 +6,7 @@ import { CopyAsGFM } from '../markdown/copy_as_gfm'; import { getSelectedFragment } from '~/lib/utils/common_utils'; export default class ShortcutsIssuable extends Shortcuts { - constructor(isMergeRequest) { + constructor() { super(); Mousetrap.bind('a', () => ShortcutsIssuable.openSidebarDropdown('assignee')); @@ -14,12 +14,6 @@ export default class ShortcutsIssuable extends Shortcuts { Mousetrap.bind('l', () => ShortcutsIssuable.openSidebarDropdown('labels')); Mousetrap.bind('r', ShortcutsIssuable.replyWithSelectedText); Mousetrap.bind('e', ShortcutsIssuable.editIssue); - - if (isMergeRequest) { - this.enabledHelp.push('.hidden-shortcut.merge_requests'); - } else { - this.enabledHelp.push('.hidden-shortcut.issues'); - } } static replyWithSelectedText() { diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcuts_navigation.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts_navigation.js index bef1553703b..b46b4132ba8 100644 --- a/app/assets/javascripts/behaviors/shortcuts/shortcuts_navigation.js +++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts_navigation.js @@ -23,7 +23,5 @@ export default class ShortcutsNavigation extends Shortcuts { Mousetrap.bind('g e', () => findAndFollowLink('.shortcuts-environments')); Mousetrap.bind('g l', () => findAndFollowLink('.shortcuts-metrics')); Mousetrap.bind('i', () => findAndFollowLink('.shortcuts-new-issue')); - - this.enabledHelp.push('.hidden-shortcut.project'); } } diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcuts_network.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts_network.js index a88c280fa3b..3e791e4673a 100644 --- a/app/assets/javascripts/behaviors/shortcuts/shortcuts_network.js +++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts_network.js @@ -11,7 +11,5 @@ export default class ShortcutsNetwork extends ShortcutsNavigation { Mousetrap.bind(['down', 'j'], graph.scrollDown); Mousetrap.bind(['shift+up', 'shift+k'], graph.scrollTop); Mousetrap.bind(['shift+down', 'shift+j'], graph.scrollBottom); - - this.enabledHelp.push('.hidden-shortcut.network'); } } diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcuts_wiki.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts_wiki.js index 208c91a1f08..8b7e6a56d25 100644 --- a/app/assets/javascripts/behaviors/shortcuts/shortcuts_wiki.js +++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts_wiki.js @@ -6,8 +6,6 @@ export default class ShortcutsWiki extends ShortcutsNavigation { constructor() { super(); Mousetrap.bind('e', ShortcutsWiki.editWiki); - - this.enabledHelp.push('.hidden-shortcut.wiki'); } static editWiki() { diff --git a/app/assets/javascripts/boards/components/board_new_issue.vue b/app/assets/javascripts/boards/components/board_new_issue.vue index f9284266b72..f9a08f151c5 100644 --- a/app/assets/javascripts/boards/components/board_new_issue.vue +++ b/app/assets/javascripts/boards/components/board_new_issue.vue @@ -2,9 +2,9 @@ import $ from 'jquery'; import { GlButton } from '@gitlab/ui'; import { getMilestone } from 'ee_else_ce/boards/boards_util'; +import ListIssue from 'ee_else_ce/boards/models/issue'; import eventHub from '../eventhub'; import ProjectSelect from './project_select.vue'; -import ListIssue from '../models/issue'; import boardsStore from '../stores/boards_store'; export default { @@ -54,6 +54,9 @@ export default { const assignees = this.list.assignee ? [this.list.assignee] : []; const milestone = getMilestone(this.list); + const { weightFeatureAvailable } = boardsStore; + const { weight } = weightFeatureAvailable ? boardsStore.state.currentBoard : {}; + const issue = new ListIssue({ title: this.title, labels, @@ -61,6 +64,7 @@ export default { assignees, milestone, project_id: this.selectedProject.id, + weight, }); eventHub.$emit(`scroll-board-list-${this.list.id}`); diff --git a/app/assets/javascripts/boards/components/boards_selector.vue b/app/assets/javascripts/boards/components/boards_selector.vue index 7296426549a..ebb2f5b23e4 100644 --- a/app/assets/javascripts/boards/components/boards_selector.vue +++ b/app/assets/javascripts/boards/components/boards_selector.vue @@ -245,6 +245,7 @@ export default { <div v-if="!loading" ref="content" + data-qa-selector="boards_dropdown_content" class="dropdown-content flex-fill" @scroll.passive="throttledSetScrollFade" > diff --git a/app/assets/javascripts/clusters/components/applications.vue b/app/assets/javascripts/clusters/components/applications.vue index b6da572b201..27959898fb7 100644 --- a/app/assets/javascripts/clusters/components/applications.vue +++ b/app/assets/javascripts/clusters/components/applications.vue @@ -458,7 +458,6 @@ export default { </div> </application-row> <application-row - v-if="isProjectCluster" id="knative" :logo-url="knativeLogo" :title="applications.knative.title" diff --git a/app/assets/javascripts/error_tracking/components/error_tracking_list.vue b/app/assets/javascripts/error_tracking/components/error_tracking_list.vue index 43ae54133af..b1d568532a6 100644 --- a/app/assets/javascripts/error_tracking/components/error_tracking_list.vue +++ b/app/assets/javascripts/error_tracking/components/error_tracking_list.vue @@ -38,6 +38,10 @@ export default { type: String, required: true, }, + userCanEnableErrorTracking: { + type: Boolean, + required: true, + }, }, computed: { ...mapState(['errors', 'externalUrl', 'loading']), @@ -111,14 +115,26 @@ export default { </gl-table> </div> </div> - <div v-else> + <div v-else-if="userCanEnableErrorTracking"> <gl-empty-state :title="__('Get started with error tracking')" - :description="__('Monitor your errors by integrating with Sentry')" + :description="__('Monitor your errors by integrating with Sentry.')" :primary-button-text="__('Enable error tracking')" :primary-button-link="enableErrorTrackingLink" :svg-path="illustrationPath" /> </div> + <div v-else> + <gl-empty-state :title="__('Get started with error tracking')" :svg-path="illustrationPath"> + <template #description> + <div> + <span>{{ __('Monitor your errors by integrating with Sentry.') }}</span> + <a href="/help/user/project/operations/error_tracking.html"> + {{ __('More information') }} + </a> + </div> + </template> + </gl-empty-state> + </div> </div> </template> diff --git a/app/assets/javascripts/error_tracking/index.js b/app/assets/javascripts/error_tracking/index.js index 3d609448efe..073e2c8f1c7 100644 --- a/app/assets/javascripts/error_tracking/index.js +++ b/app/assets/javascripts/error_tracking/index.js @@ -14,9 +14,10 @@ export default () => { render(createElement) { const domEl = document.querySelector(this.$options.el); const { indexPath, enableErrorTrackingLink, illustrationPath } = domEl.dataset; - let { errorTrackingEnabled } = domEl.dataset; + let { errorTrackingEnabled, userCanEnableErrorTracking } = domEl.dataset; errorTrackingEnabled = parseBoolean(errorTrackingEnabled); + userCanEnableErrorTracking = parseBoolean(userCanEnableErrorTracking); return createElement('error-tracking-list', { props: { @@ -24,6 +25,7 @@ export default () => { enableErrorTrackingLink, errorTrackingEnabled, illustrationPath, + userCanEnableErrorTracking, }, }); }, diff --git a/app/assets/javascripts/pipelines/components/graph/graph_component.vue b/app/assets/javascripts/pipelines/components/graph/graph_component.vue index ba0dea626dc..27c1b639889 100644 --- a/app/assets/javascripts/pipelines/components/graph/graph_component.vue +++ b/app/assets/javascripts/pipelines/components/graph/graph_component.vue @@ -20,6 +20,9 @@ export default { <stage-column-component v-for="(stage, index) in graph" :key="stage.name" + :class="{ + 'append-right-48': shouldAddRightMargin(index), + }" :title="capitalizeStageName(stage.name)" :groups="stage.groups" :stage-connector-class="stageConnectorClass(index, stage)" diff --git a/app/assets/javascripts/pipelines/mixins/graph_component_mixin.js b/app/assets/javascripts/pipelines/mixins/graph_component_mixin.js index 66e9476dadf..f383a4b3368 100644 --- a/app/assets/javascripts/pipelines/mixins/graph_component_mixin.js +++ b/app/assets/javascripts/pipelines/mixins/graph_component_mixin.js @@ -40,5 +40,15 @@ export default { refreshPipelineGraph() { this.$emit('refreshPipelineGraph'); }, + /** + * CSS class is applied: + * - if pipeline graph contains only one stage column component + * + * @param {number} index + * @returns {boolean} + */ + shouldAddRightMargin(index) { + return !(index === this.graph.length - 1); + }, }, }; diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss index e9218dcec67..b95978b6966 100644 --- a/app/assets/stylesheets/framework/common.scss +++ b/app/assets/stylesheets/framework/common.scss @@ -395,6 +395,7 @@ img.emoji { .prepend-left-default { margin-left: $gl-padding; } .prepend-left-20 { margin-left: 20px; } .prepend-left-32 { margin-left: 32px; } +.prepend-left-64 { margin-left: 64px; } .append-right-4 { margin-right: 4px; } .append-right-5 { margin-right: 5px; } .append-right-8 { margin-right: 8px; } @@ -402,6 +403,8 @@ img.emoji { .append-right-15 { margin-right: 15px; } .append-right-default { margin-right: $gl-padding; } .append-right-20 { margin-right: 20px; } +.append-right-32 { margin-right: 32px; } +.append-right-48 { margin-right: 48px; } .prepend-right-32 { margin-right: 32px; } .append-bottom-0 { margin-bottom: 0; } .append-bottom-4 { margin-bottom: $gl-padding-4; } diff --git a/app/assets/stylesheets/framework/modal.scss b/app/assets/stylesheets/framework/modal.scss index fd9a75bc5b6..9c924559135 100644 --- a/app/assets/stylesheets/framework/modal.scss +++ b/app/assets/stylesheets/framework/modal.scss @@ -2,6 +2,12 @@ max-width: 98%; } +.modal-1040 { + @include media-breakpoint-up(xl) { + max-width: 1040px; + } +} + .modal-header { background-color: $modal-body-bg; diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 5f4db37c317..d4bd5b1b7dc 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -476,10 +476,6 @@ display: inline-block; vertical-align: top; - &:not(:last-child) { - margin-right: 44px; - } - &.left-margin { &:not(:first-child) { margin-left: 44px; diff --git a/app/controllers/clusters/base_controller.rb b/app/controllers/clusters/base_controller.rb index ef42f7c4074..188805c6106 100644 --- a/app/controllers/clusters/base_controller.rb +++ b/app/controllers/clusters/base_controller.rb @@ -31,6 +31,10 @@ class Clusters::BaseController < ApplicationController access_denied! unless can?(current_user, :create_cluster, clusterable) end + def authorize_read_prometheus! + access_denied! unless can?(current_user, :read_prometheus, clusterable) + end + def clusterable raise NotImplementedError end diff --git a/app/controllers/projects/settings/operations_controller.rb b/app/controllers/projects/settings/operations_controller.rb index 5cfb0ac307d..ec89bb89edc 100644 --- a/app/controllers/projects/settings/operations_controller.rb +++ b/app/controllers/projects/settings/operations_controller.rb @@ -3,7 +3,7 @@ module Projects module Settings class OperationsController < Projects::ApplicationController - before_action :authorize_update_environment! + before_action :authorize_admin_operations! helper_method :error_tracking_setting diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index e773ec09924..fb631f09f10 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -20,6 +20,7 @@ class RegistrationsController < Devise::RegistrationsController super do |new_user| persist_accepted_terms_if_required(new_user) + yield new_user if block_given? end rescue Gitlab::Access::AccessDeniedError redirect_to(new_user_session_path) diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index b735f9ff3b8..8ed6ff56e2b 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -193,15 +193,30 @@ class IssuableFinder projects = if current_user && params[:authorized_only].presence && !current_user_related? current_user.authorized_projects(min_access_level) - elsif group - find_group_projects else - Project.public_or_visible_to_user(current_user, min_access_level) + projects_public_or_visible_to_user end @projects = projects.with_feature_available_for_user(klass, current_user).reorder(nil) # rubocop: disable CodeReuse/ActiveRecord end + def projects_public_or_visible_to_user + projects = + if group + if params[:projects] + find_group_projects.id_in(params[:projects]) + else + find_group_projects + end + elsif params[:projects] + Project.id_in(params[:projects]) + else + Project + end + + projects.public_or_visible_to_user(current_user, min_access_level) + end + def find_group_projects return Project.none unless group @@ -209,7 +224,7 @@ class IssuableFinder Project.where(namespace_id: group.self_and_descendants) # rubocop: disable CodeReuse/ActiveRecord else group.projects - end.public_or_visible_to_user(current_user, min_access_level) + end end def search diff --git a/app/graphql/types/permission_types/project.rb b/app/graphql/types/permission_types/project.rb index e9a4ea9157b..993d33c4fc2 100644 --- a/app/graphql/types/permission_types/project.rb +++ b/app/graphql/types/permission_types/project.rb @@ -16,7 +16,7 @@ module Types :create_deployment, :push_to_delete_protected_branch, :admin_wiki, :admin_project, :update_pages, :admin_remote_mirror, :create_label, :update_wiki, :destroy_wiki, - :create_pages, :destroy_pages, :read_pages_content + :create_pages, :destroy_pages, :read_pages_content, :admin_operations end end end diff --git a/app/helpers/projects/error_tracking_helper.rb b/app/helpers/projects/error_tracking_helper.rb index 6daf2e21ca2..fd1222a1dfb 100644 --- a/app/helpers/projects/error_tracking_helper.rb +++ b/app/helpers/projects/error_tracking_helper.rb @@ -1,12 +1,13 @@ # frozen_string_literal: true module Projects::ErrorTrackingHelper - def error_tracking_data(project) + def error_tracking_data(current_user, project) error_tracking_enabled = !!project.error_tracking_setting&.enabled? { 'index-path' => project_error_tracking_index_path(project, format: :json), + 'user-can-enable-error-tracking' => can?(current_user, :admin_operations, project).to_s, 'enable-error-tracking-link' => project_settings_operations_path(project), 'error-tracking-enabled' => error_tracking_enabled.to_s, 'illustration-path' => image_path('illustrations/cluster_popover.svg') diff --git a/app/helpers/user_callouts_helper.rb b/app/helpers/user_callouts_helper.rb index d5e459311f7..f10fadfdf49 100644 --- a/app/helpers/user_callouts_helper.rb +++ b/app/helpers/user_callouts_helper.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true module UserCalloutsHelper - GKE_CLUSTER_INTEGRATION = 'gke_cluster_integration'.freeze - GCP_SIGNUP_OFFER = 'gcp_signup_offer'.freeze - SUGGEST_POPOVER_DISMISSED = 'suggest_popover_dismissed'.freeze + GKE_CLUSTER_INTEGRATION = 'gke_cluster_integration' + GCP_SIGNUP_OFFER = 'gcp_signup_offer' + SUGGEST_POPOVER_DISMISSED = 'suggest_popover_dismissed' def show_gke_cluster_integration_callout?(project) can?(current_user, :create_cluster, project) && diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index d620959b538..d2271c1335c 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -23,6 +23,7 @@ module Ci belongs_to :auto_canceled_by, class_name: 'Ci::Pipeline' belongs_to :pipeline_schedule, class_name: 'Ci::PipelineSchedule' belongs_to :merge_request, class_name: 'MergeRequest' + belongs_to :external_pull_request has_internal_id :iid, scope: :project, presence: false, init: ->(s) do s&.project&.all_pipelines&.maximum(:iid) || s&.project&.all_pipelines&.count @@ -64,6 +65,11 @@ module Ci validates :merge_request, presence: { if: :merge_request_event? } validates :merge_request, absence: { unless: :merge_request_event? } validates :tag, inclusion: { in: [false], if: :merge_request_event? } + + validates :external_pull_request, presence: { if: :external_pull_request_event? } + validates :external_pull_request, absence: { unless: :external_pull_request_event? } + validates :tag, inclusion: { in: [false], if: :external_pull_request_event? } + validates :status, presence: { unless: :importing? } validate :valid_commit_sha, unless: :importing? validates :source, exclusion: { in: %w(unknown), unless: :importing? }, on: :create @@ -683,6 +689,10 @@ module Ci variables.append(key: 'CI_MERGE_REQUEST_TARGET_BRANCH_SHA', value: target_sha.to_s) variables.concat(merge_request.predefined_variables) end + + if external_pull_request_event? && external_pull_request + variables.concat(external_pull_request.predefined_variables) + end end end diff --git a/app/models/ci/pipeline_enums.rb b/app/models/ci/pipeline_enums.rb index 571c4271475..0c2bd0aa8eb 100644 --- a/app/models/ci/pipeline_enums.rb +++ b/app/models/ci/pipeline_enums.rb @@ -23,7 +23,8 @@ module Ci api: 5, external: 6, chat: 8, - merge_request_event: 10 + merge_request_event: 10, + external_pull_request_event: 11 } end diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb index ef1af1fc8bc..a976093ac0c 100644 --- a/app/models/clusters/cluster.rb +++ b/app/models/clusters/cluster.rb @@ -10,7 +10,6 @@ module Clusters self.table_name = 'clusters' PROJECT_ONLY_APPLICATIONS = { - Applications::Knative.application_name => Applications::Knative }.freeze APPLICATIONS = { Applications::Helm.application_name => Applications::Helm, @@ -18,7 +17,8 @@ module Clusters Applications::CertManager.application_name => Applications::CertManager, Applications::Prometheus.application_name => Applications::Prometheus, Applications::Runner.application_name => Applications::Runner, - Applications::Jupyter.application_name => Applications::Jupyter + Applications::Jupyter.application_name => Applications::Jupyter, + Applications::Knative.application_name => Applications::Knative }.merge(PROJECT_ONLY_APPLICATIONS).freeze DEFAULT_ENVIRONMENT = '*' KUBE_INGRESS_BASE_DOMAIN = 'KUBE_INGRESS_BASE_DOMAIN' diff --git a/app/models/external_pull_request.rb b/app/models/external_pull_request.rb new file mode 100644 index 00000000000..65ae8d95500 --- /dev/null +++ b/app/models/external_pull_request.rb @@ -0,0 +1,96 @@ +# frozen_string_literal: true + +# This model stores pull requests coming from external providers, such as +# GitHub, when GitLab project is set as CI/CD only and remote mirror. +# +# When setting up a remote mirror with GitHub we subscribe to push and +# pull_request webhook events. When a pull request is opened on GitHub, +# a webhook is sent out, we create or update the status of the pull +# request locally. +# +# When the mirror is updated and changes are pushed to branches we check +# if there are open pull requests for the source and target branch. +# If so, we create pipelines for external pull requests. +class ExternalPullRequest < ApplicationRecord + include Gitlab::Utils::StrongMemoize + include ShaAttribute + + belongs_to :project + + sha_attribute :source_sha + sha_attribute :target_sha + + validates :source_branch, presence: true + validates :target_branch, presence: true + validates :source_sha, presence: true + validates :target_sha, presence: true + validates :source_repository, presence: true + validates :target_repository, presence: true + validates :status, presence: true + + enum status: { + open: 1, + closed: 2 + } + + # We currently don't support pull requests from fork, so + # we are going to return an error to the webhook + validate :not_from_fork + + scope :by_source_branch, ->(branch) { where(source_branch: branch) } + scope :by_source_repository, -> (repository) { where(source_repository: repository) } + + def self.create_or_update_from_params(params) + find_params = params.slice(:project_id, :source_branch, :target_branch) + + safe_find_or_initialize_and_update(find: find_params, update: params) do |pull_request| + yield(pull_request) if block_given? + end + end + + def actual_branch_head? + actual_source_branch_sha == source_sha + end + + def from_fork? + source_repository != target_repository + end + + def source_ref + Gitlab::Git::BRANCH_REF_PREFIX + source_branch + end + + def predefined_variables + Gitlab::Ci::Variables::Collection.new.tap do |variables| + variables.append(key: 'CI_EXTERNAL_PULL_REQUEST_IID', value: pull_request_iid.to_s) + variables.append(key: 'CI_EXTERNAL_PULL_REQUEST_SOURCE_BRANCH_SHA', value: source_sha) + variables.append(key: 'CI_EXTERNAL_PULL_REQUEST_TARGET_BRANCH_SHA', value: target_sha) + variables.append(key: 'CI_EXTERNAL_PULL_REQUEST_SOURCE_BRANCH_NAME', value: source_branch) + variables.append(key: 'CI_EXTERNAL_PULL_REQUEST_TARGET_BRANCH_NAME', value: target_branch) + end + end + + private + + def actual_source_branch_sha + project.commit(source_ref)&.sha + end + + def not_from_fork + if from_fork? + errors.add(:base, 'Pull requests from fork are not supported') + end + end + + def self.safe_find_or_initialize_and_update(find:, update:) + safe_ensure_unique(retries: 1) do + model = find_or_initialize_by(find) + + if model.update(update) + yield(model) if block_given? + end + + model + end + end +end diff --git a/app/models/pages_domain.rb b/app/models/pages_domain.rb index 12ce717efd7..a2a471074a9 100644 --- a/app/models/pages_domain.rb +++ b/app/models/pages_domain.rb @@ -17,7 +17,7 @@ class PagesDomain < ApplicationRecord validates :certificate, certificate: true, if: ->(domain) { domain.certificate.present? } validates :key, presence: { message: 'must be present if HTTPS-only is enabled' }, if: :certificate_should_be_present? - validates :key, certificate_key: true, if: ->(domain) { domain.key.present? } + validates :key, certificate_key: true, named_ecdsa_key: true, if: ->(domain) { domain.key.present? } validates :verification_code, presence: true, allow_blank: false validate :validate_pages_domain @@ -247,7 +247,7 @@ class PagesDomain < ApplicationRecord def pkey return unless key - @pkey ||= OpenSSL::PKey::RSA.new(key) + @pkey ||= OpenSSL::PKey.read(key) rescue OpenSSL::PKey::PKeyError, OpenSSL::Cipher::CipherError nil end diff --git a/app/models/project.rb b/app/models/project.rb index 17b52d0578e..d948410e397 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -291,6 +291,8 @@ class Project < ApplicationRecord has_many :remote_mirrors, inverse_of: :project has_many :cycle_analytics_stages, class_name: 'Analytics::CycleAnalytics::ProjectStage' + has_many :external_pull_requests, inverse_of: :project + accepts_nested_attributes_for :variables, allow_destroy: true accepts_nested_attributes_for :project_feature, update_only: true accepts_nested_attributes_for :import_data diff --git a/app/policies/clusters/instance_policy.rb b/app/policies/clusters/instance_policy.rb index bd7ff413afe..c8e6c973bf5 100644 --- a/app/policies/clusters/instance_policy.rb +++ b/app/policies/clusters/instance_policy.rb @@ -8,6 +8,7 @@ module Clusters enable :create_cluster enable :update_cluster enable :admin_cluster + enable :read_prometheus end end end diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index e2634692dc7..5c36b59f07b 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -294,6 +294,7 @@ class ProjectPolicy < BasePolicy enable :destroy_release enable :destroy_artifacts enable :daily_statistics + enable :admin_operations end rule { (mirror_available & can?(:admin_project)) | admin }.enable :admin_remote_mirror diff --git a/app/serializers/merge_request_noteable_entity.rb b/app/serializers/merge_request_noteable_entity.rb index e22be6880bb..9504fdd8eac 100644 --- a/app/serializers/merge_request_noteable_entity.rb +++ b/app/serializers/merge_request_noteable_entity.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class MergeRequestNoteableEntity < Grape::Entity +class MergeRequestNoteableEntity < IssuableEntity include RequestAwareEntity # Currently this attr is exposed to be used in app/assets/javascripts/notes/stores/getters.js diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb index 8f8582afb43..4a7f62de9e1 100644 --- a/app/services/ci/create_pipeline_service.rb +++ b/app/services/ci/create_pipeline_service.rb @@ -18,7 +18,8 @@ module Ci Gitlab::Ci::Pipeline::Chain::Limit::Activity, Gitlab::Ci::Pipeline::Chain::Limit::JobActivity].freeze - def execute(source, ignore_skip_ci: false, save_on_errors: true, trigger_request: nil, schedule: nil, merge_request: nil, **options, &block) + # rubocop: disable Metrics/ParameterLists + def execute(source, ignore_skip_ci: false, save_on_errors: true, trigger_request: nil, schedule: nil, merge_request: nil, external_pull_request: nil, **options, &block) @pipeline = Ci::Pipeline.new command = Gitlab::Ci::Pipeline::Chain::Command.new( @@ -32,6 +33,7 @@ module Ci trigger_request: trigger_request, schedule: schedule, merge_request: merge_request, + external_pull_request: external_pull_request, ignore_skip_ci: ignore_skip_ci, save_incompleted: save_on_errors, seeds_block: block, @@ -62,6 +64,7 @@ module Ci pipeline end + # rubocop: enable Metrics/ParameterLists def execute!(*args, &block) execute(*args, &block).tap do |pipeline| diff --git a/app/services/external_pull_requests/create_pipeline_service.rb b/app/services/external_pull_requests/create_pipeline_service.rb new file mode 100644 index 00000000000..36411465ff1 --- /dev/null +++ b/app/services/external_pull_requests/create_pipeline_service.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +# This service is responsible for creating a pipeline for a given +# ExternalPullRequest coming from other providers such as GitHub. + +module ExternalPullRequests + class CreatePipelineService < BaseService + def execute(pull_request) + return unless pull_request.open? && pull_request.actual_branch_head? + + create_pipeline_for(pull_request) + end + + private + + def create_pipeline_for(pull_request) + Ci::CreatePipelineService.new(project, current_user, create_params(pull_request)) + .execute(:external_pull_request_event, external_pull_request: pull_request) + end + + def create_params(pull_request) + { + ref: pull_request.source_ref, + source_sha: pull_request.source_sha, + target_sha: pull_request.target_sha + } + end + end +end diff --git a/app/services/git/base_hooks_service.rb b/app/services/git/base_hooks_service.rb index 47c308c8280..35a4d2015fa 100644 --- a/app/services/git/base_hooks_service.rb +++ b/app/services/git/base_hooks_service.rb @@ -57,7 +57,9 @@ module Git Ci::CreatePipelineService .new(project, current_user, pipeline_params) - .execute(:push, pipeline_options) + .execute!(:push, pipeline_options) + rescue Ci::CreatePipelineService::CreateError => ex + log_pipeline_errors(ex) end def execute_project_hooks @@ -125,5 +127,29 @@ module Git project.mark_stuck_remote_mirrors_as_failed! project.update_remote_mirrors end + + def log_pipeline_errors(exception) + data = { + class: self.class.name, + correlation_id: Labkit::Correlation::CorrelationId.current_id.to_s, + project_id: project.id, + project_path: project.full_path, + message: "Error creating pipeline", + errors: exception.to_s, + pipeline_params: pipeline_params + } + + logger.warn(data) + end + + def logger + if Sidekiq.server? + Sidekiq.logger + else + # This service runs in Sidekiq, so this shouldn't ever be + # called, but this is included just in case. + Gitlab::ProjectServiceLogger + end + end end end diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb index 3555864f834..900e5063621 100644 --- a/app/services/issuable_base_service.rb +++ b/app/services/issuable_base_service.rb @@ -76,13 +76,16 @@ class IssuableBaseService < BaseService end def filter_labels - params[:add_label_ids] = labels_service.filter_labels_ids_in_param(:add_label_ids) if params[:add_label_ids] - params[:remove_label_ids] = labels_service.filter_labels_ids_in_param(:remove_label_ids) if params[:remove_label_ids] + label_ids_to_filter(:add_label_ids, :add_labels, false) + label_ids_to_filter(:remove_label_ids, :remove_labels, true) + label_ids_to_filter(:label_ids, :labels, false) + end - if params[:label_ids] - params[:label_ids] = labels_service.filter_labels_ids_in_param(:label_ids) - elsif params[:labels] - params[:label_ids] = labels_service.find_or_create_by_titles.map(&:id) + def label_ids_to_filter(label_id_key, label_key, find_only) + if params[label_id_key] + params[label_id_key] = labels_service.filter_labels_ids_in_param(label_id_key) + elsif params[label_key] + params[label_id_key] = labels_service.find_or_create_by_titles(label_key, find_only: find_only).map(&:id) end end diff --git a/app/services/labels/available_labels_service.rb b/app/services/labels/available_labels_service.rb index fe477d96970..8886e58d6ef 100644 --- a/app/services/labels/available_labels_service.rb +++ b/app/services/labels/available_labels_service.rb @@ -9,8 +9,8 @@ module Labels @params = params end - def find_or_create_by_titles - labels = params.delete(:labels) + def find_or_create_by_titles(key = :labels, find_only: false) + labels = params.delete(key) return [] unless labels @@ -23,7 +23,7 @@ module Labels include_ancestor_groups: true, title: label_name.strip, available_labels: available_labels - ).execute + ).execute(find_only: find_only) label end.compact diff --git a/app/services/labels/find_or_create_service.rb b/app/services/labels/find_or_create_service.rb index 628873519d7..a47dd42aea0 100644 --- a/app/services/labels/find_or_create_service.rb +++ b/app/services/labels/find_or_create_service.rb @@ -9,9 +9,9 @@ module Labels @params = params.dup.with_indifferent_access end - def execute(skip_authorization: false) + def execute(skip_authorization: false, find_only: false) @skip_authorization = skip_authorization - find_or_create_label + find_or_create_label(find_only: find_only) end private @@ -30,9 +30,11 @@ module Labels # Only creates the label if current_user can do so, if the label does not exist # and the user can not create the label, nil is returned # rubocop: disable CodeReuse/ActiveRecord - def find_or_create_label + def find_or_create_label(find_only: false) new_label = available_labels.find_by(title: title) + return new_label if find_only + if new_label.nil? && (skip_authorization || Ability.allowed?(current_user, :admin_label, parent)) create_params = params.except(:include_ancestor_groups) new_label = Labels::CreateService.new(create_params).execute(parent_type.to_sym => parent) diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb index b28f80939ae..308a3a10d1a 100644 --- a/app/services/merge_requests/build_service.rb +++ b/app/services/merge_requests/build_service.rb @@ -18,6 +18,18 @@ module MergeRequests merge_request.target_project = find_target_project filter_params(merge_request) + + # merge_request.assign_attributes(...) below is a Rails + # method that only work if all the params it is passed have + # corresponding fields in the database. As there are no fields + # in the database for :add_label_ids and :remove_label_ids, we + # need to remove them from the params before the call to + # merge_request.assign_attributes(...) + # + # IssuableBaseService#process_label_ids takes care + # of the removal. + params[:label_ids] = process_label_ids(params, extra_label_ids: merge_request.label_ids.to_a) + merge_request.assign_attributes(params.to_h.compact) merge_request.compare_commits = [] diff --git a/app/services/merge_requests/push_options_handler_service.rb b/app/services/merge_requests/push_options_handler_service.rb index b210004e6e1..0168b31005e 100644 --- a/app/services/merge_requests/push_options_handler_service.rb +++ b/app/services/merge_requests/push_options_handler_service.rb @@ -100,7 +100,8 @@ module MergeRequests merge_request = ::MergeRequests::CreateService.new( project, current_user, - merge_request.attributes.merge(assignees: merge_request.assignees) + merge_request.attributes.merge(assignees: merge_request.assignees, + label_ids: merge_request.label_ids) ).execute end @@ -122,7 +123,9 @@ module MergeRequests title: push_options[:title], description: push_options[:description], target_branch: push_options[:target], - force_remove_source_branch: push_options[:remove_source_branch] + force_remove_source_branch: push_options[:remove_source_branch], + label: push_options[:label], + unlabel: push_options[:unlabel] } params.compact! @@ -134,6 +137,9 @@ module MergeRequests ) end + params[:add_labels] = params.delete(:label).keys if params.has_key?(:label) + params[:remove_labels] = params.delete(:unlabel).keys if params.has_key?(:unlabel) + params end diff --git a/app/validators/certificate_key_validator.rb b/app/validators/certificate_key_validator.rb index 5b2bbffc066..b9d54d9636e 100644 --- a/app/validators/certificate_key_validator.rb +++ b/app/validators/certificate_key_validator.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -# UrlValidator +# CertificateKeyValidator # # Custom validator for private keys. # @@ -20,7 +20,7 @@ class CertificateKeyValidator < ActiveModel::EachValidator def valid_private_key_pem?(value) return false unless value - pkey = OpenSSL::PKey::RSA.new(value) + pkey = OpenSSL::PKey.read(value) pkey.private? rescue OpenSSL::PKey::PKeyError false diff --git a/app/validators/named_ecdsa_key_validator.rb b/app/validators/named_ecdsa_key_validator.rb new file mode 100644 index 00000000000..42ee02b6ad4 --- /dev/null +++ b/app/validators/named_ecdsa_key_validator.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +# NamedEcdsaKeyValidator +# +# Custom validator for ecdsa private keys. +# Golang currently doesn't support explicit curves for ECDSA certificates +# This validator checks if curve is set by name, not by parameters +# +# class Project < ActiveRecord::Base +# validates :certificate_key, named_ecdsa_key: true +# end +# +class NamedEcdsaKeyValidator < ActiveModel::EachValidator + def validate_each(record, attribute, value) + if explicit_ec?(value) + record.errors.add(attribute, "ECDSA keys with explicit curves are not supported") + end + end + + private + + UNNAMED_CURVE = "UNDEF" + + def explicit_ec?(value) + return false unless value + + pkey = OpenSSL::PKey.read(value) + return false unless pkey.is_a?(OpenSSL::PKey::EC) + + pkey.group.curve_name == UNNAMED_CURVE + rescue OpenSSL::PKey::PKeyError + false + end +end diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml index a996c86a256..f1ba804f920 100644 --- a/app/views/help/_shortcuts.html.haml +++ b/app/views/help/_shortcuts.html.haml @@ -1,5 +1,5 @@ #modal-shortcuts.modal{ tabindex: -1 } - .modal-dialog.modal-lg + .modal-dialog.modal-lg.modal-1040 .modal-content .modal-header %h4.modal-title @@ -11,104 +11,100 @@ .modal-body .row .col-lg-4 - %table.shortcut-mappings + %table.shortcut-mappings.text-2 %tbody %tr %th %th= _('Global Shortcuts') %tr %td.shortcut - %kbd s - %td= _('Focus Search') + %kbd ? + %td= _('Toggle this dialog') %tr %td.shortcut - %kbd f - %td= _('Focus Filter') + %kbd shift p + %td= _('Go to your projects') %tr %td.shortcut - %kbd p - %kbd b - %td= _('Toggle the Performance Bar') + %kbd shift g + %td= _('Go to your groups') %tr %td.shortcut - %kbd ? - %td= _('Show/hide this dialog') + %kbd shift a + %td= _('Go to the activity feed') %tr %td.shortcut - - if browser.platform.mac? - %kbd ⌘ shift p - - else - %kbd ctrl shift p - %td= _('Toggle Markdown preview') + %kbd shift l + %td= _('Go to the milestone list') %tr %td.shortcut - %kbd - %i.fa.fa-arrow-up - %td= _('Edit last comment (when focused on an empty textarea)') + %kbd shift s + %td= _('Go to your snippets') %tr %td.shortcut - %kbd shift t - %td - = _('Go to todos') + %kbd s + %td= _('Start search') %tr %td.shortcut - %kbd shift a - %td - = _('Go to the activity feed') + %kbd shift i + %td= _('Go to your issues') %tr %td.shortcut - %kbd shift p - %td - = _('Go to projects') + %kbd shift m + %td= _('Go to your merge requests') %tr %td.shortcut - %kbd shift i - %td - = _('Go to issues') + %kbd shift t + %td= _('Go to your To-Do list') %tr %td.shortcut - %kbd shift m - %td - = _('Go to merge requests') + %kbd p + %kbd b + %td= _('Toggle the Performance Bar') + %tbody %tr - %td.shortcut - %kbd shift g - %td - = _('Go to groups') + %th + %th= _('Web IDE') %tr %td.shortcut - %kbd shift l - %td - = _('Go to milestones') + - if browser.platform.mac? + %kbd ⌘ p + - else + %kbd ctrl p + %td= _('Go to file') %tr %td.shortcut - %kbd shift s - %td - = _('Go to snippets') + - if browser.platform.mac? + %kbd ⌘ enter + - else + %kbd ctrl enter + %td= _('Commit (when editing commit message)') %tbody %tr %th - %th= _('Finding Project File') + %th= _('Wiki pages') %tr %td.shortcut - %kbd - %i.fa.fa-arrow-up - %td= _('Move selection up') + %kbd e + %td= _('Edit wiki page') + %tbody %tr - %td.shortcut - %kbd - %i.fa.fa-arrow-down - %td= _('Move selection down') + %th + %th= _('Editing') %tr %td.shortcut - %kbd enter - %td= _('Open Selection') + - if browser.platform.mac? + %kbd ⌘ shift p + - else + %kbd ctrl shift p + %td= _('Toggle Markdown preview') %tr %td.shortcut - %kbd esc - %td= _('Go back') + %kbd + %i.fa.fa-arrow-up + %td= _('Edit your most recent comment in a thread (from an empty textarea)') .col-lg-4 - %table.shortcut-mappings + %table.shortcut-mappings.text-2 %tbody %tr %th @@ -117,105 +113,94 @@ %td.shortcut %kbd g %kbd p - %td - = _('Go to the project\'s overview page') + %td= _('Go to the project\'s overview page') %tr %td.shortcut %kbd g %kbd v - %td - = _('Go to the project\'s activity feed') + %td= _('Go to the project\'s activity feed') %tr %td.shortcut %kbd g - %kbd f - %td - = _('Go to files') + %kbd r + %td= _('Go to releases') %tr %td.shortcut %kbd g - %kbd c - %td - = _('Go to commits') + %kbd f + %td= _('Go to files') + %tr + %td.shortcut + %kbd t + %td= _('Go to find file') %tr %td.shortcut %kbd g - %kbd j - %td - = _('Go to jobs') + %kbd c + %td= _('Go to commits') %tr %td.shortcut %kbd g %kbd n - %td - = _('Go to network graph') + %td= _('Go to repository graph') %tr %td.shortcut %kbd g %kbd d - %td - = _('Go to repository charts') + %td= _('Go to repository charts') %tr %td.shortcut %kbd g %kbd i - %td - = _('Go to issues') + %td= _('Go to issues') + %tr + %td.shortcut + %kbd i + %td= _('New issue') %tr %td.shortcut %kbd g %kbd b - %td - = _('Go to issue boards') + %td= _('Go to issue boards') %tr %td.shortcut %kbd g %kbd m - %td - = _('Go to merge requests') + %td= _('Go to merge requests') %tr %td.shortcut %kbd g - %kbd e - %td - = _('Go to environments') + %kbd j + %td= _('Go to jobs') %tr %td.shortcut %kbd g %kbd l - %td - = _('Go to metrics') + %td= _('Go to metrics') + %tr + %td.shortcut + %kbd g + %kbd e + %td= _('Go to environments') %tr %td.shortcut %kbd g %kbd k - %td - = _('Go to kubernetes') + %td= _('Go to kubernetes') %tr %td.shortcut %kbd g %kbd s - %td - = _('Go to snippets') + %td= _('Go to snippets') %tr %td.shortcut %kbd g %kbd w - %td - = _('Go to wiki') - %tr - %td.shortcut - %kbd t - %td= _('Go to finding file') - %tr - %td.shortcut - %kbd i - %td= _('New issue') - + %td= _('Go to wiki') %tbody %tr %th - %th= _('Project Files browsing') + %th= _('Project Files') %tr %td.shortcut %kbd @@ -230,38 +215,87 @@ %td.shortcut %kbd enter %td= _('Open Selection') - %tbody %tr - %th - %th= _('Project File') + %td.shortcut + %kbd esc + %td= _('Go back (while searching for files') %tr %td.shortcut %kbd y - %td= _('Go to file permalink') + %td= _('Go to file permalink (while viewing a file)') + .col-lg-4 + %table.shortcut-mappings.text-2 %tbody %tr %th - %th= _('Web IDE') + %th= _('Issues / Merge Requests') + %tr + %td.shortcut + %kbd a + %td= _('Change assignee') + %tr + %td.shortcut + %kbd m + %td= _('Change milestone') + %tr + %td.shortcut + %kbd r + %td= _('Comment/Reply (quoting selected text)') + %tr + %td.shortcut + %kbd e + %td= _('Edit description') + %tr + %td.shortcut + %kbd l + %td= _('Change label') + %tr + %td.shortcut + %kbd ] + \/ + %kbd j + %td= _('Next file in diff (MRs only)') + %tr + %td.shortcut + %kbd [ + \/ + %kbd k + %td= _('Previous file in diff (MRs only)') %tr %td.shortcut - if browser.platform.mac? %kbd ⌘ p - else %kbd ctrl p - %td= _('Go to file') + %td= _('Go to file (MRs only)') %tr %td.shortcut - - if browser.platform.mac? - %kbd ⌘ enter - - else - %kbd ctrl enter - %td= _('Commit (when editing commit message)') - .col-lg-4 - %table.shortcut-mappings - %tbody.hidden-shortcut.network{ style: 'display:none' } + %kbd n + %td= _('Next unresolved discussion (MRs only)') + %tr + %td.shortcut + %kbd p + %td= _('Previous unresolved discussion (MRs only)') + %tbody %tr %th - %th= _('Network Graph') + %th= _('Epics (Ultimate / Gold license only)') + %tr + %td.shortcut + %kbd r + %td= _('Comment/Reply (quoting selected text)') + %tr + %td.shortcut + %kbd e + %td= _('Edit epic description') + %tr + %td.shortcut + %kbd l + %td= _('Change label') + %tbody + %tr + %th + %th= _('Repository Graph') %tr %td.shortcut %kbd @@ -295,92 +329,12 @@ %kbd shift %i.fa.fa-arrow-up - \/ - %kbd - shift k + \/ k %td= _('Scroll to top') %tr %td.shortcut %kbd shift %i.fa.fa-arrow-down - \/ - %kbd - shift j + \/ j %td= _('Scroll to bottom') - %tbody.hidden-shortcut.issues{ style: 'display:none' } - %tr - %th - %th= _('Issues') - %tr - %td.shortcut - %kbd a - %td= _('Change assignee') - %tr - %td.shortcut - %kbd m - %td= _('Change milestone') - %tr - %td.shortcut - %kbd r - %td= _('Reply (quoting selected text)') - %tr - %td.shortcut - %kbd e - %td= _('Edit issue') - %tr - %td.shortcut - %kbd l - %td= _('Change Label') - %tbody.hidden-shortcut.merge_requests{ style: 'display:none' } - %tr - %th - %th= _('Merge Requests') - %tr - %td.shortcut - %kbd a - %td= _('Change assignee') - %tr - %td.shortcut - %kbd m - %td= _('Change milestone') - %tr - %td.shortcut - %kbd r - %td= _('Reply (quoting selected text)') - %tr - %td.shortcut - %kbd e - %td= _('Edit merge request') - %tr - %td.shortcut - %kbd l - %td= _('Change Label') - %tr - %td.shortcut - %kbd ] - \/ - %kbd j - %td= _('Move to next file') - %tr - %td.shortcut - %kbd [ - \/ - %kbd k - %td= _('Move to previous file') - %tr - %td.shortcut - %kbd n - %td= _('Move to next unresolved discussion') - %tr - %td.shortcut - %kbd p - %td= _('Move to previous unresolved discussion') - %tbody.hidden-shortcut.wiki{ style: 'display:none' } - %tr - %th - %th= _('Wiki pages') - %tr - %td.shortcut - %kbd e - %td= _('Edit wiki page') diff --git a/app/views/layouts/nav/sidebar/_group.html.haml b/app/views/layouts/nav/sidebar/_group.html.haml index c1f4b3adfec..7cc7d1783c4 100644 --- a/app/views/layouts/nav/sidebar/_group.html.haml +++ b/app/views/layouts/nav/sidebar/_group.html.haml @@ -48,14 +48,14 @@ - if group_sidebar_link?(:issues) = nav_link(path: group_issues_sub_menu_items) do - = link_to issues_group_path(@group) do + = link_to issues_group_path(@group), data: { qa_selector: 'group_issues_item' } do .nav-icon-container = sprite_icon('issues') %span.nav-item-name = _('Issues') %span.badge.badge-pill.count= number_with_delimiter(issues_count) - %ul.sidebar-sub-level-items + %ul.sidebar-sub-level-items{ data: { qa_selector: 'group_issues_sidebar_submenu'} } = nav_link(path: ['groups#issues', 'labels#index', 'milestones#index'], html_options: { class: "fly-out-top-item" } ) do = link_to issues_group_path(@group) do %strong.fly-out-top-item-name @@ -70,7 +70,7 @@ - if group_sidebar_link?(:boards) = nav_link(path: ['boards#index', 'boards#show']) do - = link_to group_boards_path(@group), title: boards_link_text do + = link_to group_boards_path(@group), title: boards_link_text, data: { qa_selector: 'group_issue_boards_link' } do %span = boards_link_text diff --git a/app/views/projects/error_tracking/index.html.haml b/app/views/projects/error_tracking/index.html.haml index bc02c5f0e5a..96f61584a99 100644 --- a/app/views/projects/error_tracking/index.html.haml +++ b/app/views/projects/error_tracking/index.html.haml @@ -1,3 +1,3 @@ - page_title _('Errors') -#js-error_tracking{ data: error_tracking_data(@project) } +#js-error_tracking{ data: error_tracking_data(@current_user, @project) } diff --git a/app/views/projects/merge_requests/_how_to_merge.html.haml b/app/views/projects/merge_requests/_how_to_merge.html.haml index 15499c89ffb..928b54ea28f 100644 --- a/app/views/projects/merge_requests/_how_to_merge.html.haml +++ b/app/views/projects/merge_requests/_how_to_merge.html.haml @@ -13,12 +13,13 @@ %pre.dark#merge-info-1 - if @merge_request.for_fork? :preserve - git fetch #{h default_url_to_repo(@merge_request.source_project)} #{h @merge_request.source_branch} - git checkout -b #{h @merge_request.source_project_path}-#{h @merge_request.source_branch} FETCH_HEAD + -# All repo/branch refs have been quoted to allow support for special characters (such as #my-branch) + git fetch "#{h default_url_to_repo(@merge_request.source_project)}" "#{h @merge_request.source_branch}" + git checkout -b "#{h @merge_request.source_project_path}-#{h @merge_request.source_branch}" FETCH_HEAD - else :preserve git fetch origin - git checkout -b #{h @merge_request.source_branch} origin/#{h @merge_request.source_branch} + git checkout -b "#{h @merge_request.source_branch}" "origin/#{h @merge_request.source_branch}" %p %strong Step 2. Review the changes locally @@ -31,20 +32,20 @@ - if @merge_request.for_fork? :preserve git fetch origin - git checkout origin/#{h @merge_request.target_branch} - git merge --no-ff #{h @merge_request.source_project_path}-#{h @merge_request.source_branch} + git checkout "origin/#{h @merge_request.target_branch}" + git merge --no-ff "#{h @merge_request.source_project_path}-#{h @merge_request.source_branch}" - else :preserve git fetch origin - git checkout origin/#{h @merge_request.target_branch} - git merge --no-ff #{h @merge_request.source_branch} + git checkout "origin/#{h @merge_request.target_branch}" + git merge --no-ff "#{h @merge_request.source_branch}" %p %strong Step 4. Push the result of the merge to GitLab = clipboard_button(target: "pre#merge-info-4", title: "Copy commands to clipboard") %pre.dark#merge-info-4 :preserve - git push origin #{h @merge_request.target_branch} + git push origin "#{h @merge_request.target_branch}" - unless @merge_request.can_be_merged_by?(current_user) %p Note that pushing to GitLab requires write access to this repository. diff --git a/app/views/projects/mirrors/_instructions.html.haml b/app/views/projects/mirrors/_instructions.html.haml index 1a163cc4a54..7ff6c0a2019 100644 --- a/app/views/projects/mirrors/_instructions.html.haml +++ b/app/views/projects/mirrors/_instructions.html.haml @@ -3,6 +3,7 @@ %li = _('The repository must be accessible over <code>http://</code>, <code>https://</code>, <code>ssh://</code> or <code>git://</code>.').html_safe + %li= _('When using the <code>http://</code> or <code>https://</code> protocols, please provide the exact URL to the repository. HTTP redirects will not be followed.').html_safe %li= _('Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>.').html_safe %li - minutes = Gitlab.config.gitlab_shell.git_timeout / 60 diff --git a/app/views/shared/_import_form.html.haml b/app/views/shared/_import_form.html.haml index d0f9374e832..b2ea45d6f1a 100644 --- a/app/views/shared/_import_form.html.haml +++ b/app/views/shared/_import_form.html.haml @@ -27,6 +27,7 @@ %ul %li = _('The repository must be accessible over <code>http://</code>, <code>https://</code> or <code>git://</code>.').html_safe + %li= _('When using the <code>http://</code> or <code>https://</code> protocols, please provide the exact URL to the repository. HTTP redirects will not be followed.').html_safe %li = _('If your HTTP repository is not publicly accessible, add your credentials.') %li diff --git a/app/views/shared/members/_sort_dropdown.html.haml b/app/views/shared/members/_sort_dropdown.html.haml index feca109dade..5f3d49adff7 100644 --- a/app/views/shared/members/_sort_dropdown.html.haml +++ b/app/views/shared/members/_sort_dropdown.html.haml @@ -1,4 +1,4 @@ -= label_tag :sort_by, 'Sort by', class: 'col-form-label label-bold pr-2' += label_tag :sort_by, 'Sort by', class: 'col-form-label label-bold px-2' .dropdown.inline.qa-user-sort-dropdown = dropdown_toggle(member_sort_options_hash[@sort], { toggle: 'dropdown' }) %ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml index 991a177018e..a33afd436b0 100644 --- a/app/workers/all_queues.yml +++ b/app/workers/all_queues.yml @@ -160,6 +160,7 @@ - repository_import - repository_remove_remote - system_hook_push +- update_external_pull_requests - update_merge_requests - update_project_statistics - upload_checksum diff --git a/app/workers/update_external_pull_requests_worker.rb b/app/workers/update_external_pull_requests_worker.rb new file mode 100644 index 00000000000..c5acfa82ada --- /dev/null +++ b/app/workers/update_external_pull_requests_worker.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +class UpdateExternalPullRequestsWorker + include ApplicationWorker + + def perform(project_id, user_id, ref) + project = Project.find_by_id(project_id) + return unless project + + user = User.find_by_id(user_id) + return unless user + + branch = Gitlab::Git.branch_name(ref) + return unless branch + + external_pull_requests = project.external_pull_requests + .by_source_repository(project.import_source) + .by_source_branch(branch) + + external_pull_requests.find_each do |pull_request| + ExternalPullRequests::CreatePipelineService.new(project, user) + .execute(pull_request) + end + end +end |