summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/boards/models/list.js1
-rw-r--r--app/assets/javascripts/diffs/components/commit_item.vue33
-rw-r--r--app/assets/javascripts/gl_form.js2
-rw-r--r--app/assets/javascripts/notes.js18
-rw-r--r--app/assets/javascripts/notes/components/comment_form.vue9
-rw-r--r--app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue4
-rw-r--r--app/assets/stylesheets/framework/common.scss9
-rw-r--r--app/assets/stylesheets/framework/forms.scss9
-rw-r--r--app/assets/stylesheets/framework/selects.scss18
-rw-r--r--app/assets/stylesheets/framework/tables.scss9
-rw-r--r--app/controllers/projects/ci/lints_controller.rb8
-rw-r--r--app/helpers/commits_helper.rb2
-rw-r--r--app/helpers/projects_helper.rb2
-rw-r--r--app/helpers/user_callouts_helper.rb4
-rw-r--r--app/models/blob_viewer/gitlab_ci_yml.rb8
-rw-r--r--app/models/ci/pipeline.rb15
-rw-r--r--app/models/merge_request.rb20
-rw-r--r--app/services/merge_requests/create_service.rb16
-rw-r--r--app/views/admin/users/_user.html.haml2
-rw-r--r--app/views/projects/blob/viewers/_gitlab_ci_yml.html.haml4
-rw-r--r--app/views/projects/releases/index.html.haml2
-rw-r--r--app/views/shared/_flash_user_callout.html.haml11
-rw-r--r--app/views/shared/issuable/_filter.html.haml32
-rw-r--r--app/views/shared/members/_member.html.haml2
-rw-r--r--app/views/shared/notes/_form.html.haml4
-rw-r--r--app/views/shared/snippets/_snippet.html.haml2
-rw-r--r--app/workers/update_head_pipeline_for_merge_request_worker.rb21
-rw-r--r--changelogs/unreleased/53796-discard-draft-comment-button-to-easy-to-accidentally-hit-on-mobile.yml5
-rw-r--r--changelogs/unreleased/54142-pages-in-project-s-permission-should-be-named-pages-access-control.yml5
-rw-r--r--changelogs/unreleased/54311-fix-board-add-label.yml5
-rw-r--r--changelogs/unreleased/54981-extended-user-centric-tooltips-add-missing-cases.yml5
-rw-r--r--changelogs/unreleased/55670-remove-app-views-shared-issuable-_filter-html-haml.yml5
-rw-r--r--changelogs/unreleased/deprecated-migration-inheritance-2.yml5
-rw-r--r--changelogs/unreleased/include-project.yml5
-rw-r--r--changelogs/unreleased/sh-fix-issue-55914.yml5
-rw-r--r--changelogs/unreleased/user-update-head-pipeline-worker.yml5
-rw-r--r--db/migrate/20181119132520_add_indexes_to_ci_builds_and_pipelines.rb2
-rw-r--r--doc/api/releases.md2
-rw-r--r--doc/ci/yaml/README.md30
-rw-r--r--doc/development/documentation/styleguide.md135
-rw-r--r--doc/user/project/import/repo_by_url.md2
-rw-r--r--doc/user/project/index.md15
-rw-r--r--doc/user/project/releases.md60
-rw-r--r--doc/user/project/releases/img/releases.png (renamed from doc/user/project/img/releases.png)bin126093 -> 126093 bytes
-rw-r--r--doc/user/project/releases/index.md59
-rw-r--r--doc/workflow/releases.md15
-rw-r--r--lib/api/lint.rb3
-rw-r--r--lib/bitbucket_server/paginator.rb12
-rw-r--r--lib/gitlab/ci/config.rb17
-rw-r--r--lib/gitlab/ci/config/external/file/base.rb2
-rw-r--r--lib/gitlab/ci/config/external/file/project.rb72
-rw-r--r--lib/gitlab/ci/config/external/mapper.rb8
-rw-r--r--lib/gitlab/ci/config/external/processor.rb4
-rw-r--r--lib/gitlab/ci/yaml_processor.rb2
-rwxr-xr-xscripts/review_apps/review-apps.sh1
-rw-r--r--spec/features/boards/new_issue_spec.rb21
-rw-r--r--spec/helpers/commits_helper_spec.rb2
-rw-r--r--spec/helpers/user_callouts_helper_spec.rb9
-rw-r--r--spec/javascripts/diffs/components/commit_item_spec.js2
-rw-r--r--spec/javascripts/notes/components/comment_form_spec.js1
-rw-r--r--spec/lib/bitbucket_server/paginator_spec.rb11
-rw-r--r--spec/lib/gitlab/ci/config/external/file/base_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/external/file/local_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/external/file/project_spec.rb151
-rw-r--r--spec/lib/gitlab/ci/config/external/file/remote_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/external/mapper_spec.rb3
-rw-r--r--spec/lib/gitlab/ci/config/external/processor_spec.rb7
-rw-r--r--spec/lib/gitlab/ci/config_spec.rb10
-rw-r--r--spec/lib/gitlab/ci/yaml_processor_spec.rb4
-rw-r--r--spec/models/blob_viewer/gitlab_ci_yml_spec.rb10
-rw-r--r--spec/models/merge_request_spec.rb28
-rw-r--r--spec/services/ci/create_pipeline_service_spec.rb3
-rw-r--r--spec/services/merge_requests/create_service_spec.rb29
-rw-r--r--spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb10
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} &middot;
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
index f8b1b7305ad..f8b1b7305ad 100644
--- a/doc/user/project/img/releases.png
+++ b/doc/user/project/releases/img/releases.png
Binary files differ
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 &lt;script&gt;')
expect(helper.commit_author_link(commit, avatar: true))
- .to include('commit-author-name', 'Foo &lt;script&gt;')
+ .to include('commit-author-name', 'js-user-link', 'Foo &lt;script&gt;')
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