diff options
74 files changed, 773 insertions, 287 deletions
diff --git a/app/assets/javascripts/boards/models/list.js b/app/assets/javascripts/boards/models/list.js index dd3feedbc0e..9f6d9a853da 100644 --- a/app/assets/javascripts/boards/models/list.js +++ b/app/assets/javascripts/boards/models/list.js @@ -244,6 +244,7 @@ class List { issue.project = data.project; issue.path = data.real_path; issue.referencePath = data.reference_path; + issue.assignableLabelsEndpoint = data.assignable_labels_endpoint; if (this.issuesSize > 1) { const moveBeforeId = this.issues[1].id; diff --git a/app/assets/javascripts/diffs/components/commit_item.vue b/app/assets/javascripts/diffs/components/commit_item.vue index ebc4a83af4d..c02a8740a42 100644 --- a/app/assets/javascripts/diffs/components/commit_item.vue +++ b/app/assets/javascripts/diffs/components/commit_item.vue @@ -5,6 +5,7 @@ import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; import CIIcon from '~/vue_shared/components/ci_icon.vue'; import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import CommitPipelineStatus from '~/projects/tree/components/commit_pipeline_status_component.vue'; +import initUserPopovers from '../../user_popovers'; /** * CommitItem @@ -35,20 +36,30 @@ export default { }, }, computed: { + author() { + return this.commit.author || {}; + }, authorName() { - return (this.commit.author && this.commit.author.name) || this.commit.author_name; + return this.author.name || this.commit.author_name; + }, + authorClass() { + return this.author.name ? 'js-user-link' : ''; + }, + authorId() { + return this.author.id ? this.author.id : ''; }, authorUrl() { - return ( - (this.commit.author && this.commit.author.web_url) || `mailto:${this.commit.author_email}` - ); + return this.author.web_url || `mailto:${this.commit.author_email}`; }, authorAvatar() { - return ( - (this.commit.author && this.commit.author.avatar_url) || this.commit.author_gravatar_url - ); + return this.author.avatar_url || this.commit.author_gravatar_url; }, }, + created() { + this.$nextTick(() => { + initUserPopovers(this.$el.querySelectorAll('.js-user-link')); + }); + }, }; </script> @@ -81,7 +92,13 @@ export default { </button> <div class="commiter"> - <a :href="authorUrl" v-text="authorName"></a> {{ s__('CommitWidget|authored') }} + <a + :href="authorUrl" + :class="authorClass" + :data-user-id="authorId" + v-text="authorName" + ></a> + {{ s__('CommitWidget|authored') }} <time-ago-tooltip :time="commit.authored_date" /> </div> diff --git a/app/assets/javascripts/gl_form.js b/app/assets/javascripts/gl_form.js index f842d2d74db..f5e2e46237f 100644 --- a/app/assets/javascripts/gl_form.js +++ b/app/assets/javascripts/gl_form.js @@ -51,8 +51,6 @@ export default class GLForm { // form and textarea event listeners this.addEventListeners(); addMarkdownListeners(this.form); - // hide discard button - this.form.find('.js-note-discard').hide(); this.form.show(); if (this.isAutosizeable) this.setupAutosize(); } diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index dfb53c986fc..c3443c300e3 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -138,8 +138,6 @@ export default class Notes { this.$wrapperEl.on('click', '.js-note-delete', this.removeNote); // delete note attachment this.$wrapperEl.on('click', '.js-note-attachment-delete', this.removeAttachment); - // reset main target form when clicking discard - this.$wrapperEl.on('click', '.js-note-discard', this.resetMainTargetForm); // update the file name when an attachment is selected this.$wrapperEl.on('change', '.js-note-attachment-input', this.updateFormAttachment); // reply to diff/discussion notes @@ -191,7 +189,6 @@ export default class Notes { this.$wrapperEl.off('keyup input', '.js-note-text'); this.$wrapperEl.off('click', '.js-note-target-reopen'); this.$wrapperEl.off('click', '.js-note-target-close'); - this.$wrapperEl.off('click', '.js-note-discard'); this.$wrapperEl.off('keydown', '.js-note-text'); this.$wrapperEl.off('click', '.js-comment-resolve-button'); this.$wrapperEl.off('click', '.system-note-commit-list-toggler'); @@ -986,11 +983,9 @@ export default class Notes { form.find('#note_position').val(dataHolder.attr('data-position')); form - .find('.js-note-discard') + .find('.js-close-discussion-note-form') .show() - .removeClass('js-note-discard') - .addClass('js-close-discussion-note-form') - .text(form.find('.js-close-discussion-note-form').data('cancelText')); + .removeClass('hide'); form.find('.js-note-target-close').remove(); form.find('.js-note-new-discussion').remove(); this.setupNoteForm(form); @@ -1194,12 +1189,11 @@ export default class Notes { } updateTargetButtons(e) { - var closebtn, closetext, discardbtn, form, reopenbtn, reopentext, textarea; + var closebtn, closetext, form, reopenbtn, reopentext, textarea; textarea = $(e.target); form = textarea.parents('form'); reopenbtn = form.find('.js-note-target-reopen'); closebtn = form.find('.js-note-target-close'); - discardbtn = form.find('.js-note-discard'); if (textarea.val().trim().length > 0) { reopentext = reopenbtn.attr('data-alternative-text'); @@ -1216,9 +1210,6 @@ export default class Notes { if (closebtn.is(':not(.btn-comment-and-close)')) { closebtn.addClass('btn-comment-and-close'); } - if (discardbtn.is(':hidden')) { - return discardbtn.show(); - } } else { reopentext = reopenbtn.data('originalText'); closetext = closebtn.data('originalText'); @@ -1234,9 +1225,6 @@ export default class Notes { if (closebtn.is('.btn-comment-and-close')) { closebtn.removeClass('btn-comment-and-close'); } - if (discardbtn.is(':visible')) { - return discardbtn.hide(); - } } } diff --git a/app/assets/javascripts/notes/components/comment_form.vue b/app/assets/javascripts/notes/components/comment_form.vue index ce56beb1e6b..8bf02327cd2 100644 --- a/app/assets/javascripts/notes/components/comment_form.vue +++ b/app/assets/javascripts/notes/components/comment_form.vue @@ -431,15 +431,6 @@ append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown" :label="issueActionButtonTitle" @click="handleSave(true);" /> - - <button - v-if="note.length" - type="button" - class="btn btn-cancel js-note-discard" - @click="discard" - > - Discard draft - </button> </div> </form> </div> diff --git a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue index 08c7719dcf2..19d9903c988 100644 --- a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue +++ b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue @@ -325,8 +325,8 @@ export default { <project-setting-row v-if="pagesAvailable && pagesAccessControlEnabled" :help-path="pagesHelpPath" - label="Pages" - help-text="Static website for the project." + label="Pages access control" + help-text="Access control for the project's static website" > <project-feature-setting v-model="pagesAccessLevel" diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss index e037b02a30c..a499a3a9f95 100644 --- a/app/assets/stylesheets/framework/common.scss +++ b/app/assets/stylesheets/framework/common.scss @@ -384,10 +384,17 @@ img.emoji { .flex-align-self-center { align-self: center; } .flex-grow { flex-grow: 1; } .flex-no-shrink { flex-shrink: 0; } -.mw-460 { max-width: 460px; } .ws-initial { white-space: initial; } +.overflow-auto { overflow: auto; } + +/** COMMON SIZING CLASSES **/ +.w-0 { width: 0; } +.h-13em { height: 13em; } +.mw-460 { max-width: 460px; } +.mw-6em { max-width: 6em; } .min-height-0 { min-height: 0; } +/** COMMON SPACING CLASSES **/ .gl-pl-0 { padding-left: 0; } .gl-pl-1 { padding-left: #{0.5 * $grid-size}; } .gl-pl-2 { padding-left: $grid-size; } diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss index 4da2243981e..0a0ef2071e9 100644 --- a/app/assets/stylesheets/framework/forms.scss +++ b/app/assets/stylesheets/framework/forms.scss @@ -36,6 +36,15 @@ label { } } +.label-wrapper { + display: block; + margin: 0; +} + +.form-label { + @extend label; +} + .form-control-label { @extend .col-md-2; } diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss index a68f1e4e570..bcd601e198a 100644 --- a/app/assets/stylesheets/framework/selects.scss +++ b/app/assets/stylesheets/framework/selects.scss @@ -57,6 +57,16 @@ color: $gl-text-color; } } + + &.is-invalid { + ~ .invalid-feedback { + display: block; + } + + .select2-choices { + border-color: $red-500; + } + } } .select2-drop, @@ -67,10 +77,18 @@ min-width: 175px; color: $gl-text-color; z-index: 999; + + .modal-open & { + z-index: $zindex-modal + 200; + } } .select2-drop-mask { z-index: 998; + + .modal-open & { + z-index: $zindex-modal + 100; + } } .select2-drop.select2-drop-above.select2-drop-active { diff --git a/app/assets/stylesheets/framework/tables.scss b/app/assets/stylesheets/framework/tables.scss index 6954e6599b1..295a5b5ee7a 100644 --- a/app/assets/stylesheets/framework/tables.scss +++ b/app/assets/stylesheets/framework/tables.scss @@ -50,6 +50,14 @@ table { border-color: $white-normal; } } + + .thead-white { + th { + background-color: $white-light; + color: $gl-text-color-secondary; + border-top: 0; + } + } } &.responsive-table { @@ -153,3 +161,4 @@ table { border-top: 0; } } + diff --git a/app/controllers/projects/ci/lints_controller.rb b/app/controllers/projects/ci/lints_controller.rb index 2090af0a111..d7a0b7ece14 100644 --- a/app/controllers/projects/ci/lints_controller.rb +++ b/app/controllers/projects/ci/lints_controller.rb @@ -8,7 +8,7 @@ class Projects::Ci::LintsController < Projects::ApplicationController def create @content = params[:content] - @error = Gitlab::Ci::YamlProcessor.validation_message(@content, yaml_processor_options) + @error = Gitlab::Ci::YamlProcessor.validation_message(@content, yaml_processor_options) @status = @error.blank? if @error.blank? @@ -24,6 +24,10 @@ class Projects::Ci::LintsController < Projects::ApplicationController private def yaml_processor_options - { project: @project, sha: project.repository.commit.sha } + { + project: @project, + user: current_user, + sha: project.repository.commit.sha + } end end diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb index d52cfd6e37a..d58f634425b 100644 --- a/app/helpers/commits_helper.rb +++ b/app/helpers/commits_helper.rb @@ -154,7 +154,7 @@ module CommitsHelper if user.nil? mail_to(source_email, text, link_options) else - link_to(text, user_path(user), link_options) + link_to(text, user_path(user), { class: "commit-#{options[:source]}-link js-user-link", data: { user_id: user.id } }) end end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index fcab8cc3380..af7a262e32c 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -488,7 +488,7 @@ module ProjectsHelper lfsHelpPath: help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs'), pagesAvailable: Gitlab.config.pages.enabled, pagesAccessControlEnabled: Gitlab.config.pages.access_control, - pagesHelpPath: help_page_path('user/project/pages/index.md') + pagesHelpPath: help_page_path('user/project/pages/introduction', anchor: 'gitlab-pages-access-control-core-only') } end diff --git a/app/helpers/user_callouts_helper.rb b/app/helpers/user_callouts_helper.rb index 4aba48061ba..1ad7bb81784 100644 --- a/app/helpers/user_callouts_helper.rb +++ b/app/helpers/user_callouts_helper.rb @@ -13,6 +13,10 @@ module UserCalloutsHelper !user_dismissed?(GCP_SIGNUP_OFFER) end + def render_flash_user_callout(flash_type, message, feature_name) + render 'shared/flash_user_callout', flash_type: flash_type, message: message, feature_name: feature_name + end + private def user_dismissed?(feature_name) diff --git a/app/models/blob_viewer/gitlab_ci_yml.rb b/app/models/blob_viewer/gitlab_ci_yml.rb index 655241c2808..11228e620c9 100644 --- a/app/models/blob_viewer/gitlab_ci_yml.rb +++ b/app/models/blob_viewer/gitlab_ci_yml.rb @@ -10,16 +10,16 @@ module BlobViewer self.file_types = %i(gitlab_ci) self.binary = false - def validation_message(project, sha) + def validation_message(opts) return @validation_message if defined?(@validation_message) prepare! - @validation_message = Gitlab::Ci::YamlProcessor.validation_message(blob.data, { project: project, sha: sha }) + @validation_message = Gitlab::Ci::YamlProcessor.validation_message(blob.data, opts) end - def valid?(project, sha) - validation_message(project, sha).blank? + def valid?(opts) + validation_message(opts).blank? end end end diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 01134e133db..30a957b4117 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -178,6 +178,15 @@ module Ci scope :for_user, -> (user) { where(user: user) } + scope :for_merge_request, -> (merge_request, ref, sha) do + ## + # We have to filter out unrelated MR pipelines. + # When merge request is empty, it selects general pipelines, such as push sourced pipelines. + # When merge request is matched, it selects MR pipelines. + where(merge_request: [nil, merge_request], ref: ref, sha: sha) + .sort_by_merge_request_pipelines + end + # Returns the pipelines in descending order (= newest first), optionally # limited to a number of references. # @@ -265,6 +274,10 @@ module Ci sources.reject { |source| source == "external" }.values end + def self.latest_for_merge_request(merge_request, ref, sha) + for_merge_request(merge_request, ref, sha).first + end + def self.ci_sources_values config_sources.values_at(:repository_source, :auto_devops_source, :unknown_source) end @@ -496,7 +509,7 @@ module Ci return @config_processor if defined?(@config_processor) @config_processor ||= begin - ::Gitlab::Ci::YamlProcessor.new(ci_yaml_file, { project: project, sha: sha }) + ::Gitlab::Ci::YamlProcessor.new(ci_yaml_file, { project: project, sha: sha, user: user }) rescue Gitlab::Ci::YamlProcessor::ValidationError => e self.yaml_errors = e.message nil diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 6092c56b925..613860ec31a 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -1092,10 +1092,15 @@ class MergeRequest < ActiveRecord::Base def all_pipelines(shas: all_commit_shas) return Ci::Pipeline.none unless source_project - @all_pipelines ||= source_project.ci_pipelines - .where(sha: shas, ref: source_branch) - .where(merge_request: [nil, self]) - .sort_by_merge_request_pipelines + @all_pipelines ||= + source_project.ci_pipelines + .for_merge_request(self, source_branch, all_commit_shas) + end + + def update_head_pipeline + self.head_pipeline = find_actual_head_pipeline + + update_column(:head_pipeline_id, head_pipeline.id) if head_pipeline_id_changed? end def merge_request_pipeline_exists? @@ -1338,4 +1343,11 @@ class MergeRequest < ActiveRecord::Base source_project.repository.squash_in_progress?(id) end + + private + + def find_actual_head_pipeline + source_project&.ci_pipelines + &.latest_for_merge_request(self, source_branch, diff_head_sha) + end end diff --git a/app/services/merge_requests/create_service.rb b/app/services/merge_requests/create_service.rb index 7bb9fa60515..02c2388c05c 100644 --- a/app/services/merge_requests/create_service.rb +++ b/app/services/merge_requests/create_service.rb @@ -26,7 +26,7 @@ module MergeRequests todo_service.new_merge_request(issuable, current_user) issuable.cache_merge_request_closes_issues!(current_user) create_merge_request_pipeline(issuable, current_user) - update_merge_requests_head_pipeline(issuable) + issuable.update_head_pipeline super end @@ -45,20 +45,6 @@ module MergeRequests private - def update_merge_requests_head_pipeline(merge_request) - pipeline = head_pipeline_for(merge_request) - merge_request.update(head_pipeline_id: pipeline.id) if pipeline - end - - def head_pipeline_for(merge_request) - return unless merge_request.source_project - - sha = merge_request.source_branch_sha - return unless sha - - merge_request.all_pipelines(shas: sha).first - end - def set_projects! # @project is used to determine whether the user can set the merge request's # assignee, milestone and labels. Whether they can depends on their diff --git a/app/views/admin/users/_user.html.haml b/app/views/admin/users/_user.html.haml index b2163ee85fa..a4e2c3252af 100644 --- a/app/views/admin/users/_user.html.haml +++ b/app/views/admin/users/_user.html.haml @@ -3,7 +3,7 @@ = image_tag avatar_icon_for_user(user), class: "avatar", alt: '' .row-main-content .user-name.row-title.str-truncated-100 - = link_to user.name, [:admin, user] + = link_to user.name, [:admin, user], class: "js-user-link", data: { user_id: user.id } - if user.blocked? %span.badge.badge-danger blocked - if user.admin? diff --git a/app/views/projects/blob/viewers/_gitlab_ci_yml.html.haml b/app/views/projects/blob/viewers/_gitlab_ci_yml.html.haml index 5be7cc7f25a..61d67a88a5a 100644 --- a/app/views/projects/blob/viewers/_gitlab_ci_yml.html.haml +++ b/app/views/projects/blob/viewers/_gitlab_ci_yml.html.haml @@ -1,9 +1,9 @@ -- if viewer.valid?(@project, @commit.sha) +- if viewer.valid?(project: @project, sha: @commit.sha, user: @current_user) = icon('check fw') This GitLab CI configuration is valid. - else = icon('warning fw') This GitLab CI configuration is invalid: - = viewer.validation_message(@project, @commit.sha) + = viewer.validation_message(project: @project, sha: @commit.sha, user: @current_user) = link_to 'Learn more', help_page_path('ci/yaml/README') diff --git a/app/views/projects/releases/index.html.haml b/app/views/projects/releases/index.html.haml index f01d4e826b9..28bb4e032eb 100644 --- a/app/views/projects/releases/index.html.haml +++ b/app/views/projects/releases/index.html.haml @@ -2,4 +2,4 @@ - page_title _('Releases') %div{ class: container_class } - #js-releases-page{ data: { project_id: @project.id, illustration_path: image_path('illustrations/releases.svg'), documentation_path: help_page_path('user/project/releases') } } + #js-releases-page{ data: { project_id: @project.id, illustration_path: image_path('illustrations/releases.svg'), documentation_path: help_page_path('user/project/releases/index') } } diff --git a/app/views/shared/_flash_user_callout.html.haml b/app/views/shared/_flash_user_callout.html.haml new file mode 100644 index 00000000000..fe175195e66 --- /dev/null +++ b/app/views/shared/_flash_user_callout.html.haml @@ -0,0 +1,11 @@ +- callout_data = { uid: "callout_feature_#{feature_name}_dismissed", feature_id: feature_name, dismiss_endpoint: user_callouts_path } +- extra_flash_class = local_assigns.fetch(:extra_flash_class, nil) + +.flash-container.flash-container-page.user-callout{ data: callout_data } + -# We currently only support `alert`, `warning`, `notice`, `success` + %div{ class: "flash-#{flash_type}" } + %div{ class: "#{(container_class unless fluid_layout)} #{(extra_flash_class unless @no_container)} #{@content_class}" } + %span= message + %button.btn.btn-default.close.js-close{ type: 'button', + 'aria-label' => _('Dismiss') } + = sprite_icon('close', css_class: 'dismiss-icon') diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml deleted file mode 100644 index 2ca4657851c..00000000000 --- a/app/views/shared/issuable/_filter.html.haml +++ /dev/null @@ -1,32 +0,0 @@ -.issues-filters - .issues-details-filters.row-content-block.second-block - = form_tag page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name, :search]), method: :get, class: 'filter-form js-filter-form' do - - if params[:search].present? - = hidden_field_tag :search, params[:search] - .issues-other-filters - .filter-item.inline - - if params[:author_id].present? - = hidden_field_tag(:author_id, params[:author_id]) - = dropdown_tag(user_dropdown_label(params[:author_id], "Author"), options: { toggle_class: "js-user-search js-filter-submit js-author-search", title: "Filter by author", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-author js-filter-submit", - placeholder: "Search authors", data: { any_user: "Any Author", first_user: current_user&.username, current_user: true, project_id: @project&.id, group_id: @group&.id, selected: params[:author_id], field_name: "author_id", default_label: "Author" } }) - - .filter-item.inline - - if params[:assignee_id].present? - = hidden_field_tag(:assignee_id, params[:assignee_id]) - = dropdown_tag(user_dropdown_label(params[:assignee_id], "Assignee"), options: { toggle_class: "js-user-search js-filter-submit js-assignee-search", title: "Filter by assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee js-filter-submit", - placeholder: "Search assignee", data: { any_user: "Any Assignee", first_user: current_user&.username, null_user: true, current_user: true, project_id: @project&.id, group_id: @group&.id, selected: params[:assignee_id], field_name: "assignee_id", default_label: "Assignee" } }) - - .filter-item.inline.milestone-filter - = render "shared/issuable/milestone_dropdown", selected: finder.milestones.try(:first), name: :milestone_title, show_any: true, show_upcoming: true, show_started: true - - .filter-item.inline.labels-filter - = render "shared/issuable/label_dropdown", selected: selected_labels, use_id: false, selected_toggle: params[:label_name], data_options: { field_name: "label_name[]" } - - - unless @no_filters_set - .float-right - = render 'shared/issuable/sort_dropdown' - - - has_labels = @labels && @labels.any? - .row-content-block.second-block.filtered-labels{ class: ("hidden" unless has_labels) } - - if has_labels - = render 'shared/labels_row', labels: @labels diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml index 6b3841ebbc4..2db1f67a793 100644 --- a/app/views/shared/members/_member.html.haml +++ b/app/views/shared/members/_member.html.haml @@ -10,7 +10,7 @@ - if user = image_tag avatar_icon_for_user(user, 40), class: "avatar s40", alt: '' .user-info - = link_to user.name, user_path(user), class: 'member' + = link_to user.name, user_path(user), class: 'member js-user-link', data: { user_id: user.id } = user_status(user) %span.cgray= user.to_reference diff --git a/app/views/shared/notes/_form.html.haml b/app/views/shared/notes/_form.html.haml index c360f1ffe2a..493c6241257 100644 --- a/app/views/shared/notes/_form.html.haml +++ b/app/views/shared/notes/_form.html.haml @@ -40,5 +40,5 @@ = yield(:note_actions) - %a.btn.btn-cancel.js-note-discard{ role: "button", data: {cancel_text: "Cancel" } } - Discard draft + %a.btn.btn-cancel.js-close-discussion-note-form.hide{ role: "button", data: {cancel_text: "Cancel" } } + Cancel diff --git a/app/views/shared/snippets/_snippet.html.haml b/app/views/shared/snippets/_snippet.html.haml index 5069e2e4ca6..42af97bc6af 100644 --- a/app/views/shared/snippets/_snippet.html.haml +++ b/app/views/shared/snippets/_snippet.html.haml @@ -25,7 +25,7 @@ #{snippet.to_reference} · authored #{time_ago_with_tooltip(snippet.created_at, placement: 'bottom', html_class: 'snippet-created-ago')} by - = link_to user_snippets_path(snippet.author) do + = link_to user_snippets_path(snippet.author), class: "js-user-link", data: { user_id: snippet.author.id } do = snippet.author_name - if link_project && snippet.project_id? %span.d-none.d-sm-inline-block diff --git a/app/workers/update_head_pipeline_for_merge_request_worker.rb b/app/workers/update_head_pipeline_for_merge_request_worker.rb index e8494ffa002..4ec2b9d8fbe 100644 --- a/app/workers/update_head_pipeline_for_merge_request_worker.rb +++ b/app/workers/update_head_pipeline_for_merge_request_worker.rb @@ -7,25 +7,8 @@ class UpdateHeadPipelineForMergeRequestWorker queue_namespace :pipeline_processing def perform(merge_request_id) - merge_request = MergeRequest.find(merge_request_id) - - sha = merge_request.diff_head_sha - pipeline = merge_request.all_pipelines(shas: sha).first - - return unless pipeline && pipeline.latest? - - if merge_request.diff_head_sha != pipeline.sha - log_error_message_for(merge_request) - - return + MergeRequest.find_by_id(merge_request_id).try do |merge_request| + merge_request.update_head_pipeline end - - merge_request.update_attribute(:head_pipeline_id, pipeline.id) - end - - def log_error_message_for(merge_request) - Rails.logger.error( - "Outdated head pipeline for active merge request: id=#{merge_request.id}, source_branch=#{merge_request.source_branch}, diff_head_sha=#{merge_request.diff_head_sha}" - ) end end diff --git a/changelogs/unreleased/53796-discard-draft-comment-button-to-easy-to-accidentally-hit-on-mobile.yml b/changelogs/unreleased/53796-discard-draft-comment-button-to-easy-to-accidentally-hit-on-mobile.yml new file mode 100644 index 00000000000..083b5f21a52 --- /dev/null +++ b/changelogs/unreleased/53796-discard-draft-comment-button-to-easy-to-accidentally-hit-on-mobile.yml @@ -0,0 +1,5 @@ +--- +title: Removed discard draft comment button form notes +merge_request: 24185 +author: +type: removed diff --git a/changelogs/unreleased/54142-pages-in-project-s-permission-should-be-named-pages-access-control.yml b/changelogs/unreleased/54142-pages-in-project-s-permission-should-be-named-pages-access-control.yml new file mode 100644 index 00000000000..b45ebaa1a02 --- /dev/null +++ b/changelogs/unreleased/54142-pages-in-project-s-permission-should-be-named-pages-access-control.yml @@ -0,0 +1,5 @@ +--- +title: Make the Pages permission setting more clear +merge_request: 23146 +author: +type: changed diff --git a/changelogs/unreleased/54311-fix-board-add-label.yml b/changelogs/unreleased/54311-fix-board-add-label.yml new file mode 100644 index 00000000000..8fd8f7a0381 --- /dev/null +++ b/changelogs/unreleased/54311-fix-board-add-label.yml @@ -0,0 +1,5 @@ +--- +title: Fix error when creating labels in a new issue in the boards page +merge_request: 24039 +author: Ruben Moya +type: fixed diff --git a/changelogs/unreleased/54981-extended-user-centric-tooltips-add-missing-cases.yml b/changelogs/unreleased/54981-extended-user-centric-tooltips-add-missing-cases.yml new file mode 100644 index 00000000000..25ae6d88428 --- /dev/null +++ b/changelogs/unreleased/54981-extended-user-centric-tooltips-add-missing-cases.yml @@ -0,0 +1,5 @@ +--- +title: User Popovers for Commit Infos, Member Lists and Snippets +merge_request: 24132 +author: +type: added diff --git a/changelogs/unreleased/55670-remove-app-views-shared-issuable-_filter-html-haml.yml b/changelogs/unreleased/55670-remove-app-views-shared-issuable-_filter-html-haml.yml new file mode 100644 index 00000000000..9d37f798250 --- /dev/null +++ b/changelogs/unreleased/55670-remove-app-views-shared-issuable-_filter-html-haml.yml @@ -0,0 +1,5 @@ +--- +title: Remove app/views/shared/issuable/_filter.html.haml +merge_request: 24008 +author: Takuya Noguchi +type: other diff --git a/changelogs/unreleased/deprecated-migration-inheritance-2.yml b/changelogs/unreleased/deprecated-migration-inheritance-2.yml new file mode 100644 index 00000000000..467a521dbd4 --- /dev/null +++ b/changelogs/unreleased/deprecated-migration-inheritance-2.yml @@ -0,0 +1,5 @@ +--- +title: ActiveRecord::Migration -> ActiveRecord::Migration[5.0] for AddIndexesToCiBuildsAndPipelines +merge_request: 24167 +author: Jasper Maes +type: other diff --git a/changelogs/unreleased/include-project.yml b/changelogs/unreleased/include-project.yml new file mode 100644 index 00000000000..c63ac490d21 --- /dev/null +++ b/changelogs/unreleased/include-project.yml @@ -0,0 +1,5 @@ +--- +title: Allow to include files from another projects in gitlab-ci.yml +merge_request: 24101 +author: +type: added diff --git a/changelogs/unreleased/sh-fix-issue-55914.yml b/changelogs/unreleased/sh-fix-issue-55914.yml new file mode 100644 index 00000000000..f6f372f59c7 --- /dev/null +++ b/changelogs/unreleased/sh-fix-issue-55914.yml @@ -0,0 +1,5 @@ +--- +title: Fix Bitbucket Server import only including first 25 pull requests +merge_request: 24178 +author: +type: fixed diff --git a/changelogs/unreleased/user-update-head-pipeline-worker.yml b/changelogs/unreleased/user-update-head-pipeline-worker.yml new file mode 100644 index 00000000000..fd88697f239 --- /dev/null +++ b/changelogs/unreleased/user-update-head-pipeline-worker.yml @@ -0,0 +1,5 @@ +--- +title: Refactor the logic of updating head pipelines for merge requests +merge_request: 23502 +author: +type: other diff --git a/db/migrate/20181119132520_add_indexes_to_ci_builds_and_pipelines.rb b/db/migrate/20181119132520_add_indexes_to_ci_builds_and_pipelines.rb index adbc3928b26..cb01fa113eb 100644 --- a/db/migrate/20181119132520_add_indexes_to_ci_builds_and_pipelines.rb +++ b/db/migrate/20181119132520_add_indexes_to_ci_builds_and_pipelines.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class AddIndexesToCiBuildsAndPipelines < ActiveRecord::Migration +class AddIndexesToCiBuildsAndPipelines < ActiveRecord::Migration[5.0] include Gitlab::Database::MigrationHelpers DOWNTIME = false diff --git a/doc/api/releases.md b/doc/api/releases.md index bfd0cc1c4ea..4613fe3482a 100644 --- a/doc/api/releases.md +++ b/doc/api/releases.md @@ -1,7 +1,7 @@ # Releases API > - [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/41766) in GitLab 11.7. -> - Using this API you can manipulate GitLab's [Release](../user/project/releases.md) entries. +> - Using this API you can manipulate GitLab's [Release](../user/project/releases/index.md) entries. ## List Releases diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index fe09af24fa3..98542c7e82c 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -1649,7 +1649,7 @@ test: > Behaviour expanded in GitLab 10.8 to allow more flexible overriding. > [Moved](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/21603) to GitLab Core in 11.4 -> In GitLab 11.7, support for including [GitLab-supplied templates](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/lib/gitlab/ci/templates) directly [was added](https://gitlab.com/gitlab-org/gitlab-ce/issues/53445). +> In GitLab 11.7, support for [including GitLab-supplied templates directly](https://gitlab.com/gitlab-org/gitlab-ce/issues/53445) and support for [including templates from another repository](https://gitlab.com/gitlab-org/gitlab-ce/issues/53903) was added. Using the `include` keyword, you can allow the inclusion of external YAML files. @@ -1724,7 +1724,7 @@ include: --- -`include` supports three types of files: +`include` supports four types of files: - **local** to the same repository, referenced by using full paths in the same repository, with `/` being the root directory. For example: @@ -1750,6 +1750,32 @@ include: NOTE: **Note:** We don't support the inclusion of local files through Git submodules paths. +- **file** from another repository, referenced by using full paths in the same + repository, with `/` being the root directory. For example: + + ```yaml + include: + project: 'my-group/my-project' + file: '/templates/.gitlab-ci-template.yml' + ``` + + You can also specify `ref:`. The default `ref:` is the `HEAD` of the project: + + ```yaml + include: + - project: 'my-group/my-project' + ref: master + file: '/templates/.gitlab-ci-template.yml' + + - project: 'my-group/my-project' + ref: v1.0.0 + file: '/templates/.gitlab-ci-template.yml' + + - project: 'my-group/my-project' + ref: 787123b47f14b552955ca2786bc9542ae66fee5b # git sha + file: '/templates/.gitlab-ci-template.yml' + ``` + - **remote** in a different location, accessed using HTTP/HTTPS, referenced using the full URL. For example: diff --git a/doc/development/documentation/styleguide.md b/doc/development/documentation/styleguide.md index 829dcf18926..a18c21d921a 100644 --- a/doc/development/documentation/styleguide.md +++ b/doc/development/documentation/styleguide.md @@ -62,7 +62,7 @@ with files organized in the [correct directory](index.md#documentation-directory link out to their external sites, documentation, and resources. - Do not duplicate information. - Structure content in alphabetical order in tables, lists, etc., unless there is -a logical reason not to (for example, when mirroring the UI or an ordered sequence). +a logical reason not to (for example, when mirroring the UI or an ordered sequence). ## Language @@ -210,7 +210,7 @@ For other punctuation rules, please refer to the - Use inline link markdown markup `[Text](https://example.com)`. It's easier to read, review, and maintain. **Do not** use `[Text][identifier]`. - To link to internal documentation, use relative links, not full URLs. Use `../` to - navigate tp high-level directories, and always add the file name `file.md` at the + navigate to high-level directories, and always add the file name `file.md` at the end of the link with the `.md` extension, not `.html`. Example: instead of `[text](../../merge_requests/)`, use `[text](../../merge_requests/index.md)` or, `[text](../../ci/README.md)`, or, @@ -386,8 +386,32 @@ Which renders to: ## Specific sections and terms -To mention and/or reference specific terms in GitLab, please follow the styles -below. +To maintain consistency through GitLab documentation, the following guides documentation authors +on agreed styles and usage of terms. + +### Describing UI elements + +The following are styles to follow when describing UI elements on a screen: + +- For elements with a visible label, use that label in bold with matching case. For example, `the **Cancel** button`. +- For elements with a tooltip or hover label, use that label in bold with matching case. For example, `the **Add status emoji** button`. + +### Verbs for UI elements + +The following are recommended verbs for specific uses. + +| Recommended | Used for | Alternatives | +|:------------|:---------------------------|:---------------------------| +| "click" | buttons, links, menu items | "hit", "press", "select" | +| "check" | checkboxes | "enable", "click", "press" | +| "select" | dropdowns | "pick" | +| "expand" | expandable sections | "open" | + +### Other Verbs + +| Recommended | Used for | Alternatives | +|:------------|:--------------------------------|:-------------------| +| "go" | making a browser go to location | "navigate", "open" | ### GitLab versions and tiers @@ -460,6 +484,10 @@ the special markup `**[STARTER]**` will generate a `span` element to trigger the badges and tooltips (`<span class="badge-trigger starter">`). When the keyword "only" is added, the corresponding GitLab.com badge will not be displayed. +## Specific sections + +Certain styles should be applied to specific sections. Styles for specific sections are outlined below. + ### GitLab Restart There are many cases that a restart/reconfigure of GitLab is required. To @@ -536,13 +564,64 @@ the style below as a guide: In this case: -- Before each step list the installation method is declared in bold +- Before each step list the installation method is declared in bold. - Three dashes (`---`) are used to create a horizontal line and separate the - two methods + two methods. - The code blocks are indented one or more spaces under the list item to render - correctly -- Different highlighting languages are used for each config in the code block -- The [references](#references) guide is used for reconfigure/restart + correctly. +- Different highlighting languages are used for each config in the code block. +- The [references](#references) guide is used for reconfigure/restart. + +## API + +Here is a list of must-have items. Use them in the exact order that appears +on this document. Further explanation is given below. + +- Every method must have the REST API request. For example: + + ``` + GET /projects/:id/repository/branches + ``` + +- Every method must have a detailed + [description of the parameters](#method-description). +- Every method must have a cURL example. +- Every method must have a response body (in JSON format). + +### API topic template + +The following can be used as a template to get started: + +```md +## Descriptive title + +One or two sentence description of what endpoint does. + +``` +METHOD /endpoint +``` + +| Attribute | Type | Required | Description | +|:------------|:---------|:---------|:----------------------| +| `attribute` | datatype | yes/no | Detailed description. | +| `attribute` | datatype | yes/no | Detailed description. | + +Example request: + +```sh +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" 'https://gitlab.example.com/api/v4/endpoint?parameters' +``` + +Example response: + +```json +[ + { + } +] +``` + +``` ### Fake tokens @@ -553,7 +632,7 @@ low. You can use the following fake tokens as examples. -| **Token type** | **Token value** | +| Token type | Token value | |:----------------------|:-------------------------------------------------------------------| | Private user token | `<your_access_token>` | | Personal access token | `n671WNGecHugsdEDPsyo` | @@ -567,23 +646,7 @@ You can use the following fake tokens as examples. | Health check token | `Tu7BgjR9qeZTEyRzGG2P` | | Request profile token | `7VgpS4Ax5utVD2esNstz` | -### API - -Here is a list of must-have items. Use them in the exact order that appears -on this document. Further explanation is given below. - -- Every method must have the REST API request. For example: - - ``` - GET /projects/:id/repository/branches - ``` - -- Every method must have a detailed - [description of the parameters](#method-description). -- Every method must have a cURL example. -- Every method must have a response body (in JSON format). - -#### Method description +### Method description Use the following table headers to describe the methods. Attributes should always be in code blocks using backticks (``` ` ```). @@ -599,7 +662,7 @@ Rendered example: |:----------|:-------|:---------|:--------------------| | `user` | string | yes | The GitLab username | -#### cURL commands +### cURL commands - Use `https://gitlab.example.com/api/v4/` as an endpoint. - Wherever needed use this personal access token: `<your_access_token>`. @@ -616,11 +679,11 @@ Rendered example: | `-X PUT` | Use this method when updating existing objects | | `-X DELETE` | Use this method when removing existing objects | -#### cURL Examples +### cURL Examples Below is a set of [cURL][] examples that you can use in the API documentation. -##### Simple cURL command +#### Simple cURL command Get the details of a group: @@ -628,7 +691,7 @@ Get the details of a group: curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/gitlab-org ``` -##### cURL example with parameters passed in the URL +#### cURL example with parameters passed in the URL Create a new project under the authenticated user's namespace: @@ -636,7 +699,7 @@ Create a new project under the authenticated user's namespace: curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects?name=foo" ``` -##### Post data using cURL's --data +#### Post data using cURL's --data Instead of using `-X POST` and appending the parameters to the URI, you can use cURL's `--data` option. The example below will create a new project `foo` under @@ -646,7 +709,7 @@ the authenticated user's namespace. curl --data "name=foo" --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects" ``` -##### Post data using JSON content +#### Post data using JSON content > **Note:** In this example we create a new group. Watch carefully the single and double quotes. @@ -655,7 +718,7 @@ and double quotes. curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" --header "Content-Type: application/json" --data '{"path": "my-group", "name": "My group"}' https://gitlab.example.com/api/v4/groups ``` -##### Post data using form-data +#### Post data using form-data Instead of using JSON or urlencode you can use multipart/form-data which properly handles data encoding: @@ -667,7 +730,7 @@ curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" --form "title= The above example is run by and administrator and will add an SSH public key titled ssh-key to user's account which has an id of 25. -##### Escape special characters +#### Escape special characters Spaces or slashes (`/`) may sometimes result to errors, thus it is recommended to escape them when possible. In the example below we create a new issue which @@ -680,7 +743,7 @@ curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitla Use `%2F` for slashes (`/`). -##### Pass arrays to API calls +#### Pass arrays to API calls The GitLab API sometimes accepts arrays of strings or integers. For example, to restrict the sign-up e-mail domains of a GitLab instance to `*.example.com` and diff --git a/doc/user/project/import/repo_by_url.md b/doc/user/project/import/repo_by_url.md index f43e384de88..c20b1cb7f5e 100644 --- a/doc/user/project/import/repo_by_url.md +++ b/doc/user/project/import/repo_by_url.md @@ -6,7 +6,7 @@ You can import your existing repositories by providing the Git URL: 1. Switch to the **Import project** tab 1. Click on the **Repo by URL** button 1. Fill in the "Git repository URL" and the remaining project fields -1. Click **Create project** to being the import process +1. Click **Create project** to begin the import process 1. Once complete, you will be redirected to your newly created project ![Import project by repo URL](img/import_projects_from_repo_url.png) diff --git a/doc/user/project/index.md b/doc/user/project/index.md index e63ed88249d..d46ae31580a 100644 --- a/doc/user/project/index.md +++ b/doc/user/project/index.md @@ -72,12 +72,15 @@ website with GitLab Pages **Other features:** -- [Wiki](wiki/index.md): Document your GitLab project in an integrated Wiki -- [Snippets](../snippets.md): Store, share and collaborate on code snippets -- [Cycle Analytics](cycle_analytics.md): Review your development lifecycle -- [Syntax highlighting](highlighting.md): An alternative to customize -your code blocks, overriding GitLab's default choice of language -- [Badges](badges.md): Badges for the project overview +- [Wiki](wiki/index.md): document your GitLab project in an integrated Wiki. +- [Snippets](../snippets.md): store, share and collaborate on code snippets. +- [Cycle Analytics](cycle_analytics.md): review your development lifecycle. +- [Syntax highlighting](highlighting.md): an alternative to customize +your code blocks, overriding GitLab's default choice of language. +- [Badges](badges.md): badges for the project overview. +- [Releases](releases/index.md): a way to track deliverables in your project as snapshot in time of +the source, build output, and other metadata or artifacts +associated with a released version of your code. ### Project's integrations diff --git a/doc/user/project/releases.md b/doc/user/project/releases.md index 3f3525829b8..737842962a9 100644 --- a/doc/user/project/releases.md +++ b/doc/user/project/releases.md @@ -1,59 +1 @@ -# Releases - -> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/41766) in GitLab 11.7. - -It's typical to create a [Git tag](../../university/training/topics/tags.md) at -the moment of release to introduce a checkpoint in your source code -history, but in most cases your users will need compiled objects or other -assets output by your CI system to use them, not just the raw source -code. - -GitLab's **Releases** are a way to track deliverables in your project. Consider them -a snapshot in time of the source, build output, and other metadata or artifacts -associated with a released version of your code. - -At the moment, you can create Release entries via the [Releases API](../../api/releases.md); -we recommend doing this as one of the last steps in your CI/CD release pipeline. - -## Getting started with Releases - -Start by giving a [description](#release-description) to the Release and -including its [assets](#release-assets), as follows. - -### Release description - -Every Release has a description. You can add any text you like, but we recommend -including a changelog to describe the content of your release. This will allow -your users to quickly scan the differences between each one you publish. - -NOTE: **Note:** -[Git's tagging messages](https://git-scm.com/book/en/v2/Git-Basics-Tagging) and -Release descriptions are unrelated. Description supports [markdown](../markdown.md). - -### Release assets - -You can currently add the following types of assets to each Release: - -- [Source code](#source-code): state of the repo at the time of the Release -- [Links](#links): to content such as built binaries or documentation - -GitLab will support more asset types in the future, including objects such -as pre-built packages, compliance/security evidence, or container images. - -#### Source code - -GitLab automatically generate `zip`, `tar.gz`, `tar.bz2` and `tar` -archived source code from the given Git tag. These are read-only assets. - -#### Links - -A link is any URL which can point to whatever you like; documentation, built -binaries, or other related materials. These can be both internal or external -links from your GitLab instance. - -## Releases list - -Navigate to **Project > Releases** in order to see the list of releases for a given -project. - -![Releases list](img/releases.png) +This document was moved to [another location](releases/index.md). diff --git a/doc/user/project/img/releases.png b/doc/user/project/releases/img/releases.png Binary files differindex f8b1b7305ad..f8b1b7305ad 100644 --- a/doc/user/project/img/releases.png +++ b/doc/user/project/releases/img/releases.png diff --git a/doc/user/project/releases/index.md b/doc/user/project/releases/index.md new file mode 100644 index 00000000000..890d6fbc6c7 --- /dev/null +++ b/doc/user/project/releases/index.md @@ -0,0 +1,59 @@ +# Releases + +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/41766) in GitLab 11.7. + +It's typical to create a [Git tag](../../../university/training/topics/tags.md) at +the moment of release to introduce a checkpoint in your source code +history, but in most cases your users will need compiled objects or other +assets output by your CI system to use them, not just the raw source +code. + +GitLab's **Releases** are a way to track deliverables in your project. Consider them +a snapshot in time of the source, build output, and other metadata or artifacts +associated with a released version of your code. + +At the moment, you can create Release entries via the [Releases API](../../../api/releases.md); +we recommend doing this as one of the last steps in your CI/CD release pipeline. + +## Getting started with Releases + +Start by giving a [description](#release-description) to the Release and +including its [assets](#release-assets), as follows. + +### Release description + +Every Release has a description. You can add any text you like, but we recommend +including a changelog to describe the content of your release. This will allow +your users to quickly scan the differences between each one you publish. + +NOTE: **Note:** +[Git's tagging messages](https://git-scm.com/book/en/v2/Git-Basics-Tagging) and +Release descriptions are unrelated. Description supports [markdown](../../markdown.md). + +### Release assets + +You can currently add the following types of assets to each Release: + +- [Source code](#source-code): state of the repo at the time of the Release +- [Links](#links): to content such as built binaries or documentation + +GitLab will support more asset types in the future, including objects such +as pre-built packages, compliance/security evidence, or container images. + +#### Source code + +GitLab automatically generate `zip`, `tar.gz`, `tar.bz2` and `tar` +archived source code from the given Git tag. These are read-only assets. + +#### Links + +A link is any URL which can point to whatever you like; documentation, built +binaries, or other related materials. These can be both internal or external +links from your GitLab instance. + +## Releases list + +Navigate to **Project > Releases** in order to see the list of releases for a given +project. + +![Releases list](img/releases.png) diff --git a/doc/workflow/releases.md b/doc/workflow/releases.md index 02388bb73ea..aa0d2a7f799 100644 --- a/doc/workflow/releases.md +++ b/doc/workflow/releases.md @@ -1,16 +1,17 @@ # Releases -NOTE: In GitLab 11.7, we introduced the full fledged [releases](../user/project/releases.md) feature. You can still create release notes on this page, but the new method is preferred. +NOTE: In GitLab 11.7, we introduced the full fledged [Releases](../user/project/releases/index.md) +feature. You can still create release notes on this page, but the new method is preferred. -You can add release notes to any git tag using the notes feature. Release notes -behave like any other markdown form in GitLab so you can write text and +You can add release notes to any git tag using the notes feature. Release notes +behave like any other markdown form in GitLab so you can write text and drag-n-drop files to it. Release notes are stored in GitLab's database. -There are several ways to add release notes: +There are several ways to add release notes: -* In the interface, when you create a new git tag -* In the interface, by adding a note to an existing git tag -* Using the GitLab API +- In the interface, when you create a new git tag +- In the interface, by adding a note to an existing git tag +- Using the GitLab API ## New tag page with release notes text area diff --git a/lib/api/lint.rb b/lib/api/lint.rb index 0342a4b6654..a7672021db0 100644 --- a/lib/api/lint.rb +++ b/lib/api/lint.rb @@ -8,7 +8,8 @@ module API requires :content, type: String, desc: 'Content of .gitlab-ci.yml' end post '/lint' do - error = Gitlab::Ci::YamlProcessor.validation_message(params[:content]) + error = Gitlab::Ci::YamlProcessor.validation_message(params[:content], + user: current_user) status 200 diff --git a/lib/bitbucket_server/paginator.rb b/lib/bitbucket_server/paginator.rb index aa5f84f44b3..9eda1c921b2 100644 --- a/lib/bitbucket_server/paginator.rb +++ b/lib/bitbucket_server/paginator.rb @@ -12,7 +12,7 @@ module BitbucketServer @url = url @page = nil @page_offset = page_offset - @limit = limit || PAGE_LENGTH + @limit = limit @total = 0 end @@ -34,6 +34,8 @@ module BitbucketServer attr_reader :connection, :page, :url, :type, :limit def over_limit? + return false unless @limit + @limit.positive? && @total >= @limit end @@ -42,11 +44,15 @@ module BitbucketServer end def starting_offset - [0, page_offset - 1].max * limit + [0, page_offset - 1].max * max_per_page + end + + def max_per_page + limit || PAGE_LENGTH end def fetch_next_page - parsed_response = connection.get(@url, start: next_offset, limit: @limit) + parsed_response = connection.get(@url, start: next_offset, limit: max_per_page) Page.new(parsed_response, type) end end diff --git a/lib/gitlab/ci/config.rb b/lib/gitlab/ci/config.rb index 11e0352975d..5875479183e 100644 --- a/lib/gitlab/ci/config.rb +++ b/lib/gitlab/ci/config.rb @@ -8,9 +8,9 @@ module Gitlab class Config ConfigError = Class.new(StandardError) - def initialize(config, opts = {}) + def initialize(config, project: nil, sha: nil, user: nil) @config = Config::Extendable - .new(build_config(config, opts)) + .new(build_config(config, project: project, sha: sha, user: user)) .to_hash @global = Entry::Global.new(@config) @@ -70,20 +70,21 @@ module Gitlab private - def build_config(config, opts = {}) + def build_config(config, project:, sha:, user:) initial_config = Gitlab::Config::Loader::Yaml.new(config).load! - project = opts.fetch(:project, nil) if project - process_external_files(initial_config, project, opts) + process_external_files(initial_config, project: project, sha: sha, user: user) else initial_config end end - def process_external_files(config, project, opts) - sha = opts.fetch(:sha) { project.repository.root_ref_sha } - Config::External::Processor.new(config, project: project, sha: sha).perform + def process_external_files(config, project:, sha:, user:) + Config::External::Processor.new(config, + project: project, + sha: sha || project.repository.root_ref_sha, + user: user).perform end end end diff --git a/lib/gitlab/ci/config/external/file/base.rb b/lib/gitlab/ci/config/external/file/base.rb index 2ac6656a703..a747886093c 100644 --- a/lib/gitlab/ci/config/external/file/base.rb +++ b/lib/gitlab/ci/config/external/file/base.rb @@ -12,7 +12,7 @@ module Gitlab YAML_WHITELIST_EXTENSION = /.+\.(yml|yaml)$/i.freeze - Context = Struct.new(:project, :sha) + Context = Struct.new(:project, :sha, :user) def initialize(params, context) @params = params diff --git a/lib/gitlab/ci/config/external/file/project.rb b/lib/gitlab/ci/config/external/file/project.rb new file mode 100644 index 00000000000..e75540dbe5a --- /dev/null +++ b/lib/gitlab/ci/config/external/file/project.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +module Gitlab + module Ci + class Config + module External + module File + class Project < Base + include Gitlab::Utils::StrongMemoize + + attr_reader :project_name, :ref_name + + def initialize(params, context = {}) + @location = params[:file] + @project_name = params[:project] + @ref_name = params[:ref] || 'HEAD' + + super + end + + def matching? + super && project_name.present? + end + + def content + strong_memoize(:content) { fetch_local_content } + end + + private + + def validate_content! + if !can_access_local_content? + errors.push("Project `#{project_name}` not found or access denied!") + elsif sha.nil? + errors.push("Project `#{project_name}` reference `#{ref_name}` does not exist!") + elsif content.nil? + errors.push("Project `#{project_name}` file `#{location}` does not exist!") + elsif content.blank? + errors.push("Project `#{project_name}` file `#{location}` is empty!") + end + end + + def project + strong_memoize(:project) do + ::Project.find_by_full_path(project_name) + end + end + + def can_access_local_content? + Ability.allowed?(context.user, :download_code, project) + end + + def fetch_local_content + return unless can_access_local_content? + return unless sha + + project.repository.blob_data_at(sha, location) + rescue GRPC::NotFound, GRPC::Internal + nil + end + + def sha + strong_memoize(:sha) do + project.commit(ref_name).try(:sha) + end + end + end + end + end + end + end +end diff --git a/lib/gitlab/ci/config/external/mapper.rb b/lib/gitlab/ci/config/external/mapper.rb index 74bd927da39..108bfd5eb43 100644 --- a/lib/gitlab/ci/config/external/mapper.rb +++ b/lib/gitlab/ci/config/external/mapper.rb @@ -10,15 +10,17 @@ module Gitlab FILE_CLASSES = [ External::File::Remote, External::File::Template, - External::File::Local + External::File::Local, + External::File::Project ].freeze AmbigiousSpecificationError = Class.new(StandardError) - def initialize(values, project:, sha:) + def initialize(values, project:, sha:, user:) @locations = Array.wrap(values.fetch(:include, [])) @project = project @sha = sha + @user = user end def process @@ -61,7 +63,7 @@ module Gitlab def context strong_memoize(:context) do - External::File::Base::Context.new(project, sha) + External::File::Base::Context.new(project, sha, user) end end end diff --git a/lib/gitlab/ci/config/external/processor.rb b/lib/gitlab/ci/config/external/processor.rb index 1d310b29dc8..69bc164a039 100644 --- a/lib/gitlab/ci/config/external/processor.rb +++ b/lib/gitlab/ci/config/external/processor.rb @@ -7,9 +7,9 @@ module Gitlab class Processor IncludeError = Class.new(StandardError) - def initialize(values, project:, sha:) + def initialize(values, project:, sha:, user:) @values = values - @external_files = External::Mapper.new(values, project: project, sha: sha).process + @external_files = External::Mapper.new(values, project: project, sha: sha, user: user).process @content = {} rescue External::Mapper::AmbigiousSpecificationError => e raise IncludeError, e.message diff --git a/lib/gitlab/ci/yaml_processor.rb b/lib/gitlab/ci/yaml_processor.rb index 15097188061..0c48a6ab3ac 100644 --- a/lib/gitlab/ci/yaml_processor.rb +++ b/lib/gitlab/ci/yaml_processor.rb @@ -10,7 +10,7 @@ module Gitlab attr_reader :cache, :stages, :jobs def initialize(config, opts = {}) - @ci_config = Gitlab::Ci::Config.new(config, opts) + @ci_config = Gitlab::Ci::Config.new(config, **opts) @config = @ci_config.to_hash unless @ci_config.valid? diff --git a/scripts/review_apps/review-apps.sh b/scripts/review_apps/review-apps.sh index 9e52366f800..118a7c7f638 100755 --- a/scripts/review_apps/review-apps.sh +++ b/scripts/review_apps/review-apps.sh @@ -344,6 +344,7 @@ function wait_for_job_to_be_done() { if [[ "${job_status}" == "failed" ]]; then echo "The '${job_name}' failed." + exit 1 elif [[ "${job_status}" == "manual" ]]; then echo "The '${job_name}' is manual." else diff --git a/spec/features/boards/new_issue_spec.rb b/spec/features/boards/new_issue_spec.rb index 164442a47f5..d0c4534e317 100644 --- a/spec/features/boards/new_issue_spec.rb +++ b/spec/features/boards/new_issue_spec.rb @@ -86,6 +86,27 @@ describe 'Issue Boards new issue', :js do expect(page).to have_selector('.issue-boards-sidebar') end + + it 'successfuly loads labels to be added to newly created issue' do + page.within(first('.board')) do + find('.issue-count-badge-add-button').click + end + + page.within(first('.board-new-issue-form')) do + find('.form-control').set('new issue') + click_button 'Submit issue' + end + + wait_for_requests + + page.within(first('.issue-boards-sidebar')) do + find('.labels .edit-link').click + + wait_for_requests + + expect(page).to have_selector('.labels .dropdown-content li a') + end + end end context 'unauthorized user' do diff --git a/spec/helpers/commits_helper_spec.rb b/spec/helpers/commits_helper_spec.rb index 9c0e55739d6..824b3ab4fc1 100644 --- a/spec/helpers/commits_helper_spec.rb +++ b/spec/helpers/commits_helper_spec.rb @@ -21,7 +21,7 @@ describe CommitsHelper do expect(helper.commit_author_link(commit)) .to include('Foo <script>') expect(helper.commit_author_link(commit, avatar: true)) - .to include('commit-author-name', 'Foo <script>') + .to include('commit-author-name', 'js-user-link', 'Foo <script>') end end diff --git a/spec/helpers/user_callouts_helper_spec.rb b/spec/helpers/user_callouts_helper_spec.rb index 27455705d23..8fa479a4474 100644 --- a/spec/helpers/user_callouts_helper_spec.rb +++ b/spec/helpers/user_callouts_helper_spec.rb @@ -44,4 +44,13 @@ describe UserCalloutsHelper do it { is_expected.to be false } end end + + describe '.render_flash_user_callout' do + it 'renders the flash_user_callout partial' do + expect(helper).to receive(:render) + .with(/flash_user_callout/, flash_type: :warning, message: 'foo', feature_name: 'bar') + + helper.render_flash_user_callout(:warning, 'foo', 'bar') + end + end end diff --git a/spec/javascripts/diffs/components/commit_item_spec.js b/spec/javascripts/diffs/components/commit_item_spec.js index 8b2ca6506c4..50e45f48af3 100644 --- a/spec/javascripts/diffs/components/commit_item_spec.js +++ b/spec/javascripts/diffs/components/commit_item_spec.js @@ -80,6 +80,8 @@ describe('diffs/components/commit_item', () => { expect(trimText(committerElement.textContent)).toEqual(expectedText); expect(nameElement).toHaveAttr('href', commit.author.web_url); expect(nameElement).toHaveText(commit.author.name); + expect(nameElement).toHaveClass('js-user-link'); + expect(nameElement.dataset.userId).toEqual(commit.author.id.toString()); }); describe('without commit description', () => { diff --git a/spec/javascripts/notes/components/comment_form_spec.js b/spec/javascripts/notes/components/comment_form_spec.js index 3c57fe51352..362963ddaf4 100644 --- a/spec/javascripts/notes/components/comment_form_spec.js +++ b/spec/javascripts/notes/components/comment_form_spec.js @@ -223,7 +223,6 @@ describe('issue_comment_form component', () => { 'Comment & close issue', ); - expect(vm.$el.querySelector('.js-note-discard')).toBeDefined(); done(); }); }); diff --git a/spec/lib/bitbucket_server/paginator_spec.rb b/spec/lib/bitbucket_server/paginator_spec.rb index d268d4f23cf..eadd7f68bfb 100644 --- a/spec/lib/bitbucket_server/paginator_spec.rb +++ b/spec/lib/bitbucket_server/paginator_spec.rb @@ -30,6 +30,17 @@ describe BitbucketServer::Paginator do expect { limited.items }.to raise_error(StopIteration) end + it 'does not stop if limit is unspecified' do + stub_const("BitbucketServer::Paginator::PAGE_LENGTH", 1) + paginator = described_class.new(connection, 'http://more-data', :pull_request, page_offset: 0, limit: nil) + allow(paginator).to receive(:fetch_next_page).and_return(first_page, last_page) + + expect(paginator.has_next_page?).to be_truthy + expect(paginator.items).to match(['item_1']) + expect(paginator.has_next_page?).to be_truthy + expect(paginator.items).to match(['item_2']) + end + it 'calls the connection with different offsets' do expect(connection).to receive(:get).with('http://more-data', start: 0, limit: BitbucketServer::Paginator::PAGE_LENGTH).and_return(page_attrs) diff --git a/spec/lib/gitlab/ci/config/external/file/base_spec.rb b/spec/lib/gitlab/ci/config/external/file/base_spec.rb index ada8775c489..1a6b3587599 100644 --- a/spec/lib/gitlab/ci/config/external/file/base_spec.rb +++ b/spec/lib/gitlab/ci/config/external/file/base_spec.rb @@ -3,7 +3,7 @@ require 'fast_spec_helper' describe Gitlab::Ci::Config::External::File::Base do - let(:context) { described_class::Context.new(nil, 'HEAD') } + let(:context) { described_class::Context.new(nil, 'HEAD', nil) } let(:test_class) do Class.new(described_class) do diff --git a/spec/lib/gitlab/ci/config/external/file/local_spec.rb b/spec/lib/gitlab/ci/config/external/file/local_spec.rb index 83be43e240b..ff67a765da0 100644 --- a/spec/lib/gitlab/ci/config/external/file/local_spec.rb +++ b/spec/lib/gitlab/ci/config/external/file/local_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' describe Gitlab::Ci::Config::External::File::Local do set(:project) { create(:project, :repository) } - let(:context) { described_class::Context.new(project, '12345') } + let(:context) { described_class::Context.new(project, '12345', nil) } let(:params) { { local: location } } let(:local_file) { described_class.new(params, context) } diff --git a/spec/lib/gitlab/ci/config/external/file/project_spec.rb b/spec/lib/gitlab/ci/config/external/file/project_spec.rb new file mode 100644 index 00000000000..11809adcaf6 --- /dev/null +++ b/spec/lib/gitlab/ci/config/external/file/project_spec.rb @@ -0,0 +1,151 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Ci::Config::External::File::Project do + set(:project) { create(:project, :repository) } + set(:user) { create(:user) } + + let(:context_user) { user } + let(:context) { described_class::Context.new(nil, '12345', context_user) } + let(:subject) { described_class.new(params, context) } + + before do + project.add_developer(user) + end + + describe '#matching?' do + context 'when a file and project is specified' do + let(:params) { { file: 'file.yml', project: 'project' } } + + it 'should return true' do + expect(subject).to be_matching + end + end + + context 'with only file is specified' do + let(:params) { { file: 'file.yml' } } + + it 'should return false' do + expect(subject).not_to be_matching + end + end + + context 'with only project is specified' do + let(:params) { { project: 'project' } } + + it 'should return false' do + expect(subject).not_to be_matching + end + end + + context 'with a missing local key' do + let(:params) { {} } + + it 'should return false' do + expect(subject).not_to be_matching + end + end + end + + describe '#valid?' do + context 'when a valid path is used' do + let(:params) do + { project: project.full_path, file: '/file.yml' } + end + + let(:root_ref_sha) { project.repository.root_ref_sha } + + before do + stub_project_blob(root_ref_sha, '/file.yml') { 'image: ruby:2.1' } + end + + it 'should return true' do + expect(subject).to be_valid + end + + context 'when user does not have permission to access file' do + let(:context_user) { create(:user) } + + it 'should return false' do + expect(subject).not_to be_valid + expect(subject.error_message).to include("Project `#{project.full_path}` not found or access denied!") + end + end + end + + context 'when a valid path with custom ref is used' do + let(:params) do + { project: project.full_path, ref: 'master', file: '/file.yml' } + end + + let(:ref_sha) { project.commit('master').sha } + + before do + stub_project_blob(ref_sha, '/file.yml') { 'image: ruby:2.1' } + end + + it 'should return true' do + expect(subject).to be_valid + end + end + + context 'when an empty file is used' do + let(:params) do + { project: project.full_path, file: '/file.yml' } + end + + let(:root_ref_sha) { project.repository.root_ref_sha } + + before do + stub_project_blob(root_ref_sha, '/file.yml') { '' } + end + + it 'should return false' do + expect(subject).not_to be_valid + expect(subject.error_message).to include("Project `#{project.full_path}` file `/file.yml` is empty!") + end + end + + context 'when non-existing ref is used' do + let(:params) do + { project: project.full_path, ref: 'I-Do-Not-Exist', file: '/file.yml' } + end + + it 'should return false' do + expect(subject).not_to be_valid + expect(subject.error_message).to include("Project `#{project.full_path}` reference `I-Do-Not-Exist` does not exist!") + end + end + + context 'when non-existing file is requested' do + let(:params) do + { project: project.full_path, file: '/invalid-file.yml' } + end + + it 'should return false' do + expect(subject).not_to be_valid + expect(subject.error_message).to include("Project `#{project.full_path}` file `/invalid-file.yml` does not exist!") + end + end + + context 'when file is not a yaml file' do + let(:params) do + { project: project.full_path, file: '/invalid-file' } + end + + it 'should return false' do + expect(subject).not_to be_valid + expect(subject.error_message).to include('Included file `/invalid-file` does not have YAML extension!') + end + end + end + + private + + def stub_project_blob(ref, path) + allow_any_instance_of(Repository) + .to receive(:blob_data_at) + .with(ref, path) { yield } + end +end diff --git a/spec/lib/gitlab/ci/config/external/file/remote_spec.rb b/spec/lib/gitlab/ci/config/external/file/remote_spec.rb index 319e7137f9f..3e0fda9c308 100644 --- a/spec/lib/gitlab/ci/config/external/file/remote_spec.rb +++ b/spec/lib/gitlab/ci/config/external/file/remote_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Gitlab::Ci::Config::External::File::Remote do - let(:context) { described_class::Context.new(nil, '12345') } + let(:context) { described_class::Context.new(nil, '12345', nil) } let(:params) { { remote: location } } let(:remote_file) { described_class.new(params, context) } let(:location) { 'https://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' } diff --git a/spec/lib/gitlab/ci/config/external/mapper_spec.rb b/spec/lib/gitlab/ci/config/external/mapper_spec.rb index e27d2cd9422..4cab4961b0f 100644 --- a/spec/lib/gitlab/ci/config/external/mapper_spec.rb +++ b/spec/lib/gitlab/ci/config/external/mapper_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' describe Gitlab::Ci::Config::External::Mapper do set(:project) { create(:project, :repository) } + set(:user) { create(:user) } let(:local_file) { '/lib/gitlab/ci/templates/non-existent-file.yml' } let(:remote_url) { 'https://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' } @@ -20,7 +21,7 @@ describe Gitlab::Ci::Config::External::Mapper do end describe '#process' do - subject { described_class.new(values, project: project, sha: '123456').process } + subject { described_class.new(values, project: project, sha: '123456', user: user).process } context "when single 'include' keyword is defined" do context 'when the string is a local file' do diff --git a/spec/lib/gitlab/ci/config/external/processor_spec.rb b/spec/lib/gitlab/ci/config/external/processor_spec.rb index d2d4fbc5115..1ac58139b25 100644 --- a/spec/lib/gitlab/ci/config/external/processor_spec.rb +++ b/spec/lib/gitlab/ci/config/external/processor_spec.rb @@ -4,8 +4,13 @@ require 'spec_helper' describe Gitlab::Ci::Config::External::Processor do set(:project) { create(:project, :repository) } + set(:user) { create(:user) } - let(:processor) { described_class.new(values, project: project, sha: '12345') } + let(:processor) { described_class.new(values, project: project, sha: '12345', user: user) } + + before do + project.add_developer(user) + end describe "#perform" do context 'when no external files defined' do diff --git a/spec/lib/gitlab/ci/config_spec.rb b/spec/lib/gitlab/ci/config_spec.rb index 49988468d1a..cd6d2a2f343 100644 --- a/spec/lib/gitlab/ci/config_spec.rb +++ b/spec/lib/gitlab/ci/config_spec.rb @@ -1,8 +1,10 @@ require 'spec_helper' describe Gitlab::Ci::Config do + set(:user) { create(:user) } + let(:config) do - described_class.new(yml) + described_class.new(yml, project: nil, sha: nil, user: nil) end context 'when config is valid' do @@ -154,7 +156,7 @@ describe Gitlab::Ci::Config do end let(:config) do - described_class.new(gitlab_ci_yml, project: project, sha: '12345') + described_class.new(gitlab_ci_yml, project: project, sha: '12345', user: user) end before do @@ -228,7 +230,7 @@ describe Gitlab::Ci::Config do expect(project.repository).to receive(:blob_data_at) .with('eeff1122', local_location) - described_class.new(gitlab_ci_yml, project: project, sha: 'eeff1122') + described_class.new(gitlab_ci_yml, project: project, sha: 'eeff1122', user: user) end end @@ -236,7 +238,7 @@ describe Gitlab::Ci::Config do it 'is using latest SHA on the default branch' do expect(project.repository).to receive(:root_ref_sha) - described_class.new(gitlab_ci_yml, project: project) + described_class.new(gitlab_ci_yml, project: project, sha: nil, user: user) end end end diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb index 63e1f167ce2..b6c3431728c 100644 --- a/spec/lib/gitlab/ci/yaml_processor_spec.rb +++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb @@ -3,10 +3,10 @@ require 'spec_helper' module Gitlab module Ci describe YamlProcessor do - subject { described_class.new(config) } + subject { described_class.new(config, user: nil) } describe '#build_attributes' do - subject { described_class.new(config).build_attributes(:rspec) } + subject { described_class.new(config, user: nil).build_attributes(:rspec) } describe 'attributes list' do let(:config) do diff --git a/spec/models/blob_viewer/gitlab_ci_yml_spec.rb b/spec/models/blob_viewer/gitlab_ci_yml_spec.rb index 01c555a7a90..16bf947b493 100644 --- a/spec/models/blob_viewer/gitlab_ci_yml_spec.rb +++ b/spec/models/blob_viewer/gitlab_ci_yml_spec.rb @@ -4,7 +4,9 @@ describe BlobViewer::GitlabCiYml do include FakeBlobHelpers include RepoHelpers - let(:project) { create(:project, :repository) } + set(:project) { create(:project, :repository) } + set(:user) { create(:user) } + let(:data) { File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')) } let(:blob) { fake_blob(path: '.gitlab-ci.yml', data: data) } let(:sha) { sample_commit.id } @@ -14,12 +16,12 @@ describe BlobViewer::GitlabCiYml do it 'calls prepare! on the viewer' do expect(subject).to receive(:prepare!) - subject.validation_message(project, sha) + subject.validation_message(project: project, sha: sha, user: user) end context 'when the configuration is valid' do it 'returns nil' do - expect(subject.validation_message(project, sha)).to be_nil + expect(subject.validation_message(project: project, sha: sha, user: user)).to be_nil end end @@ -27,7 +29,7 @@ describe BlobViewer::GitlabCiYml do let(:data) { 'oof' } it 'returns the error message' do - expect(subject.validation_message(project, sha)).to eq('Invalid configuration format') + expect(subject.validation_message(project: project, sha: sha, user: user)).to eq('Invalid configuration format') end end end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 4cc3a6a3644..96d49e86dab 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -1372,6 +1372,34 @@ describe MergeRequest do end end + describe '#update_head_pipeline' do + subject { merge_request.update_head_pipeline } + + let(:merge_request) { create(:merge_request) } + + context 'when there is a pipeline with the diff head sha' do + let!(:pipeline) do + create(:ci_empty_pipeline, + project: merge_request.project, + sha: merge_request.diff_head_sha, + ref: merge_request.source_branch) + end + + it 'updates the head pipeline' do + expect { subject } + .to change { merge_request.reload.head_pipeline } + .from(nil).to(pipeline) + end + end + + context 'when there are no pipelines with the diff head sha' do + it 'does not update the head pipeline' do + expect { subject } + .not_to change { merge_request.reload.head_pipeline } + end + end + end + describe '#has_test_reports?' do subject { merge_request.has_test_reports? } diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb index 87b60387c52..8497e90bd8b 100644 --- a/spec/services/ci/create_pipeline_service_spec.rb +++ b/spec/services/ci/create_pipeline_service_spec.rb @@ -143,7 +143,8 @@ describe Ci::CreatePipelineService do target_branch: "branch_1", source_project: project) - allow_any_instance_of(Ci::Pipeline).to receive(:latest?).and_return(false) + allow_any_instance_of(MergeRequest) + .to receive(:find_actual_head_pipeline) { } execute_service diff --git a/spec/services/merge_requests/create_service_spec.rb b/spec/services/merge_requests/create_service_spec.rb index 5a3ecb1019b..308f99dc0da 100644 --- a/spec/services/merge_requests/create_service_spec.rb +++ b/spec/services/merge_requests/create_service_spec.rb @@ -128,9 +128,9 @@ describe MergeRequests::CreateService do end context 'when head pipelines already exist for merge request source branch' do - let(:sha) { project.commit(opts[:source_branch]).id } - let!(:pipeline_1) { create(:ci_pipeline, project: project, ref: opts[:source_branch], project_id: project.id, sha: sha) } - let!(:pipeline_2) { create(:ci_pipeline, project: project, ref: opts[:source_branch], project_id: project.id, sha: sha) } + let(:shas) { project.repository.commits(opts[:source_branch], limit: 2).map(&:id) } + let!(:pipeline_1) { create(:ci_pipeline, project: project, ref: opts[:source_branch], project_id: project.id, sha: shas[1]) } + let!(:pipeline_2) { create(:ci_pipeline, project: project, ref: opts[:source_branch], project_id: project.id, sha: shas[0]) } let!(:pipeline_3) { create(:ci_pipeline, project: project, ref: "other_branch", project_id: project.id) } before do @@ -144,17 +144,30 @@ describe MergeRequests::CreateService do it 'sets head pipeline' do merge_request = service.execute - expect(merge_request.head_pipeline).to eq(pipeline_2) + expect(merge_request.reload.head_pipeline).to eq(pipeline_2) expect(merge_request).to be_persisted end - context 'when merge request head commit sha does not match pipeline sha' do - it 'sets the head pipeline correctly' do - pipeline_2.update(sha: 1234) + context 'when the new pipeline is associated with an old sha' do + let!(:pipeline_1) { create(:ci_pipeline, project: project, ref: opts[:source_branch], project_id: project.id, sha: shas[0]) } + let!(:pipeline_2) { create(:ci_pipeline, project: project, ref: opts[:source_branch], project_id: project.id, sha: shas[1]) } + it 'sets an old pipeline with associated with the latest sha as the head pipeline' do merge_request = service.execute - expect(merge_request.head_pipeline).to eq(pipeline_1) + expect(merge_request.reload.head_pipeline).to eq(pipeline_1) + expect(merge_request).to be_persisted + end + end + + context 'when there are no pipelines with the diff head sha' do + let!(:pipeline_1) { create(:ci_pipeline, project: project, ref: opts[:source_branch], project_id: project.id, sha: shas[1]) } + let!(:pipeline_2) { create(:ci_pipeline, project: project, ref: opts[:source_branch], project_id: project.id, sha: shas[1]) } + + it 'does not set the head pipeline' do + merge_request = service.execute + + expect(merge_request.reload.head_pipeline).to be_nil expect(merge_request).to be_persisted end end diff --git a/spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb b/spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb index a2bc264b0f6..963237ceadf 100644 --- a/spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb +++ b/spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb @@ -21,17 +21,17 @@ describe UpdateHeadPipelineForMergeRequestWorker do merge_request.merge_request_diff.update(head_commit_sha: 'different_sha') end - it 'does not update head_pipeline_id' do - expect { subject.perform(merge_request.id) }.not_to raise_error - - expect(merge_request.reload.head_pipeline_id).to eq(nil) + it 'does not update head pipeline' do + expect { subject.perform(merge_request.id) } + .not_to change { merge_request.reload.head_pipeline_id } end end end context 'when pipeline does not exist for the source project and branch' do it 'does not update the head_pipeline_id of the merge_request' do - expect { subject.perform(merge_request.id) }.not_to change { merge_request.reload.head_pipeline_id } + expect { subject.perform(merge_request.id) } + .not_to change { merge_request.reload.head_pipeline_id } end end |