summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--GITLAB_SHELL_VERSION2
-rw-r--r--Gemfile10
-rw-r--r--Gemfile.lock26
-rw-r--r--app/assets/javascripts/blob_edit/blob_bundle.js9
-rw-r--r--app/assets/javascripts/blob_edit/edit_blob.js20
-rw-r--r--app/assets/javascripts/boards/components/modal/empty_state.vue2
-rw-r--r--app/assets/javascripts/lib/utils/text_markdown.js162
-rw-r--r--app/assets/javascripts/notes/components/note_form.vue2
-rw-r--r--app/assets/javascripts/notes/components/toggle_replies_widget.vue8
-rw-r--r--app/assets/javascripts/vue_shared/components/callout.vue5
-rw-r--r--app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_image.vue1
-rw-r--r--app/assets/stylesheets/bootstrap_migration.scss18
-rw-r--r--app/assets/stylesheets/framework.scss1
-rw-r--r--app/assets/stylesheets/framework/animations.scss22
-rw-r--r--app/assets/stylesheets/framework/header.scss8
-rw-r--r--app/assets/stylesheets/framework/layout.scss8
-rw-r--r--app/assets/stylesheets/framework/markdown_area.scss6
-rw-r--r--app/assets/stylesheets/framework/mobile.scss88
-rw-r--r--app/assets/stylesheets/framework/modal.scss4
-rw-r--r--app/assets/stylesheets/framework/variables.scss15
-rw-r--r--app/assets/stylesheets/pages/editor.scss4
-rw-r--r--app/controllers/projects/git_http_controller.rb4
-rw-r--r--app/helpers/application_settings_helper.rb4
-rw-r--r--app/helpers/blob_helper.rb3
-rw-r--r--app/models/ci/build.rb4
-rw-r--r--app/models/clusters/applications/ingress.rb2
-rw-r--r--app/models/clusters/applications/knative.rb11
-rw-r--r--app/models/clusters/applications/prometheus.rb7
-rw-r--r--app/models/clusters/cluster.rb1
-rw-r--r--app/models/merge_request.rb2
-rw-r--r--app/models/project.rb56
-rw-r--r--app/models/project_services/kubernetes_service.rb2
-rw-r--r--app/views/dashboard/_activities.html.haml2
-rw-r--r--app/views/groups/_activities.html.haml2
-rw-r--r--app/views/groups/_home_panel.html.haml2
-rw-r--r--app/views/projects/_activity.html.haml4
-rw-r--r--app/views/projects/_md_preview.html.haml12
-rw-r--r--app/views/projects/blob/_editor.html.haml4
-rw-r--r--app/views/projects/blob/_markdown_buttons.html.haml13
-rw-r--r--app/views/projects/issues/show.html.haml2
-rw-r--r--app/views/projects/services/prometheus/_metrics.html.haml2
-rw-r--r--app/views/projects/services/prometheus/_show.html.haml2
-rw-r--r--app/views/projects/tags/index.html.haml2
-rw-r--r--app/views/shared/issuable/_search_bar.html.haml2
-rw-r--r--app/views/users/show.html.haml2
-rw-r--r--changelogs/unreleased/27861-add-markdown-editing-buttons-to-the-file-editor.yml5
-rw-r--r--changelogs/unreleased/29951-issue-creation-by-email-without-subaddressing.yml5
-rw-r--r--changelogs/unreleased/54386-integrate-mobile-css-framework-into-specific-frameworks.yml5
-rw-r--r--changelogs/unreleased/54844-report-syntax-dep-scan-ado.yml5
-rw-r--r--changelogs/unreleased/55883-modal-header-titles-have-an-unnecessary-top-margin.yml5
-rw-r--r--changelogs/unreleased/allow-basic-auth-on-go-get-middleware.yml5
-rw-r--r--changelogs/unreleased/deprecated-positional-spec-arguments.yml5
-rw-r--r--changelogs/unreleased/knative-prometheus.yml5
-rw-r--r--changelogs/unreleased/sh-drop-webhooks-project-export.yml5
-rw-r--r--changelogs/unreleased/sh-fix-clone-url-for-https.yml5
-rw-r--r--changelogs/unreleased/tz-user-popover-follow-up.yml4
-rw-r--r--doc/api/services.md2
-rw-r--r--doc/ci/yaml/README.md6
-rw-r--r--doc/development/documentation/index.md2
-rw-r--r--doc/install/openshift_and_gitlab/index.md4
-rw-r--r--doc/topics/authentication/index.md2
-rw-r--r--doc/topics/autodevops/index.md6
-rw-r--r--doc/user/project/integrations/prometheus.md2
-rw-r--r--doc/user/project/settings/import_export.md1
-rw-r--r--lib/gitlab/checks/base_checker.rb6
-rw-r--r--lib/gitlab/checks/change_access.rb5
-rw-r--r--lib/gitlab/checks/diff_check.rb2
-rw-r--r--lib/gitlab/checks/push_check.rb4
-rw-r--r--lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml3
-rw-r--r--lib/gitlab/email/handler/base_handler.rb4
-rw-r--r--lib/gitlab/email/handler/create_issue_handler.rb28
-rw-r--r--lib/gitlab/email/handler/create_merge_request_handler.rb24
-rw-r--r--lib/gitlab/email/handler/create_note_handler.rb2
-rw-r--r--lib/gitlab/email/handler/reply_processing.rb19
-rw-r--r--lib/gitlab/email/handler/unsubscribe_handler.rb24
-rw-r--r--lib/gitlab/git_access.rb50
-rw-r--r--lib/gitlab/git_access_wiki.rb2
-rw-r--r--lib/gitlab/import_export/import_export.yml7
-rw-r--r--lib/gitlab/incoming_email.rb6
-rw-r--r--lib/gitlab/middleware/go.rb21
-rw-r--r--qa/Gemfile3
-rw-r--r--qa/Gemfile.lock13
-rw-r--r--qa/qa.rb1
-rw-r--r--qa/qa/page/component/note.rb51
-rw-r--r--qa/qa/page/merge_request/show.rb27
-rw-r--r--qa/qa/page/project/issue/show.rb1
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue/collapse_comments_in_discussions_spec.rb37
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb8
-rw-r--r--spec/controllers/projects/snippets_controller_spec.rb2
-rw-r--r--spec/controllers/projects_controller_spec.rb2
-rw-r--r--spec/controllers/snippets_controller_spec.rb6
-rw-r--r--spec/fixtures/emails/merge_request_multiple_patches.eml2
-rw-r--r--spec/fixtures/emails/merge_request_with_conflicting_patch.eml2
-rw-r--r--spec/fixtures/emails/merge_request_with_patch_and_target_branch.eml2
-rw-r--r--spec/fixtures/emails/valid_merge_request_with_patch.eml2
-rw-r--r--spec/fixtures/emails/valid_new_issue.eml4
-rw-r--r--spec/fixtures/emails/valid_new_issue_empty.eml4
-rw-r--r--spec/fixtures/emails/valid_new_issue_legacy.eml23
-rw-r--r--spec/fixtures/emails/valid_new_issue_with_quote.eml4
-rw-r--r--spec/fixtures/emails/valid_new_merge_request.eml6
-rw-r--r--spec/fixtures/emails/valid_new_merge_request_legacy.eml20
-rw-r--r--spec/fixtures/emails/valid_new_merge_request_no_description.eml4
-rw-r--r--spec/fixtures/emails/valid_new_merge_request_no_subject.eml4
-rw-r--r--spec/fixtures/emails/wrong_issue_incoming_email_token.eml (renamed from spec/fixtures/emails/wrong_incoming_email_token.eml)4
-rw-r--r--spec/fixtures/emails/wrong_merge_request_incoming_email_token.eml18
-rw-r--r--spec/helpers/application_settings_helper_spec.rb39
-rw-r--r--spec/javascripts/blob_edit/blob_bundle_spec.js13
-rw-r--r--spec/javascripts/lib/utils/text_markdown_spec.js387
-rw-r--r--spec/javascripts/user_popovers_spec.js11
-rw-r--r--spec/javascripts/vue_shared/components/callout_spec.js65
-rw-r--r--spec/javascripts/vue_shared/components/user_popover/user_popover_spec.js9
-rw-r--r--spec/lib/gitlab/checks/push_check_spec.rb2
-rw-r--r--spec/lib/gitlab/email/handler/create_issue_handler_spec.rb65
-rw-r--r--spec/lib/gitlab/email/handler/create_merge_request_handler_spec.rb75
-rw-r--r--spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb38
-rw-r--r--spec/lib/gitlab/email/handler_spec.rb3
-rw-r--r--spec/lib/gitlab/git_access_spec.rb18
-rw-r--r--spec/lib/gitlab/git_access_wiki_spec.rb2
-rw-r--r--spec/lib/gitlab/incoming_email_spec.rb2
-rw-r--r--spec/lib/gitlab/middleware/go_spec.rb43
-rw-r--r--spec/models/clusters/applications/knative_spec.rb17
-rw-r--r--spec/models/clusters/applications/prometheus_spec.rb15
-rw-r--r--spec/models/clusters/cluster_spec.rb1
-rw-r--r--spec/models/project_spec.rb44
-rw-r--r--spec/serializers/pipeline_serializer_spec.rb2
-rw-r--r--spec/services/ci/retry_pipeline_service_spec.rb2
-rw-r--r--spec/support/shared_contexts/email_shared_context.rb (renamed from spec/support/shared_contexts/email_shared_blocks.rb)0
-rw-r--r--vendor/prometheus/values.yaml102
128 files changed, 1412 insertions, 647 deletions
diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION
index 016dac34bf9..917d38ec9f9 100644
--- a/GITLAB_SHELL_VERSION
+++ b/GITLAB_SHELL_VERSION
@@ -1 +1 @@
-8.4.3
+8.4.4
diff --git a/Gemfile b/Gemfile
index 34e6fd17129..8d5fd8bbbcb 100644
--- a/Gemfile
+++ b/Gemfile
@@ -127,7 +127,7 @@ gem 'asciidoctor-plantuml', '0.0.8'
gem 'rouge', '~> 3.1'
gem 'truncato', '~> 0.7.9'
gem 'bootstrap_form', '~> 2.7.0'
-gem 'nokogiri', '~> 1.8.4'
+gem 'nokogiri', '~> 1.8.5'
gem 'escape_utils', '~> 1.1'
# Calendar rendering
@@ -322,7 +322,7 @@ end
group :development, :test do
gem 'bootsnap', '~> 1.3'
gem 'bullet', '~> 5.5.0', require: !!ENV['ENABLE_BULLET']
- gem 'pry-byebug', '~> 3.4.1', platform: :mri
+ gem 'pry-byebug', '~> 3.5.1', platform: :mri
gem 'pry-rails', '~> 0.3.4'
gem 'awesome_print', require: false
@@ -337,13 +337,13 @@ group :development, :test do
gem 'rspec-parameterized', require: false
# Prevent occasions where minitest is not bundled in packaged versions of ruby (see #3826)
- gem 'minitest', '~> 5.7.0'
+ gem 'minitest', '~> 5.11.0'
# Generate Fake data
gem 'ffaker', '~> 2.10'
- gem 'capybara', '~> 2.15'
- gem 'capybara-screenshot', '~> 1.0.0'
+ gem 'capybara', '~> 2.16.1'
+ gem 'capybara-screenshot', '~> 1.0.18'
gem 'selenium-webdriver', '~> 3.12'
gem 'spring', '~> 2.0.0'
diff --git a/Gemfile.lock b/Gemfile.lock
index 1f7f58d59e0..4dda0bcad18 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -97,16 +97,16 @@ GEM
bundler-audit (0.5.0)
bundler (~> 1.2)
thor (~> 0.18)
- byebug (9.0.6)
- capybara (2.15.1)
+ byebug (9.1.0)
+ capybara (2.16.1)
addressable
mini_mime (>= 0.1.3)
nokogiri (>= 1.3.3)
rack (>= 1.0.0)
rack-test (>= 0.5.4)
xpath (~> 2.0)
- capybara-screenshot (1.0.14)
- capybara (>= 1.0, < 3)
+ capybara-screenshot (1.0.22)
+ capybara (>= 1.0, < 4)
launchy
carrierwave (1.3.0)
activemodel (>= 4.0.0)
@@ -463,7 +463,7 @@ GEM
mini_magick (4.8.0)
mini_mime (1.0.1)
mini_portile2 (2.3.0)
- minitest (5.7.0)
+ minitest (5.11.3)
msgpack (1.2.4)
multi_json (1.13.1)
multi_xml (0.6.0)
@@ -595,8 +595,8 @@ GEM
pry (0.11.3)
coderay (~> 1.1.0)
method_source (~> 0.9.0)
- pry-byebug (3.4.3)
- byebug (>= 9.0, < 9.1)
+ pry-byebug (3.5.1)
+ byebug (~> 9.1)
pry (~> 0.10)
pry-rails (0.3.6)
pry (>= 0.10.4)
@@ -963,8 +963,8 @@ DEPENDENCIES
browser (~> 2.5)
bullet (~> 5.5.0)
bundler-audit (~> 0.5.0)
- capybara (~> 2.15)
- capybara-screenshot (~> 1.0.0)
+ capybara (~> 2.16.1)
+ capybara-screenshot (~> 1.0.18)
carrierwave (~> 1.3)
charlock_holmes (~> 0.7.5)
chronic (~> 0.10.2)
@@ -1054,12 +1054,12 @@ DEPENDENCIES
method_source (~> 0.8)
mimemagic (~> 0.3.2)
mini_magick
- minitest (~> 5.7.0)
+ minitest (~> 5.11.0)
mysql2 (~> 0.4.10)
nakayoshi_fork (~> 0.0.4)
net-ldap
net-ssh (~> 5.0)
- nokogiri (~> 1.8.4)
+ nokogiri (~> 1.8.5)
oauth2 (~> 1.4)
octokit (~> 4.9)
omniauth (~> 1.8)
@@ -1087,7 +1087,7 @@ DEPENDENCIES
pg (~> 0.18.2)
premailer-rails (~> 1.9.7)
prometheus-client-mmap (~> 0.9.4)
- pry-byebug (~> 3.4.1)
+ pry-byebug (~> 3.5.1)
pry-rails (~> 0.3.4)
puma (~> 3.12)
puma_worker_killer
@@ -1171,4 +1171,4 @@ DEPENDENCIES
wikicloth (= 0.8.1)
BUNDLED WITH
- 1.17.1
+ 1.17.3
diff --git a/app/assets/javascripts/blob_edit/blob_bundle.js b/app/assets/javascripts/blob_edit/blob_bundle.js
index b07f951346e..5f64175362d 100644
--- a/app/assets/javascripts/blob_edit/blob_bundle.js
+++ b/app/assets/javascripts/blob_edit/blob_bundle.js
@@ -16,6 +16,7 @@ export default () => {
const filePath = editBlobForm.data('blobFilename');
const currentAction = $('.js-file-title').data('currentAction');
const projectId = editBlobForm.data('project-id');
+ const isMarkdown = editBlobForm.data('is-markdown');
const commitButton = $('.js-commit-button');
const cancelLink = $('.btn.btn-cancel');
@@ -27,7 +28,13 @@ export default () => {
window.onbeforeunload = null;
});
- new EditBlob(`${urlRoot}${assetsPath}`, filePath, currentAction, projectId);
+ new EditBlob({
+ assetsPath: `${urlRoot}${assetsPath}`,
+ filePath,
+ currentAction,
+ projectId,
+ isMarkdown,
+ });
new NewCommitForm(editBlobForm);
// returning here blocks page navigation
diff --git a/app/assets/javascripts/blob_edit/edit_blob.js b/app/assets/javascripts/blob_edit/edit_blob.js
index 6e19548eed2..011898a5e7a 100644
--- a/app/assets/javascripts/blob_edit/edit_blob.js
+++ b/app/assets/javascripts/blob_edit/edit_blob.js
@@ -6,22 +6,31 @@ import createFlash from '~/flash';
import { __ } from '~/locale';
import TemplateSelectorMediator from '../blob/file_template_mediator';
import getModeByFileExtension from '~/lib/utils/ace_utils';
+import { addEditorMarkdownListeners } from '~/lib/utils/text_markdown';
export default class EditBlob {
- constructor(assetsPath, aceMode, currentAction, projectId) {
- this.configureAceEditor(aceMode, assetsPath);
+ // The options object has:
+ // assetsPath, filePath, currentAction, projectId, isMarkdown
+ constructor(options) {
+ this.options = options;
+ this.configureAceEditor();
this.initModePanesAndLinks();
this.initSoftWrap();
- this.initFileSelectors(currentAction, projectId);
+ this.initFileSelectors();
}
- configureAceEditor(filePath, assetsPath) {
+ configureAceEditor() {
+ const { filePath, assetsPath, isMarkdown } = this.options;
ace.config.set('modePath', `${assetsPath}/ace`);
ace.config.loadModule('ace/ext/searchbox');
ace.config.loadModule('ace/ext/modelist');
this.editor = ace.edit('editor');
+ if (isMarkdown) {
+ addEditorMarkdownListeners(this.editor);
+ }
+
// This prevents warnings re: automatic scrolling being logged
this.editor.$blockScrolling = Infinity;
@@ -32,7 +41,8 @@ export default class EditBlob {
}
}
- initFileSelectors(currentAction, projectId) {
+ initFileSelectors() {
+ const { currentAction, projectId } = this.options;
this.fileTemplateMediator = new TemplateSelectorMediator({
currentAction,
editor: this.editor,
diff --git a/app/assets/javascripts/boards/components/modal/empty_state.vue b/app/assets/javascripts/boards/components/modal/empty_state.vue
index 08408eb0b52..defd857b92c 100644
--- a/app/assets/javascripts/boards/components/modal/empty_state.vue
+++ b/app/assets/javascripts/boards/components/modal/empty_state.vue
@@ -45,7 +45,7 @@ export default {
<section class="empty-state">
<div class="row">
<div class="col-12 col-md-6 order-md-last">
- <aside class="svg-content"><img :src="emptyStateSvg" /></aside>
+ <aside class="svg-content d-none d-md-block"><img :src="emptyStateSvg" /></aside>
</div>
<div class="col-12 col-md-6 order-md-first">
<div class="text-content">
diff --git a/app/assets/javascripts/lib/utils/text_markdown.js b/app/assets/javascripts/lib/utils/text_markdown.js
index 1254ec798a6..84a617acb42 100644
--- a/app/assets/javascripts/lib/utils/text_markdown.js
+++ b/app/assets/javascripts/lib/utils/text_markdown.js
@@ -8,6 +8,10 @@ function selectedText(text, textarea) {
return text.substring(textarea.selectionStart, textarea.selectionEnd);
}
+function addBlockTags(blockTag, selected) {
+ return `${blockTag}\n${selected}\n${blockTag}`;
+}
+
function lineBefore(text, textarea) {
var split;
split = text
@@ -24,19 +28,45 @@ function lineAfter(text, textarea) {
.split('\n')[0];
}
+function editorBlockTagText(text, blockTag, selected, editor) {
+ const lines = text.split('\n');
+ const selectionRange = editor.getSelectionRange();
+ const shouldRemoveBlock =
+ lines[selectionRange.start.row - 1] === blockTag &&
+ lines[selectionRange.end.row + 1] === blockTag;
+
+ if (shouldRemoveBlock) {
+ if (blockTag !== null) {
+ // ace is globally defined
+ // eslint-disable-next-line no-undef
+ const { Range } = ace.require('ace/range');
+ const lastLine = lines[selectionRange.end.row + 1];
+ const rangeWithBlockTags = new Range(
+ lines[selectionRange.start.row - 1],
+ 0,
+ selectionRange.end.row + 1,
+ lastLine.length,
+ );
+ editor.getSelection().setSelectionRange(rangeWithBlockTags);
+ }
+ return selected;
+ }
+ return addBlockTags(blockTag, selected);
+}
+
function blockTagText(text, textArea, blockTag, selected) {
- const before = lineBefore(text, textArea);
- const after = lineAfter(text, textArea);
- if (before === blockTag && after === blockTag) {
+ const shouldRemoveBlock =
+ lineBefore(text, textArea) === blockTag && lineAfter(text, textArea) === blockTag;
+
+ if (shouldRemoveBlock) {
// To remove the block tag we have to select the line before & after
if (blockTag != null) {
textArea.selectionStart = textArea.selectionStart - (blockTag.length + 1);
textArea.selectionEnd = textArea.selectionEnd + (blockTag.length + 1);
}
return selected;
- } else {
- return blockTag + '\n' + selected + '\n' + blockTag;
}
+ return addBlockTags(blockTag, selected);
}
function moveCursor({
@@ -46,33 +76,48 @@ function moveCursor({
positionBetweenTags,
removedLastNewLine,
select,
+ editor,
+ editorSelectionStart,
+ editorSelectionEnd,
}) {
var pos;
- if (!textArea.setSelectionRange) {
+ if (textArea && !textArea.setSelectionRange) {
return;
}
if (select && select.length > 0) {
- // calculate the part of the text to be selected
- const startPosition = textArea.selectionStart - (tag.length - tag.indexOf(select));
- const endPosition = startPosition + select.length;
- return textArea.setSelectionRange(startPosition, endPosition);
- }
- if (textArea.selectionStart === textArea.selectionEnd) {
- if (positionBetweenTags) {
- pos = textArea.selectionStart - tag.length;
- } else {
- pos = textArea.selectionStart;
+ if (textArea) {
+ // calculate the part of the text to be selected
+ const startPosition = textArea.selectionStart - (tag.length - tag.indexOf(select));
+ const endPosition = startPosition + select.length;
+ return textArea.setSelectionRange(startPosition, endPosition);
+ } else if (editor) {
+ editor.navigateLeft(tag.length - tag.indexOf(select));
+ editor.getSelection().selectAWord();
+ return;
}
+ }
+ if (textArea) {
+ if (textArea.selectionStart === textArea.selectionEnd) {
+ if (positionBetweenTags) {
+ pos = textArea.selectionStart - tag.length;
+ } else {
+ pos = textArea.selectionStart;
+ }
- if (removedLastNewLine) {
- pos -= 1;
- }
+ if (removedLastNewLine) {
+ pos -= 1;
+ }
- if (cursorOffset) {
- pos -= cursorOffset;
- }
+ if (cursorOffset) {
+ pos -= cursorOffset;
+ }
- return textArea.setSelectionRange(pos, pos);
+ return textArea.setSelectionRange(pos, pos);
+ }
+ } else if (editor && editorSelectionStart.row === editorSelectionEnd.row) {
+ if (positionBetweenTags) {
+ editor.navigateLeft(tag.length);
+ }
}
}
@@ -85,6 +130,7 @@ export function insertMarkdownText({
selected = '',
wrap,
select,
+ editor,
}) {
var textToInsert,
selectedSplit,
@@ -92,11 +138,20 @@ export function insertMarkdownText({
removedLastNewLine,
removedFirstNewLine,
currentLineEmpty,
- lastNewLine;
+ lastNewLine,
+ editorSelectionStart,
+ editorSelectionEnd;
removedLastNewLine = false;
removedFirstNewLine = false;
currentLineEmpty = false;
+ if (editor) {
+ const selectionRange = editor.getSelectionRange();
+
+ editorSelectionStart = selectionRange.start;
+ editorSelectionEnd = selectionRange.end;
+ }
+
// check for link pattern and selected text is an URL
// if so fill in the url part instead of the text part of the pattern.
if (tag === LINK_TAG_PATTERN) {
@@ -119,14 +174,27 @@ export function insertMarkdownText({
}
// Remove the last newline
- if (textArea.selectionEnd - textArea.selectionStart > selected.replace(/\n$/, '').length) {
- removedLastNewLine = true;
- selected = selected.replace(/\n$/, '');
+ if (textArea) {
+ if (textArea.selectionEnd - textArea.selectionStart > selected.replace(/\n$/, '').length) {
+ removedLastNewLine = true;
+ selected = selected.replace(/\n$/, '');
+ }
+ } else if (editor) {
+ if (editorSelectionStart.row !== editorSelectionEnd.row) {
+ removedLastNewLine = true;
+ selected = selected.replace(/\n$/, '');
+ }
}
selectedSplit = selected.split('\n');
- if (!wrap) {
+ if (editor && !wrap) {
+ lastNewLine = editor.getValue().split('\n')[editorSelectionStart.row];
+
+ if (/^\s*$/.test(lastNewLine)) {
+ currentLineEmpty = true;
+ }
+ } else if (textArea && !wrap) {
lastNewLine = textArea.value.substr(0, textArea.selectionStart).lastIndexOf('\n');
// Check whether the current line is empty or consists only of spaces(=handle as empty)
@@ -135,13 +203,19 @@ export function insertMarkdownText({
}
}
- startChar = !wrap && !currentLineEmpty && textArea.selectionStart > 0 ? '\n' : '';
+ const isBeginning =
+ (textArea && textArea.selectionStart === 0) ||
+ (editor && editorSelectionStart.column === 0 && editorSelectionStart.row === 0);
+
+ startChar = !wrap && !currentLineEmpty && !isBeginning ? '\n' : '';
const textPlaceholder = '{text}';
if (selectedSplit.length > 1 && (!wrap || (blockTag != null && blockTag !== ''))) {
if (blockTag != null && blockTag !== '') {
- textToInsert = blockTagText(text, textArea, blockTag, selected);
+ textToInsert = editor
+ ? editorBlockTagText(text, blockTag, selected, editor)
+ : blockTagText(text, textArea, blockTag, selected);
} else {
textToInsert = selectedSplit
.map(function(val) {
@@ -170,7 +244,11 @@ export function insertMarkdownText({
textToInsert += '\n';
}
- insertText(textArea, textToInsert);
+ if (editor) {
+ editor.insert(textToInsert);
+ } else {
+ insertText(textArea, textToInsert);
+ }
return moveCursor({
textArea,
tag: tag.replace(textPlaceholder, selected),
@@ -178,6 +256,9 @@ export function insertMarkdownText({
positionBetweenTags: wrap && selected.length === 0,
removedLastNewLine,
select,
+ editor,
+ editorSelectionStart,
+ editorSelectionEnd,
});
}
@@ -217,6 +298,25 @@ export function addMarkdownListeners(form) {
});
}
+export function addEditorMarkdownListeners(editor) {
+ $('.js-md')
+ .off('click')
+ .on('click', function(e) {
+ const { mdTag, mdBlock, mdPrepend, mdSelect } = $(e.currentTarget).data();
+
+ insertMarkdownText({
+ tag: mdTag,
+ blockTag: mdBlock,
+ wrap: !mdPrepend,
+ select: mdSelect,
+ selected: editor.getSelectedText(),
+ text: editor.getValue(),
+ editor,
+ });
+ editor.focus();
+ });
+}
+
export function removeMarkdownListeners(form) {
return $('.js-md', form).off('click');
}
diff --git a/app/assets/javascripts/notes/components/note_form.vue b/app/assets/javascripts/notes/components/note_form.vue
index 9b7f3d3588d..e78596f8b52 100644
--- a/app/assets/javascripts/notes/components/note_form.vue
+++ b/app/assets/javascripts/notes/components/note_form.vue
@@ -226,7 +226,7 @@ export default {
<button
:disabled="isDisabled"
type="button"
- class="js-vue-issue-save btn btn-success js-comment-button"
+ class="js-vue-issue-save btn btn-success js-comment-button qa-reply-comment-button"
@click="handleUpdate();"
>
{{ saveButtonTitle }}
diff --git a/app/assets/javascripts/notes/components/toggle_replies_widget.vue b/app/assets/javascripts/notes/components/toggle_replies_widget.vue
index 72a8ff28466..f1b0b12bdce 100644
--- a/app/assets/javascripts/notes/components/toggle_replies_widget.vue
+++ b/app/assets/javascripts/notes/components/toggle_replies_widget.vue
@@ -57,7 +57,7 @@ export default {
tooltip-placement="bottom"
/>
</div>
- <button class="btn btn-link js-replies-text" type="button" @click="toggle">
+ <button class="btn btn-link js-replies-text qa-expand-replies" type="button" @click="toggle">
{{ replies.length }} {{ n__('reply', 'replies', replies.length) }}
</button>
{{ __('Last reply by') }}
@@ -66,7 +66,11 @@ export default {
</a>
<time-ago-tooltip :time="lastReply.created_at" tooltip-placement="bottom" />
</template>
- <span v-else class="collapse-replies-btn js-collapse-replies" @click="toggle">
+ <span
+ v-else
+ class="collapse-replies-btn js-collapse-replies qa-collapse-replies"
+ @click="toggle"
+ >
<icon name="chevron-down" /> {{ s__('Notes|Collapse replies') }}
</span>
</li>
diff --git a/app/assets/javascripts/vue_shared/components/callout.vue b/app/assets/javascripts/vue_shared/components/callout.vue
index ddbb14ae812..56bafebf4ce 100644
--- a/app/assets/javascripts/vue_shared/components/callout.vue
+++ b/app/assets/javascripts/vue_shared/components/callout.vue
@@ -11,13 +11,14 @@ export default {
},
message: {
type: String,
- required: true,
+ required: false,
+ default: '',
},
},
};
</script>
<template>
<div :class="`bs-callout bs-callout-${category}`" role="alert" aria-live="assertive">
- {{ message }}
+ {{ message }} <slot></slot>
</div>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_image.vue b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_image.vue
index e833a8e0483..95f4395ac13 100644
--- a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_image.vue
+++ b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_image.vue
@@ -67,6 +67,7 @@ export default {
// In both cases we should render the defaultAvatarUrl
sanitizedSource() {
let baseSrc = this.imgSrc === '' || this.imgSrc === null ? defaultAvatarUrl : this.imgSrc;
+ // Only adds the width to the URL if its not a base64 data image
if (!baseSrc.startsWith('data:') && !baseSrc.includes('?')) baseSrc += `?width=${this.size}`;
return baseSrc;
},
diff --git a/app/assets/stylesheets/bootstrap_migration.scss b/app/assets/stylesheets/bootstrap_migration.scss
index f0671e36130..587127bb059 100644
--- a/app/assets/stylesheets/bootstrap_migration.scss
+++ b/app/assets/stylesheets/bootstrap_migration.scss
@@ -70,6 +70,17 @@ h6,
margin-bottom: 10px;
}
+/* Our adjustments to hx & .hx above add unnecessary margins to modal-title
+ and page-title in modals, so we set them to 0 in order to have properly
+ formatted modal headers. */
+.modal-header {
+ .modal-title,
+ .page-title {
+ margin-top: 0;
+ margin-bottom: 0;
+ }
+}
+
h5,
.h5 {
font-size: $gl-font-size;
@@ -134,7 +145,8 @@ table {
pointer-events: none;
}
-.popover {
+.popover,
+.popover-header {
font-size: 14px;
}
@@ -142,7 +154,9 @@ table {
@include media-breakpoint-up($breakpoint) {
$infix: breakpoint-infix($breakpoint, $grid-breakpoints);
- .d#{$infix}-table-header-group { display: table-header-group !important; }
+ .d#{$infix}-table-header-group {
+ display: table-header-group !important;
+ }
}
}
diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss
index 834e7ffce81..62d471bc30c 100644
--- a/app/assets/stylesheets/framework.scss
+++ b/app/assets/stylesheets/framework.scss
@@ -31,7 +31,6 @@
@import 'framework/logo';
@import 'framework/markdown_area';
@import 'framework/media_object';
-@import 'framework/mobile';
@import 'framework/modal';
@import 'framework/pagination';
@import 'framework/panels';
diff --git a/app/assets/stylesheets/framework/animations.scss b/app/assets/stylesheets/framework/animations.scss
index 549a8730301..43d4044033f 100644
--- a/app/assets/stylesheets/framework/animations.scss
+++ b/app/assets/stylesheets/framework/animations.scss
@@ -260,3 +260,25 @@ $skeleton-line-widths: (
.slide-down-leave-to {
transform: translateY(-30%);
}
+
+@keyframes spin {
+ 0% { transform: rotate(0deg);}
+ 100% { transform: rotate(360deg);}
+}
+
+/** COMMON ANIMATION CLASSES **/
+.transform-origin-center { @include webkit-prefix(transform-origin, 50% 50%); }
+.animate-n-spin { @include webkit-prefix(animation-name, spin); }
+.animate-c-infinite { @include webkit-prefix(animation-iteration-count, infinite); }
+.animate-t-linear { @include webkit-prefix(animation-timing-function, linear); }
+.animate-d-1 { @include webkit-prefix(animation-duration, 1s); }
+.animate-d-2 { @include webkit-prefix(animation-duration, 2s); }
+
+/** COMPOSITE ANIMATION CLASSES **/
+.gl-spinner {
+ @include webkit-prefix(animation-name, spin);
+ @include webkit-prefix(animation-iteration-count, infinite);
+ @include webkit-prefix(animation-timing-function, linear);
+ @include webkit-prefix(animation-duration, 1s);
+ transform-origin: 50% 50%;
+}
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index 7d283dcfb71..5574873fa22 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -597,3 +597,11 @@
@include emoji-menu-toggle-button;
}
}
+
+.nav-links > li > a {
+ .badge.badge-pill {
+ @include media-breakpoint-down(xs) { display: none; }
+ }
+
+ @include media-breakpoint-down(xs) { margin-right: 3px; }
+}
diff --git a/app/assets/stylesheets/framework/layout.scss b/app/assets/stylesheets/framework/layout.scss
index 9218df9b40f..97cb9d90ff0 100644
--- a/app/assets/stylesheets/framework/layout.scss
+++ b/app/assets/stylesheets/framework/layout.scss
@@ -40,6 +40,14 @@ body {
.content {
margin: 0;
+
+ @include media-breakpoint-down(xs) { margin-top: 20px; }
+ }
+
+ @include media-breakpoint-down(xs) {
+ .container .title {
+ padding-left: 15px !important;
+ }
}
}
diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss
index 5609a2086e6..ce46d760d7b 100644
--- a/app/assets/stylesheets/framework/markdown_area.scss
+++ b/app/assets/stylesheets/framework/markdown_area.scss
@@ -173,7 +173,7 @@
svg {
width: 14px;
height: 14px;
- margin-top: 3px;
+ vertical-align: middle;
fill: $gl-text-color-secondary;
}
@@ -307,4 +307,8 @@
overflow: hidden;
text-overflow: ellipsis;
}
+
+ .referenced-users {
+ margin-right: 0;
+ }
}
diff --git a/app/assets/stylesheets/framework/mobile.scss b/app/assets/stylesheets/framework/mobile.scss
deleted file mode 100644
index 3bb046d0e51..00000000000
--- a/app/assets/stylesheets/framework/mobile.scss
+++ /dev/null
@@ -1,88 +0,0 @@
-/** Common mobile (screen XS, SM) styles **/
-@include media-breakpoint-down(xs) {
- .container .content {
- margin-top: 20px;
- }
-
- .nav-links > li > a {
- padding: 10px;
- font-size: 12px;
- margin-right: 3px;
-
- .badge.badge-pill {
- display: none;
- }
- }
-
- .referenced-users {
- margin-right: 0;
- }
-
- .issues-details-filters:not(.filtered-search-block),
- .dash-projects-filters,
- .check-all-holder {
- display: none;
- }
-
- .rss-btn {
- display: none;
- }
-
- .project-home-links {
- display: none;
- }
-
- .project-home-panel {
- padding-left: 0 !important;
-
- .project-repo-buttons,
- .git-clone-holder {
- display: none;
- }
- }
-
- .group-buttons {
- display: none;
- }
-
- .container .title {
- padding-left: 15px !important;
- }
-
- .nav-links,
- .nav-links {
- li a {
- font-size: 14px;
- padding: 19px 10px;
- }
- }
-
- .activity-filter-block {
- display: none;
- }
-
- .projects-search-form {
- .btn {
- display: none;
- }
- }
-}
-
-@include media-breakpoint-down(sm) {
- .issues-filters {
- .milestone-filter {
- display: none;
- }
- }
-
- .page-title {
- .note-created-ago,
- .new-issue-link {
- display: none;
- }
- }
-
- aside:not(.right-sidebar) {
- display: none;
- }
-}
diff --git a/app/assets/stylesheets/framework/modal.scss b/app/assets/stylesheets/framework/modal.scss
index 95291b4a9ad..46d40ea7aa5 100644
--- a/app/assets/stylesheets/framework/modal.scss
+++ b/app/assets/stylesheets/framework/modal.scss
@@ -29,10 +29,6 @@
padding-right: 28px;
}
}
-
- .page-title {
- margin-top: 0;
- }
}
.modal-body {
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 7e53f1ec48d..d92d81b2cb5 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -32,6 +32,15 @@ $gray-dark: darken($gray-light, $darken-dark-factor);
$gray-darker: #eee;
$gray-darkest: #c4c4c4;
+$black: #000;
+$black-transparent: rgba(0, 0, 0, 0.3);
+$almost-black: #242424;
+
+$t-gray-a-02: rgba($black, 0.02);
+$t-gray-a-04: rgba($black, 0.04);
+$t-gray-a-06: rgba($black, 0.06);
+$t-gray-a-08: rgba($black, 0.08);
+
$gl-gray-100: #dddddd;
$gl-gray-200: #cccccc;
$gl-gray-350: #aaaaaa;
@@ -170,11 +179,6 @@ $theme-light-red-500: #c24b38;
$theme-light-red-600: #b03927;
$theme-light-red-700: #a62e21;
-$black: #000;
-$black-transparent: rgba(0, 0, 0, 0.3);
-$shadow-color: rgba($black, 0.1);
-$almost-black: #242424;
-
$border-white-light: darken($white-light, $darken-border-factor);
$border-white-normal: darken($white-normal, $darken-border-factor);
@@ -187,6 +191,7 @@ $border-gray-dark: darken($white-normal, $darken-border-factor);
* UI elements
*/
$border-color: #e5e5e5;
+$shadow-color: $t-gray-a-08;
$well-expand-item: #e8f2f7;
$well-inner-border: #eef0f2;
$well-light-border: #f1f1f1;
diff --git a/app/assets/stylesheets/pages/editor.scss b/app/assets/stylesheets/pages/editor.scss
index f46ff360496..5a988b184b6 100644
--- a/app/assets/stylesheets/pages/editor.scss
+++ b/app/assets/stylesheets/pages/editor.scss
@@ -128,6 +128,10 @@
width: 100%;
}
}
+
+ @media(max-width: map-get($grid-breakpoints, md)-1) {
+ clear: both;
+ }
}
.editor-ref {
diff --git a/app/controllers/projects/git_http_controller.rb b/app/controllers/projects/git_http_controller.rb
index c0aa39d87c6..30e436365de 100644
--- a/app/controllers/projects/git_http_controller.rb
+++ b/app/controllers/projects/git_http_controller.rb
@@ -80,9 +80,7 @@ class Projects::GitHttpController < Projects::GitHttpClientController
end
def access_check
- # Use the magic string '_any' to indicate we do not know what the
- # changes are. This is also what gitlab-shell does.
- access.check(git_command, '_any')
+ access.check(git_command, Gitlab::GitAccess::ANY)
@project ||= access.project
end
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index 5a7c005fd06..c8e4e2e3df9 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -20,7 +20,7 @@ module ApplicationSettingsHelper
def enabled_protocol
case Gitlab::CurrentSettings.enabled_git_access_protocol
when 'http'
- gitlab_config.protocol
+ Gitlab.config.gitlab.protocol
when 'ssh'
'ssh'
end
@@ -35,7 +35,7 @@ module ApplicationSettingsHelper
end
def http_enabled?
- all_protocols_enabled? || enabled_protocol == 'http'
+ all_protocols_enabled? || Gitlab::CurrentSettings.enabled_git_access_protocol == 'http'
end
def enabled_project_button(project, protocol)
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index 3dea0975beb..23d6684a8e6 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -177,7 +177,8 @@ module BlobHelper
'relative-url-root' => Rails.application.config.relative_url_root,
'assets-prefix' => Gitlab::Application.config.assets.prefix,
'blob-filename' => @blob && @blob.path,
- 'project-id' => project.id
+ 'project-id' => project.id,
+ 'is-markdown' => @blob && @blob.path && Gitlab::MarkupHelper.gitlab_markdown?(@blob.path)
}
end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index b128a254b96..aeb35538d67 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -221,6 +221,10 @@ module Ci
next unless build.project
build.deployment&.drop
+ end
+
+ after_transition any => [:failed] do |build|
+ next unless build.project
if build.retry_failure?
begin
diff --git a/app/models/clusters/applications/ingress.rb b/app/models/clusters/applications/ingress.rb
index 8f8790585a3..7799f069742 100644
--- a/app/models/clusters/applications/ingress.rb
+++ b/app/models/clusters/applications/ingress.rb
@@ -23,7 +23,7 @@ module Clusters
FETCH_IP_ADDRESS_DELAY = 30.seconds
state_machine :status do
- before_transition any => [:installed] do |application|
+ after_transition any => [:installed] do |application|
application.run_after_commit do
ClusterWaitForIngressIpAddressWorker.perform_in(
FETCH_IP_ADDRESS_DELAY, application.name, application.id)
diff --git a/app/models/clusters/applications/knative.rb b/app/models/clusters/applications/knative.rb
index 0c72d7d8340..0a3168afe68 100644
--- a/app/models/clusters/applications/knative.rb
+++ b/app/models/clusters/applications/knative.rb
@@ -5,7 +5,7 @@ module Clusters
class Knative < ActiveRecord::Base
VERSION = '0.2.2'.freeze
REPOSITORY = 'https://storage.googleapis.com/triggermesh-charts'.freeze
-
+ METRICS_CONFIG = 'https://storage.googleapis.com/triggermesh-charts/istio-metrics.yaml'.freeze
FETCH_IP_ADDRESS_DELAY = 30.seconds
self.table_name = 'clusters_applications_knative'
@@ -20,7 +20,7 @@ module Clusters
self.reactive_cache_key = ->(knative) { [knative.class.model_name.singular, knative.id] }
state_machine :status do
- before_transition any => [:installed] do |application|
+ after_transition any => [:installed] do |application|
application.run_after_commit do
ClusterWaitForIngressIpAddressWorker.perform_in(
FETCH_IP_ADDRESS_DELAY, application.name, application.id)
@@ -49,7 +49,8 @@ module Clusters
rbac: cluster.platform_kubernetes_rbac?,
chart: chart,
files: files,
- repository: REPOSITORY
+ repository: REPOSITORY,
+ postinstall: install_knative_metrics
)
end
@@ -94,6 +95,10 @@ module Clusters
rescue Kubeclient::ResourceNotFoundError
[]
end
+
+ def install_knative_metrics
+ ["kubectl apply -f #{METRICS_CONFIG}"] if cluster.application_prometheus_available?
+ end
end
end
end
diff --git a/app/models/clusters/applications/prometheus.rb b/app/models/clusters/applications/prometheus.rb
index 46d0388a464..e25be522d68 100644
--- a/app/models/clusters/applications/prometheus.rb
+++ b/app/models/clusters/applications/prometheus.rb
@@ -50,7 +50,8 @@ module Clusters
version: VERSION,
rbac: cluster.platform_kubernetes_rbac?,
chart: chart,
- files: files
+ files: files,
+ postinstall: install_knative_metrics
)
end
@@ -74,6 +75,10 @@ module Clusters
def kube_client
cluster&.kubeclient&.core_client
end
+
+ def install_knative_metrics
+ ["kubectl apply -f #{Clusters::Applications::Knative::METRICS_CONFIG}"] if cluster.application_knative_available?
+ end
end
end
end
diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb
index 7fe43cd2de0..6050955fbd8 100644
--- a/app/models/clusters/cluster.rb
+++ b/app/models/clusters/cluster.rb
@@ -63,6 +63,7 @@ module Clusters
delegate :available?, to: :application_helm, prefix: true, allow_nil: true
delegate :available?, to: :application_ingress, prefix: true, allow_nil: true
delegate :available?, to: :application_prometheus, prefix: true, allow_nil: true
+ delegate :available?, to: :application_knative, prefix: true, allow_nil: true
enum cluster_type: {
instance_type: 1,
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index b937bef100b..6092c56b925 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -105,7 +105,9 @@ class MergeRequest < ActiveRecord::Base
before_transition any => :opened do |merge_request|
merge_request.merge_jid = nil
+ end
+ after_transition any => :opened do |merge_request|
merge_request.run_after_commit do
UpdateHeadPipelineForMergeRequestWorker.perform_async(merge_request.id)
end
diff --git a/app/models/project.rb b/app/models/project.rb
index cd558752080..7a2bc8b78b3 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -912,11 +912,16 @@ class Project < ActiveRecord::Base
def new_issuable_address(author, address_type)
return unless Gitlab::IncomingEmail.supports_issue_creation? && author
+ # check since this can come from a request parameter
+ return unless %w(issue merge_request).include?(address_type)
+
author.ensure_incoming_email_token!
- suffix = address_type == 'merge_request' ? '+merge-request' : ''
- Gitlab::IncomingEmail.reply_address(
- "#{full_path}#{suffix}+#{author.incoming_email_token}")
+ suffix = address_type.dasherize
+
+ # example: incoming+h5bp-html5-boilerplate-8-1234567890abcdef123456789-issue@localhost.com
+ # example: incoming+h5bp-html5-boilerplate-8-1234567890abcdef123456789-merge-request@localhost.com
+ Gitlab::IncomingEmail.reply_address("#{full_path_slug}-#{project_id}-#{author.incoming_email_token}-#{suffix}")
end
def build_commit_note(commit)
@@ -1928,23 +1933,15 @@ class Project < ActiveRecord::Base
.where('project_authorizations.project_id = merge_requests.target_project_id')
.limit(1)
.select(1)
- source_of_merge_requests.opened
- .where(allow_collaboration: true)
- .where('EXISTS (?)', developer_access_exists)
+ merge_requests_allowing_collaboration.where('EXISTS (?)', developer_access_exists)
end
- def branch_allows_collaboration?(user, branch_name)
- return false unless user
-
- cache_key = "user:#{user.id}:#{branch_name}:branch_allows_push"
-
- memoized_results = strong_memoize(:branch_allows_collaboration) do
- Hash.new do |result, cache_key|
- result[cache_key] = fetch_branch_allows_collaboration?(user, branch_name)
- end
- end
+ def any_branch_allows_collaboration?(user)
+ fetch_branch_allows_collaboration(user)
+ end
- memoized_results[cache_key]
+ def branch_allows_collaboration?(user, branch_name)
+ fetch_branch_allows_collaboration(user, branch_name)
end
def licensed_features
@@ -2018,6 +2015,12 @@ class Project < ActiveRecord::Base
private
+ def merge_requests_allowing_collaboration(source_branch = nil)
+ relation = source_of_merge_requests.opened.where(allow_collaboration: true)
+ relation = relation.where(source_branch: source_branch) if source_branch
+ relation
+ end
+
def create_new_pool_repository
pool = begin
create_pool_repository!(shard: Shard.by_name(repository_storage), source_project: self)
@@ -2142,26 +2145,19 @@ class Project < ActiveRecord::Base
raise ex
end
- def fetch_branch_allows_collaboration?(user, branch_name)
- check_access = -> do
- next false if empty_repo?
+ def fetch_branch_allows_collaboration(user, branch_name = nil)
+ return false unless user
- merge_requests = source_of_merge_requests.opened
- .where(allow_collaboration: true)
+ Gitlab::SafeRequestStore.fetch("project-#{id}:branch-#{branch_name}:user-#{user.id}:branch_allows_collaboration") do
+ next false if empty_repo?
# Issue for N+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/49322
Gitlab::GitalyClient.allow_n_plus_1_calls do
- if branch_name
- merge_requests.find_by(source_branch: branch_name)&.can_be_merged_by?(user)
- else
- merge_requests.any? { |merge_request| merge_request.can_be_merged_by?(user) }
+ merge_requests_allowing_collaboration(branch_name).any? do |merge_request|
+ merge_request.can_be_merged_by?(user)
end
end
end
-
- Gitlab::SafeRequestStore.fetch("project-#{id}:branch-#{branch_name}:user-#{user.id}:branch_allows_collaboration") do
- check_access.call
- end
end
def services_templates
diff --git a/app/models/project_services/kubernetes_service.rb b/app/models/project_services/kubernetes_service.rb
index b801fd84a07..f69edd60003 100644
--- a/app/models/project_services/kubernetes_service.rb
+++ b/app/models/project_services/kubernetes_service.rb
@@ -53,7 +53,7 @@ class KubernetesService < DeploymentService
end
def description
- 'Kubernetes / Openshift integration'
+ 'Kubernetes / OpenShift integration'
end
def help
diff --git a/app/views/dashboard/_activities.html.haml b/app/views/dashboard/_activities.html.haml
index 9c246e19faa..4359a2c3c2b 100644
--- a/app/views/dashboard/_activities.html.haml
+++ b/app/views/dashboard/_activities.html.haml
@@ -1,7 +1,7 @@
.nav-block.activities
= render 'shared/event_filter'
.controls
- = link_to dashboard_projects_path(rss_url_options), class: 'btn rss-btn has-tooltip', title: 'Subscribe' do
+ = link_to dashboard_projects_path(rss_url_options), class: 'btn d-none d-sm-inline-block has-tooltip', title: 'Subscribe' do
%i.fa.fa-rss
.content_list
diff --git a/app/views/groups/_activities.html.haml b/app/views/groups/_activities.html.haml
index 82a497289f3..13df1e57125 100644
--- a/app/views/groups/_activities.html.haml
+++ b/app/views/groups/_activities.html.haml
@@ -1,7 +1,7 @@
.nav-block.activities
= render 'shared/event_filter'
.controls
- = link_to group_path(@group, rss_url_options), class: 'btn rss-btn has-tooltip' , title: 'Subscribe' do
+ = link_to group_path(@group, rss_url_options), class: 'btn d-none d-sm-inline-block has-tooltip' , title: 'Subscribe' do
%i.fa.fa-rss
.content_list
diff --git a/app/views/groups/_home_panel.html.haml b/app/views/groups/_home_panel.html.haml
index 6219da2c715..88e401081f4 100644
--- a/app/views/groups/_home_panel.html.haml
+++ b/app/views/groups/_home_panel.html.haml
@@ -12,6 +12,6 @@
= markdown_field(@group, :description)
- if current_user
- .group-buttons
+ .group-buttons.d-none.d-sm-block
= render 'shared/members/access_request_buttons', source: @group
= render 'shared/notifications/button', notification_setting: @notification_setting
diff --git a/app/views/projects/_activity.html.haml b/app/views/projects/_activity.html.haml
index 6bf21570d41..31f1cf560e2 100644
--- a/app/views/projects/_activity.html.haml
+++ b/app/views/projects/_activity.html.haml
@@ -1,8 +1,8 @@
%div{ class: container_class }
- .nav-block.activity-filter-block.activities
+ .nav-block.d-none.d-sm-block.activities
= render 'shared/event_filter'
.controls
- = link_to project_path(@project, rss_url_options), title: s_("ProjectActivityRSS|Subscribe"), class: 'btn rss-btn has-tooltip' do
+ = link_to project_path(@project, rss_url_options), title: s_("ProjectActivityRSS|Subscribe"), class: 'btn d-none d-sm-inline-block has-tooltip' do
= icon('rss')
.content_list.project-activity{ :"data-href" => activity_project_path(@project) }
diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml
index 0f709c65d0e..03ba1104507 100644
--- a/app/views/projects/_md_preview.html.haml
+++ b/app/views/projects/_md_preview.html.haml
@@ -18,17 +18,7 @@
Preview
%li.md-header-toolbar.active
- = markdown_toolbar_button({ icon: "bold", data: { "md-tag" => "**" }, title: s_("MarkdownToolbar|Add bold text") })
- = markdown_toolbar_button({ icon: "italic", data: { "md-tag" => "*" }, title: s_("MarkdownToolbar|Add italic text") })
- = markdown_toolbar_button({ icon: "quote", data: { "md-tag" => "> ", "md-prepend" => true }, title: s_("MarkdownToolbar|Insert a quote") })
- = markdown_toolbar_button({ icon: "code", data: { "md-tag" => "`", "md-block" => "```" }, title: s_("MarkdownToolbar|Insert code") })
- = markdown_toolbar_button({ icon: "link", data: { "md-tag" => "[{text}](url)", "md-select" => "url" }, title: s_("MarkdownToolbar|Add a link") })
- = markdown_toolbar_button({ icon: "list-bulleted", data: { "md-tag" => "* ", "md-prepend" => true }, title: s_("MarkdownToolbar|Add a bullet list") })
- = markdown_toolbar_button({ icon: "list-numbered", data: { "md-tag" => "1. ", "md-prepend" => true }, title: s_("MarkdownToolbar|Add a numbered list") })
- = markdown_toolbar_button({ icon: "task-done", data: { "md-tag" => "* [ ] ", "md-prepend" => true }, title: s_("MarkdownToolbar|Add a task list") })
- = markdown_toolbar_button({ icon: "table", data: { "md-tag" => "| header | header |\n| ------ | ------ |\n| cell | cell |\n| cell | cell |", "md-prepend" => true }, title: s_("MarkdownToolbar|Add a table") })
- %button.toolbar-btn.toolbar-fullscreen-btn.js-zen-enter.has-tooltip{ type: "button", tabindex: -1, "aria-label": "Go full screen", title: s_("MarkdownToolbar|Go full screen"), data: { container: "body" } }
- = sprite_icon("screen-full")
+ = render 'projects/blob/markdown_buttons', show_fullscreen_button: true
.md-write-holder
= yield
diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml
index 3c1f33ea95e..a54460f1196 100644
--- a/app/views/projects/blob/_editor.html.haml
+++ b/app/views/projects/blob/_editor.html.haml
@@ -1,4 +1,6 @@
- action = current_action?(:edit) || current_action?(:update) ? 'edit' : 'create'
+- file_name = params[:id].split("/").last ||= ""
+- is_markdown = Gitlab::MarkupHelper.gitlab_markdown?(file_name)
.file-holder-bottom-radius.file-holder.file.append-bottom-default
.js-file-title.file-title.clearfix{ data: { current_action: action } }
@@ -17,6 +19,8 @@
required: true, class: 'form-control new-file-name js-file-path-name-input'
.file-buttons
+ - if is_markdown
+ = render 'projects/blob/markdown_buttons', show_fullscreen_button: false
= button_tag class: 'soft-wrap-toggle btn', type: 'button', tabindex: '-1' do
%span.no-wrap
= custom_icon('icon_no_wrap')
diff --git a/app/views/projects/blob/_markdown_buttons.html.haml b/app/views/projects/blob/_markdown_buttons.html.haml
new file mode 100644
index 00000000000..1d6acd86108
--- /dev/null
+++ b/app/views/projects/blob/_markdown_buttons.html.haml
@@ -0,0 +1,13 @@
+.md-header-toolbar.active
+ = markdown_toolbar_button({ icon: "bold", data: { "md-tag" => "**" }, title: s_("MarkdownToolbar|Add bold text") })
+ = markdown_toolbar_button({ icon: "italic", data: { "md-tag" => "*" }, title: s_("MarkdownToolbar|Add italic text") })
+ = markdown_toolbar_button({ icon: "quote", data: { "md-tag" => "> ", "md-prepend" => true }, title: s_("MarkdownToolbar|Insert a quote") })
+ = markdown_toolbar_button({ icon: "code", data: { "md-tag" => "`", "md-block" => "```" }, title: s_("MarkdownToolbar|Insert code") })
+ = markdown_toolbar_button({ icon: "link", data: { "md-tag" => "[{text}](url)", "md-select" => "url" }, title: s_("MarkdownToolbar|Add a link") })
+ = markdown_toolbar_button({ icon: "list-bulleted", data: { "md-tag" => "* ", "md-prepend" => true }, title: s_("MarkdownToolbar|Add a bullet list") })
+ = markdown_toolbar_button({ icon: "list-numbered", data: { "md-tag" => "1. ", "md-prepend" => true }, title: s_("MarkdownToolbar|Add a numbered list") })
+ = markdown_toolbar_button({ icon: "task-done", data: { "md-tag" => "* [ ] ", "md-prepend" => true }, title: s_("MarkdownToolbar|Add a task list") })
+ = markdown_toolbar_button({ icon: "table", data: { "md-tag" => "| header | header |\n| ------ | ------ |\n| cell | cell |\n| cell | cell |", "md-prepend" => true }, title: s_("MarkdownToolbar|Add a table") })
+ - if show_fullscreen_button
+ %button.toolbar-btn.toolbar-fullscreen-btn.js-zen-enter.has-tooltip{ type: "button", tabindex: -1, "aria-label": "Go full screen", title: s_("MarkdownToolbar|Go full screen"), data: { container: "body" } }
+ = sprite_icon("screen-full")
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index 8c2fe2625c7..f048fb91304 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -55,7 +55,7 @@
- if can_report_spam
= link_to 'Submit as spam', mark_as_spam_project_issue_path(@project, @issue), method: :post, class: 'd-none d-sm-none d-md-block btn btn-grouped btn-spam', title: 'Submit as spam'
- if can_create_issue
- = link_to new_project_issue_path(@project), class: 'd-none d-sm-none d-md-block btn btn-grouped new-issue-link btn-success btn-inverted', title: 'New issue', id: 'new_issue_link' do
+ = link_to new_project_issue_path(@project), class: 'd-none d-sm-none d-md-block btn btn-grouped btn-success btn-inverted', title: 'New issue', id: 'new_issue_link' do
New issue
.issue-details.issuable-details
diff --git a/app/views/projects/services/prometheus/_metrics.html.haml b/app/views/projects/services/prometheus/_metrics.html.haml
index 597c029f755..a1d74b91002 100644
--- a/app/views/projects/services/prometheus/_metrics.html.haml
+++ b/app/views/projects/services/prometheus/_metrics.html.haml
@@ -1,6 +1,6 @@
- project = local_assigns.fetch(:project)
-.card.js-panel-monitored-metrics{ data: { active_metrics: active_common_project_prometheus_metrics_path(project, :json), metrics_help_path: help_page_path('user/project/integrations/prometheus_library/metrics') } }
+.card.js-panel-monitored-metrics{ data: { active_metrics: active_common_project_prometheus_metrics_path(project, :json), metrics_help_path: help_page_path('user/project/integrations/prometheus_library/index') } }
.card-header
= s_('PrometheusService|Common metrics')
%span.badge.badge-pill.js-monitored-count 0
diff --git a/app/views/projects/services/prometheus/_show.html.haml b/app/views/projects/services/prometheus/_show.html.haml
index 1d0b0265bb7..9d4574c4590 100644
--- a/app/views/projects/services/prometheus/_show.html.haml
+++ b/app/views/projects/services/prometheus/_show.html.haml
@@ -4,7 +4,7 @@
= s_('PrometheusService|Metrics')
%p
= s_('PrometheusService|Common metrics are automatically monitored based on a library of metrics from popular exporters.')
- = link_to s_('PrometheusService|More information'), help_page_path('user/project/integrations/prometheus_library/metrics'), target: '_blank', rel: "noopener noreferrer"
+ = link_to s_('PrometheusService|More information'), help_page_path('user/project/integrations/prometheus_library/index'), target: '_blank', rel: "noopener noreferrer"
.col-lg-9
= render 'projects/services/prometheus/metrics', project: @project
diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml
index 026bc44a05f..458096f9dd6 100644
--- a/app/views/projects/tags/index.html.haml
+++ b/app/views/projects/tags/index.html.haml
@@ -27,7 +27,7 @@
- if can?(current_user, :push_code, @project)
= link_to new_project_tag_path(@project), class: 'btn btn-success new-tag-btn' do
= s_('TagsPage|New tag')
- = link_to project_tags_path(@project, rss_url_options), title: _("Tags feed"), class: 'btn rss-btn has-tooltip' do
+ = link_to project_tags_path(@project, rss_url_options), title: _("Tags feed"), class: 'btn d-none d-sm-inline-block has-tooltip' do
= icon("rss")
= render_if_exists 'projects/commits/mirror_status'
diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml
index 46634693067..20847378495 100644
--- a/app/views/shared/issuable/_search_bar.html.haml
+++ b/app/views/shared/issuable/_search_bar.html.haml
@@ -11,7 +11,7 @@
- if params[:search].present?
= hidden_field_tag :search, params[:search]
- if @can_bulk_update
- .check-all-holder.hidden
+ .check-all-holder.d-none.d-sm-block.hidden
= check_box_tag "check-all-issues", nil, false, class: "check-all-issues left"
.issues-other-filters.filtered-search-wrapper
.filtered-search-box
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index dd2cd36eac2..8da63a29ca6 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -71,7 +71,7 @@
= icon('twitter-square')
- unless @user.website_url.blank?
.profile-link-holder.middle-dot-divider
- = link_to @user.short_website_url, @user.full_website_url, class: 'text-link', target: '_blank', rel: 'noopener noreferrer nofollow'
+ = link_to @user.short_website_url, @user.full_website_url, class: 'text-link', target: '_blank', rel: 'me noopener noreferrer nofollow'
- unless @user.location.blank?
.profile-link-holder.middle-dot-divider
= icon('map-marker')
diff --git a/changelogs/unreleased/27861-add-markdown-editing-buttons-to-the-file-editor.yml b/changelogs/unreleased/27861-add-markdown-editing-buttons-to-the-file-editor.yml
new file mode 100644
index 00000000000..00eb5223d58
--- /dev/null
+++ b/changelogs/unreleased/27861-add-markdown-editing-buttons-to-the-file-editor.yml
@@ -0,0 +1,5 @@
+---
+title: Add markdown helper buttons to file editor
+merge_request: 23480
+author:
+type: added
diff --git a/changelogs/unreleased/29951-issue-creation-by-email-without-subaddressing.yml b/changelogs/unreleased/29951-issue-creation-by-email-without-subaddressing.yml
new file mode 100644
index 00000000000..4139099eac3
--- /dev/null
+++ b/changelogs/unreleased/29951-issue-creation-by-email-without-subaddressing.yml
@@ -0,0 +1,5 @@
+---
+title: No longer require email subaddressing for issue creation by email
+merge_request: 23523
+author:
+type: changed
diff --git a/changelogs/unreleased/54386-integrate-mobile-css-framework-into-specific-frameworks.yml b/changelogs/unreleased/54386-integrate-mobile-css-framework-into-specific-frameworks.yml
new file mode 100644
index 00000000000..e446d2a2781
--- /dev/null
+++ b/changelogs/unreleased/54386-integrate-mobile-css-framework-into-specific-frameworks.yml
@@ -0,0 +1,5 @@
+---
+title: Remove framework/mobile.scss
+merge_request: 23301
+author: Takuya Noguchi
+type: other
diff --git a/changelogs/unreleased/54844-report-syntax-dep-scan-ado.yml b/changelogs/unreleased/54844-report-syntax-dep-scan-ado.yml
new file mode 100644
index 00000000000..95fc5cb804d
--- /dev/null
+++ b/changelogs/unreleased/54844-report-syntax-dep-scan-ado.yml
@@ -0,0 +1,5 @@
+---
+title: Use reports syntax for Dependency scanning in Auto DevOps
+merge_request: 24081
+author:
+type: added
diff --git a/changelogs/unreleased/55883-modal-header-titles-have-an-unnecessary-top-margin.yml b/changelogs/unreleased/55883-modal-header-titles-have-an-unnecessary-top-margin.yml
new file mode 100644
index 00000000000..7dc783cc2b8
--- /dev/null
+++ b/changelogs/unreleased/55883-modal-header-titles-have-an-unnecessary-top-margin.yml
@@ -0,0 +1,5 @@
+---
+title: Remove top margin in modal header titles
+merge_request: 24108
+author:
+type: fixed
diff --git a/changelogs/unreleased/allow-basic-auth-on-go-get-middleware.yml b/changelogs/unreleased/allow-basic-auth-on-go-get-middleware.yml
new file mode 100644
index 00000000000..fda3fdc28cf
--- /dev/null
+++ b/changelogs/unreleased/allow-basic-auth-on-go-get-middleware.yml
@@ -0,0 +1,5 @@
+---
+title: Allow basic authentication on go get middleware
+merge_request: 23497
+author: Morty Choi @mortyccp
+type: changed
diff --git a/changelogs/unreleased/deprecated-positional-spec-arguments.yml b/changelogs/unreleased/deprecated-positional-spec-arguments.yml
new file mode 100644
index 00000000000..8e541df1ad4
--- /dev/null
+++ b/changelogs/unreleased/deprecated-positional-spec-arguments.yml
@@ -0,0 +1,5 @@
+---
+title: 'Fix deprecation: Using positional arguments in integration tests'
+merge_request: 24110
+author: Jasper Maes
+type: other
diff --git a/changelogs/unreleased/knative-prometheus.yml b/changelogs/unreleased/knative-prometheus.yml
new file mode 100644
index 00000000000..606c5332474
--- /dev/null
+++ b/changelogs/unreleased/knative-prometheus.yml
@@ -0,0 +1,5 @@
+---
+title: Add Knative metrics to Prometheus
+merge_request: 23972
+author: Chris Baumbauer
+type: added
diff --git a/changelogs/unreleased/sh-drop-webhooks-project-export.yml b/changelogs/unreleased/sh-drop-webhooks-project-export.yml
new file mode 100644
index 00000000000..217373bce66
--- /dev/null
+++ b/changelogs/unreleased/sh-drop-webhooks-project-export.yml
@@ -0,0 +1,5 @@
+---
+title: Drop Webhooks from project import/export config
+merge_request: 24121
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-fix-clone-url-for-https.yml b/changelogs/unreleased/sh-fix-clone-url-for-https.yml
new file mode 100644
index 00000000000..6a17d566685
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-clone-url-for-https.yml
@@ -0,0 +1,5 @@
+---
+title: Fix clone URL not showing if protocol is HTTPS
+merge_request: 24131
+author:
+type: fixed
diff --git a/changelogs/unreleased/tz-user-popover-follow-up.yml b/changelogs/unreleased/tz-user-popover-follow-up.yml
new file mode 100644
index 00000000000..d8f004beaa0
--- /dev/null
+++ b/changelogs/unreleased/tz-user-popover-follow-up.yml
@@ -0,0 +1,4 @@
+title: Changed Userpopover Fixtures and shadow color
+merge_request: 23768
+author:
+type: other
diff --git a/doc/api/services.md b/doc/api/services.md
index f122bac6f1f..c4edaa17815 100644
--- a/doc/api/services.md
+++ b/doc/api/services.md
@@ -573,7 +573,7 @@ DELETE /projects/:id/services/jira
## Kubernetes
-Kubernetes / Openshift integration
+Kubernetes / OpenShift integration
CAUTION: **Warning:**
Kubernetes service integration has been deprecated in GitLab 10.3. API service endpoints will continue to work as long as the Kubernetes service is active, however if the service is inactive API endpoints will automatically return a `400 Bad Request`. Read [GitLab 10.3 release post](https://about.gitlab.com/2017/12/22/gitlab-10-3-released/#kubernetes-integration-service) for more information.
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 5d87a5c0a1f..e94b5211d59 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -1678,9 +1678,9 @@ NOTE: **Note:**
`include` requires the external YAML files to have the extensions `.yml` or `.yaml`.
The external file will not be included if the extension is missing.
-You can define it either as a single string, or, in case you want to include
-more than one files, an array of different values . The following examples
-are both valid cases:
+You can include your extra YAML file either as a single string or
+as an array of multiple values. You can also use full paths or
+relative URLs. The following examples are both valid:
```yaml
# Single string
diff --git a/doc/development/documentation/index.md b/doc/development/documentation/index.md
index 256e0476c2f..8ffddb9b669 100644
--- a/doc/development/documentation/index.md
+++ b/doc/development/documentation/index.md
@@ -4,7 +4,7 @@ description: Learn how to contribute to GitLab Documentation.
# GitLab Documentation guidelines
-GitLab's documentation is [intended as the single source of truth (SSOT)](https://about.gitab.com/handbook/documentation/) for information about how to configure, use, and troubleshoot GitLab. The documentation contains use cases and usage instructions covering every GitLab feature, organized by product area and subject. This includes topics and workflows that span multiple GitLab features, as well as the use of GitLab with other applications.
+GitLab's documentation is [intended as the single source of truth (SSOT)](https://about.gitlab.com/handbook/documentation/) for information about how to configure, use, and troubleshoot GitLab. The documentation contains use cases and usage instructions covering every GitLab feature, organized by product area and subject. This includes topics and workflows that span multiple GitLab features, as well as the use of GitLab with other applications.
In addition to this page, the following resources to help craft and contribute documentation are available:
diff --git a/doc/install/openshift_and_gitlab/index.md b/doc/install/openshift_and_gitlab/index.md
index 4c88b6f97fc..25a87dbd9de 100644
--- a/doc/install/openshift_and_gitlab/index.md
+++ b/doc/install/openshift_and_gitlab/index.md
@@ -18,7 +18,7 @@ In this tutorial, we will see how to deploy GitLab in OpenShift using GitLab's
official Docker image while getting familiar with the web interface and CLI
tools that will help us achieve our goal.
-For a video demonstration on installing GitLab on Openshift, check the article [In 13 minutes from Kubernetes to a complete application development tool](https://about.gitlab.com/2016/11/14/idea-to-production/).
+For a video demonstration on installing GitLab on OpenShift, check the article [In 13 minutes from Kubernetes to a complete application development tool](https://about.gitlab.com/2016/11/14/idea-to-production/).
---
@@ -518,7 +518,7 @@ PaaS and managing your applications with the ease of containers.
[templates]: https://docs.openshift.org/latest/architecture/core_concepts/templates.html "Documentation - OpenShift templates"
[old-post]: https://blog.openshift.com/deploy-gitlab-openshift/ "Old post - Deploy GitLab on OpenShift"
[line]: https://gitlab.com/gitlab-org/omnibus-gitlab/blob/658c065c8d022ce858dd63eaeeadb0b2ddc8deea/docker/openshift-template.json#L239 "GitLab - OpenShift template"
-[oc-gh]: https://github.com/openshift/origin/releases/tag/v1.3.0 "Openshift 1.3.0 release on GitHub"
+[oc-gh]: https://github.com/openshift/origin/releases/tag/v1.3.0 "OpenShift Origin 1.3.0 release on GitHub"
[ha]: ../../administration/high_availability/gitlab.html "Documentation - GitLab High Availability"
[replicas]: https://docs.openshift.org/latest/architecture/core_concepts/deployments.html#replication-controllers "Documentation - Replication controller"
[autoscaling]: https://docs.openshift.org/latest/dev_guide/pod_autoscaling.html "Documentation - Autoscale"
diff --git a/doc/topics/authentication/index.md b/doc/topics/authentication/index.md
index 766a23c419d..a354d3e7884 100644
--- a/doc/topics/authentication/index.md
+++ b/doc/topics/authentication/index.md
@@ -45,4 +45,4 @@ This page gathers all the resources for the topic **Authentication** within GitL
- [Kanboard Plugin GitLab Authentication](https://github.com/kanboard/plugin-gitlab-auth)
- [Jenkins GitLab OAuth Plugin](https://wiki.jenkins.io/display/JENKINS/GitLab+OAuth+Plugin)
- [How to customize GitLab to support OpenID authentication](http://eric.van-der-vlist.com/blog/2013/11/23/how-to-customize-gitlab-to-support-openid-authentication/)
-- [Openshift - Configuring Authentication and User Agent](https://docs.openshift.org/latest/install_config/configuring_authentication.html#GitLab)
+- [OKD - Configuring Authentication and User Agent](https://docs.okd.io/latest/install_config/configuring_authentication.html#GitLab)
diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md
index b41f401e14c..e937d372384 100644
--- a/doc/topics/autodevops/index.md
+++ b/doc/topics/autodevops/index.md
@@ -340,6 +340,9 @@ Any security warnings are also
NOTE: **Note:**
The Auto SAST stage will be skipped on licenses other than Ultimate.
+NOTE: **Note:**
+The Auto SAST job requires GitLab Runner 11.5 or above.
+
### Auto Dependency Scanning **[ULTIMATE]**
> Introduced in [GitLab Ultimate][ee] 10.7.
@@ -356,6 +359,9 @@ Any security warnings are also
NOTE: **Note:**
The Auto Dependency Scanning stage will be skipped on licenses other than Ultimate.
+NOTE: **Note:**
+The Auto Dependency Scanning job requires GitLab Runner 11.5 or above.
+
### Auto License Management **[ULTIMATE]**
> Introduced in [GitLab Ultimate][ee] 11.0.
diff --git a/doc/user/project/integrations/prometheus.md b/doc/user/project/integrations/prometheus.md
index 7a3ee5a404c..5de8e66e7eb 100644
--- a/doc/user/project/integrations/prometheus.md
+++ b/doc/user/project/integrations/prometheus.md
@@ -88,7 +88,7 @@ to integrate with.
Once configured, GitLab will attempt to retrieve performance metrics for any
environment which has had a successful deployment.
-GitLab will automatically scan the Prometheus server for metrics from known serves like Kubernetes and NGINX, and attempt to identify individual environment. The supported metrics and scan process is detailed in our [Prometheus Metric Library documentation](prometheus_library/metrics.html).
+GitLab will automatically scan the Prometheus server for metrics from known serves like Kubernetes and NGINX, and attempt to identify individual environment. The supported metrics and scan process is detailed in our [Prometheus Metric Library documentation](prometheus_library/index.md).
You can view the performance dashboard for an environment by [clicking on the monitoring button](../../../ci/environments.md#monitoring-environments).
diff --git a/doc/user/project/settings/import_export.md b/doc/user/project/settings/import_export.md
index cb68c9318bc..3bbfa74f4b7 100644
--- a/doc/user/project/settings/import_export.md
+++ b/doc/user/project/settings/import_export.md
@@ -71,6 +71,7 @@ The following items will NOT be exported:
- Build traces and artifacts
- Container registry images
- CI variables
+- Webhooks
- Any encrypted tokens
## Exporting a project and its data
diff --git a/lib/gitlab/checks/base_checker.rb b/lib/gitlab/checks/base_checker.rb
index 7fbcf6a4ff4..09b17b5b76b 100644
--- a/lib/gitlab/checks/base_checker.rb
+++ b/lib/gitlab/checks/base_checker.rb
@@ -18,12 +18,16 @@ module Gitlab
private
+ def creation?
+ Gitlab::Git.blank_ref?(oldrev)
+ end
+
def deletion?
Gitlab::Git.blank_ref?(newrev)
end
def update?
- !Gitlab::Git.blank_ref?(oldrev) && !deletion?
+ !creation? && !deletion?
end
def updated_from_web?
diff --git a/lib/gitlab/checks/change_access.rb b/lib/gitlab/checks/change_access.rb
index 7778d3068cc..8a57a3a6d9a 100644
--- a/lib/gitlab/checks/change_access.rb
+++ b/lib/gitlab/checks/change_access.rb
@@ -10,7 +10,7 @@ module Gitlab
attr_reader(*ATTRIBUTES)
def initialize(
- change, user_access:, project:, skip_authorization: false,
+ change, user_access:, project:,
skip_lfs_integrity_check: false, protocol:, logger:
)
@oldrev, @newrev, @ref = change.values_at(:oldrev, :newrev, :ref)
@@ -18,7 +18,6 @@ module Gitlab
@tag_name = Gitlab::Git.tag_name(@ref)
@user_access = user_access
@project = project
- @skip_authorization = skip_authorization
@skip_lfs_integrity_check = skip_lfs_integrity_check
@protocol = protocol
@@ -27,8 +26,6 @@ module Gitlab
end
def exec
- return true if skip_authorization
-
ref_level_checks
# Check of commits should happen as the last step
# given they're expensive in terms of performance
diff --git a/lib/gitlab/checks/diff_check.rb b/lib/gitlab/checks/diff_check.rb
index 63da9a3d6b5..ea0d8c85a66 100644
--- a/lib/gitlab/checks/diff_check.rb
+++ b/lib/gitlab/checks/diff_check.rb
@@ -11,7 +11,7 @@ module Gitlab
}.freeze
def validate!
- return if deletion? || newrev.nil?
+ return if deletion?
return unless should_run_diff_validations?
return if commits.empty?
diff --git a/lib/gitlab/checks/push_check.rb b/lib/gitlab/checks/push_check.rb
index f3a52f09868..91f8d0bdbc8 100644
--- a/lib/gitlab/checks/push_check.rb
+++ b/lib/gitlab/checks/push_check.rb
@@ -6,7 +6,7 @@ module Gitlab
def validate!
logger.log_timed("Checking if you are allowed to push...") do
unless can_push?
- raise GitAccess::UnauthorizedError, 'You are not allowed to push code to this project.'
+ raise GitAccess::UnauthorizedError, GitAccess::ERROR_MESSAGES[:push_code]
end
end
end
@@ -15,7 +15,7 @@ module Gitlab
def can_push?
user_access.can_do_action?(:push_code) ||
- user_access.can_push_to_branch?(branch_name)
+ project.branch_allows_collaboration?(user_access.user, branch_name)
end
end
end
diff --git a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
index a9e361b0b32..8f6cf8d2d03 100644
--- a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
@@ -185,7 +185,8 @@ dependency_scanning:
- setup_docker
- dependency_scanning
artifacts:
- paths: [gl-dependency-scanning-report.json]
+ reports:
+ dependency_scanning: gl-dependency-scanning-report.json
only:
refs:
- branches
diff --git a/lib/gitlab/email/handler/base_handler.rb b/lib/gitlab/email/handler/base_handler.rb
index 35bb49ad19a..f89d1d15010 100644
--- a/lib/gitlab/email/handler/base_handler.rb
+++ b/lib/gitlab/email/handler/base_handler.rb
@@ -6,12 +6,14 @@ module Gitlab
class BaseHandler
attr_reader :mail, :mail_key
+ HANDLER_ACTION_BASE_REGEX ||= /(?<project_slug>.+)-(?<project_id>\d+)/.freeze
+
def initialize(mail, mail_key)
@mail = mail
@mail_key = mail_key
end
- def can_execute?
+ def can_handle?
raise NotImplementedError
end
diff --git a/lib/gitlab/email/handler/create_issue_handler.rb b/lib/gitlab/email/handler/create_issue_handler.rb
index 69982efbbe6..78a3a9489ac 100644
--- a/lib/gitlab/email/handler/create_issue_handler.rb
+++ b/lib/gitlab/email/handler/create_issue_handler.rb
@@ -2,21 +2,33 @@
require 'gitlab/email/handler/base_handler'
+# handles issue creation emails with these formats:
+# incoming+gitlab-org-gitlab-ce-20-Author_Token12345678-issue@incoming.gitlab.com
+# incoming+gitlab-org/gitlab-ce+Author_Token12345678@incoming.gitlab.com (legacy)
module Gitlab
module Email
module Handler
class CreateIssueHandler < BaseHandler
include ReplyProcessing
- attr_reader :project_path, :incoming_email_token
+
+ HANDLER_REGEX = /\A#{HANDLER_ACTION_BASE_REGEX}-(?<incoming_email_token>.+)-issue\z/.freeze
+ HANDLER_REGEX_LEGACY = /\A(?<project_path>[^\+]*)\+(?<incoming_email_token>.*)\z/.freeze
def initialize(mail, mail_key)
super(mail, mail_key)
- @project_path, @incoming_email_token =
- mail_key && mail_key.split('+', 2)
+
+ if !mail_key&.include?('/') && (matched = HANDLER_REGEX.match(mail_key.to_s))
+ @project_slug = matched[:project_slug]
+ @project_id = matched[:project_id]&.to_i
+ @incoming_email_token = matched[:incoming_email_token]
+ elsif matched = HANDLER_REGEX_LEGACY.match(mail_key.to_s)
+ @project_path = matched[:project_path]
+ @incoming_email_token = matched[:incoming_email_token]
+ end
end
def can_handle?
- !incoming_email_token.nil? && !incoming_email_token.include?("+") && !mail_key.include?(Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX)
+ incoming_email_token && (project_id || can_handle_legacy_format?)
end
def execute
@@ -36,10 +48,6 @@ module Gitlab
end
# rubocop: enable CodeReuse/ActiveRecord
- def project
- @project ||= Project.find_by_full_path(project_path)
- end
-
private
def create_issue
@@ -50,6 +58,10 @@ module Gitlab
description: message_including_reply
).execute
end
+
+ def can_handle_legacy_format?
+ project_path && !incoming_email_token.include?('+') && !mail_key.include?(Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX_LEGACY)
+ end
end
end
end
diff --git a/lib/gitlab/email/handler/create_merge_request_handler.rb b/lib/gitlab/email/handler/create_merge_request_handler.rb
index 5772727e855..b3b5063f2ca 100644
--- a/lib/gitlab/email/handler/create_merge_request_handler.rb
+++ b/lib/gitlab/email/handler/create_merge_request_handler.rb
@@ -3,23 +3,33 @@
require 'gitlab/email/handler/base_handler'
require 'gitlab/email/handler/reply_processing'
+# handles merge request creation emails with these formats:
+# incoming+gitlab-org-gitlab-ce-20-Author_Token12345678-merge-request@incoming.gitlab.com
+# incoming+gitlab-org/gitlab-ce+merge-request+Author_Token12345678@incoming.gitlab.com (legacy)
module Gitlab
module Email
module Handler
class CreateMergeRequestHandler < BaseHandler
include ReplyProcessing
- attr_reader :project_path, :incoming_email_token
+
+ HANDLER_REGEX = /\A#{HANDLER_ACTION_BASE_REGEX}-(?<incoming_email_token>.+)-merge-request\z/.freeze
+ HANDLER_REGEX_LEGACY = /\A(?<project_path>[^\+]*)\+merge-request\+(?<incoming_email_token>.*)/.freeze
def initialize(mail, mail_key)
super(mail, mail_key)
- if m = /\A([^\+]*)\+merge-request\+(.*)/.match(mail_key.to_s)
- @project_path, @incoming_email_token = m.captures
+ if !mail_key&.include?('/') && (matched = HANDLER_REGEX.match(mail_key.to_s))
+ @project_slug = matched[:project_slug]
+ @project_id = matched[:project_id]&.to_i
+ @incoming_email_token = matched[:incoming_email_token]
+ elsif matched = HANDLER_REGEX_LEGACY.match(mail_key.to_s)
+ @project_path = matched[:project_path]
+ @incoming_email_token = matched[:incoming_email_token]
end
end
def can_handle?
- @project_path && @incoming_email_token
+ incoming_email_token && (project_id || project_path)
end
def execute
@@ -40,10 +50,6 @@ module Gitlab
end
# rubocop: enable CodeReuse/ActiveRecord
- def project
- @project ||= Project.find_by_full_path(project_path)
- end
-
def metrics_params
super.merge(includes_patches: patch_attachments.any?)
end
@@ -97,7 +103,7 @@ module Gitlab
def remove_patch_attachments
patch_attachments.each { |patch| mail.parts.delete(patch) }
- # reset the message, so it needs to be reporocessed when the attachments
+ # reset the message, so it needs to be reprocessed when the attachments
# have been modified
@message = nil
end
diff --git a/lib/gitlab/email/handler/create_note_handler.rb b/lib/gitlab/email/handler/create_note_handler.rb
index c7c573595fa..b00af15364d 100644
--- a/lib/gitlab/email/handler/create_note_handler.rb
+++ b/lib/gitlab/email/handler/create_note_handler.rb
@@ -3,6 +3,8 @@
require 'gitlab/email/handler/base_handler'
require 'gitlab/email/handler/reply_processing'
+# handles note/reply creation emails with these formats:
+# incoming+1234567890abcdef1234567890abcdef@incoming.gitlab.com
module Gitlab
module Email
module Handler
diff --git a/lib/gitlab/email/handler/reply_processing.rb b/lib/gitlab/email/handler/reply_processing.rb
index ff6b2c729b2..ba9730d2685 100644
--- a/lib/gitlab/email/handler/reply_processing.rb
+++ b/lib/gitlab/email/handler/reply_processing.rb
@@ -6,13 +6,26 @@ module Gitlab
module ReplyProcessing
private
+ attr_reader :project_id, :project_slug, :project_path, :incoming_email_token
+
def author
raise NotImplementedError
end
+ # rubocop:disable Gitlab/ModuleWithInstanceVariables
def project
- raise NotImplementedError
+ return @project if instance_variable_defined?(:@project)
+
+ if project_id
+ @project = Project.find_by_id(project_id)
+ @project = nil unless valid_project_slug?(@project)
+ else
+ @project = Project.find_by_full_path(project_path)
+ end
+
+ @project
end
+ # rubocop:enable Gitlab/ModuleWithInstanceVariables
def message
@message ||= process_message
@@ -58,6 +71,10 @@ module Gitlab
raise invalid_exception, msg
end
+
+ def valid_project_slug?(found_project)
+ project_slug == found_project.full_path_slug
+ end
end
end
end
diff --git a/lib/gitlab/email/handler/unsubscribe_handler.rb b/lib/gitlab/email/handler/unsubscribe_handler.rb
index d2f617b868a..20e4c125626 100644
--- a/lib/gitlab/email/handler/unsubscribe_handler.rb
+++ b/lib/gitlab/email/handler/unsubscribe_handler.rb
@@ -2,14 +2,28 @@
require 'gitlab/email/handler/base_handler'
+# handles unsubscribe emails with these formats:
+# incoming+1234567890abcdef1234567890abcdef-unsubscribe@incoming.gitlab.com
+# incoming+1234567890abcdef1234567890abcdef+unsubscribe@incoming.gitlab.com (legacy)
module Gitlab
module Email
module Handler
class UnsubscribeHandler < BaseHandler
delegate :project, to: :sent_notification, allow_nil: true
+ HANDLER_REGEX_FOR = -> (suffix) { /\A(?<reply_token>\w+)#{Regexp.escape(suffix)}\z/ }.freeze
+ HANDLER_REGEX = HANDLER_REGEX_FOR.call(Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX).freeze
+ HANDLER_REGEX_LEGACY = HANDLER_REGEX_FOR.call(Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX_LEGACY).freeze
+
+ def initialize(mail, mail_key)
+ super(mail, mail_key)
+
+ matched = HANDLER_REGEX.match(mail_key.to_s) || HANDLER_REGEX_LEGACY.match(mail_key.to_s)
+ @reply_token = matched[:reply_token] if matched
+ end
+
def can_handle?
- mail_key =~ /\A\w+#{Regexp.escape(Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX)}\z/
+ reply_token.present?
end
def execute
@@ -24,12 +38,10 @@ module Gitlab
private
- def sent_notification
- @sent_notification ||= SentNotification.for(reply_key)
- end
+ attr_reader :reply_token
- def reply_key
- mail_key.sub(Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX, '')
+ def sent_notification
+ @sent_notification ||= SentNotification.for(reply_token)
end
end
end
diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb
index 802fa65dd63..010bd0e520c 100644
--- a/lib/gitlab/git_access.rb
+++ b/lib/gitlab/git_access.rb
@@ -12,6 +12,10 @@ module Gitlab
TimeoutError = Class.new(StandardError)
ProjectMovedError = Class.new(NotFoundError)
+ # Use the magic string '_any' to indicate we do not know what the
+ # changes are. This is also what gitlab-shell does.
+ ANY = '_any'
+
ERROR_MESSAGES = {
upload: 'You are not allowed to upload code for this project.',
download: 'You are not allowed to download code from this project.',
@@ -24,7 +28,8 @@ module Gitlab
upload_pack_disabled_over_http: 'Pulling over HTTP is not allowed.',
receive_pack_disabled_over_http: 'Pushing over HTTP is not allowed.',
read_only: 'The repository is temporarily read-only. Please try again later.',
- cannot_push_to_read_only: "You can't push code to a read-only GitLab instance."
+ cannot_push_to_read_only: "You can't push code to a read-only GitLab instance.",
+ push_code: 'You are not allowed to push code to this project.'
}.freeze
INTERNAL_TIMEOUT = 50.seconds.freeze
@@ -199,7 +204,7 @@ module Gitlab
def ensure_project_on_push!(cmd, changes)
return if project || deploy_key?
- return unless receive_pack?(cmd) && changes == '_any' && authentication_abilities.include?(:push_code)
+ return unless receive_pack?(cmd) && changes == ANY && authentication_abilities.include?(:push_code)
namespace = Namespace.find_by_full_path(namespace_path)
@@ -256,24 +261,34 @@ module Gitlab
raise UnauthorizedError, ERROR_MESSAGES[:upload]
end
- return if changes.blank? # Allow access this is needed for EE.
-
check_change_access!
end
def check_change_access!
- # If there are worktrees with a HEAD pointing to a non-existent object,
- # calls to `git rev-list --all` will fail in git 2.15+. This should also
- # clear stale lock files.
- project.repository.clean_stale_repository_files
-
- # Iterate over all changes to find if user allowed all of them to be applied
- changes_list.each.with_index do |change, index|
- first_change = index == 0
-
- # If user does not have access to make at least one change, cancel all
- # push by allowing the exception to bubble up
- check_single_change_access(change, skip_lfs_integrity_check: !first_change)
+ # Deploy keys with write access can push anything
+ return if deploy_key?
+
+ if changes == ANY
+ can_push = user_access.can_do_action?(:push_code) ||
+ project.any_branch_allows_collaboration?(user_access.user)
+
+ unless can_push
+ raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:push_code]
+ end
+ else
+ # If there are worktrees with a HEAD pointing to a non-existent object,
+ # calls to `git rev-list --all` will fail in git 2.15+. This should also
+ # clear stale lock files.
+ project.repository.clean_stale_repository_files
+
+ # Iterate over all changes to find if user allowed all of them to be applied
+ changes_list.each.with_index do |change, index|
+ first_change = index == 0
+
+ # If user does not have access to make at least one change, cancel all
+ # push by allowing the exception to bubble up
+ check_single_change_access(change, skip_lfs_integrity_check: !first_change)
+ end
end
end
@@ -282,7 +297,6 @@ module Gitlab
change,
user_access: user_access,
project: project,
- skip_authorization: deploy_key?,
skip_lfs_integrity_check: skip_lfs_integrity_check,
protocol: protocol,
logger: logger
@@ -348,7 +362,7 @@ module Gitlab
protected
def changes_list
- @changes_list ||= Gitlab::ChangesList.new(changes)
+ @changes_list ||= Gitlab::ChangesList.new(changes == ANY ? [] : changes)
end
def user
diff --git a/lib/gitlab/git_access_wiki.rb b/lib/gitlab/git_access_wiki.rb
index 3f24001e4ee..0af91957fa8 100644
--- a/lib/gitlab/git_access_wiki.rb
+++ b/lib/gitlab/git_access_wiki.rb
@@ -15,7 +15,7 @@ module Gitlab
authentication_abilities.include?(:download_code) && user_access.can_do_action?(:download_wiki_code)
end
- def check_single_change_access(change, _options = {})
+ def check_change_access!
unless user_access.can_do_action?(:create_wiki)
raise UnauthorizedError, ERROR_MESSAGES[:write_to_wiki]
end
diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml
index 3cd8ede830c..25cdd5ab121 100644
--- a/lib/gitlab/import_export/import_export.yml
+++ b/lib/gitlab/import_export/import_export.yml
@@ -63,7 +63,6 @@ project_tree:
- :triggers
- :pipeline_schedules
- :services
- - :hooks
- protected_branches:
- :merge_access_levels
- :push_access_levels
@@ -156,12 +155,6 @@ excluded_attributes:
- :reference
- :reference_html
- :epic_id
- hooks:
- - :token
- - :encrypted_token
- - :encrypted_token_iv
- - :encrypted_url
- - :encrypted_url_iv
runners:
- :token
- :token_encrypted
diff --git a/lib/gitlab/incoming_email.rb b/lib/gitlab/incoming_email.rb
index 20fc8226611..cc0c633b943 100644
--- a/lib/gitlab/incoming_email.rb
+++ b/lib/gitlab/incoming_email.rb
@@ -2,8 +2,9 @@
module Gitlab
module IncomingEmail
- UNSUBSCRIBE_SUFFIX = '+unsubscribe'.freeze
- WILDCARD_PLACEHOLDER = '%{key}'.freeze
+ UNSUBSCRIBE_SUFFIX = '-unsubscribe'.freeze
+ UNSUBSCRIBE_SUFFIX_LEGACY = '+unsubscribe'.freeze
+ WILDCARD_PLACEHOLDER = '%{key}'.freeze
class << self
def enabled?
@@ -22,6 +23,7 @@ module Gitlab
config.address.sub(WILDCARD_PLACEHOLDER, key)
end
+ # example: incoming+1234567890abcdef1234567890abcdef-unsubscribe@incoming.gitlab.com
def unsubscribe_address(key)
config.address.sub(WILDCARD_PLACEHOLDER, "#{key}#{UNSUBSCRIBE_SUFFIX}")
end
diff --git a/lib/gitlab/middleware/go.rb b/lib/gitlab/middleware/go.rb
index d1a87c3b3bb..72a788022ef 100644
--- a/lib/gitlab/middleware/go.rb
+++ b/lib/gitlab/middleware/go.rb
@@ -6,6 +6,7 @@ module Gitlab
module Middleware
class Go
include ActionView::Helpers::TagHelper
+ include ActionController::HttpAuthentication::Basic
PROJECT_PATH_REGEX = %r{\A(#{Gitlab::PathRegex.full_namespace_route_regex}/#{Gitlab::PathRegex.project_route_regex})/}.freeze
@@ -14,7 +15,7 @@ module Gitlab
end
def call(env)
- request = Rack::Request.new(env)
+ request = ActionDispatch::Request.new(env)
render_go_doc(request) || @app.call(env)
end
@@ -110,21 +111,23 @@ module Gitlab
def project_for_paths(paths, request)
project = Project.where_full_path_in(paths).first
- return unless Ability.allowed?(current_user(request), :read_project, project)
+ return unless Ability.allowed?(current_user(request, project), :read_project, project)
project
end
- def current_user(request)
- authenticator = Gitlab::Auth::RequestAuthenticator.new(request)
- user = authenticator.find_user_from_access_token || authenticator.find_user_from_warden
+ def current_user(request, project)
+ return unless has_basic_credentials?(request)
- return unless user&.can?(:access_api)
+ login, password = user_name_and_password(request)
+ auth_result = Gitlab::Auth.find_for_git_client(login, password, project: project, ip: request.ip)
+ return unless auth_result.success?
- # Right now, the `api` scope is the only one that should be able to determine private project existence.
- return unless authenticator.valid_access_token?(scopes: [:api])
+ return unless auth_result.actor&.can?(:access_git)
- user
+ return unless auth_result.authentication_abilities.include?(:read_project)
+
+ auth_result.actor
end
end
end
diff --git a/qa/Gemfile b/qa/Gemfile
index d69c71003ae..75ad7bd07af 100644
--- a/qa/Gemfile
+++ b/qa/Gemfile
@@ -5,5 +5,6 @@ gem 'capybara', '~> 2.16.1'
gem 'capybara-screenshot', '~> 1.0.18'
gem 'rake', '~> 12.3.0'
gem 'rspec', '~> 3.7'
-gem 'selenium-webdriver', '~> 3.8.0'
+gem 'selenium-webdriver', '~> 3.12'
gem 'airborne', '~> 0.2.13'
+gem 'nokogiri', '~> 1.8.5'
diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock
index d61ecf8fbb5..55f3211482b 100644
--- a/qa/Gemfile.lock
+++ b/qa/Gemfile.lock
@@ -25,7 +25,7 @@ GEM
capybara-screenshot (1.0.18)
capybara (>= 1.0, < 3)
launchy
- childprocess (0.8.0)
+ childprocess (0.9.0)
ffi (~> 1.0, >= 1.0.11)
coderay (1.1.2)
concurrent-ruby (1.0.5)
@@ -47,7 +47,7 @@ GEM
mini_portile2 (2.3.0)
minitest (5.11.1)
netrc (0.11.0)
- nokogiri (1.8.2)
+ nokogiri (1.8.5)
mini_portile2 (~> 2.3.0)
pry (0.11.3)
coderay (~> 1.1.0)
@@ -78,9 +78,9 @@ GEM
rspec-support (~> 3.7.0)
rspec-support (3.7.0)
rubyzip (1.2.2)
- selenium-webdriver (3.8.0)
+ selenium-webdriver (3.141.0)
childprocess (~> 0.5)
- rubyzip (~> 1.0)
+ rubyzip (~> 1.2, >= 1.2.2)
thread_safe (0.3.6)
tzinfo (1.2.4)
thread_safe (~> 0.1)
@@ -97,10 +97,11 @@ DEPENDENCIES
airborne (~> 0.2.13)
capybara (~> 2.16.1)
capybara-screenshot (~> 1.0.18)
+ nokogiri (~> 1.8.5)
pry-byebug (~> 3.5.1)
rake (~> 12.3.0)
rspec (~> 3.7)
- selenium-webdriver (~> 3.8.0)
+ selenium-webdriver (~> 3.12)
BUNDLED WITH
- 1.17.1
+ 1.17.3
diff --git a/qa/qa.rb b/qa/qa.rb
index bf05b6b53ca..ef6a92f9768 100644
--- a/qa/qa.rb
+++ b/qa/qa.rb
@@ -283,6 +283,7 @@ module QA
autoload :Select2, 'qa/page/component/select2'
autoload :DropdownFilter, 'qa/page/component/dropdown_filter'
autoload :UsersSelect, 'qa/page/component/users_select'
+ autoload :Note, 'qa/page/component/note'
module Issuable
autoload :Common, 'qa/page/component/issuable/common'
diff --git a/qa/qa/page/component/note.rb b/qa/qa/page/component/note.rb
new file mode 100644
index 00000000000..67d7f114786
--- /dev/null
+++ b/qa/qa/page/component/note.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Component
+ module Note
+ def self.included(base)
+ base.view 'app/assets/javascripts/notes/components/comment_form.vue' do
+ element :note_dropdown
+ element :discussion_option
+ end
+
+ base.view 'app/assets/javascripts/notes/components/note_form.vue' do
+ element :reply_input
+ element :reply_comment_button
+ end
+
+ base.view 'app/assets/javascripts/notes/components/noteable_discussion.vue' do
+ element :discussion_reply
+ end
+
+ base.view 'app/assets/javascripts/notes/components/toggle_replies_widget.vue' do
+ element :expand_replies
+ element :collapse_replies
+ end
+ end
+
+ def start_discussion(text)
+ fill_element :comment_input, text
+ click_element :note_dropdown
+ click_element :discussion_option
+ click_element :comment_button
+ end
+
+ def reply_to_discussion(reply_text)
+ all_elements(:discussion_reply).last.click
+ fill_element :reply_input, reply_text
+ click_element :reply_comment_button
+ end
+
+ def collapse_replies
+ click_element :collapse_replies
+ end
+
+ def expand_replies
+ click_element :expand_replies
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/merge_request/show.rb b/qa/qa/page/merge_request/show.rb
index 869dc0b9d21..4f21ed602d9 100644
--- a/qa/qa/page/merge_request/show.rb
+++ b/qa/qa/page/merge_request/show.rb
@@ -4,6 +4,8 @@ module QA
module Page
module MergeRequest
class Show < Page::Base
+ include Page::Component::Note
+
view 'app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue' do
element :merge_button
element :fast_forward_message, 'Fast-forward merge without a merge commit' # rubocop:disable QA/ElementWithPattern
@@ -34,19 +36,6 @@ module QA
element :diff_comment
end
- view 'app/assets/javascripts/notes/components/comment_form.vue' do
- element :note_dropdown
- element :discussion_option
- end
-
- view 'app/assets/javascripts/notes/components/note_form.vue' do
- element :reply_input
- end
-
- view 'app/assets/javascripts/notes/components/noteable_discussion.vue' do
- element :discussion_reply
- end
-
view 'app/assets/javascripts/diffs/components/inline_diff_table_row.vue' do
element :new_diff_line
end
@@ -163,18 +152,6 @@ module QA
fill_element :reply_input, text
end
- def start_discussion(text)
- fill_element :comment_input, text
- click_element :note_dropdown
- click_element :discussion_option
- click_element :comment_button
- end
-
- def reply_to_discussion(reply_text)
- all_elements(:discussion_reply).last.click
- fill_element :reply_input, reply_text
- end
-
def edit!
click_element :edit_button
end
diff --git a/qa/qa/page/project/issue/show.rb b/qa/qa/page/project/issue/show.rb
index 9ec6d90719e..1028cc045a0 100644
--- a/qa/qa/page/project/issue/show.rb
+++ b/qa/qa/page/project/issue/show.rb
@@ -6,6 +6,7 @@ module QA
module Issue
class Show < Page::Base
include Page::Component::Issuable::Common
+ include Page::Component::Note
view 'app/views/shared/notes/_form.html.haml' do
element :new_note_form, 'new-note' # rubocop:disable QA/ElementWithPattern
diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/collapse_comments_in_discussions_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/collapse_comments_in_discussions_spec.rb
new file mode 100644
index 00000000000..fa779bd1f4e
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/2_plan/issue/collapse_comments_in_discussions_spec.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+module QA
+ context 'Plan' do
+ describe 'collapse comments in issue discussions' do
+ let(:issue_title) { 'issue title' }
+
+ it 'user collapses reply for comments in an issue' do
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
+ Page::Main::Login.perform(&:sign_in_using_credentials)
+
+ Resource::Issue.fabricate! do |issue|
+ issue.title = issue_title
+ end
+
+ expect(page).to have_content(issue_title)
+
+ Page::Project::Issue::Show.perform do |show_page|
+ show_page.select_all_activities_filter
+ show_page.start_discussion("My first discussion")
+ expect(show_page).to have_content("My first discussion")
+
+ show_page.reply_to_discussion("My First Reply")
+ expect(show_page).to have_content("My First Reply")
+
+ show_page.collapse_replies
+ expect(show_page).to have_content("1 reply")
+ expect(show_page).not_to have_content("My First Reply")
+
+ show_page.expand_replies
+ expect(show_page).to have_content("My First Reply")
+ expect(show_page).not_to have_content("1 reply")
+ end
+ end
+ end
+ end
+end
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index d46b9ffb3ce..4f4d3ca226f 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -950,7 +950,7 @@ describe Projects::MergeRequestsController do
end
it 'returns 200' do
- get :discussions, namespace_id: project.namespace, project_id: project, id: merge_request.iid
+ get :discussions, params: { namespace_id: project.namespace, project_id: project, id: merge_request.iid }
expect(response.status).to eq(200)
end
@@ -969,7 +969,7 @@ describe Projects::MergeRequestsController do
expect(collection).to receive(:find_by_id).with(note_diff_file.id).and_call_original
end
- get :discussions, namespace_id: project.namespace, project_id: project, id: merge_request.iid
+ get :discussions, params: { namespace_id: project.namespace, project_id: project, id: merge_request.iid }
end
end
@@ -986,7 +986,7 @@ describe Projects::MergeRequestsController do
expect(collection).to receive(:find_by_id).with(note_diff_file.id).and_call_original
end
- get :discussions, namespace_id: project.namespace, project_id: project, id: merge_request.iid
+ get :discussions, params: { namespace_id: project.namespace, project_id: project, id: merge_request.iid }
end
it 'does not preload highlights when diff note is resolved' do
@@ -999,7 +999,7 @@ describe Projects::MergeRequestsController do
expect(collection).to receive(:find_by_id).with(note_diff_file.id).and_call_original
end
- get :discussions, namespace_id: project.namespace, project_id: project, id: merge_request.iid
+ get :discussions, params: { namespace_id: project.namespace, project_id: project, id: merge_request.iid }
end
end
end
diff --git a/spec/controllers/projects/snippets_controller_spec.rb b/spec/controllers/projects/snippets_controller_spec.rb
index e4b78aff25d..75c9839dd9b 100644
--- a/spec/controllers/projects/snippets_controller_spec.rb
+++ b/spec/controllers/projects/snippets_controller_spec.rb
@@ -385,7 +385,7 @@ describe Projects::SnippetsController do
before do
sign_in(user)
- get :show, namespace_id: project.namespace, project_id: project, id: project_snippet.to_param, format: :js
+ get :show, params: { namespace_id: project.namespace, project_id: project, id: project_snippet.to_param }, format: :js
end
context 'when snippet is private' do
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index 4747d837273..f84f069f4db 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -667,7 +667,7 @@ describe ProjectsController do
project.add_guest(user)
sign_in(user)
- get :refs, namespace_id: project.namespace, id: project
+ get :refs, params: { namespace_id: project.namespace, id: project }
expect(response).to have_gitlab_http_status(404)
end
diff --git a/spec/controllers/snippets_controller_spec.rb b/spec/controllers/snippets_controller_spec.rb
index d762531da7e..5c6858dc7b2 100644
--- a/spec/controllers/snippets_controller_spec.rb
+++ b/spec/controllers/snippets_controller_spec.rb
@@ -82,7 +82,7 @@ describe SnippetsController do
end
it 'responds with status 404 when embeddable content is requested' do
- get :show, id: personal_snippet.to_param, format: :js
+ get :show, params: { id: personal_snippet.to_param }, format: :js
expect(response).to have_gitlab_http_status(404)
end
@@ -114,7 +114,7 @@ describe SnippetsController do
end
it 'responds with status 404 when embeddable content is requested' do
- get :show, id: personal_snippet.to_param, format: :js
+ get :show, params: { id: personal_snippet.to_param }, format: :js
expect(response).to have_gitlab_http_status(404)
end
@@ -145,7 +145,7 @@ describe SnippetsController do
end
it 'responds with status 200 when embeddable content is requested' do
- get :show, id: personal_snippet.to_param, format: :js
+ get :show, params: { id: personal_snippet.to_param }, format: :js
expect(assigns(:snippet)).to eq(personal_snippet)
expect(response).to have_gitlab_http_status(200)
diff --git a/spec/fixtures/emails/merge_request_multiple_patches.eml b/spec/fixtures/emails/merge_request_multiple_patches.eml
index 311b99a525d..7d2e0cd4e50 100644
--- a/spec/fixtures/emails/merge_request_multiple_patches.eml
+++ b/spec/fixtures/emails/merge_request_multiple_patches.eml
@@ -1,5 +1,5 @@
From: "Jake the Dog" <jake@adventuretime.ooo>
-To: incoming+gitlabhq/gitlabhq+merge-request+auth_token@appmail.adventuretime.ooo
+To: incoming+gitlabhq-gitlabhq-project_id-auth_token-merge-request@appmail.adventuretime.ooo
Subject: new-branch-with-a-patch
Date: Wed, 31 Oct 2018 17:27:52 +0100
X-Mailer: MailMate (1.12r5523)
diff --git a/spec/fixtures/emails/merge_request_with_conflicting_patch.eml b/spec/fixtures/emails/merge_request_with_conflicting_patch.eml
index ddfdfe9e24a..5c9eda640bc 100644
--- a/spec/fixtures/emails/merge_request_with_conflicting_patch.eml
+++ b/spec/fixtures/emails/merge_request_with_conflicting_patch.eml
@@ -1,5 +1,5 @@
From: "Jake the Dog" <jake@adventuretime.ooo>
-To: incoming+gitlabhq/gitlabhq+merge-request+auth_token@appmail.adventuretime.ooo
+To: incoming+gitlabhq-gitlabhq-project_id-auth_token-merge-request@appmail.adventuretime.ooo
Subject: feature
Date: Wed, 31 Oct 2018 17:27:52 +0100
X-Mailer: MailMate (1.12r5523)
diff --git a/spec/fixtures/emails/merge_request_with_patch_and_target_branch.eml b/spec/fixtures/emails/merge_request_with_patch_and_target_branch.eml
index 965658721cd..9fabfc23e3b 100644
--- a/spec/fixtures/emails/merge_request_with_patch_and_target_branch.eml
+++ b/spec/fixtures/emails/merge_request_with_patch_and_target_branch.eml
@@ -1,5 +1,5 @@
From: "Jake the Dog" <jake@adventuretime.ooo>
-To: incoming+gitlabhq/gitlabhq+merge-request+auth_token@appmail.adventuretime.ooo
+To: incoming+gitlabhq-gitlabhq-project_id-auth_token-merge-request@appmail.adventuretime.ooo
Subject: new-branch-with-a-patch
Date: Wed, 24 Oct 2018 16:39:49 +0200
X-Mailer: MailMate (1.12r5523)
diff --git a/spec/fixtures/emails/valid_merge_request_with_patch.eml b/spec/fixtures/emails/valid_merge_request_with_patch.eml
index 143fa77d1fa..e0f406639a3 100644
--- a/spec/fixtures/emails/valid_merge_request_with_patch.eml
+++ b/spec/fixtures/emails/valid_merge_request_with_patch.eml
@@ -1,5 +1,5 @@
From: "Jake the Dog" <jake@adventuretime.ooo>
-To: incoming+gitlabhq/gitlabhq+merge-request+auth_token@appmail.adventuretime.ooo
+To: incoming+gitlabhq-gitlabhq-project_id-auth_token-merge-request@appmail.adventuretime.ooo
Subject: new-branch-with-a-patch
Date: Wed, 24 Oct 2018 16:39:49 +0200
X-Mailer: MailMate (1.12r5523)
diff --git a/spec/fixtures/emails/valid_new_issue.eml b/spec/fixtures/emails/valid_new_issue.eml
index 3cf53a656a5..7d63016ed04 100644
--- a/spec/fixtures/emails/valid_new_issue.eml
+++ b/spec/fixtures/emails/valid_new_issue.eml
@@ -1,11 +1,11 @@
Return-Path: <jake@adventuretime.ooo>
Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400
Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400
-Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700
+Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq-gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700
Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700
Date: Thu, 13 Jun 2013 17:03:48 -0400
From: Jake the Dog <jake@adventuretime.ooo>
-To: incoming+gitlabhq/gitlabhq+auth_token@appmail.adventuretime.ooo
+To: incoming+gitlabhq-gitlabhq-project_id-auth_token-issue@appmail.adventuretime.ooo
Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com>
Subject: New Issue by email
Mime-Version: 1.0
diff --git a/spec/fixtures/emails/valid_new_issue_empty.eml b/spec/fixtures/emails/valid_new_issue_empty.eml
index fc1d52a3f42..58a6ef29d69 100644
--- a/spec/fixtures/emails/valid_new_issue_empty.eml
+++ b/spec/fixtures/emails/valid_new_issue_empty.eml
@@ -1,11 +1,11 @@
Return-Path: <jake@adventuretime.ooo>
Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400
Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400
-Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700
+Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq-gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700
Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700
Date: Thu, 13 Jun 2013 17:03:48 -0400
From: Jake the Dog <jake@adventuretime.ooo>
-To: incoming+gitlabhq/gitlabhq+auth_token@appmail.adventuretime.ooo
+To: incoming+gitlabhq-gitlabhq-project_id-auth_token-issue@appmail.adventuretime.ooo
Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com>
Subject: New Issue by email
Mime-Version: 1.0
diff --git a/spec/fixtures/emails/valid_new_issue_legacy.eml b/spec/fixtures/emails/valid_new_issue_legacy.eml
new file mode 100644
index 00000000000..3cf53a656a5
--- /dev/null
+++ b/spec/fixtures/emails/valid_new_issue_legacy.eml
@@ -0,0 +1,23 @@
+Return-Path: <jake@adventuretime.ooo>
+Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400
+Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400
+Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700
+Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700
+Date: Thu, 13 Jun 2013 17:03:48 -0400
+From: Jake the Dog <jake@adventuretime.ooo>
+To: incoming+gitlabhq/gitlabhq+auth_token@appmail.adventuretime.ooo
+Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com>
+Subject: New Issue by email
+Mime-Version: 1.0
+Content-Type: text/plain;
+ charset=ISO-8859-1
+Content-Transfer-Encoding: 7bit
+X-Sieve: CMU Sieve 2.2
+X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu,
+ 13 Jun 2013 14:03:48 -0700 (PDT)
+X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1
+
+The reply by email functionality should be extended to allow creating a new issue by email.
+
+* Allow an admin to specify which project the issue should be created under by checking the sender domain.
+* Possibly allow the use of regular expression matches within the subject/body to specify which project the issue should be created under.
diff --git a/spec/fixtures/emails/valid_new_issue_with_quote.eml b/spec/fixtures/emails/valid_new_issue_with_quote.eml
index 0caf8ed4e9e..3a9b9dbbba5 100644
--- a/spec/fixtures/emails/valid_new_issue_with_quote.eml
+++ b/spec/fixtures/emails/valid_new_issue_with_quote.eml
@@ -1,11 +1,11 @@
Return-Path: <jake@adventuretime.ooo>
Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400
Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400
-Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700
+Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq-gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700
Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700
Date: Thu, 13 Jun 2013 17:03:48 -0400
From: Jake the Dog <jake@adventuretime.ooo>
-To: incoming+gitlabhq/gitlabhq+auth_token@appmail.adventuretime.ooo
+To: incoming+gitlabhq-gitlabhq-project_id-auth_token-issue@appmail.adventuretime.ooo
Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com>
Subject: New Issue by email
Mime-Version: 1.0
diff --git a/spec/fixtures/emails/valid_new_merge_request.eml b/spec/fixtures/emails/valid_new_merge_request.eml
index 729df674604..e12843ea76b 100644
--- a/spec/fixtures/emails/valid_new_merge_request.eml
+++ b/spec/fixtures/emails/valid_new_merge_request.eml
@@ -1,11 +1,11 @@
Return-Path: <jake@adventuretime.ooo>
Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400
-Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400
-Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700
+Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <incoming+gitlabhq-gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400
+Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq-gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700
Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700
Date: Thu, 13 Jun 2013 17:03:48 -0400
From: Jake the Dog <jake@adventuretime.ooo>
-To: incoming+gitlabhq/gitlabhq+merge-request+auth_token@appmail.adventuretime.ooo
+To: incoming+gitlabhq-gitlabhq-project_id-auth_token-merge-request@appmail.adventuretime.ooo
Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com>
Subject: feature
Mime-Version: 1.0
diff --git a/spec/fixtures/emails/valid_new_merge_request_legacy.eml b/spec/fixtures/emails/valid_new_merge_request_legacy.eml
new file mode 100644
index 00000000000..b6cf064af19
--- /dev/null
+++ b/spec/fixtures/emails/valid_new_merge_request_legacy.eml
@@ -0,0 +1,20 @@
+Return-Path: <jake@adventuretime.ooo>
+Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400
+Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <incoming+gitlabhq-gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400
+Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700
+Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700
+Date: Thu, 13 Jun 2013 17:03:48 -0400
+From: Jake the Dog <jake@adventuretime.ooo>
+To: incoming+gitlabhq/gitlabhq+merge-request+auth_token@appmail.adventuretime.ooo
+Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com>
+Subject: feature
+Mime-Version: 1.0
+Content-Type: text/plain;
+ charset=ISO-8859-1
+Content-Transfer-Encoding: 7bit
+X-Sieve: CMU Sieve 2.2
+X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu,
+ 13 Jun 2013 14:03:48 -0700 (PDT)
+X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1
+
+Merge request description
diff --git a/spec/fixtures/emails/valid_new_merge_request_no_description.eml b/spec/fixtures/emails/valid_new_merge_request_no_description.eml
index 480675a6d7e..3ac0ea191a9 100644
--- a/spec/fixtures/emails/valid_new_merge_request_no_description.eml
+++ b/spec/fixtures/emails/valid_new_merge_request_no_description.eml
@@ -1,11 +1,11 @@
Return-Path: <jake@adventuretime.ooo>
Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400
Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400
-Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700
+Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq-gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700
Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700
Date: Thu, 13 Jun 2013 17:03:48 -0400
From: Jake the Dog <jake@adventuretime.ooo>
-To: incoming+gitlabhq/gitlabhq+merge-request+auth_token@appmail.adventuretime.ooo
+To: incoming+gitlabhq-gitlabhq-project_id-auth_token-merge-request@appmail.adventuretime.ooo
Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com>
Subject: feature
Mime-Version: 1.0
diff --git a/spec/fixtures/emails/valid_new_merge_request_no_subject.eml b/spec/fixtures/emails/valid_new_merge_request_no_subject.eml
index 27eb1b7d922..c2735ccb08a 100644
--- a/spec/fixtures/emails/valid_new_merge_request_no_subject.eml
+++ b/spec/fixtures/emails/valid_new_merge_request_no_subject.eml
@@ -1,11 +1,11 @@
Return-Path: <jake@adventuretime.ooo>
Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400
Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400
-Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700
+Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq-gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700
Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700
Date: Thu, 13 Jun 2013 17:03:48 -0400
From: Jake the Dog <jake@adventuretime.ooo>
-To: incoming+gitlabhq/gitlabhq+merge-request+auth_token@appmail.adventuretime.ooo
+To: incoming+gitlabhq-gitlabhq-project_id-auth_token-merge-request@appmail.adventuretime.ooo
Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com>
Subject:
Mime-Version: 1.0
diff --git a/spec/fixtures/emails/wrong_incoming_email_token.eml b/spec/fixtures/emails/wrong_issue_incoming_email_token.eml
index 0994c2f7775..d3ba6943a90 100644
--- a/spec/fixtures/emails/wrong_incoming_email_token.eml
+++ b/spec/fixtures/emails/wrong_issue_incoming_email_token.eml
@@ -1,11 +1,11 @@
Return-Path: <jake@adventuretime.ooo>
Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400
Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400
-Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700
+Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq-gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700
Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700
Date: Thu, 13 Jun 2013 17:03:48 -0400
From: Jake the Dog <jake@adventuretime.ooo>
-To: incoming+gitlabhq/gitlabhq+bad_token@appmail.adventuretime.ooo
+To: incoming+gitlabhq-gitlabhq-project_id-bad_token-issue@appmail.adventuretime.ooo
Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com>
Subject: New Issue by email
Mime-Version: 1.0
diff --git a/spec/fixtures/emails/wrong_merge_request_incoming_email_token.eml b/spec/fixtures/emails/wrong_merge_request_incoming_email_token.eml
new file mode 100644
index 00000000000..c7b758b8f1f
--- /dev/null
+++ b/spec/fixtures/emails/wrong_merge_request_incoming_email_token.eml
@@ -0,0 +1,18 @@
+Return-Path: <jake@adventuretime.ooo>
+Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400
+Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400
+Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq-gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700
+Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700
+Date: Thu, 13 Jun 2013 17:03:48 -0400
+From: Jake the Dog <jake@adventuretime.ooo>
+To: incoming+gitlabhq-gitlabhq-project_id-bad_token-merge-request@appmail.adventuretime.ooo
+Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com>
+Subject: New Issue by email
+Mime-Version: 1.0
+Content-Type: text/plain;
+ charset=ISO-8859-1
+Content-Transfer-Encoding: 7bit
+X-Sieve: CMU Sieve 2.2
+X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu,
+ 13 Jun 2013 14:03:48 -0700 (PDT)
+X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1
diff --git a/spec/helpers/application_settings_helper_spec.rb b/spec/helpers/application_settings_helper_spec.rb
new file mode 100644
index 00000000000..705523f1110
--- /dev/null
+++ b/spec/helpers/application_settings_helper_spec.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ApplicationSettingsHelper do
+ context 'when all protocols in use' do
+ before do
+ stub_application_setting(enabled_git_access_protocol: '')
+ end
+
+ it { expect(all_protocols_enabled?).to be_truthy }
+ it { expect(http_enabled?).to be_truthy }
+ it { expect(ssh_enabled?).to be_truthy }
+ end
+
+ context 'when SSH is only in use' do
+ before do
+ stub_application_setting(enabled_git_access_protocol: 'ssh')
+ end
+
+ it { expect(all_protocols_enabled?).to be_falsey }
+ it { expect(http_enabled?).to be_falsey }
+ it { expect(ssh_enabled?).to be_truthy }
+ end
+
+ shared_examples 'when HTTP protocol is in use' do |protocol|
+ before do
+ allow(Gitlab.config.gitlab).to receive(:protocol).and_return(protocol)
+ stub_application_setting(enabled_git_access_protocol: 'http')
+ end
+
+ it { expect(all_protocols_enabled?).to be_falsey }
+ it { expect(http_enabled?).to be_truthy }
+ it { expect(ssh_enabled?).to be_falsey }
+ end
+
+ it_behaves_like 'when HTTP protocol is in use', 'https'
+ it_behaves_like 'when HTTP protocol is in use', 'http'
+end
diff --git a/spec/javascripts/blob_edit/blob_bundle_spec.js b/spec/javascripts/blob_edit/blob_bundle_spec.js
index 57f60a4a3dd..48af0148e3f 100644
--- a/spec/javascripts/blob_edit/blob_bundle_spec.js
+++ b/spec/javascripts/blob_edit/blob_bundle_spec.js
@@ -1,18 +1,11 @@
import blobBundle from '~/blob_edit/blob_bundle';
import $ from 'jquery';
-window.ace = {
- config: {
- set: () => {},
- loadModule: () => {},
- },
- edit: () => ({ focus: () => {} }),
-};
-
-describe('EditBlob', () => {
+describe('BlobBundle', () => {
beforeEach(() => {
+ spyOnDependency(blobBundle, 'EditBlob').and.stub();
setFixtures(`
- <div class="js-edit-blob-form">
+ <div class="js-edit-blob-form" data-blob-filename="blah">
<button class="js-commit-button"></button>
<a class="btn btn-cancel" href="#"></a>
</div>`);
diff --git a/spec/javascripts/lib/utils/text_markdown_spec.js b/spec/javascripts/lib/utils/text_markdown_spec.js
index f71d27eb4e4..df4029555bb 100644
--- a/spec/javascripts/lib/utils/text_markdown_spec.js
+++ b/spec/javascripts/lib/utils/text_markdown_spec.js
@@ -13,215 +13,296 @@ describe('init markdown', () => {
textArea.parentNode.removeChild(textArea);
});
- describe('without selection', () => {
- it('inserts the tag on an empty line', () => {
- const initialValue = '';
+ describe('textArea', () => {
+ describe('without selection', () => {
+ it('inserts the tag on an empty line', () => {
+ const initialValue = '';
- textArea.value = initialValue;
- textArea.selectionStart = 0;
- textArea.selectionEnd = 0;
-
- insertMarkdownText({
- textArea,
- text: textArea.value,
- tag: '*',
- blockTag: null,
- selected: '',
- wrap: false,
- });
-
- expect(textArea.value).toEqual(`${initialValue}* `);
- });
-
- it('inserts the tag on a new line if the current one is not empty', () => {
- const initialValue = 'some text';
+ textArea.value = initialValue;
+ textArea.selectionStart = 0;
+ textArea.selectionEnd = 0;
- textArea.value = initialValue;
- textArea.setSelectionRange(initialValue.length, initialValue.length);
+ insertMarkdownText({
+ textArea,
+ text: textArea.value,
+ tag: '*',
+ blockTag: null,
+ selected: '',
+ wrap: false,
+ });
- insertMarkdownText({
- textArea,
- text: textArea.value,
- tag: '*',
- blockTag: null,
- selected: '',
- wrap: false,
+ expect(textArea.value).toEqual(`${initialValue}* `);
});
- expect(textArea.value).toEqual(`${initialValue}\n* `);
- });
+ it('inserts the tag on a new line if the current one is not empty', () => {
+ const initialValue = 'some text';
- it('inserts the tag on the same line if the current line only contains spaces', () => {
- const initialValue = ' ';
+ textArea.value = initialValue;
+ textArea.setSelectionRange(initialValue.length, initialValue.length);
- textArea.value = initialValue;
- textArea.setSelectionRange(initialValue.length, initialValue.length);
+ insertMarkdownText({
+ textArea,
+ text: textArea.value,
+ tag: '*',
+ blockTag: null,
+ selected: '',
+ wrap: false,
+ });
- insertMarkdownText({
- textArea,
- text: textArea.value,
- tag: '*',
- blockTag: null,
- selected: '',
- wrap: false,
+ expect(textArea.value).toEqual(`${initialValue}\n* `);
});
- expect(textArea.value).toEqual(`${initialValue}* `);
- });
+ it('inserts the tag on the same line if the current line only contains spaces', () => {
+ const initialValue = ' ';
- it('inserts the tag on the same line if the current line only contains tabs', () => {
- const initialValue = '\t\t\t';
+ textArea.value = initialValue;
+ textArea.setSelectionRange(initialValue.length, initialValue.length);
- textArea.value = initialValue;
- textArea.setSelectionRange(initialValue.length, initialValue.length);
+ insertMarkdownText({
+ textArea,
+ text: textArea.value,
+ tag: '*',
+ blockTag: null,
+ selected: '',
+ wrap: false,
+ });
- insertMarkdownText({
- textArea,
- text: textArea.value,
- tag: '*',
- blockTag: null,
- selected: '',
- wrap: false,
+ expect(textArea.value).toEqual(`${initialValue}* `);
});
- expect(textArea.value).toEqual(`${initialValue}* `);
- });
+ it('inserts the tag on the same line if the current line only contains tabs', () => {
+ const initialValue = '\t\t\t';
- it('places the cursor inside the tags', () => {
- const start = 'lorem ';
- const end = ' ipsum';
- const tag = '*';
+ textArea.value = initialValue;
+ textArea.setSelectionRange(initialValue.length, initialValue.length);
- textArea.value = `${start}${end}`;
- textArea.setSelectionRange(start.length, start.length);
+ insertMarkdownText({
+ textArea,
+ text: textArea.value,
+ tag: '*',
+ blockTag: null,
+ selected: '',
+ wrap: false,
+ });
- insertMarkdownText({
- textArea,
- text: textArea.value,
- tag,
- blockTag: null,
- selected: '',
- wrap: true,
+ expect(textArea.value).toEqual(`${initialValue}* `);
});
- expect(textArea.value).toEqual(`${start}**${end}`);
+ it('places the cursor inside the tags', () => {
+ const start = 'lorem ';
+ const end = ' ipsum';
+ const tag = '*';
- // cursor placement should be between tags
- expect(textArea.selectionStart).toBe(start.length + tag.length);
- });
- });
+ textArea.value = `${start}${end}`;
+ textArea.setSelectionRange(start.length, start.length);
- describe('with selection', () => {
- const text = 'initial selected value';
- const selected = 'selected';
- beforeEach(() => {
- textArea.value = text;
- const selectedIndex = text.indexOf(selected);
- textArea.setSelectionRange(selectedIndex, selectedIndex + selected.length);
- });
+ insertMarkdownText({
+ textArea,
+ text: textArea.value,
+ tag,
+ blockTag: null,
+ selected: '',
+ wrap: true,
+ });
- it('applies the tag to the selected value', () => {
- const selectedIndex = text.indexOf(selected);
- const tag = '*';
+ expect(textArea.value).toEqual(`${start}**${end}`);
- insertMarkdownText({
- textArea,
- text: textArea.value,
- tag,
- blockTag: null,
- selected,
- wrap: true,
+ // cursor placement should be between tags
+ expect(textArea.selectionStart).toBe(start.length + tag.length);
});
-
- expect(textArea.value).toEqual(text.replace(selected, `*${selected}*`));
-
- // cursor placement should be after selection + 2 tag lengths
- expect(textArea.selectionStart).toBe(selectedIndex + selected.length + 2 * tag.length);
});
- it('replaces the placeholder in the tag', () => {
- insertMarkdownText({
- textArea,
- text: textArea.value,
- tag: '[{text}](url)',
- blockTag: null,
- selected,
- wrap: false,
+ describe('with selection', () => {
+ const text = 'initial selected value';
+ const selected = 'selected';
+ beforeEach(() => {
+ textArea.value = text;
+ const selectedIndex = text.indexOf(selected);
+ textArea.setSelectionRange(selectedIndex, selectedIndex + selected.length);
});
- expect(textArea.value).toEqual(text.replace(selected, `[${selected}](url)`));
- });
+ it('applies the tag to the selected value', () => {
+ const selectedIndex = text.indexOf(selected);
+ const tag = '*';
- describe('and text to be selected', () => {
- const tag = '[{text}](url)';
- const select = 'url';
-
- it('selects the text', () => {
insertMarkdownText({
textArea,
text: textArea.value,
tag,
blockTag: null,
selected,
- wrap: false,
- select,
+ wrap: true,
});
- const expectedText = text.replace(selected, `[${selected}](url)`);
+ expect(textArea.value).toEqual(text.replace(selected, `*${selected}*`));
- expect(textArea.value).toEqual(expectedText);
- expect(textArea.selectionStart).toEqual(expectedText.indexOf(select));
- expect(textArea.selectionEnd).toEqual(expectedText.indexOf(select) + select.length);
+ // cursor placement should be after selection + 2 tag lengths
+ expect(textArea.selectionStart).toBe(selectedIndex + selected.length + 2 * tag.length);
});
- it('selects the right text when multiple tags are present', () => {
- const initialValue = `${tag} ${tag} ${selected}`;
- textArea.value = initialValue;
- const selectedIndex = initialValue.indexOf(selected);
- textArea.setSelectionRange(selectedIndex, selectedIndex + selected.length);
+ it('replaces the placeholder in the tag', () => {
insertMarkdownText({
textArea,
text: textArea.value,
- tag,
+ tag: '[{text}](url)',
blockTag: null,
selected,
wrap: false,
- select,
});
- const expectedText = initialValue.replace(selected, `[${selected}](url)`);
-
- expect(textArea.value).toEqual(expectedText);
- expect(textArea.selectionStart).toEqual(expectedText.lastIndexOf(select));
- expect(textArea.selectionEnd).toEqual(expectedText.lastIndexOf(select) + select.length);
+ expect(textArea.value).toEqual(text.replace(selected, `[${selected}](url)`));
});
- it('should support selected urls', () => {
- const expectedUrl = 'http://www.gitlab.com';
- const expectedSelectionText = 'text';
- const expectedText = `text [${expectedSelectionText}](${expectedUrl}) text`;
- const initialValue = `text ${expectedUrl} text`;
+ describe('and text to be selected', () => {
+ const tag = '[{text}](url)';
+ const select = 'url';
+
+ it('selects the text', () => {
+ insertMarkdownText({
+ textArea,
+ text: textArea.value,
+ tag,
+ blockTag: null,
+ selected,
+ wrap: false,
+ select,
+ });
+
+ const expectedText = text.replace(selected, `[${selected}](url)`);
+
+ expect(textArea.value).toEqual(expectedText);
+ expect(textArea.selectionStart).toEqual(expectedText.indexOf(select));
+ expect(textArea.selectionEnd).toEqual(expectedText.indexOf(select) + select.length);
+ });
- textArea.value = initialValue;
- const selectedIndex = initialValue.indexOf(expectedUrl);
- textArea.setSelectionRange(selectedIndex, selectedIndex + expectedUrl.length);
+ it('selects the right text when multiple tags are present', () => {
+ const initialValue = `${tag} ${tag} ${selected}`;
+ textArea.value = initialValue;
+ const selectedIndex = initialValue.indexOf(selected);
+ textArea.setSelectionRange(selectedIndex, selectedIndex + selected.length);
+ insertMarkdownText({
+ textArea,
+ text: textArea.value,
+ tag,
+ blockTag: null,
+ selected,
+ wrap: false,
+ select,
+ });
+
+ const expectedText = initialValue.replace(selected, `[${selected}](url)`);
+
+ expect(textArea.value).toEqual(expectedText);
+ expect(textArea.selectionStart).toEqual(expectedText.lastIndexOf(select));
+ expect(textArea.selectionEnd).toEqual(expectedText.lastIndexOf(select) + select.length);
+ });
- insertMarkdownText({
- textArea,
- text: textArea.value,
- tag,
- blockTag: null,
- selected: expectedUrl,
- wrap: false,
- select,
+ it('should support selected urls', () => {
+ const expectedUrl = 'http://www.gitlab.com';
+ const expectedSelectionText = 'text';
+ const expectedText = `text [${expectedSelectionText}](${expectedUrl}) text`;
+ const initialValue = `text ${expectedUrl} text`;
+
+ textArea.value = initialValue;
+ const selectedIndex = initialValue.indexOf(expectedUrl);
+ textArea.setSelectionRange(selectedIndex, selectedIndex + expectedUrl.length);
+
+ insertMarkdownText({
+ textArea,
+ text: textArea.value,
+ tag,
+ blockTag: null,
+ selected: expectedUrl,
+ wrap: false,
+ select,
+ });
+
+ expect(textArea.value).toEqual(expectedText);
+ expect(textArea.selectionStart).toEqual(expectedText.indexOf(expectedSelectionText, 1));
+ expect(textArea.selectionEnd).toEqual(
+ expectedText.indexOf(expectedSelectionText, 1) + expectedSelectionText.length,
+ );
});
+ });
+ });
+ });
+
+ describe('Ace Editor', () => {
+ let editor;
+
+ beforeEach(() => {
+ editor = {
+ getSelectionRange: () => ({
+ start: 0,
+ end: 0,
+ }),
+ getValue: () => 'this is text \n in two lines',
+ insert: () => {},
+ navigateLeft: () => {},
+ };
+ });
+
+ it('uses ace editor insert text when editor is passed in', () => {
+ spyOn(editor, 'insert');
- expect(textArea.value).toEqual(expectedText);
- expect(textArea.selectionStart).toEqual(expectedText.indexOf(expectedSelectionText, 1));
- expect(textArea.selectionEnd).toEqual(
- expectedText.indexOf(expectedSelectionText, 1) + expectedSelectionText.length,
- );
+ insertMarkdownText({
+ text: editor.getValue,
+ tag: '*',
+ blockTag: null,
+ selected: '',
+ wrap: false,
+ editor,
+ });
+
+ expect(editor.insert).toHaveBeenCalled();
+ });
+
+ it('adds block tags on line above and below selection', () => {
+ spyOn(editor, 'insert');
+
+ const selected = 'this text \n is multiple \n lines';
+ const text = `before \n ${selected} \n after`;
+
+ insertMarkdownText({
+ text,
+ tag: '',
+ blockTag: '***',
+ selected,
+ wrap: true,
+ editor,
+ });
+
+ expect(editor.insert).toHaveBeenCalledWith(`***\n${selected}\n***`);
+ });
+
+ it('uses ace editor to navigate back tag length when nothing is selected', () => {
+ spyOn(editor, 'navigateLeft');
+
+ insertMarkdownText({
+ text: editor.getValue,
+ tag: '*',
+ blockTag: null,
+ selected: '',
+ wrap: true,
+ editor,
});
+
+ expect(editor.navigateLeft).toHaveBeenCalledWith(1);
+ });
+
+ it('ace editor does not navigate back when there is selected text', () => {
+ spyOn(editor, 'navigateLeft');
+
+ insertMarkdownText({
+ text: editor.getValue,
+ tag: '*',
+ blockTag: null,
+ selected: 'foobar',
+ wrap: true,
+ editor,
+ });
+
+ expect(editor.navigateLeft).not.toHaveBeenCalled();
});
});
});
diff --git a/spec/javascripts/user_popovers_spec.js b/spec/javascripts/user_popovers_spec.js
index 6cf8dd81b36..c6537e71850 100644
--- a/spec/javascripts/user_popovers_spec.js
+++ b/spec/javascripts/user_popovers_spec.js
@@ -2,6 +2,9 @@ import initUserPopovers from '~/user_popovers';
import UsersCache from '~/lib/utils/users_cache';
describe('User Popovers', () => {
+ const fixtureTemplate = 'merge_requests/diff_comment.html.raw';
+ preloadFixtures(fixtureTemplate);
+
const selector = '.js-user-link';
const dummyUser = { name: 'root' };
@@ -15,11 +18,7 @@ describe('User Popovers', () => {
};
beforeEach(() => {
- setFixtures(`
- <a href="/root" data-user-id="1" class="js-user-link" data-username="root" data-original-title="" title="">
- Root
- </a>
- `);
+ loadFixtures(fixtureTemplate);
const usersCacheSpy = () => Promise.resolve(dummyUser);
spyOn(UsersCache, 'retrieveById').and.callFake(userId => usersCacheSpy(userId));
@@ -39,7 +38,7 @@ describe('User Popovers', () => {
expect(shownPopover).not.toBeNull();
expect(shownPopover.innerHTML).toContain(dummyUser.name);
- expect(UsersCache.retrieveById).toHaveBeenCalledWith('1');
+ expect(UsersCache.retrieveById).toHaveBeenCalledWith('58');
triggerEvent('mouseleave', document.querySelector(selector));
diff --git a/spec/javascripts/vue_shared/components/callout_spec.js b/spec/javascripts/vue_shared/components/callout_spec.js
index e62bd86f4ca..91208dfb31a 100644
--- a/spec/javascripts/vue_shared/components/callout_spec.js
+++ b/spec/javascripts/vue_shared/components/callout_spec.js
@@ -1,45 +1,66 @@
-import Vue from 'vue';
-import callout from '~/vue_shared/components/callout.vue';
-import createComponent from 'spec/helpers/vue_mount_component_helper';
+import { createLocalVue, shallowMount } from '@vue/test-utils';
+import Callout from '~/vue_shared/components/callout.vue';
+
+const TEST_MESSAGE = 'This is a callout message!';
+const TEST_SLOT = '<button>This is a callout slot!</button>';
+
+const localVue = createLocalVue();
describe('Callout Component', () => {
- let CalloutComponent;
- let vm;
- const exampleMessage = 'This is a callout message!';
+ let wrapper;
- beforeEach(() => {
- CalloutComponent = Vue.extend(callout);
- });
+ const factory = options => {
+ wrapper = shallowMount(localVue.extend(Callout), {
+ localVue,
+ ...options,
+ });
+ };
afterEach(() => {
- vm.$destroy();
+ wrapper.destroy();
});
it('should render the appropriate variant of callout', () => {
- vm = createComponent(CalloutComponent, {
- category: 'info',
- message: exampleMessage,
+ factory({
+ propsData: {
+ category: 'info',
+ message: TEST_MESSAGE,
+ },
});
- expect(vm.$el.getAttribute('class')).toEqual('bs-callout bs-callout-info');
+ expect(wrapper.classes()).toEqual(['bs-callout', 'bs-callout-info']);
- expect(vm.$el.tagName).toEqual('DIV');
+ expect(wrapper.element.tagName).toEqual('DIV');
});
it('should render accessibility attributes', () => {
- vm = createComponent(CalloutComponent, {
- message: exampleMessage,
+ factory({
+ propsData: {
+ message: TEST_MESSAGE,
+ },
});
- expect(vm.$el.getAttribute('role')).toEqual('alert');
- expect(vm.$el.getAttribute('aria-live')).toEqual('assertive');
+ expect(wrapper.attributes('role')).toEqual('alert');
+ expect(wrapper.attributes('aria-live')).toEqual('assertive');
});
it('should render the provided message', () => {
- vm = createComponent(CalloutComponent, {
- message: exampleMessage,
+ factory({
+ propsData: {
+ message: TEST_MESSAGE,
+ },
+ });
+
+ expect(wrapper.element.innerHTML.trim()).toEqual(TEST_MESSAGE);
+ });
+
+ it('should render the provided slot', () => {
+ factory({
+ slots: {
+ default: TEST_SLOT,
+ },
});
- expect(vm.$el.innerHTML.trim()).toEqual(exampleMessage);
+ expect(wrapper.element.innerHTML.trim()).toEqual(TEST_SLOT);
});
});
diff --git a/spec/javascripts/vue_shared/components/user_popover/user_popover_spec.js b/spec/javascripts/vue_shared/components/user_popover/user_popover_spec.js
index 25b6e3b6bc8..de3e0c149de 100644
--- a/spec/javascripts/vue_shared/components/user_popover/user_popover_spec.js
+++ b/spec/javascripts/vue_shared/components/user_popover/user_popover_spec.js
@@ -17,14 +17,13 @@ const DEFAULT_PROPS = {
const UserPopover = Vue.extend(userPopover);
describe('User Popover Component', () => {
+ const fixtureTemplate = 'merge_requests/diff_comment.html.raw';
+ preloadFixtures(fixtureTemplate);
+
let vm;
beforeEach(() => {
- setFixtures(`
- <a href="/root" data-user-id="1" class="js-user-link" title="testuser">
- Root
- </a>
- `);
+ loadFixtures(fixtureTemplate);
});
afterEach(() => {
diff --git a/spec/lib/gitlab/checks/push_check_spec.rb b/spec/lib/gitlab/checks/push_check_spec.rb
index 25f0d428cb9..e1bd52d6c0b 100644
--- a/spec/lib/gitlab/checks/push_check_spec.rb
+++ b/spec/lib/gitlab/checks/push_check_spec.rb
@@ -13,7 +13,7 @@ describe Gitlab::Checks::PushCheck do
context 'when the user is not allowed to push to the repo' do
it 'raises an error' do
expect(user_access).to receive(:can_do_action?).with(:push_code).and_return(false)
- expect(user_access).to receive(:can_push_to_branch?).with('master').and_return(false)
+ expect(project).to receive(:branch_allows_collaboration?).with(user_access.user, 'master').and_return(false)
expect { subject.validate! }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to push code to this project.')
end
diff --git a/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb b/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb
index 1d75e8cb5da..48139c2f9dc 100644
--- a/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb
+++ b/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb
@@ -11,7 +11,7 @@ describe Gitlab::Email::Handler::CreateIssueHandler do
stub_config_setting(host: 'localhost')
end
- let(:email_raw) { fixture_file('emails/valid_new_issue.eml') }
+ let(:email_raw) { email_fixture('emails/valid_new_issue.eml') }
let(:namespace) { create(:namespace, path: 'gitlabhq') }
let!(:project) { create(:project, :public, namespace: namespace, path: 'gitlabhq') }
@@ -23,21 +23,58 @@ describe Gitlab::Email::Handler::CreateIssueHandler do
)
end
+ context "when email key" do
+ let(:mail) { Mail::Message.new(email_raw) }
+
+ it "matches the new format" do
+ handler = described_class.new(mail, "gitlabhq-gitlabhq-#{project.project_id}-#{user.incoming_email_token}-issue")
+
+ expect(handler.instance_variable_get(:@project_id)).to eq project.project_id
+ expect(handler.instance_variable_get(:@project_slug)).to eq project.full_path_slug
+ expect(handler.instance_variable_get(:@incoming_email_token)).to eq user.incoming_email_token
+ expect(handler.can_handle?).to be_truthy
+ end
+
+ it "matches the legacy format" do
+ handler = described_class.new(mail, "h5bp/html5-boilerplate+#{user.incoming_email_token}")
+
+ expect(handler.instance_variable_get(:@project_path)).to eq 'h5bp/html5-boilerplate'
+ expect(handler.instance_variable_get(:@incoming_email_token)).to eq user.incoming_email_token
+ expect(handler.can_handle?).to be_truthy
+ end
+
+ it "doesn't match either format" do
+ handler = described_class.new(mail, "h5bp-html5-boilerplate+something+invalid")
+
+ expect(handler.can_handle?).to be_falsey
+ end
+ end
+
context "when everything is fine" do
- it "creates a new issue" do
- setup_attachment
+ shared_examples "a new issue" do
+ it "creates a new issue" do
+ setup_attachment
- expect { receiver.execute }.to change { project.issues.count }.by(1)
- issue = project.issues.last
+ expect { receiver.execute }.to change { project.issues.count }.by(1)
+ issue = project.issues.last
+
+ expect(issue.author).to eq(user)
+ expect(issue.title).to eq('New Issue by email')
+ expect(issue.description).to include('reply by email')
+ expect(issue.description).to include(markdown)
+ end
+ end
+
+ it_behaves_like "a new issue"
- expect(issue.author).to eq(user)
- expect(issue.title).to eq('New Issue by email')
- expect(issue.description).to include('reply by email')
- expect(issue.description).to include(markdown)
+ context "creates a new issue with legacy email address" do
+ let(:email_raw) { fixture_file('emails/valid_new_issue_legacy.eml') }
+
+ it_behaves_like "a new issue"
end
context "when the reply is blank" do
- let(:email_raw) { fixture_file("emails/valid_new_issue_empty.eml") }
+ let(:email_raw) { email_fixture("emails/valid_new_issue_empty.eml") }
it "creates a new issue" do
expect { receiver.execute }.to change { project.issues.count }.by(1)
@@ -50,7 +87,7 @@ describe Gitlab::Email::Handler::CreateIssueHandler do
end
context "when there are quotes in email" do
- let(:email_raw) { fixture_file("emails/valid_new_issue_with_quote.eml") }
+ let(:email_raw) { email_fixture("emails/valid_new_issue_with_quote.eml") }
it "creates a new issue" do
expect { receiver.execute }.to change { project.issues.count }.by(1)
@@ -76,7 +113,7 @@ describe Gitlab::Email::Handler::CreateIssueHandler do
end
context "when we can't find the incoming_email_token" do
- let(:email_raw) { fixture_file("emails/wrong_incoming_email_token.eml") }
+ let(:email_raw) { email_fixture("emails/wrong_issue_incoming_email_token.eml") }
it "raises an UserNotFoundError" do
expect { receiver.execute }.to raise_error(Gitlab::Email::UserNotFoundError)
@@ -91,4 +128,8 @@ describe Gitlab::Email::Handler::CreateIssueHandler do
end
end
end
+
+ def email_fixture(path)
+ fixture_file(path).gsub('project_id', project.project_id.to_s)
+ end
end
diff --git a/spec/lib/gitlab/email/handler/create_merge_request_handler_spec.rb b/spec/lib/gitlab/email/handler/create_merge_request_handler_spec.rb
index f276f1a8ddf..2fa86b2b46f 100644
--- a/spec/lib/gitlab/email/handler/create_merge_request_handler_spec.rb
+++ b/spec/lib/gitlab/email/handler/create_merge_request_handler_spec.rb
@@ -15,7 +15,7 @@ describe Gitlab::Email::Handler::CreateMergeRequestHandler do
TestEnv.clean_test_path
end
- let(:email_raw) { fixture_file('emails/valid_new_merge_request.eml') }
+ let(:email_raw) { email_fixture('emails/valid_new_merge_request.eml') }
let(:namespace) { create(:namespace, path: 'gitlabhq') }
let!(:project) { create(:project, :public, :repository, namespace: namespace, path: 'gitlabhq') }
@@ -27,6 +27,33 @@ describe Gitlab::Email::Handler::CreateMergeRequestHandler do
)
end
+ context "when email key" do
+ let(:mail) { Mail::Message.new(email_raw) }
+
+ it "matches the new format" do
+ handler = described_class.new(mail, "gitlabhq-gitlabhq-#{project.project_id}-#{user.incoming_email_token}-merge-request")
+
+ expect(handler.instance_variable_get(:@project_id)).to eq project.project_id
+ expect(handler.instance_variable_get(:@project_slug)).to eq project.full_path_slug
+ expect(handler.instance_variable_get(:@incoming_email_token)).to eq user.incoming_email_token
+ expect(handler.can_handle?).to be_truthy
+ end
+
+ it "matches the legacy format" do
+ handler = described_class.new(mail, "h5bp/html5-boilerplate+merge-request+#{user.incoming_email_token}")
+
+ expect(handler.instance_variable_get(:@project_path)).to eq 'h5bp/html5-boilerplate'
+ expect(handler.instance_variable_get(:@incoming_email_token)).to eq user.incoming_email_token
+ expect(handler.can_handle?).to be_truthy
+ end
+
+ it "doesn't match either format" do
+ handler = described_class.new(mail, "h5bp-html5-boilerplate+merge-request")
+
+ expect(handler.can_handle?).to be_falsey
+ end
+ end
+
context "as a non-developer" do
before do
project.add_guest(user)
@@ -43,15 +70,25 @@ describe Gitlab::Email::Handler::CreateMergeRequestHandler do
end
context "when everything is fine" do
- it "creates a new merge request" do
- expect { receiver.execute }.to change { project.merge_requests.count }.by(1)
- merge_request = project.merge_requests.last
-
- expect(merge_request.author).to eq(user)
- expect(merge_request.source_branch).to eq('feature')
- expect(merge_request.title).to eq('Feature added')
- expect(merge_request.description).to eq('Merge request description')
- expect(merge_request.target_branch).to eq(project.default_branch)
+ shared_examples "a new merge request" do
+ it "creates a new merge request" do
+ expect { receiver.execute }.to change { project.merge_requests.count }.by(1)
+ merge_request = project.merge_requests.last
+
+ expect(merge_request.author).to eq(user)
+ expect(merge_request.source_branch).to eq('feature')
+ expect(merge_request.title).to eq('Feature added')
+ expect(merge_request.description).to eq('Merge request description')
+ expect(merge_request.target_branch).to eq(project.default_branch)
+ end
+ end
+
+ it_behaves_like "a new merge request"
+
+ context "creates a new merge request with legacy email address" do
+ let(:email_raw) { fixture_file('emails/valid_new_merge_request_legacy.eml') }
+
+ it_behaves_like "a new merge request"
end
end
@@ -67,7 +104,7 @@ describe Gitlab::Email::Handler::CreateMergeRequestHandler do
end
context "when we can't find the incoming_email_token" do
- let(:email_raw) { fixture_file("emails/wrong_incoming_email_token.eml") }
+ let(:email_raw) { email_fixture("emails/wrong_merge_request_incoming_email_token.eml") }
it "raises an UserNotFoundError" do
expect { receiver.execute }.to raise_error(Gitlab::Email::UserNotFoundError)
@@ -75,7 +112,7 @@ describe Gitlab::Email::Handler::CreateMergeRequestHandler do
end
context "when the subject is blank" do
- let(:email_raw) { fixture_file("emails/valid_new_merge_request_no_subject.eml") }
+ let(:email_raw) { email_fixture("emails/valid_new_merge_request_no_subject.eml") }
it "raises an InvalidMergeRequestError" do
expect { receiver.execute }.to raise_error(Gitlab::Email::InvalidMergeRequestError)
@@ -83,7 +120,7 @@ describe Gitlab::Email::Handler::CreateMergeRequestHandler do
end
context "when the message body is blank" do
- let(:email_raw) { fixture_file("emails/valid_new_merge_request_no_description.eml") }
+ let(:email_raw) { email_fixture("emails/valid_new_merge_request_no_description.eml") }
it "creates a new merge request with description set from the last commit" do
expect { receiver.execute }.to change { project.merge_requests.count }.by(1)
@@ -95,7 +132,7 @@ describe Gitlab::Email::Handler::CreateMergeRequestHandler do
end
context 'when the email contains patch attachments' do
- let(:email_raw) { fixture_file("emails/valid_merge_request_with_patch.eml") }
+ let(:email_raw) { email_fixture("emails/valid_merge_request_with_patch.eml") }
it 'creates the source branch and applies the patches' do
receiver.execute
@@ -120,7 +157,7 @@ describe Gitlab::Email::Handler::CreateMergeRequestHandler do
end
context 'when the patch could not be applied' do
- let(:email_raw) { fixture_file("emails/merge_request_with_conflicting_patch.eml") }
+ let(:email_raw) { email_fixture("emails/merge_request_with_conflicting_patch.eml") }
it 'raises an error' do
expect { receiver.execute }.to raise_error(Gitlab::Email::InvalidAttachment)
@@ -128,7 +165,7 @@ describe Gitlab::Email::Handler::CreateMergeRequestHandler do
end
context 'when specifying the target branch using quick actions' do
- let(:email_raw) { fixture_file('emails/merge_request_with_patch_and_target_branch.eml') }
+ let(:email_raw) { email_fixture('emails/merge_request_with_patch_and_target_branch.eml') }
it 'creates the merge request with the correct target branch' do
receiver.execute
@@ -150,7 +187,7 @@ describe Gitlab::Email::Handler::CreateMergeRequestHandler do
end
describe '#patch_attachments' do
- let(:email_raw) { fixture_file('emails/merge_request_multiple_patches.eml') }
+ let(:email_raw) { email_fixture('emails/merge_request_multiple_patches.eml') }
let(:mail) { Mail::Message.new(email_raw) }
subject(:handler) { described_class.new(mail, mail_key) }
@@ -163,4 +200,8 @@ describe Gitlab::Email::Handler::CreateMergeRequestHandler do
expect(attachments).to eq(expected_filenames)
end
end
+
+ def email_fixture(path)
+ fixture_file(path).gsub('project_id', project.project_id.to_s)
+ end
end
diff --git a/spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb b/spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb
index b8660b133ec..dcddd00df59 100644
--- a/spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb
+++ b/spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb
@@ -10,13 +10,35 @@ describe Gitlab::Email::Handler::UnsubscribeHandler do
stub_config_setting(host: 'localhost')
end
- let(:email_raw) { fixture_file('emails/valid_reply.eml').gsub(mail_key, "#{mail_key}+unsubscribe") }
- let(:project) { create(:project, :public) }
- let(:user) { create(:user) }
- let(:noteable) { create(:issue, project: project) }
+ let(:email_raw) { fixture_file('emails/valid_reply.eml').gsub(mail_key, "#{mail_key}#{Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX}") }
+ let(:project) { create(:project, :public) }
+ let(:user) { create(:user) }
+ let(:noteable) { create(:issue, project: project) }
let!(:sent_notification) { SentNotification.record(noteable, user.id, mail_key) }
+ context "when email key" do
+ let(:mail) { Mail::Message.new(email_raw) }
+
+ it "matches the new format" do
+ handler = described_class.new(mail, "#{mail_key}#{Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX}")
+
+ expect(handler.can_handle?).to be_truthy
+ end
+
+ it "matches the legacy format" do
+ handler = described_class.new(mail, "#{mail_key}#{Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX_LEGACY}")
+
+ expect(handler.can_handle?).to be_truthy
+ end
+
+ it "doesn't match either format" do
+ handler = described_class.new(mail, "+#{mail_key}#{Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX}")
+
+ expect(handler.can_handle?).to be_falsey
+ end
+ end
+
context 'when notification concerns a commit' do
let(:commit) { create(:commit, project: project) }
let!(:sent_notification) { SentNotification.record(commit, user.id, mail_key) }
@@ -40,6 +62,14 @@ describe Gitlab::Email::Handler::UnsubscribeHandler do
it 'unsubscribes user from notable' do
expect { receiver.execute }.to change { noteable.subscribed?(user) }.from(true).to(false)
end
+
+ context 'when using old style unsubscribe link' do
+ let(:email_raw) { fixture_file('emails/valid_reply.eml').gsub(mail_key, "#{mail_key}#{Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX_LEGACY}") }
+
+ it 'unsubscribes user from notable' do
+ expect { receiver.execute }.to change { noteable.subscribed?(user) }.from(true).to(false)
+ end
+ end
end
context 'when the noteable could not be found' do
diff --git a/spec/lib/gitlab/email/handler_spec.rb b/spec/lib/gitlab/email/handler_spec.rb
index c651765dc0f..d2920b08956 100644
--- a/spec/lib/gitlab/email/handler_spec.rb
+++ b/spec/lib/gitlab/email/handler_spec.rb
@@ -19,7 +19,8 @@ describe Gitlab::Email::Handler do
describe 'regexps are set properly' do
let(:addresses) do
- %W(sent_notification_key#{Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX} sent_notification_key path/to/project+merge-request+user_email_token path/to/project+user_email_token)
+ %W(sent_notification_key#{Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX} sent_notification_key path-to-project-123-user_email_token-merge-request path-to-project-123-user_email_token-issue) +
+ %W(sent_notification_key#{Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX_LEGACY} sent_notification_key path/to/project+merge-request+user_email_token path/to/project+user_email_token)
end
it 'picks each handler at least once' do
diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb
index 8d8eb50ad76..3e34dd592f2 100644
--- a/spec/lib/gitlab/git_access_spec.rb
+++ b/spec/lib/gitlab/git_access_spec.rb
@@ -14,7 +14,7 @@ describe Gitlab::GitAccess do
let(:authentication_abilities) { %i[read_project download_code push_code] }
let(:redirected_path) { nil }
let(:auth_result_type) { nil }
- let(:changes) { '_any' }
+ let(:changes) { Gitlab::GitAccess::ANY }
let(:push_access_check) { access.check('git-receive-pack', changes) }
let(:pull_access_check) { access.check('git-upload-pack', changes) }
@@ -437,7 +437,7 @@ describe Gitlab::GitAccess do
let(:project) { nil }
context 'when changes is _any' do
- let(:changes) { '_any' }
+ let(:changes) { Gitlab::GitAccess::ANY }
context 'when authentication abilities include push code' do
let(:authentication_abilities) { [:push_code] }
@@ -483,7 +483,7 @@ describe Gitlab::GitAccess do
end
context 'when project exists' do
- let(:changes) { '_any' }
+ let(:changes) { Gitlab::GitAccess::ANY }
let!(:project) { create(:project) }
it 'does not create a new project' do
@@ -497,7 +497,7 @@ describe Gitlab::GitAccess do
let(:project_path) { "nonexistent" }
let(:project) { nil }
let(:namespace_path) { user.namespace.path }
- let(:changes) { '_any' }
+ let(:changes) { Gitlab::GitAccess::ANY }
it 'does not create a new project' do
expect { access.send(:ensure_project_on_push!, cmd, changes) }.not_to change { Project.count }
@@ -507,7 +507,7 @@ describe Gitlab::GitAccess do
context 'when pull' do
let(:cmd) { 'git-upload-pack' }
- let(:changes) { '_any' }
+ let(:changes) { Gitlab::GitAccess::ANY }
context 'when project does not exist' do
let(:project_path) { "new-project" }
@@ -736,7 +736,8 @@ describe Gitlab::GitAccess do
end
let(:changes) do
- { push_new_branch: "#{Gitlab::Git::BLANK_SHA} 570e7b2ab refs/heads/wow",
+ { any: Gitlab::GitAccess::ANY,
+ push_new_branch: "#{Gitlab::Git::BLANK_SHA} 570e7b2ab refs/heads/wow",
push_master: '6f6d7e7ed 570e7b2ab refs/heads/master',
push_protected_branch: '6f6d7e7ed 570e7b2ab refs/heads/feature',
push_remove_protected_branch: "570e7b2ab #{Gitlab::Git::BLANK_SHA} "\
@@ -798,6 +799,7 @@ describe Gitlab::GitAccess do
permissions_matrix = {
admin: {
+ any: true,
push_new_branch: true,
push_master: true,
push_protected_branch: true,
@@ -809,6 +811,7 @@ describe Gitlab::GitAccess do
},
maintainer: {
+ any: true,
push_new_branch: true,
push_master: true,
push_protected_branch: true,
@@ -820,6 +823,7 @@ describe Gitlab::GitAccess do
},
developer: {
+ any: true,
push_new_branch: true,
push_master: true,
push_protected_branch: false,
@@ -831,6 +835,7 @@ describe Gitlab::GitAccess do
},
reporter: {
+ any: false,
push_new_branch: false,
push_master: false,
push_protected_branch: false,
@@ -842,6 +847,7 @@ describe Gitlab::GitAccess do
},
guest: {
+ any: false,
push_new_branch: false,
push_master: false,
push_protected_branch: false,
diff --git a/spec/lib/gitlab/git_access_wiki_spec.rb b/spec/lib/gitlab/git_access_wiki_spec.rb
index 9c6c9fe13bf..6ba65b56618 100644
--- a/spec/lib/gitlab/git_access_wiki_spec.rb
+++ b/spec/lib/gitlab/git_access_wiki_spec.rb
@@ -38,7 +38,7 @@ describe Gitlab::GitAccessWiki do
end
describe '#access_check_download!' do
- subject { access.check('git-upload-pack', '_any') }
+ subject { access.check('git-upload-pack', Gitlab::GitAccess::ANY) }
before do
project.add_developer(user)
diff --git a/spec/lib/gitlab/incoming_email_spec.rb b/spec/lib/gitlab/incoming_email_spec.rb
index 4c0c3fcbcc7..2db62ab983a 100644
--- a/spec/lib/gitlab/incoming_email_spec.rb
+++ b/spec/lib/gitlab/incoming_email_spec.rb
@@ -61,7 +61,7 @@ describe Gitlab::IncomingEmail do
end
it 'returns the address with interpolated reply key and unsubscribe suffix' do
- expect(described_class.unsubscribe_address('key')).to eq('replies+key+unsubscribe@example.com')
+ expect(described_class.unsubscribe_address('key')).to eq("replies+key#{Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX}@example.com")
end
end
diff --git a/spec/lib/gitlab/middleware/go_spec.rb b/spec/lib/gitlab/middleware/go_spec.rb
index 7a3a9ab875b..f52095bf633 100644
--- a/spec/lib/gitlab/middleware/go_spec.rb
+++ b/spec/lib/gitlab/middleware/go_spec.rb
@@ -96,43 +96,36 @@ describe Gitlab::Middleware::Go do
it_behaves_like 'unauthorized'
end
- end
-
- context 'using warden' do
- before do
- env['warden'] = double(authenticate: current_user)
- end
- context 'when active' do
- it_behaves_like 'authenticated'
- end
-
- context 'when blocked' do
+ context 'with user is blocked' do
before do
- current_user.block!
+ current_user.block
end
it_behaves_like 'unauthorized'
end
end
- context 'using a personal access token' do
- let(:personal_access_token) { create(:personal_access_token, user: current_user) }
-
- before do
- env['HTTP_PRIVATE_TOKEN'] = personal_access_token.token
- end
-
- context 'with api scope' do
- it_behaves_like 'authenticated'
- end
+ context 'using basic auth' do
+ context 'using a personal access token' do
+ let(:personal_access_token) { create(:personal_access_token, user: current_user) }
- context 'with read_user scope' do
before do
- personal_access_token.update_attribute(:scopes, [:read_user])
+ env['REMOTE_ADDR'] = "192.168.0.1"
+ env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials(current_user.username, personal_access_token.token)
end
- it_behaves_like 'unauthorized'
+ context 'with api scope' do
+ it_behaves_like 'authenticated'
+ end
+
+ context 'with read_user scope' do
+ before do
+ personal_access_token.update_attribute(:scopes, [:read_user])
+ end
+
+ it_behaves_like 'unauthorized'
+ end
end
end
end
diff --git a/spec/models/clusters/applications/knative_spec.rb b/spec/models/clusters/applications/knative_spec.rb
index 809880f5969..8fc755d2a26 100644
--- a/spec/models/clusters/applications/knative_spec.rb
+++ b/spec/models/clusters/applications/knative_spec.rb
@@ -108,6 +108,23 @@ describe Clusters::Applications::Knative do
expect(subject.version).to eq('0.2.2')
expect(subject.files).to eq(knative.files)
end
+
+ it 'should not install metrics for prometheus' do
+ expect(subject.postinstall).to be_nil
+ end
+
+ context 'with prometheus installed' do
+ let(:prometheus) { create(:clusters_applications_prometheus, :installed) }
+ let(:knative) { create(:clusters_applications_knative, cluster: prometheus.cluster) }
+
+ subject { knative.install_command }
+
+ it 'should install metrics' do
+ expect(subject.postinstall).not_to be_nil
+ expect(subject.postinstall.length).to be(1)
+ expect(subject.postinstall[0]).to eql("kubectl apply -f #{Clusters::Applications::Knative::METRICS_CONFIG}")
+ end
+ end
end
describe '#files' do
diff --git a/spec/models/clusters/applications/prometheus_spec.rb b/spec/models/clusters/applications/prometheus_spec.rb
index 893ed3e3f64..27143f29350 100644
--- a/spec/models/clusters/applications/prometheus_spec.rb
+++ b/spec/models/clusters/applications/prometheus_spec.rb
@@ -165,6 +165,10 @@ describe Clusters::Applications::Prometheus do
expect(subject.files).to eq(prometheus.files)
end
+ it 'should not install knative metrics' do
+ expect(subject.postinstall).to be_nil
+ end
+
context 'on a rbac enabled cluster' do
before do
prometheus.cluster.platform_kubernetes.rbac!
@@ -180,6 +184,17 @@ describe Clusters::Applications::Prometheus do
expect(subject.version).to eq('6.7.3')
end
end
+
+ context 'with knative installed' do
+ let(:knative) { create(:clusters_applications_knative, :installed ) }
+ let(:prometheus) { create(:clusters_applications_prometheus, cluster: knative.cluster) }
+
+ subject { prometheus.install_command }
+
+ it 'should install metrics' do
+ expect(subject.postinstall).to include("kubectl apply -f #{Clusters::Applications::Knative::METRICS_CONFIG}")
+ end
+ end
end
describe '#files' do
diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb
index 840f74c9890..f447e64b029 100644
--- a/spec/models/clusters/cluster_spec.rb
+++ b/spec/models/clusters/cluster_spec.rb
@@ -29,6 +29,7 @@ describe Clusters::Cluster do
it { is_expected.to delegate_method(:available?).to(:application_helm).with_prefix }
it { is_expected.to delegate_method(:available?).to(:application_ingress).with_prefix }
it { is_expected.to delegate_method(:available?).to(:application_prometheus).with_prefix }
+ it { is_expected.to delegate_method(:available?).to(:application_knative).with_prefix }
it { is_expected.to respond_to :project }
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 4b6592020c1..2e95d35e134 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -610,16 +610,20 @@ describe Project do
end
it 'returns the address to create a new issue' do
- address = "p+#{project.full_path}+#{user.incoming_email_token}@gl.ab"
+ address = "p+#{project.full_path_slug}-#{project.project_id}-#{user.incoming_email_token}-issue@gl.ab"
expect(project.new_issuable_address(user, 'issue')).to eq(address)
end
it 'returns the address to create a new merge request' do
- address = "p+#{project.full_path}+merge-request+#{user.incoming_email_token}@gl.ab"
+ address = "p+#{project.full_path_slug}-#{project.project_id}-#{user.incoming_email_token}-merge-request@gl.ab"
expect(project.new_issuable_address(user, 'merge_request')).to eq(address)
end
+
+ it 'returns nil with invalid address type' do
+ expect(project.new_issuable_address(user, 'invalid_param')).to be_nil
+ end
end
context 'incoming email disabled' do
@@ -3831,6 +3835,16 @@ describe Project do
let(:user) { create(:user) }
let(:target_project) { create(:project, :repository) }
let(:project) { fork_project(target_project, nil, repository: true) }
+ let!(:local_merge_request) do
+ create(
+ :merge_request,
+ target_project: project,
+ target_branch: 'target-branch',
+ source_project: project,
+ source_branch: 'awesome-feature-1',
+ allow_collaboration: true
+ )
+ end
let!(:merge_request) do
create(
:merge_request,
@@ -3875,14 +3889,23 @@ describe Project do
end
end
- describe '#branch_allows_collaboration_push?' do
- it 'allows access if the user can merge the merge request' do
- expect(project.branch_allows_collaboration?(user, 'awesome-feature-1'))
+ describe '#any_branch_allows_collaboration?' do
+ it 'allows access when there are merge requests open allowing collaboration' do
+ expect(project.any_branch_allows_collaboration?(user))
.to be_truthy
end
- it 'allows access when there are merge requests open but no branch name is given' do
- expect(project.branch_allows_collaboration?(user, nil))
+ it 'does not allow access when there are no merge requests open allowing collaboration' do
+ merge_request.close!
+
+ expect(project.any_branch_allows_collaboration?(user))
+ .to be_falsey
+ end
+ end
+
+ describe '#branch_allows_collaboration?' do
+ it 'allows access if the user can merge the merge request' do
+ expect(project.branch_allows_collaboration?(user, 'awesome-feature-1'))
.to be_truthy
end
@@ -3913,13 +3936,6 @@ describe Project do
.to be_falsy
end
- it 'caches the result' do
- control = ActiveRecord::QueryRecorder.new { project.branch_allows_collaboration?(user, 'awesome-feature-1') }
-
- expect { 3.times { project.branch_allows_collaboration?(user, 'awesome-feature-1') } }
- .not_to exceed_query_limit(control)
- end
-
context 'when the requeststore is active', :request_store do
it 'only queries per project across instances' do
control = ActiveRecord::QueryRecorder.new { project.branch_allows_collaboration?(user, 'awesome-feature-1') }
diff --git a/spec/serializers/pipeline_serializer_spec.rb b/spec/serializers/pipeline_serializer_spec.rb
index cf57776346a..79aa32b29bb 100644
--- a/spec/serializers/pipeline_serializer_spec.rb
+++ b/spec/serializers/pipeline_serializer_spec.rb
@@ -144,7 +144,7 @@ describe PipelineSerializer do
# pipeline. With the same ref this check is cached but if refs are
# different then there is an extra query per ref
# https://gitlab.com/gitlab-org/gitlab-ce/issues/46368
- expect(recorded.count).to be_within(2).of(34)
+ expect(recorded.count).to be_within(2).of(38)
expect(recorded.cached_count).to eq(0)
end
end
diff --git a/spec/services/ci/retry_pipeline_service_spec.rb b/spec/services/ci/retry_pipeline_service_spec.rb
index 55445e71539..75042b29bea 100644
--- a/spec/services/ci/retry_pipeline_service_spec.rb
+++ b/spec/services/ci/retry_pipeline_service_spec.rb
@@ -285,7 +285,7 @@ describe Ci::RetryPipelineService, '#execute' do
end
it 'allows to retry failed pipeline' do
- allow_any_instance_of(Project).to receive(:fetch_branch_allows_collaboration?).and_return(true)
+ allow_any_instance_of(Project).to receive(:branch_allows_collaboration?).and_return(true)
allow_any_instance_of(Project).to receive(:empty_repo?).and_return(false)
service.execute(pipeline)
diff --git a/spec/support/shared_contexts/email_shared_blocks.rb b/spec/support/shared_contexts/email_shared_context.rb
index 9d806fc524d..9d806fc524d 100644
--- a/spec/support/shared_contexts/email_shared_blocks.rb
+++ b/spec/support/shared_contexts/email_shared_context.rb
diff --git a/vendor/prometheus/values.yaml b/vendor/prometheus/values.yaml
index 02ec3e2d9fe..60241c65202 100644
--- a/vendor/prometheus/values.yaml
+++ b/vendor/prometheus/values.yaml
@@ -141,3 +141,105 @@ serverFiles:
- __meta_kubernetes_pod_name
action: replace
target_label: kubernetes_pod_name
+ # Sourced from Knative monitoring config: https://github.com/knative/serving/blob/master/config/monitoring/metrics/prometheus/100-prometheus-scrape-config.yaml
+ - job_name: autoscaler
+ scrape_interval: 3s
+ scrape_timeout: 3s
+ kubernetes_sd_configs:
+ - role: pod
+ relabel_configs:
+ # Scrape only the the targets matching the following metadata
+ - source_labels: [__meta_kubernetes_namespace, __meta_kubernetes_pod_label_app, __meta_kubernetes_pod_container_port_name]
+ action: keep
+ regex: knative-serving;autoscaler;metrics
+ # Rename metadata labels to be reader friendly
+ - source_labels: [__meta_kubernetes_namespace]
+ target_label: namespace
+ - source_labels: [__meta_kubernetes_pod_name]
+ target_label: pod
+ - source_labels: [__meta_kubernetes_service_name]
+ target_label: service
+ - job_name: activator
+ scrape_interval: 3s
+ scrape_timeout: 3s
+ kubernetes_sd_configs:
+ - role: pod
+ relabel_configs:
+ # Scrape only the the targets matching the following metadata
+ - source_labels: [__meta_kubernetes_namespace, __meta_kubernetes_pod_label_app, __meta_kubernetes_pod_container_port_name]
+ action: keep
+ regex: knative-serving;activator;metrics-port
+ # Rename metadata labels to be reader friendly
+ - source_labels: [__meta_kubernetes_namespace]
+ target_label: namespace
+ - source_labels: [__meta_kubernetes_pod_name]
+ target_label: pod
+ - source_labels: [__meta_kubernetes_service_name]
+ target_label: service
+ # Istio mesh
+ - job_name: istio-mesh
+ scrape_interval: 5s
+ kubernetes_sd_configs:
+ - role: endpoints
+ relabel_configs:
+ # Scrape only the the targets matching the following metadata
+ - source_labels: [__meta_kubernetes_namespace, __meta_kubernetes_service_name, __meta_kubernetes_endpoint_port_name]
+ action: keep
+ regex: istio-system;istio-telemetry;prometheus
+ # Rename metadata labels to be reader friendly
+ - source_labels: [__meta_kubernetes_namespace]
+ target_label: namespace
+ - source_labels: [__meta_kubernetes_pod_name]
+ target_label: pod
+ - source_labels: [__meta_kubernetes_service_name]
+ target_label: service
+ - job_name: istio-policy
+ scrape_interval: 5s
+ kubernetes_sd_configs:
+ - role: endpoints
+ relabel_configs:
+ # Scrape only the the targets matching the following metadata
+ - source_labels: [__meta_kubernetes_namespace, __meta_kubernetes_service_name, __meta_kubernetes_endpoint_port_name]
+ action: keep
+ regex: istio-system;istio-policy;http-monitoring
+ # Rename metadata labels to be reader friendly
+ - source_labels: [__meta_kubernetes_namespace]
+ target_label: namespace
+ - source_labels: [__meta_kubernetes_pod_name]
+ target_label: pod
+ - source_labels: [__meta_kubernetes_service_name]
+ target_label: service
+ # Istio telemetry
+ - job_name: istio-telemetry
+ scrape_interval: 5s
+ kubernetes_sd_configs:
+ - role: endpoints
+ relabel_configs:
+ # Scrape only the the targets matching the following metadata
+ - source_labels: [__meta_kubernetes_namespace, __meta_kubernetes_service_name, __meta_kubernetes_endpoint_port_name]
+ action: keep
+ regex: istio-system;istio-telemetry;http-monitoring
+ # Rename metadata labels to be reader friendly
+ - source_labels: [__meta_kubernetes_namespace]
+ target_label: namespace
+ - source_labels: [__meta_kubernetes_pod_name]
+ target_label: pod
+ - source_labels: [__meta_kubernetes_service_name]
+ target_label: service
+ # Istio pilot
+ - job_name: istio-pilot
+ scrape_interval: 5s
+ kubernetes_sd_configs:
+ - role: endpoints
+ relabel_configs:
+ # Scrape only the the targets matching the following metadata
+ - source_labels: [__meta_kubernetes_namespace, __meta_kubernetes_service_name, __meta_kubernetes_endpoint_port_name]
+ action: keep
+ regex: istio-system;istio-pilot;http-monitoring
+ # Rename metadata labels to be reader friendly
+ - source_labels: [__meta_kubernetes_namespace]
+ target_label: namespace
+ - source_labels: [__meta_kubernetes_pod_name]
+ target_label: pod
+ - source_labels: [__meta_kubernetes_service_name]
+ target_label: service