summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--GITLAB_WORKHORSE_VERSION2
-rw-r--r--app/assets/javascripts/diffs/components/diff_discussions.vue1
-rw-r--r--app/assets/javascripts/diffs/components/diff_line_gutter_content.vue6
-rw-r--r--app/assets/javascripts/diffs/components/diff_table_cell.vue10
-rw-r--r--app/assets/javascripts/diffs/components/inline_diff_table_row.vue6
-rw-r--r--app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue10
-rw-r--r--app/assets/javascripts/diffs/components/parallel_diff_table_row.vue6
-rw-r--r--app/assets/javascripts/diffs/constants.js2
-rw-r--r--app/assets/javascripts/diffs/store/mutations.js8
-rw-r--r--app/assets/javascripts/environments/components/environment_external_url.vue9
-rw-r--r--app/assets/javascripts/environments/components/environment_item.vue6
-rw-r--r--app/assets/javascripts/environments/components/environment_monitoring.vue8
-rw-r--r--app/assets/javascripts/environments/components/environment_rollback.vue17
-rw-r--r--app/assets/javascripts/environments/components/environment_stop.vue14
-rw-r--r--app/assets/javascripts/environments/components/environment_terminal_button.vue7
-rw-r--r--app/assets/javascripts/environments/components/stop_environment_modal.vue6
-rw-r--r--app/assets/javascripts/jobs/components/job_app.vue6
-rw-r--r--app/assets/javascripts/notes/components/comment_form.vue8
-rw-r--r--app/assets/javascripts/notes/components/discussion_counter.vue30
-rw-r--r--app/assets/javascripts/notes/components/note_actions.vue63
-rw-r--r--app/assets/javascripts/notes/components/note_awards_list.vue27
-rw-r--r--app/assets/javascripts/notes/components/note_header.vue12
-rw-r--r--app/assets/javascripts/notes/components/noteable_discussion.vue170
-rw-r--r--app/assets/javascripts/notes/components/noteable_note.vue3
-rw-r--r--app/assets/javascripts/notes/components/notes_app.vue6
-rw-r--r--app/assets/javascripts/notes/components/toggle_replies_widget.vue94
-rw-r--r--app/assets/javascripts/vue_shared/components/changed_file_icon.vue8
-rw-r--r--app/assets/javascripts/vue_shared/components/ci_badge_link.vue6
-rw-r--r--app/assets/javascripts/vue_shared/components/commit.vue7
-rw-r--r--app/assets/javascripts/vue_shared/components/icon.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/notes/skeleton_note.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/notes/system_note.vue2
-rw-r--r--app/assets/stylesheets/framework/awards.scss5
-rw-r--r--app/assets/stylesheets/framework/buttons.scss42
-rw-r--r--app/assets/stylesheets/framework/files.scss1
-rw-r--r--app/assets/stylesheets/framework/timeline.scss6
-rw-r--r--app/assets/stylesheets/pages/diff.scss3
-rw-r--r--app/assets/stylesheets/pages/note_form.scss14
-rw-r--r--app/assets/stylesheets/pages/notes.scss293
-rw-r--r--app/controllers/projects/blob_controller.rb2
-rw-r--r--app/controllers/projects/merge_requests/diffs_controller.rb6
-rw-r--r--app/helpers/projects_helper.rb9
-rw-r--r--app/models/diff_note.rb45
-rw-r--r--app/models/project_import_state.rb13
-rw-r--r--app/services/merge_requests/reload_diffs_service.rb4
-rw-r--r--app/services/notes/base_service.rb15
-rw-r--r--app/services/notes/create_service.rb3
-rw-r--r--app/services/notes/destroy_service.rb4
-rw-r--r--app/views/dashboard/snippets/index.html.haml5
-rw-r--r--app/views/layouts/header/_default.html.haml2
-rw-r--r--app/views/layouts/nav/sidebar/_project.html.haml7
-rw-r--r--app/views/shared/notes/_notes_with_form.html.haml4
-rw-r--r--app/workers/stuck_import_jobs_worker.rb46
-rw-r--r--changelogs/unreleased/51259-ci-cd-tooltips.yml6
-rw-r--r--changelogs/unreleased/53640-follow-up-from-resolve-redesign-activity-feed.yml4
-rw-r--r--changelogs/unreleased/fix-stuck-import-jobs-query-performance-issue.yml5
-rw-r--r--changelogs/unreleased/gt-align-sign-in-button.yml5
-rw-r--r--changelogs/unreleased/osw-comment-on-any-line-on-diffs.yml5
-rw-r--r--changelogs/unreleased/remove-duplicate-primary-button-in-dashboard-snippets.yml5
-rw-r--r--changelogs/unreleased/upgrade-workhorse-7-1-0.yml5
-rw-r--r--doc/administration/auth/okta.md2
-rw-r--r--doc/administration/container_registry.md5
-rw-r--r--doc/administration/monitoring/prometheus/gitlab_metrics.md2
-rw-r--r--doc/ci/yaml/README.md2
-rw-r--r--doc/install/database_mysql.md48
-rw-r--r--doc/install/kubernetes/gitlab_chart.md2
-rw-r--r--doc/raketasks/import.md72
-rw-r--r--doc/university/training/topics/additional_resources.md12
-rw-r--r--doc/university/training/topics/tags.md2
-rw-r--r--doc/university/training/user_training.md2
-rw-r--r--doc/user/project/clusters/index.md119
-rw-r--r--lib/gitlab/ci/config/entry/job.rb3
-rw-r--r--lib/gitlab/diff/file.rb19
-rw-r--r--lib/gitlab/diff/line.rb13
-rw-r--r--lib/gitlab/diff/lines_unfolder.rb235
-rw-r--r--lib/gitlab/diff/position.rb12
-rw-r--r--locale/gitlab.pot11
-rw-r--r--qa/qa/git/repository.rb26
-rw-r--r--qa/qa/page/project/issue/show.rb5
-rw-r--r--qa/qa/runtime/env.rb19
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb1
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb5
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_ssh_spec.rb84
-rw-r--r--qa/qa/specs/runner.rb4
-rw-r--r--qa/spec/git/repository_spec.rb30
-rw-r--r--qa/spec/runtime/env_spec.rb47
-rw-r--r--qa/spec/specs/runner_spec.rb14
-rwxr-xr-xscripts/trigger-build3
-rw-r--r--spec/controllers/projects/blob_controller_spec.rb4
-rw-r--r--spec/features/merge_request/user_posts_diff_notes_spec.rb13
-rw-r--r--spec/javascripts/diffs/store/mutations_spec.js4
-rw-r--r--spec/javascripts/notes/components/noteable_discussion_spec.js16
-rw-r--r--spec/javascripts/notes/components/toggle_replies_widget_spec.js78
-rw-r--r--spec/lib/gitlab/ci/config/entry/job_spec.rb10
-rw-r--r--spec/lib/gitlab/diff/file_spec.rb46
-rw-r--r--spec/lib/gitlab/diff/lines_unfolder_spec.rb750
-rw-r--r--spec/lib/gitlab/import_export/attribute_configuration_spec.rb15
-rw-r--r--spec/services/merge_requests/reload_diffs_service_spec.rb21
-rw-r--r--spec/services/notes/create_service_spec.rb51
-rw-r--r--spec/services/notes/destroy_service_spec.rb33
-rw-r--r--spec/support/features/discussion_comments_shared_example.rb14
-rw-r--r--spec/support/helpers/test_env.rb5
-rw-r--r--spec/workers/stuck_import_jobs_worker_spec.rb22
-rw-r--r--vendor/gitignore/Android.gitignore4
-rw-r--r--vendor/gitignore/Delphi.gitignore3
-rw-r--r--vendor/gitignore/Elixir.gitignore1
-rw-r--r--vendor/gitignore/Global/Images.gitignore63
-rw-r--r--vendor/gitignore/Global/NetBeans.gitignore2
-rw-r--r--vendor/gitignore/Global/PSoCCreator.gitignore18
-rw-r--r--vendor/gitignore/Global/Xcode.gitignore74
-rw-r--r--vendor/gitignore/Laravel.gitignore2
-rw-r--r--vendor/gitignore/Magento.gitignore2
-rw-r--r--vendor/gitignore/Node.gitignore3
-rw-r--r--vendor/gitignore/Python.gitignore3
-rw-r--r--vendor/gitignore/Rails.gitignore1
-rw-r--r--vendor/gitignore/Unity.gitignore1
-rw-r--r--vendor/licenses.csv52
117 files changed, 1477 insertions, 1761 deletions
diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION
index 4122521804f..a3fcc7121bb 100644
--- a/GITLAB_WORKHORSE_VERSION
+++ b/GITLAB_WORKHORSE_VERSION
@@ -1 +1 @@
-7.0.0 \ No newline at end of file
+7.1.0
diff --git a/app/assets/javascripts/diffs/components/diff_discussions.vue b/app/assets/javascripts/diffs/components/diff_discussions.vue
index e19207bdc95..b9de487a737 100644
--- a/app/assets/javascripts/diffs/components/diff_discussions.vue
+++ b/app/assets/javascripts/diffs/components/diff_discussions.vue
@@ -76,7 +76,6 @@ export default {
<noteable-discussion
v-show="isExpanded(discussion)"
:discussion="discussion"
- :render-header="false"
:render-diff-file="false"
:always-expanded="true"
:discussions-by-diff-order="true"
diff --git a/app/assets/javascripts/diffs/components/diff_line_gutter_content.vue b/app/assets/javascripts/diffs/components/diff_line_gutter_content.vue
index e31a3546b69..f4a9be19496 100644
--- a/app/assets/javascripts/diffs/components/diff_line_gutter_content.vue
+++ b/app/assets/javascripts/diffs/components/diff_line_gutter_content.vue
@@ -55,6 +55,11 @@ export default {
required: false,
default: false,
},
+ isContextLine: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
isHover: {
type: Boolean,
required: false,
@@ -76,6 +81,7 @@ export default {
this.showCommentButton &&
this.isHover &&
!this.isMatchLine &&
+ !this.isContextLine &&
!this.isMetaLine &&
!this.hasDiscussions
);
diff --git a/app/assets/javascripts/diffs/components/diff_table_cell.vue b/app/assets/javascripts/diffs/components/diff_table_cell.vue
index e26aa9c9b00..5d9a0b123fe 100644
--- a/app/assets/javascripts/diffs/components/diff_table_cell.vue
+++ b/app/assets/javascripts/diffs/components/diff_table_cell.vue
@@ -3,6 +3,7 @@ import { mapGetters } from 'vuex';
import DiffLineGutterContent from './diff_line_gutter_content.vue';
import {
MATCH_LINE_TYPE,
+ CONTEXT_LINE_TYPE,
EMPTY_CELL_TYPE,
OLD_LINE_TYPE,
OLD_NO_NEW_LINE_TYPE,
@@ -70,6 +71,9 @@ export default {
isMatchLine() {
return this.line.type === MATCH_LINE_TYPE;
},
+ isContextLine() {
+ return this.line.type === CONTEXT_LINE_TYPE;
+ },
isMetaLine() {
const { type } = this.line;
@@ -84,7 +88,11 @@ export default {
[type]: type,
[LINE_UNFOLD_CLASS_NAME]: this.isMatchLine,
[LINE_HOVER_CLASS_NAME]:
- this.isLoggedIn && this.isHover && !this.isMatchLine && !this.isMetaLine,
+ this.isLoggedIn &&
+ this.isHover &&
+ !this.isMatchLine &&
+ !this.isContextLine &&
+ !this.isMetaLine,
};
},
lineNumber() {
diff --git a/app/assets/javascripts/diffs/components/inline_diff_table_row.vue b/app/assets/javascripts/diffs/components/inline_diff_table_row.vue
index 44c05e4b634..542acd3d930 100644
--- a/app/assets/javascripts/diffs/components/inline_diff_table_row.vue
+++ b/app/assets/javascripts/diffs/components/inline_diff_table_row.vue
@@ -4,6 +4,8 @@ import DiffTableCell from './diff_table_cell.vue';
import {
NEW_LINE_TYPE,
OLD_LINE_TYPE,
+ CONTEXT_LINE_TYPE,
+ CONTEXT_LINE_CLASS_NAME,
PARALLEL_DIFF_VIEW_TYPE,
LINE_POSITION_LEFT,
LINE_POSITION_RIGHT,
@@ -39,9 +41,13 @@ export default {
},
computed: {
...mapGetters('diffs', ['isInlineView']),
+ isContextLine() {
+ return this.line.type === CONTEXT_LINE_TYPE;
+ },
classNameMap() {
return {
[this.line.type]: this.line.type,
+ [CONTEXT_LINE_CLASS_NAME]: this.isContextLine,
[PARALLEL_DIFF_VIEW_TYPE]: this.isParallelView,
};
},
diff --git a/app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue b/app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue
index 3339c56cbb6..3b71c0a1fd4 100644
--- a/app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue
+++ b/app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue
@@ -76,8 +76,9 @@ export default {
:class="className"
class="notes_holder"
>
- <td class="notes_line old"></td>
- <td class="notes_content parallel old">
+ <td
+ class="notes_content parallel old"
+ colspan="2">
<div
v-if="shouldRenderDiscussionsOnLeft"
class="content"
@@ -95,8 +96,9 @@ export default {
line-position="left"
/>
</td>
- <td class="notes_line new"></td>
- <td class="notes_content parallel new">
+ <td
+ class="notes_content parallel new"
+ colspan="2">
<div
v-if="shouldRenderDiscussionsOnRight"
class="content"
diff --git a/app/assets/javascripts/diffs/components/parallel_diff_table_row.vue b/app/assets/javascripts/diffs/components/parallel_diff_table_row.vue
index 39312cddfce..fcc3b3e9117 100644
--- a/app/assets/javascripts/diffs/components/parallel_diff_table_row.vue
+++ b/app/assets/javascripts/diffs/components/parallel_diff_table_row.vue
@@ -5,6 +5,8 @@ import DiffTableCell from './diff_table_cell.vue';
import {
NEW_LINE_TYPE,
OLD_LINE_TYPE,
+ CONTEXT_LINE_TYPE,
+ CONTEXT_LINE_CLASS_NAME,
OLD_NO_NEW_LINE_TYPE,
PARALLEL_DIFF_VIEW_TYPE,
NEW_NO_NEW_LINE_TYPE,
@@ -41,8 +43,12 @@ export default {
};
},
computed: {
+ isContextLine() {
+ return this.line.left && this.line.left.type === CONTEXT_LINE_TYPE;
+ },
classNameMap() {
return {
+ [CONTEXT_LINE_CLASS_NAME]: this.isContextLine,
[PARALLEL_DIFF_VIEW_TYPE]: true,
};
},
diff --git a/app/assets/javascripts/diffs/constants.js b/app/assets/javascripts/diffs/constants.js
index f5f5c0ffc29..78a39baa4cb 100644
--- a/app/assets/javascripts/diffs/constants.js
+++ b/app/assets/javascripts/diffs/constants.js
@@ -3,6 +3,7 @@ export const PARALLEL_DIFF_VIEW_TYPE = 'parallel';
export const MATCH_LINE_TYPE = 'match';
export const OLD_NO_NEW_LINE_TYPE = 'old-nonewline';
export const NEW_NO_NEW_LINE_TYPE = 'new-nonewline';
+export const CONTEXT_LINE_TYPE = 'context';
export const EMPTY_CELL_TYPE = 'empty-cell';
export const COMMENT_FORM_TYPE = 'commentForm';
export const DIFF_NOTE_TYPE = 'DiffNote';
@@ -21,6 +22,7 @@ export const LINE_SIDE_RIGHT = 'right-side';
export const DIFF_VIEW_COOKIE_NAME = 'diff_view';
export const LINE_HOVER_CLASS_NAME = 'is-over';
export const LINE_UNFOLD_CLASS_NAME = 'unfold js-unfold';
+export const CONTEXT_LINE_CLASS_NAME = 'diff-expanded';
export const UNFOLD_COUNT = 20;
export const COUNT_OF_AVATARS_IN_GUTTER = 3;
diff --git a/app/assets/javascripts/diffs/store/mutations.js b/app/assets/javascripts/diffs/store/mutations.js
index e651c197968..a7eea2c1449 100644
--- a/app/assets/javascripts/diffs/store/mutations.js
+++ b/app/assets/javascripts/diffs/store/mutations.js
@@ -65,13 +65,7 @@ export default {
const { highlightedDiffLines, parallelDiffLines } = diffFile;
removeMatchLine(diffFile, lineNumbers, bottom);
-
- const lines = addLineReferences(contextLines, lineNumbers, bottom).map(line => ({
- ...line,
- lineCode: line.lineCode || `${fileHash}_${line.oldLine}_${line.newLine}`,
- discussions: line.discussions || [],
- }));
-
+ const lines = addLineReferences(contextLines, lineNumbers, bottom);
addContextLines({
inlineLines: highlightedDiffLines,
parallelLines: parallelDiffLines,
diff --git a/app/assets/javascripts/environments/components/environment_external_url.vue b/app/assets/javascripts/environments/components/environment_external_url.vue
index 7446196de13..1e8a892c0b8 100644
--- a/app/assets/javascripts/environments/components/environment_external_url.vue
+++ b/app/assets/javascripts/environments/components/environment_external_url.vue
@@ -1,7 +1,7 @@
<script>
+import { GlTooltipDirective } from '@gitlab-org/gitlab-ui';
import Icon from '~/vue_shared/components/icon.vue';
-import tooltip from '../../vue_shared/directives/tooltip';
-import { s__ } from '../../locale';
+import { s__ } from '~/locale';
/**
* Renders the external url link in environments table.
@@ -11,7 +11,7 @@ export default {
Icon,
},
directives: {
- tooltip,
+ GlTooltip: GlTooltipDirective,
},
props: {
externalUrl: {
@@ -28,12 +28,11 @@ export default {
</script>
<template>
<a
- v-tooltip
+ v-gl-tooltip
:title="title"
:aria-label="title"
:href="externalUrl"
class="btn external-url"
- data-container="body"
target="_blank"
rel="noopener noreferrer nofollow"
>
diff --git a/app/assets/javascripts/environments/components/environment_item.vue b/app/assets/javascripts/environments/components/environment_item.vue
index 41f59447905..50b0e9747ee 100644
--- a/app/assets/javascripts/environments/components/environment_item.vue
+++ b/app/assets/javascripts/environments/components/environment_item.vue
@@ -1,7 +1,7 @@
<script>
import Timeago from 'timeago.js';
import _ from 'underscore';
-import tooltip from '~/vue_shared/directives/tooltip';
+import { GlTooltipDirective } from '@gitlab-org/gitlab-ui';
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
import { humanize } from '~/lib/utils/text_utility';
import Icon from '~/vue_shared/components/icon.vue';
@@ -36,7 +36,7 @@ export default {
},
directives: {
- tooltip,
+ GlTooltip: GlTooltipDirective,
},
props: {
@@ -455,7 +455,7 @@ export default {
class="gl-responsive-table-row"
role="row">
<div
- v-tooltip
+ v-gl-tooltip
:title="model.name"
class="table-section section-wrap section-15 text-truncate"
role="gridcell"
diff --git a/app/assets/javascripts/environments/components/environment_monitoring.vue b/app/assets/javascripts/environments/components/environment_monitoring.vue
index 26bec125445..7c723fa8979 100644
--- a/app/assets/javascripts/environments/components/environment_monitoring.vue
+++ b/app/assets/javascripts/environments/components/environment_monitoring.vue
@@ -2,9 +2,8 @@
/**
* Renders the Monitoring (Metrics) link in environments table.
*/
-import { GlButton } from '@gitlab-org/gitlab-ui';
+import { GlButton, GlTooltipDirective } from '@gitlab-org/gitlab-ui';
import Icon from '~/vue_shared/components/icon.vue';
-import tooltip from '../../vue_shared/directives/tooltip';
export default {
components: {
@@ -12,7 +11,7 @@ export default {
GlButton,
},
directives: {
- tooltip,
+ GlTooltip: GlTooltipDirective,
},
props: {
monitoringUrl: {
@@ -29,12 +28,11 @@ export default {
</script>
<template>
<gl-button
- v-tooltip
+ v-gl-tooltip
:href="monitoringUrl"
:title="title"
:aria-label="title"
class="monitoring-url d-none d-sm-none d-md-block"
- data-container="body"
rel="noopener noreferrer nofollow"
variant="default"
>
diff --git a/app/assets/javascripts/environments/components/environment_rollback.vue b/app/assets/javascripts/environments/components/environment_rollback.vue
index 69856abc2d5..298469e6482 100644
--- a/app/assets/javascripts/environments/components/environment_rollback.vue
+++ b/app/assets/javascripts/environments/components/environment_rollback.vue
@@ -6,21 +6,18 @@
* Makes a post request when the button is clicked.
*/
import { s__ } from '~/locale';
+import { GlTooltipDirective, GlLoadingIcon } from '@gitlab-org/gitlab-ui';
import Icon from '~/vue_shared/components/icon.vue';
-import tooltip from '~/vue_shared/directives/tooltip';
import eventHub from '../event_hub';
-import { GlLoadingIcon } from '@gitlab-org/gitlab-ui';
export default {
components: {
Icon,
GlLoadingIcon,
},
-
directives: {
- tooltip,
+ GlTooltip: GlTooltipDirective,
},
-
props: {
retryUrl: {
type: String,
@@ -57,21 +54,21 @@ export default {
</script>
<template>
<button
- v-tooltip
+ v-gl-tooltip
:disabled="isLoading"
:title="title"
type="button"
class="btn d-none d-sm-none d-md-block"
@click="onClick"
>
-
<icon
v-if="isLastDeployment"
- name="repeat" />
+ name="repeat"
+ />
<icon
v-else
- name="redo"/>
-
+ name="redo"
+ />
<gl-loading-icon v-if="isLoading" />
</button>
</template>
diff --git a/app/assets/javascripts/environments/components/environment_stop.vue b/app/assets/javascripts/environments/components/environment_stop.vue
index a814b3405f5..327c96a93e9 100644
--- a/app/assets/javascripts/environments/components/environment_stop.vue
+++ b/app/assets/javascripts/environments/components/environment_stop.vue
@@ -5,49 +5,42 @@
*/
import $ from 'jquery';
+import { GlTooltipDirective } from '@gitlab-org/gitlab-ui';
import Icon from '~/vue_shared/components/icon.vue';
import { s__ } from '~/locale';
import eventHub from '../event_hub';
import LoadingButton from '../../vue_shared/components/loading_button.vue';
-import tooltip from '../../vue_shared/directives/tooltip';
export default {
components: {
Icon,
LoadingButton,
},
-
directives: {
- tooltip,
+ GlTooltip: GlTooltipDirective,
},
-
props: {
environment: {
type: Object,
required: true,
},
},
-
data() {
return {
isLoading: false,
};
},
-
computed: {
title() {
return s__('Environments|Stop environment');
},
},
-
mounted() {
eventHub.$on('stopEnvironment', this.onStopEnvironment);
},
-
beforeDestroy() {
eventHub.$off('stopEnvironment', this.onStopEnvironment);
},
-
methods: {
onClick() {
$(this.$el).tooltip('dispose');
@@ -63,12 +56,11 @@ export default {
</script>
<template>
<loading-button
- v-tooltip
+ v-gl-tooltip
:loading="isLoading"
:title="title"
:aria-label="title"
container-class="btn btn-danger d-none d-sm-none d-md-block"
- data-container="body"
data-toggle="modal"
data-target="#stop-environment-modal"
@click="onClick"
diff --git a/app/assets/javascripts/environments/components/environment_terminal_button.vue b/app/assets/javascripts/environments/components/environment_terminal_button.vue
index 350417e5ad0..b8b909f350c 100644
--- a/app/assets/javascripts/environments/components/environment_terminal_button.vue
+++ b/app/assets/javascripts/environments/components/environment_terminal_button.vue
@@ -3,15 +3,15 @@
* Renders a terminal button to open a web terminal.
* Used in environments table.
*/
+import { GlTooltipDirective } from '@gitlab-org/gitlab-ui';
import Icon from '~/vue_shared/components/icon.vue';
-import tooltip from '../../vue_shared/directives/tooltip';
export default {
components: {
Icon,
},
directives: {
- tooltip,
+ GlTooltip: GlTooltipDirective,
},
props: {
terminalPath: {
@@ -29,12 +29,11 @@ export default {
</script>
<template>
<a
- v-tooltip
+ v-gl-tooltip
:title="title"
:aria-label="title"
:href="terminalPath"
class="btn terminal-button d-none d-sm-none d-md-block"
- data-container="body"
>
<icon name="terminal" />
</a>
diff --git a/app/assets/javascripts/environments/components/stop_environment_modal.vue b/app/assets/javascripts/environments/components/stop_environment_modal.vue
index 657cc8cd1aa..6397f6caf1b 100644
--- a/app/assets/javascripts/environments/components/stop_environment_modal.vue
+++ b/app/assets/javascripts/environments/components/stop_environment_modal.vue
@@ -1,7 +1,7 @@
<script>
+import { GlTooltipDirective } from '@gitlab-org/gitlab-ui';
import GlModal from '~/vue_shared/components/gl_modal.vue';
import { s__, sprintf } from '~/locale';
-import tooltip from '~/vue_shared/directives/tooltip';
import LoadingButton from '~/vue_shared/components/loading_button.vue';
import eventHub from '../event_hub';
@@ -15,7 +15,7 @@ export default {
},
directives: {
- tooltip,
+ GlTooltip: GlTooltipDirective,
},
props: {
@@ -67,7 +67,7 @@ export default {
>
Stopping
<span
- v-tooltip
+ v-gl-tooltip
:title="environment.name"
class="text-truncate ml-1 mr-1 flex-fill"
>{{ environment.name }}</span>
diff --git a/app/assets/javascripts/jobs/components/job_app.vue b/app/assets/javascripts/jobs/components/job_app.vue
index 35104c80694..90216b04e92 100644
--- a/app/assets/javascripts/jobs/components/job_app.vue
+++ b/app/assets/javascripts/jobs/components/job_app.vue
@@ -240,16 +240,16 @@ export default {
:erased-at="job.erased_at"
/>
- <div
+ <div
v-if="job.archived"
ref="sticky"
class="js-archived-job prepend-top-default archived-sticky sticky-top"
>
- <icon
+ <icon
name="lock"
class="align-text-bottom"
/>
-
+
{{ __('This job is archived. Only the complete pipeline can be retried.') }}
</div>
<!--job log -->
diff --git a/app/assets/javascripts/notes/components/comment_form.vue b/app/assets/javascripts/notes/components/comment_form.vue
index 554db102027..754c6e79ee4 100644
--- a/app/assets/javascripts/notes/components/comment_form.vue
+++ b/app/assets/javascripts/notes/components/comment_form.vue
@@ -321,10 +321,10 @@ Please check your network connection and try again.`;
v-else-if="!canCreateNote"
:issuable-type="issuableTypeTitle"
/>
- <ul
+ <div
v-else-if="canCreateNote"
class="notes notes-form timeline">
- <li class="timeline-entry">
+ <div class="timeline-entry note-form">
<div class="timeline-entry-inner">
<div class="flash-container error-alert timeline-content"></div>
<div class="timeline-icon d-none d-sm-none d-md-block">
@@ -462,7 +462,7 @@ append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown"
</form>
</div>
</div>
- </li>
- </ul>
+ </div>
+ </div>
</div>
</template>
diff --git a/app/assets/javascripts/notes/components/discussion_counter.vue b/app/assets/javascripts/notes/components/discussion_counter.vue
index 1f80f24e045..a4d76a70696 100644
--- a/app/assets/javascripts/notes/components/discussion_counter.vue
+++ b/app/assets/javascripts/notes/components/discussion_counter.vue
@@ -1,9 +1,6 @@
<script>
import { mapActions, mapGetters } from 'vuex';
-import resolveSvg from 'icons/_icon_resolve_discussion.svg';
-import resolvedSvg from 'icons/_icon_status_success_solid.svg';
-import mrIssueSvg from 'icons/_icon_mr_issue.svg';
-import nextDiscussionSvg from 'icons/_next_discussion.svg';
+import Icon from '~/vue_shared/components/icon.vue';
import { pluralize } from '../../lib/utils/text_utility';
import discussionNavigation from '../mixins/discussion_navigation';
import tooltip from '../../vue_shared/directives/tooltip';
@@ -12,6 +9,9 @@ export default {
directives: {
tooltip,
},
+ components: {
+ Icon,
+ },
mixins: [discussionNavigation],
computed: {
...mapGetters([
@@ -37,12 +37,6 @@ export default {
return this.getNoteableData.create_issue_to_resolve_discussions_path;
},
},
- created() {
- this.resolveSvg = resolveSvg;
- this.resolvedSvg = resolvedSvg;
- this.mrIssueSvg = mrIssueSvg;
- this.nextDiscussionSvg = nextDiscussionSvg;
- },
methods: {
...mapActions(['expandDiscussion']),
jumpToFirstUnresolvedDiscussion() {
@@ -66,15 +60,9 @@ export default {
<span
:class="{ 'is-active': allResolved }"
class="line-resolve-btn is-disabled"
- type="button">
- <span
- v-if="allResolved"
- v-html="resolvedSvg"
- ></span>
- <span
- v-else
- v-html="resolveSvg"
- ></span>
+ type="button"
+ >
+ <icon name="check-circle" />
</span>
<span class="line-resolve-text">
{{ resolvedDiscussionCount }}/{{ discussionCount }} {{ countText }} resolved
@@ -90,7 +78,7 @@ export default {
:title="s__('Resolve all discussions in new issue')"
data-container="body"
class="new-issue-for-discussion btn btn-default discussion-create-issue-btn">
- <span v-html="mrIssueSvg"></span>
+ <icon name="issue-new" />
</a>
</div>
<div
@@ -103,7 +91,7 @@ export default {
data-container="body"
class="btn btn-default discussion-next-btn"
@click="jumpToFirstUnresolvedDiscussion">
- <span v-html="nextDiscussionSvg"></span>
+ <icon name="comment-next" />
</button>
</div>
</div>
diff --git a/app/assets/javascripts/notes/components/note_actions.vue b/app/assets/javascripts/notes/components/note_actions.vue
index 01cbe40f444..f7a61fbfcd4 100644
--- a/app/assets/javascripts/notes/components/note_actions.vue
+++ b/app/assets/javascripts/notes/components/note_actions.vue
@@ -1,12 +1,5 @@
<script>
import { mapGetters } from 'vuex';
-import emojiSmiling from 'icons/_emoji_slightly_smiling_face.svg';
-import emojiSmile from 'icons/_emoji_smile.svg';
-import emojiSmiley from 'icons/_emoji_smiley.svg';
-import editSvg from 'icons/_icon_pencil.svg';
-import resolveDiscussionSvg from 'icons/_icon_resolve_discussion.svg';
-import resolvedDiscussionSvg from 'icons/_icon_status_success_solid.svg';
-import ellipsisSvg from 'icons/_ellipsis_v.svg';
import Icon from '~/vue_shared/components/icon.vue';
import tooltip from '~/vue_shared/directives/tooltip';
import { GlLoadingIcon } from '@gitlab-org/gitlab-ui';
@@ -110,15 +103,6 @@ export default {
return title;
},
},
- created() {
- this.emojiSmiling = emojiSmiling;
- this.emojiSmile = emojiSmile;
- this.emojiSmiley = emojiSmiley;
- this.editSvg = editSvg;
- this.ellipsisSvg = ellipsisSvg;
- this.resolveDiscussionSvg = resolveDiscussionSvg;
- this.resolvedDiscussionSvg = resolvedDiscussionSvg;
- },
methods: {
onEdit() {
this.$emit('handleEdit');
@@ -152,12 +136,7 @@ export default {
class="line-resolve-btn note-action-button"
@click="onResolve">
<template v-if="!isResolving">
- <div
- v-if="isResolved"
- v-html="resolvedDiscussionSvg"></div>
- <div
- v-else
- v-html="resolveDiscussionSvg"></div>
+ <icon name="check-circle" />
</template>
<gl-loading-icon
v-else
@@ -179,18 +158,18 @@ export default {
title="Add reaction"
>
<gl-loading-icon inline/>
- <span
- class="link-highlight award-control-icon-neutral"
- v-html="emojiSmiling">
- </span>
- <span
- class="link-highlight award-control-icon-positive"
- v-html="emojiSmiley">
- </span>
- <span
- class="link-highlight award-control-icon-super-positive"
- v-html="emojiSmile">
- </span>
+ <icon
+ css-classes="link-highlight award-control-icon-neutral"
+ name="emoji_slightly_smiling_face"
+ />
+ <icon
+ css-classes="link-highlight award-control-icon-positive"
+ name="emoji_smiley"
+ />
+ <icon
+ css-classes="link-highlight award-control-icon-super-positive"
+ name="emoji_smiley"
+ />
</a>
</div>
<div
@@ -204,10 +183,10 @@ export default {
data-container="body"
data-placement="bottom"
@click="onEdit">
- <span
- class="link-highlight"
- v-html="editSvg">
- </span>
+ <icon
+ name="pencil"
+ css-classes="link-highlight"
+ />
</button>
</div>
<div
@@ -240,10 +219,10 @@ export default {
data-toggle="dropdown"
data-container="body"
data-placement="bottom">
- <span
- class="icon"
- v-html="ellipsisSvg">
- </span>
+ <icon
+ css-classes="icon"
+ name="ellipsis_v"
+ />
</button>
<ul class="dropdown-menu more-actions-dropdown dropdown-open-left">
<li v-if="canReportAsAbuse">
diff --git a/app/assets/javascripts/notes/components/note_awards_list.vue b/app/assets/javascripts/notes/components/note_awards_list.vue
index df7ab4502a6..401bcfabbe4 100644
--- a/app/assets/javascripts/notes/components/note_awards_list.vue
+++ b/app/assets/javascripts/notes/components/note_awards_list.vue
@@ -1,13 +1,14 @@
<script>
import { mapActions, mapGetters } from 'vuex';
-import emojiSmiling from 'icons/_emoji_slightly_smiling_face.svg';
-import emojiSmile from 'icons/_emoji_smile.svg';
-import emojiSmiley from 'icons/_emoji_smiley.svg';
+import Icon from '~/vue_shared/components/icon.vue';
import Flash from '../../flash';
import { glEmojiTag } from '../../emoji';
import tooltip from '../../vue_shared/directives/tooltip';
export default {
+ components: {
+ Icon,
+ },
directives: {
tooltip,
},
@@ -72,11 +73,6 @@ export default {
return this.noteAuthorId === this.getUserData.id;
},
},
- created() {
- this.emojiSmiling = emojiSmiling;
- this.emojiSmile = emojiSmile;
- this.emojiSmiley = emojiSmiley;
- },
methods: {
...mapActions(['toggleAwardRequest']),
getAwardHTML(name) {
@@ -196,17 +192,14 @@ export default {
data-boundary="viewport"
data-placement="bottom"
type="button">
- <span
- class="award-control-icon award-control-icon-neutral"
- v-html="emojiSmiling">
+ <span class="award-control-icon award-control-icon-neutral">
+ <icon name="emoji_slightly_smiling_face" />
</span>
- <span
- class="award-control-icon award-control-icon-positive"
- v-html="emojiSmiley">
+ <span class="award-control-icon award-control-icon-positive">
+ <icon name="emoji_smiley" />
</span>
- <span
- class="award-control-icon award-control-icon-super-positive"
- v-html="emojiSmile">
+ <span class="award-control-icon award-control-icon-super-positive">
+ <icon name="emoji_smiley" />
</span>
<i
aria-hidden="true"
diff --git a/app/assets/javascripts/notes/components/note_header.vue b/app/assets/javascripts/notes/components/note_header.vue
index 7b6e7b72caf..dd7313d7b10 100644
--- a/app/assets/javascripts/notes/components/note_header.vue
+++ b/app/assets/javascripts/notes/components/note_header.vue
@@ -45,6 +45,9 @@ export default {
noteTimestampLink() {
return `#note_${this.noteId}`;
},
+ hasAuthor() {
+ return this.author && Object.keys(this.author).length;
+ },
},
methods: {
...mapActions(['setTargetNoteHash']),
@@ -76,7 +79,7 @@ export default {
</button>
</div>
<a
- v-if="Object.keys(author).length"
+ v-if="hasAuthor"
:href="author.path"
>
<span class="note-header-author-name">{{ author.name }}</span>
@@ -92,9 +95,6 @@ export default {
</span>
<span class="note-headline-light">
<span class="note-headline-meta">
- <template v-if="actionText">
- {{ actionText }}
- </template>
<span class="system-note-message">
<slot></slot>
</span>
@@ -102,7 +102,9 @@ export default {
v-if="createdAt"
>
<span class="system-note-separator">
- &middot;
+ <template v-if="actionText">
+ {{ actionText }}
+ </template>
</span>
<a
:href="noteTimestampLink"
diff --git a/app/assets/javascripts/notes/components/noteable_discussion.vue b/app/assets/javascripts/notes/components/noteable_discussion.vue
index 07115ca07c4..a153edd0476 100644
--- a/app/assets/javascripts/notes/components/noteable_discussion.vue
+++ b/app/assets/javascripts/notes/components/noteable_discussion.vue
@@ -1,16 +1,16 @@
<script>
import { mapActions, mapGetters } from 'vuex';
-import resolveDiscussionsSvg from 'icons/_icon_mr_issue.svg';
-import nextDiscussionsSvg from 'icons/_next_discussion.svg';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { truncateSha } from '~/lib/utils/text_utility';
-import systemNote from '~/vue_shared/components/notes/system_note.vue';
import { s__ } from '~/locale';
+import systemNote from '~/vue_shared/components/notes/system_note.vue';
+import icon from '~/vue_shared/components/icon.vue';
import Flash from '../../flash';
import { SYSTEM_NOTE } from '../constants';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import noteableNote from './noteable_note.vue';
import noteHeader from './note_header.vue';
+import toggleRepliesWidget from './toggle_replies_widget.vue';
import noteSignedOutWidget from './note_signed_out_widget.vue';
import noteEditedText from './note_edited_text.vue';
import noteForm from './note_form.vue';
@@ -26,6 +26,7 @@ import tooltip from '../../vue_shared/directives/tooltip';
export default {
name: 'NoteableDiscussion',
components: {
+ icon,
noteableNote,
diffWithNote,
userAvatarLink,
@@ -33,6 +34,7 @@ export default {
noteSignedOutWidget,
noteEditedText,
noteForm,
+ toggleRepliesWidget,
placeholderNote,
placeholderSystemNote,
systemNote,
@@ -46,11 +48,6 @@ export default {
type: Object,
required: true,
},
- renderHeader: {
- type: Boolean,
- required: false,
- default: true,
- },
renderDiffFile: {
type: Boolean,
required: false,
@@ -72,6 +69,7 @@ export default {
isReplying: false,
isResolving: false,
resolveAsThread: true,
+ isRepliesCollapsed: (!this.discussion.diff_discussion && this.discussion.resolved) || false,
};
},
computed: {
@@ -112,6 +110,15 @@ export default {
newNotePath() {
return this.getNoteableData.create_note_path;
},
+ hasReplies() {
+ return this.discussion.notes.length > 1;
+ },
+ initialDiscussion() {
+ return this.discussion.notes.slice(0, 1)[0];
+ },
+ replies() {
+ return this.discussion.notes.slice(1);
+ },
lastUpdatedBy() {
const { notes } = this.discussion;
@@ -147,6 +154,12 @@ export default {
return diffDiscussion && diffFile && this.renderDiffFile;
},
+ shouldGroupReplies() {
+ return !this.shouldRenderDiffs && !this.transformedDiscussion.diffDiscussion;
+ },
+ shouldRenderHeader() {
+ return this.shouldRenderDiffs;
+ },
wrapperComponent() {
return this.shouldRenderDiffs ? diffWithNote : 'div';
},
@@ -160,6 +173,22 @@ export default {
wrapperClass() {
return this.isDiffDiscussion ? '' : 'card discussion-wrapper';
},
+ componentClassName() {
+ if (this.shouldRenderDiffs) {
+ if (!this.lastUpdatedAt && !this.discussion.resolved) {
+ return 'unresolved';
+ }
+ }
+
+ return '';
+ },
+ shouldShowDiscussions() {
+ const isExpanded = this.discussion.expanded;
+ const { diffDiscussion, resolved } = this.transformedDiscussion;
+ const isResolvedNonDiffDiscussion = !diffDiscussion && resolved;
+
+ return isExpanded || this.alwaysExpanded || isResolvedNonDiffDiscussion;
+ },
},
watch: {
isReplying() {
@@ -173,10 +202,6 @@ export default {
}
},
},
- created() {
- this.resolveDiscussionsSvg = resolveDiscussionsSvg;
- this.nextDiscussionsSvg = nextDiscussionsSvg;
- },
methods: {
...mapActions([
'saveNote',
@@ -207,6 +232,9 @@ export default {
toggleDiscussionHandler() {
this.toggleDiscussion({ discussionId: this.discussion.id });
},
+ toggleReplies() {
+ this.isRepliesCollapsed = !this.isRepliesCollapsed;
+ },
showReplyForm() {
this.isReplying = true;
},
@@ -274,26 +302,29 @@ Please check your network connection and try again.`;
</script>
<template>
- <li class="note note-discussion timeline-entry">
+ <li
+ class="note note-discussion timeline-entry"
+ :class="componentClassName"
+ >
<div class="timeline-entry-inner">
- <div class="timeline-icon">
- <user-avatar-link
- v-if="author"
- :link-href="author.path"
- :img-src="author.avatar_url"
- :img-alt="author.name"
- :img-size="40"
- />
- </div>
<div class="timeline-content">
<div
:data-discussion-id="transformedDiscussion.discussion_id"
class="discussion js-discussion-container"
>
<div
- v-if="renderHeader"
- class="discussion-header"
+ v-if="shouldRenderHeader"
+ class="discussion-header note-wrapper"
>
+ <div class="timeline-icon">
+ <user-avatar-link
+ v-if="author"
+ :link-href="author.path"
+ :img-src="author.avatar_url"
+ :img-alt="author.name"
+ :img-size="40"
+ />
+ </div>
<note-header
:author="author"
:created-at="transformedDiscussion.created_at"
@@ -339,7 +370,7 @@ Please check your network connection and try again.`;
/>
</div>
<div
- v-if="discussion.expanded || alwaysExpanded"
+ v-if="shouldShowDiscussions"
class="discussion-body">
<component
:is="wrapperComponent"
@@ -348,45 +379,70 @@ Please check your network connection and try again.`;
>
<div class="discussion-notes">
<ul class="notes">
- <component
- :is="componentName(note)"
- v-for="(note, index) in discussion.notes"
- :key="note.id"
- :note="componentData(note)"
- @handleDeleteNote="deleteNoteHandler"
- >
- <slot
- v-if="index === 0"
- slot="avatar-badge"
- name="avatar-badge"
+ <template v-if="shouldGroupReplies">
+ <component
+ :is="componentName(initialDiscussion)"
+ :note="componentData(initialDiscussion)"
+ @handleDeleteNote="deleteNoteHandler"
>
- </slot>
- </component>
+ <slot
+ slot="avatar-badge"
+ name="avatar-badge"
+ >
+ </slot>
+ </component>
+ <toggle-replies-widget
+ v-if="hasReplies"
+ :collapsed="isRepliesCollapsed"
+ :replies="replies"
+ @toggle="toggleReplies"
+ />
+ <template v-if="!isRepliesCollapsed">
+ <component
+ :is="componentName(note)"
+ v-for="note in replies"
+ :key="note.id"
+ :note="componentData(note)"
+ @handleDeleteNote="deleteNoteHandler"
+ />
+ </template>
+ </template>
+ <template v-else>
+ <component
+ :is="componentName(note)"
+ v-for="(note, index) in discussion.notes"
+ :key="note.id"
+ :note="componentData(note)"
+ @handleDeleteNote="deleteNoteHandler"
+ >
+ <slot
+ v-if="index === 0"
+ slot="avatar-badge"
+ name="avatar-badge"
+ >
+ </slot>
+ </component>
+ </template>
</ul>
<div
+ v-if="!isRepliesCollapsed"
:class="{ 'is-replying': isReplying }"
class="discussion-reply-holder"
>
<template v-if="!isReplying && canReply">
- <div
- class="btn-group d-flex discussion-with-resolve-btn"
- role="group">
- <div
- class="btn-group w-100"
- role="group">
- <button
- type="button"
- class="js-vue-discussion-reply btn btn-text-field mr-2 qa-discussion-reply"
- title="Add a reply"
- @click="showReplyForm">Reply...</button>
- </div>
- <div
- v-if="discussion.resolvable"
- class="btn-group"
- role="group">
+ <div class="discussion-with-resolve-btn">
+ <button
+ type="button"
+ class="js-vue-discussion-reply btn btn-text-field mr-2 qa-discussion-reply"
+ title="Add a reply"
+ @click="showReplyForm"
+ >
+ Reply...
+ </button>
+ <div v-if="discussion.resolvable">
<button
type="button"
- class="btn btn-default"
+ class="btn btn-default mx-sm-2"
@click="resolveHandler()"
>
<i
@@ -414,7 +470,7 @@ Please check your network connection and try again.`;
btn-default discussion-create-issue-btn"
data-container="body"
>
- <span v-html="resolveDiscussionsSvg"></span>
+ <icon name="issue-new" />
</a>
</div>
<div
@@ -428,7 +484,7 @@ Please check your network connection and try again.`;
data-container="body"
@click="jumpToNextDiscussion"
>
- <span v-html="nextDiscussionsSvg"></span>
+ <icon name="comment-next" />
</button>
</div>
</div>
diff --git a/app/assets/javascripts/notes/components/noteable_note.vue b/app/assets/javascripts/notes/components/noteable_note.vue
index 40222ac4a80..e302a89ee95 100644
--- a/app/assets/javascripts/notes/components/noteable_note.vue
+++ b/app/assets/javascripts/notes/components/noteable_note.vue
@@ -173,7 +173,7 @@ export default {
:class="classNameBindings"
:data-award-url="note.toggle_award_path"
:data-note-id="note.id"
- class="note timeline-entry"
+ class="note timeline-entry note-wrapper"
>
<div class="timeline-entry-inner">
<div class="timeline-icon">
@@ -196,6 +196,7 @@ export default {
:author="author"
:created-at="note.created_at"
:note-id="note.id"
+ action-text="commented"
/>
<note-actions
:author-id="author.id"
diff --git a/app/assets/javascripts/notes/components/notes_app.vue b/app/assets/javascripts/notes/components/notes_app.vue
index ed5ac112dc0..e555279a6ac 100644
--- a/app/assets/javascripts/notes/components/notes_app.vue
+++ b/app/assets/javascripts/notes/components/notes_app.vue
@@ -50,6 +50,7 @@ export default {
},
data() {
return {
+ isFetching: false,
currentFilter: null,
};
},
@@ -141,6 +142,10 @@ export default {
return discussion.individual_note ? { note: discussion.notes[0] } : { discussion };
},
fetchNotes() {
+ if (this.isFetching) return null;
+
+ this.isFetching = true;
+
return this.fetchDiscussions({ path: this.getNotesDataByProp('discussionsPath') })
.then(() => {
this.initPolling();
@@ -149,6 +154,7 @@ export default {
this.setLoadingState(false);
this.setNotesFetchedState(true);
eventHub.$emit('fetchedNotesData');
+ this.isFetching = false;
})
.then(() => this.$nextTick())
.then(() => this.checkLocationHash())
diff --git a/app/assets/javascripts/notes/components/toggle_replies_widget.vue b/app/assets/javascripts/notes/components/toggle_replies_widget.vue
new file mode 100644
index 00000000000..78ecbbb9247
--- /dev/null
+++ b/app/assets/javascripts/notes/components/toggle_replies_widget.vue
@@ -0,0 +1,94 @@
+<script>
+import _ from 'underscore';
+import Icon from '~/vue_shared/components/icon.vue';
+import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
+import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+
+export default {
+ components: {
+ Icon,
+ UserAvatarLink,
+ TimeAgoTooltip,
+ },
+ props: {
+ collapsed: {
+ type: Boolean,
+ required: true,
+ },
+ replies: {
+ type: Array,
+ required: true,
+ },
+ },
+ computed: {
+ lastReply() {
+ return this.replies[this.replies.length - 1];
+ },
+ uniqueAuthors() {
+ const authors = this.replies.map(reply => reply.author || {});
+
+ return _.uniq(authors, author => author.username);
+ },
+ className() {
+ return this.collapsed ? 'collapsed' : 'expanded';
+ },
+ },
+ methods: {
+ toggle() {
+ this.$emit('toggle');
+ },
+ },
+};
+</script>
+
+<template>
+ <li
+ :class="className"
+ class="replies-toggle"
+ >
+ <template v-if="collapsed">
+ <icon
+ name="chevron-right"
+ @click.native="toggle"
+ />
+ <div>
+ <user-avatar-link
+ v-for="author in uniqueAuthors"
+ :key="author.username"
+ :link-href="author.path"
+ :img-alt="author.name"
+ :img-src="author.avatar_url"
+ :img-size="26"
+ :tooltip-text="author.name"
+ tooltip-placement="bottom"
+ />
+ </div>
+ <button
+ class="btn btn-link js-replies-text"
+ type="button"
+ @click="toggle"
+ >
+ {{ replies.length }} {{ n__('reply', 'replies', replies.length) }}
+ </button>
+ {{ __('Last reply by') }}
+ <a
+ :href="lastReply.author.path"
+ class="btn btn-link author-link"
+ >
+ {{ lastReply.author.name }}
+ </a>
+ <time-ago-tooltip
+ :time="lastReply.created_at"
+ tooltip-placement="bottom"
+ />
+ </template>
+ <span
+ v-else
+ class="collapse-replies-btn js-collapse-replies"
+ @click="toggle"
+ >
+ <icon name="chevron-down" />
+ {{ s__('Notes|Collapse replies') }}
+ </span>
+ </li>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/changed_file_icon.vue b/app/assets/javascripts/vue_shared/components/changed_file_icon.vue
index 8684005e0fb..766fc211bf5 100644
--- a/app/assets/javascripts/vue_shared/components/changed_file_icon.vue
+++ b/app/assets/javascripts/vue_shared/components/changed_file_icon.vue
@@ -1,5 +1,5 @@
<script>
-import tooltip from '~/vue_shared/directives/tooltip';
+import { GlTooltipDirective } from '@gitlab-org/gitlab-ui';
import Icon from '~/vue_shared/components/icon.vue';
import { pluralize } from '~/lib/utils/text_utility';
import { __, sprintf } from '~/locale';
@@ -10,7 +10,7 @@ export default {
Icon,
},
directives: {
- tooltip,
+ GlTooltip: GlTooltipDirective,
},
props: {
file: {
@@ -79,10 +79,8 @@ export default {
<template>
<span
- v-tooltip
+ v-gl-tooltip.right
:title="tooltipTitle"
- data-container="body"
- data-placement="right"
class="file-changed-icon ml-auto"
>
<icon
diff --git a/app/assets/javascripts/vue_shared/components/ci_badge_link.vue b/app/assets/javascripts/vue_shared/components/ci_badge_link.vue
index c60052fec50..6780254827f 100644
--- a/app/assets/javascripts/vue_shared/components/ci_badge_link.vue
+++ b/app/assets/javascripts/vue_shared/components/ci_badge_link.vue
@@ -1,6 +1,6 @@
<script>
+import { GlTooltipDirective } from '@gitlab-org/gitlab-ui';
import CiIcon from './ci_icon.vue';
-import tooltip from '../directives/tooltip';
/**
* Renders CI Badge link with CI icon and status text based on
* API response shared between all places where it is used.
@@ -27,7 +27,7 @@ export default {
CiIcon,
},
directives: {
- tooltip,
+ GlTooltip: GlTooltipDirective,
},
props: {
status: {
@@ -50,7 +50,7 @@ export default {
</script>
<template>
<a
- v-tooltip
+ v-gl-tooltip
:href="status.details_path"
:class="cssClass"
:title="!showText ? status.text : ''"
diff --git a/app/assets/javascripts/vue_shared/components/commit.vue b/app/assets/javascripts/vue_shared/components/commit.vue
index 151eee75d44..b1139f34e41 100644
--- a/app/assets/javascripts/vue_shared/components/commit.vue
+++ b/app/assets/javascripts/vue_shared/components/commit.vue
@@ -1,11 +1,11 @@
<script>
+import { GlTooltipDirective } from '@gitlab-org/gitlab-ui';
import UserAvatarLink from './user_avatar/user_avatar_link.vue';
-import tooltip from '../directives/tooltip';
import Icon from '../../vue_shared/components/icon.vue';
export default {
directives: {
- tooltip,
+ GlTooltip: GlTooltipDirective,
},
components: {
UserAvatarLink,
@@ -124,11 +124,10 @@ export default {
</div>
<a
- v-tooltip
+ v-gl-tooltip
:href="commitRef.ref_url"
:title="commitRef.name"
class="ref-name"
- data-container="body"
>
{{ commitRef.name }}
</a>
diff --git a/app/assets/javascripts/vue_shared/components/icon.vue b/app/assets/javascripts/vue_shared/components/icon.vue
index cddebfae115..a25841fc02f 100644
--- a/app/assets/javascripts/vue_shared/components/icon.vue
+++ b/app/assets/javascripts/vue_shared/components/icon.vue
@@ -1,6 +1,6 @@
<script>
// only allow classes in images.scss e.g. s12
-const validSizes = [8, 10, 12, 16, 18, 24, 32, 48, 72];
+const validSizes = [8, 10, 12, 14, 16, 18, 24, 32, 48, 72];
let iconValidator = () => true;
/*
diff --git a/app/assets/javascripts/vue_shared/components/notes/skeleton_note.vue b/app/assets/javascripts/vue_shared/components/notes/skeleton_note.vue
index f56414c3c63..8cb72afcdc0 100644
--- a/app/assets/javascripts/vue_shared/components/notes/skeleton_note.vue
+++ b/app/assets/javascripts/vue_shared/components/notes/skeleton_note.vue
@@ -10,7 +10,7 @@ export default {
</script>
<template>
- <li class="timeline-entry note">
+ <li class="timeline-entry note note-wrapper">
<div class="timeline-entry-inner">
<div class="timeline-icon">
</div>
diff --git a/app/assets/javascripts/vue_shared/components/notes/system_note.vue b/app/assets/javascripts/vue_shared/components/notes/system_note.vue
index de3c7a80365..6a44e6a29ed 100644
--- a/app/assets/javascripts/vue_shared/components/notes/system_note.vue
+++ b/app/assets/javascripts/vue_shared/components/notes/system_note.vue
@@ -76,7 +76,7 @@ export default {
<li
:id="noteAnchorId"
:class="{ target: isTargetNote }"
- class="note system-note timeline-entry">
+ class="note system-note timeline-entry note-wrapper">
<div class="timeline-entry-inner">
<div
class="timeline-icon"
diff --git a/app/assets/stylesheets/framework/awards.scss b/app/assets/stylesheets/framework/awards.scss
index 702276780e9..7a95db5976d 100644
--- a/app/assets/stylesheets/framework/awards.scss
+++ b/app/assets/stylesheets/framework/awards.scss
@@ -148,10 +148,7 @@
.award-control-icon svg {
background: $award-emoji-positive-add-bg;
-
- path {
- fill: $award-emoji-positive-add-lines;
- }
+ fill: $award-emoji-positive-add-lines;
}
.award-control-icon-neutral {
diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss
index c4296c7a88a..219fd99b097 100644
--- a/app/assets/stylesheets/framework/buttons.scss
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -207,6 +207,10 @@
@include btn-with-margin;
}
+ &.btn-icon {
+ color: $gl-gray-700;
+ }
+
.fa-caret-down,
.fa-chevron-down {
margin-left: 5px;
@@ -218,6 +222,25 @@
}
}
+ &.btn-text-field {
+ width: 100%;
+ text-align: left;
+ padding: 6px 16px;
+ border-color: $border-color;
+ color: $gray-darkest;
+ background-color: $gray-light;
+
+ &:hover,
+ &:active,
+ &:focus {
+ cursor: text;
+ box-shadow: none;
+ border-color: lighten($blue-300, 20%);
+ color: $gray-darkest;
+ background-color: $gray-light;
+ }
+ }
+
&.dot-highlight::after {
content: '';
background-color: $blue-500;
@@ -335,25 +358,6 @@
}
}
-.btn-text-field {
- width: 100%;
- text-align: left;
- padding: 6px 16px;
- border-color: $border-color;
- color: $gray-darkest;
- background-color: $gray-light;
-
- &:hover,
- &:active,
- &:focus {
- cursor: text;
- box-shadow: none;
- border-color: lighten($blue-300, 20%);
- color: $gray-darkest;
- background-color: $gray-light;
- }
-}
-
.btn-build {
margin-left: 10px;
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss
index 53f198b47c6..6bdcb20210b 100644
--- a/app/assets/stylesheets/framework/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
@@ -36,7 +36,6 @@
text-align: left;
padding: 10px $gl-padding;
word-wrap: break-word;
- border-radius: $border-radius-default $border-radius-default 0 0;
&.file-title-clear {
padding-left: 0;
diff --git a/app/assets/stylesheets/framework/timeline.scss b/app/assets/stylesheets/framework/timeline.scss
index dfb145debe7..4a311da1675 100644
--- a/app/assets/stylesheets/framework/timeline.scss
+++ b/app/assets/stylesheets/framework/timeline.scss
@@ -1,7 +1,7 @@
.timeline {
- @include basic-list;
margin: 0;
padding: 0;
+ list-style: none;
&::before {
@include notes-media('max', map-get($grid-breakpoints, sm)) {
@@ -26,10 +26,8 @@
}
.timeline-entry {
- border-color: $white-normal;
color: $gl-text-color;
- border-bottom: 1px solid $border-white-light;
- background: $white-light;
+ background-color: $white-light;
.timeline-entry-inner {
position: relative;
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index 19bc4262e21..6d998fa1e07 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -59,6 +59,7 @@
margin: 0;
padding: 0;
table-layout: fixed;
+ border-radius: 0 0 $border-radius-default $border-radius-default;
.diff-line-num {
width: 50px;
@@ -859,7 +860,7 @@
}
.diff-file .note-container > .new-note,
-.note-container .discussion-notes {
+.note-container .discussion-notes.diff-discussions {
margin-left: 100px;
border-left: 1px solid $white-normal;
}
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index c60bb360a03..855d73a9939 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -239,6 +239,7 @@
.discussion-reply-holder {
background-color: $white-light;
padding: 10px 16px;
+ border-radius: 0 0 $border-radius-default $border-radius-default;
&.is-replying {
padding-bottom: $gl-padding;
@@ -247,10 +248,15 @@
}
.discussion-with-resolve-btn {
+ @include media-breakpoint-up(sm) {
+ display: flex;
+ }
+
+
.discussion-actions {
display: table;
- .btn-default path {
+ svg {
fill: $gray-darkest;
}
@@ -270,6 +276,12 @@
.btn {
width: 100%;
}
+
+ .btn-text-field {
+ @include media-breakpoint-down(xs) {
+ margin-bottom: $gl-padding-8;
+ }
+ }
}
.discussion-notes-count {
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index be535ade0a6..dcb1275d182 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -1,26 +1,135 @@
-/**
- * Notes
- */
+$system-note-icon-size: 32px;
+$system-note-svg-size: 16px;
+$note-form-margin-left: 70px;
-@-webkit-keyframes targe3-note {
- from {
- background: $note-targe3-outside;
+@mixin vertical-line($left) {
+ &::before {
+ content: '';
+ border-left: 2px solid $theme-gray-100;
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: $left;
}
+}
- 50% {
- background: $note-targe3-inside;
- }
+.note-wrapper {
+ padding: $gl-padding;
+}
+
+.issuable-discussion {
+ .notes.timeline > .timeline-entry {
+ border: 1px solid $border-color;
+ border-radius: $border-radius-default;
+ margin: $gl-padding 0;
+
+ &.system-note,
+ &.note-form {
+ border: 0;
+ }
+
+ &.note-form {
+ margin-left: 0;
- to {
- background: $note-targe3-outside;
+ @include notes-media('min', map-get($grid-breakpoints, md)) {
+ margin-left: $note-form-margin-left;
+ }
+
+ .timeline-icon {
+ @include notes-media('min', map-get($grid-breakpoints, sm)) {
+ margin-left: -$note-icon-gutter-width;
+ }
+ }
+
+ .timeline-content {
+ margin-left: 0;
+ }
+ }
+
+ .notes_content {
+ border: 0;
+ border-top: 1px solid $border-color;
+ }
}
}
-ul.notes {
+.main-notes-list {
+ @include vertical-line(39px);
+}
+
+.notes {
display: block;
list-style: none;
margin: 0;
padding: 0;
+ position: relative;
+
+ > .note-discussion {
+ .card {
+ border: 0;
+ }
+
+ li.note {
+ border-bottom: 1px solid $border-color;
+
+ &:first-child {
+ border-radius: $border-radius-default $border-radius-default 0 0;
+ }
+ }
+ }
+
+ .replies-toggle {
+ background-color: $gray-light;
+ padding: $gl-padding-8 $gl-padding;
+
+ .collapse-replies-btn:hover {
+ color: $blue-600;
+ }
+
+ &.expanded {
+ border-bottom: 1px solid $border-color;
+
+ span {
+ cursor: pointer;
+ }
+
+ svg {
+ position: relative;
+ top: 3px;
+ }
+ }
+
+ &.collapsed {
+ color: $gl-text-color-secondary;
+
+ svg {
+ float: left;
+ position: relative;
+ top: $gl-padding-4;
+ margin-right: $gl-padding-8;
+ cursor: pointer;
+ }
+
+ img {
+ margin: -2px 4px 0 0;
+ }
+
+ .author-link {
+ color: $gl-text-color;
+ }
+ }
+
+ .user-avatar-link {
+ &:last-child img {
+ margin-right: $gl-padding-8;
+ }
+ }
+
+ .btn-link {
+ border: 0;
+ vertical-align: baseline;
+ }
+ }
.note-created-ago,
.note-updated-at {
@@ -28,8 +137,6 @@ ul.notes {
}
.discussion-body {
- padding-top: 8px;
-
.card {
margin-bottom: 0;
}
@@ -46,21 +153,10 @@ ul.notes {
}
> li {
- // .timeline-entry
- padding: 0;
display: block;
position: relative;
border-bottom: 0;
- @include notes-media('min', map-get($grid-breakpoints, sm)) {
- padding-left: $note-icon-gutter-width;
- }
-
- .timeline-entry-inner {
- padding: $gl-padding $gl-btn-padding;
- border-bottom: 1px solid $white-normal;
- }
-
&:target,
&.target {
border-bottom: 1px solid $white-normal;
@@ -75,23 +171,10 @@ ul.notes {
}
}
- .timeline-icon {
- @include notes-media('min', map-get($grid-breakpoints, sm)) {
- margin-left: -$note-icon-gutter-width;
- }
- }
-
- .timeline-content {
- margin-left: $note-icon-gutter-width;
-
- @include notes-media('min', map-get($grid-breakpoints, sm)) {
- margin-left: 0;
- }
- }
-
&.being-posted {
pointer-events: none;
opacity: 0.5;
+ padding: $gl-padding;
.dummy-avatar {
background-color: $gl-gray-200;
@@ -104,12 +187,6 @@ ul.notes {
}
}
- &.note-discussion {
- .timeline-entry-inner {
- padding: $gl-padding 10px;
- }
- }
-
.editing-spinner {
display: none;
}
@@ -191,8 +268,9 @@ ul.notes {
}
.system-note {
- font-size: 14px;
- clear: both;
+ padding: 6px $gl-padding-24;
+ margin: $gl-padding-24 0;
+ background-color: transparent;
.note-header-info {
padding-bottom: 0;
@@ -225,17 +303,21 @@ ul.notes {
.timeline-icon {
float: left;
-
- @include notes-media('min', map-get($grid-breakpoints, sm)) {
- margin-left: 0;
- width: auto;
- }
+ display: flex;
+ align-items: center;
+ background-color: $white-light;
+ width: $system-note-icon-size;
+ height: $system-note-icon-size;
+ border: 1px solid $border-color;
+ border-radius: $system-note-icon-size;
+ margin: -6px $gl-padding 0 0;
svg {
- width: 16px;
- height: 16px;
+ width: $system-note-svg-size;
+ height: $system-note-svg-size;
fill: $gray-darkest;
- margin-top: 2px;
+ display: block;
+ margin: 0 auto;
}
}
@@ -302,10 +384,17 @@ ul.notes {
.discussion-body .diff-file {
.file-title {
cursor: default;
+ line-height: 42px;
+ padding: 0 $gl-padding;
+ border-top: 1px solid $border-color;
&:hover {
background-color: $gray-light;
}
+
+ .btn-clipboard {
+ top: 10px;
+ }
}
.line_content {
@@ -320,6 +409,23 @@ ul.notes {
}
}
+ .discussion-notes {
+ &:not(:first-child) {
+ border-top: 1px solid $white-normal;
+ margin-top: 20px;
+ }
+
+ &:not(:last-child) {
+ border-bottom: 1px solid $white-normal;
+ margin-bottom: 20px;
+ }
+
+ .system-note {
+ margin: 0;
+ padding: $gl-padding;
+ }
+ }
+
// Merge request notes in diffs
// Diff is inline
.notes_content .note-header .note-headline-light {
@@ -335,7 +441,6 @@ ul.notes {
border-left: 0;
&.notes_content {
- background-color: $gray-light;
border-width: 1px 0;
padding: 0;
vertical-align: top;
@@ -349,18 +454,6 @@ ul.notes {
}
}
- .discussion-notes {
- &:not(:first-child) {
- border-top: 1px solid $white-normal;
- margin-top: 20px;
- }
-
- &:not(:last-child) {
- border-bottom: 1px solid $white-normal;
- margin-bottom: 20px;
- }
- }
-
.notes {
background-color: $white-light;
}
@@ -374,6 +467,30 @@ ul.notes {
}
}
+.diffs {
+ .discussion-notes {
+ margin-left: 0;
+ border-left: 0;
+
+ .notes {
+ position: relative;
+ @include vertical-line(52px);
+ }
+ }
+
+ .note-wrapper {
+ margin: $gl-padding;
+ border: 1px solid $border-color;
+ border-radius: $border-radius-default;
+ }
+
+ .discussion-reply-holder {
+ border-radius: 0 0 $border-radius-default $border-radius-default;
+ border-top: 1px solid $border-color;
+ position: relative;
+ }
+}
+
.discussion-header,
.note-header-info {
a {
@@ -399,7 +516,17 @@ ul.notes {
}
.discussion-header {
- font-size: 14px;
+ min-height: 72px;
+
+ .note-header-info {
+ padding-bottom: 0;
+ }
+}
+
+.unresolved {
+ .note-header-info {
+ margin-top: $gl-padding-8;
+ }
}
.note-header {
@@ -409,7 +536,7 @@ ul.notes {
.note-header-info {
min-width: 0;
- padding-bottom: 8px;
+ padding-bottom: $gl-padding-8;
&.discussion {
padding-bottom: 0;
@@ -471,9 +598,18 @@ ul.notes {
margin-left: 10px;
color: $gray-darkest;
+ @include media-breakpoint-down(xs) {
+ width: 100%;
+ margin: $gl-padding-8 0;
+ }
+
.btn-group > .discussion-next-btn {
margin-left: -1px;
}
+
+ svg {
+ height: 15px;
+ }
}
.note-actions {
@@ -585,19 +721,6 @@ ul.notes {
z-index: 10;
}
-.discussion-body,
-.diff-file {
- .notes .note {
- border-bottom: 1px solid $white-normal;
-
- .timeline-entry-inner {
- padding-left: $gl-padding;
- padding-right: $gl-padding;
- border-bottom: 0;
- }
- }
-}
-
.disabled-comment {
background-color: $gray-light;
border-radius: $border-radius-base;
@@ -634,7 +757,7 @@ ul.notes {
}
.btn {
- svg path {
+ svg {
fill: $gray-darkest;
}
@@ -659,7 +782,7 @@ ul.notes {
.line-resolve-all {
vertical-align: middle;
display: inline-block;
- padding: 5px 10px 6px;
+ padding: 6px 10px;
background-color: $gray-light;
border: 1px solid $border-color;
border-radius: $border-radius-default;
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index 2a6fe3b9c97..c02ec407262 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -122,7 +122,7 @@ class Projects::BlobController < Projects::ApplicationController
@lines.map! do |line|
# These are marked as context lines but are loaded from blobs.
# We also have context lines loaded from diffs in other places.
- diff_line = Gitlab::Diff::Line.new(line, nil, nil, nil, nil)
+ diff_line = Gitlab::Diff::Line.new(line, 'context', nil, nil, nil)
diff_line.rich_text = line
diff_line
end
diff --git a/app/controllers/projects/merge_requests/diffs_controller.rb b/app/controllers/projects/merge_requests/diffs_controller.rb
index 740f41c0642..5307cd0720a 100644
--- a/app/controllers/projects/merge_requests/diffs_controller.rb
+++ b/app/controllers/projects/merge_requests/diffs_controller.rb
@@ -22,12 +22,6 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic
def render_diffs
@environment = @merge_request.environments_for(current_user).last
- notes_grouped_by_path = @notes.group_by { |note| note.position.file_path }
-
- @diffs.diff_files.each do |diff_file|
- notes = notes_grouped_by_path.fetch(diff_file.file_path, [])
- notes.each { |note| diff_file.unfold_diff_lines(note.position) }
- end
@diffs.write_cache
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index d9713f9c9b0..0a7f930110a 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -540,4 +540,13 @@ module ProjectsHelper
network
]
end
+
+ def sidebar_operations_paths
+ %w[
+ environments
+ clusters
+ user
+ gcp
+ ]
+ end
end
diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb
index c32008aa9c7..5f59e4832db 100644
--- a/app/models/diff_note.rb
+++ b/app/models/diff_note.rb
@@ -66,10 +66,6 @@ class DiffNote < Note
self.original_position.diff_refs == diff_refs
end
- def discussion_first_note?
- self == discussion.first_note
- end
-
private
def enqueue_diff_file_creation_job
@@ -82,33 +78,26 @@ class DiffNote < Note
end
def should_create_diff_file?
- on_text? && note_diff_file.nil? && discussion_first_note?
+ on_text? && note_diff_file.nil? && self == discussion.first_note
end
def fetch_diff_file
- file =
- if note_diff_file
- diff = Gitlab::Git::Diff.new(note_diff_file.to_hash)
- Gitlab::Diff::File.new(diff,
- repository: project.repository,
- diff_refs: original_position.diff_refs)
- elsif created_at_diff?(noteable.diff_refs)
- # We're able to use the already persisted diffs (Postgres) if we're
- # presenting a "current version" of the MR discussion diff.
- # So no need to make an extra Gitaly diff request for it.
- # As an extra benefit, the returned `diff_file` already
- # has `highlighted_diff_lines` data set from Redis on
- # `Diff::FileCollection::MergeRequestDiff`.
- noteable.diffs(original_position.diff_options).diff_files.first
- else
- original_position.diff_file(self.project.repository)
- end
-
- # Since persisted diff files already have its content "unfolded"
- # there's no need to make it pass through the unfolding process.
- file&.unfold_diff_lines(position) unless note_diff_file
-
- file
+ if note_diff_file
+ diff = Gitlab::Git::Diff.new(note_diff_file.to_hash)
+ Gitlab::Diff::File.new(diff,
+ repository: project.repository,
+ diff_refs: original_position.diff_refs)
+ elsif created_at_diff?(noteable.diff_refs)
+ # We're able to use the already persisted diffs (Postgres) if we're
+ # presenting a "current version" of the MR discussion diff.
+ # So no need to make an extra Gitaly diff request for it.
+ # As an extra benefit, the returned `diff_file` already
+ # has `highlighted_diff_lines` data set from Redis on
+ # `Diff::FileCollection::MergeRequestDiff`.
+ noteable.diffs(original_position.diff_options).diff_files.first
+ else
+ original_position.diff_file(self.project.repository)
+ end
end
def supported?
diff --git a/app/models/project_import_state.rb b/app/models/project_import_state.rb
index d59cb43dea4..7126bb66d80 100644
--- a/app/models/project_import_state.rb
+++ b/app/models/project_import_state.rb
@@ -56,4 +56,17 @@ class ProjectImportState < ActiveRecord::Base
end
end
end
+
+ def mark_as_failed(error_message)
+ original_errors = errors.dup
+ sanitized_message = Gitlab::UrlSanitizer.sanitize(error_message)
+
+ fail_op
+
+ update_column(:last_error, sanitized_message)
+ rescue ActiveRecord::ActiveRecordError => e
+ Rails.logger.error("Error setting import status to failed: #{e.message}. Original error: #{sanitized_message}")
+ ensure
+ @errors = original_errors
+ end
end
diff --git a/app/services/merge_requests/reload_diffs_service.rb b/app/services/merge_requests/reload_diffs_service.rb
index c64b2e99b52..b47d8f3f63a 100644
--- a/app/services/merge_requests/reload_diffs_service.rb
+++ b/app/services/merge_requests/reload_diffs_service.rb
@@ -29,6 +29,10 @@ module MergeRequests
# rubocop: disable CodeReuse/ActiveRecord
def clear_cache(new_diff)
+ # Executing the iteration we cache highlighted diffs for each diff file of
+ # MergeRequestDiff.
+ cacheable_collection(new_diff).write_cache
+
# Remove cache for all diffs on this MR. Do not use the association on the
# model, as that will interfere with other actions happening when
# reloading the diff.
diff --git a/app/services/notes/base_service.rb b/app/services/notes/base_service.rb
deleted file mode 100644
index 431ff6c11c4..00000000000
--- a/app/services/notes/base_service.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-# frozen_string_literal: true
-
-module Notes
- class BaseService < ::BaseService
- def clear_noteable_diffs_cache(note)
- noteable = note.noteable
-
- if note.is_a?(DiffNote) &&
- note.discussion_first_note? &&
- note.position.unfolded_diff?(project.repository)
- noteable.diffs.clear_cache
- end
- end
- end
-end
diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb
index e03789e3ca9..049e6c5a871 100644
--- a/app/services/notes/create_service.rb
+++ b/app/services/notes/create_service.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module Notes
- class CreateService < ::Notes::BaseService
+ class CreateService < ::BaseService
def execute
merge_request_diff_head_sha = params.delete(:merge_request_diff_head_sha)
@@ -35,7 +35,6 @@ module Notes
if !only_commands && note.save
todo_service.new_note(note, current_user)
- clear_noteable_diffs_cache(note)
end
if command_params.present?
diff --git a/app/services/notes/destroy_service.rb b/app/services/notes/destroy_service.rb
index fa0c2c5c86b..64e9accd97f 100644
--- a/app/services/notes/destroy_service.rb
+++ b/app/services/notes/destroy_service.rb
@@ -1,13 +1,11 @@
# frozen_string_literal: true
module Notes
- class DestroyService < ::Notes::BaseService
+ class DestroyService < BaseService
def execute(note)
TodoService.new.destroy_target(note) do |note|
note.destroy
end
-
- clear_noteable_diffs_cache(note)
end
end
end
diff --git a/app/views/dashboard/snippets/index.html.haml b/app/views/dashboard/snippets/index.html.haml
index b11dc2c8e9b..6eb067da95c 100644
--- a/app/views/dashboard/snippets/index.html.haml
+++ b/app/views/dashboard/snippets/index.html.haml
@@ -5,9 +5,4 @@
= render 'dashboard/snippets_head'
= render partial: 'snippets/snippets_scope_menu', locals: { include_private: true }
-.d-block.d-sm-none
- &nbsp;
- = link_to new_snippet_path, class: "btn btn-success btn-block", title: "New snippet" do
- New snippet
-
= render partial: 'snippets/snippets', locals: { link_project: true }
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index b7d69539eb7..474ef25cef7 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -71,7 +71,7 @@
= link_to admin_impersonation_path, class: 'nav-link impersonation-btn', method: :delete, title: _('Stop impersonation'), aria: { label: _('Stop impersonation') }, data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do
= icon('user-secret')
- if header_link?(:sign_in)
- %li.nav-item
+ %li.nav-item.m-auto
%div
- sign_in_text = allow_signup? ? _('Sign in / Register') : _('Sign in')
= link_to sign_in_text, new_session_path(:user, redirect_to_referer: 'yes'), class: 'btn btn-sign-in'
diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml
index 174033f3d49..ab15889a465 100644
--- a/app/views/layouts/nav/sidebar/_project.html.haml
+++ b/app/views/layouts/nav/sidebar/_project.html.haml
@@ -195,7 +195,7 @@
= _('Charts')
- if project_nav_tab? :operations
- = nav_link(controller: [:environments, :clusters, :user, :gcp]) do
+ = nav_link(controller: sidebar_operations_paths) do
= link_to metrics_project_environments_path(@project), class: 'shortcuts-operations' do
.nav-icon-container
= sprite_icon('cloud-gear')
@@ -203,7 +203,7 @@
= _('Operations')
%ul.sidebar-sub-level-items
- = nav_link(controller: [:environments, :clusters, :user, :gcp], html_options: { class: "fly-out-top-item" } ) do
+ = nav_link(controller: sidebar_operations_paths, html_options: { class: "fly-out-top-item" } ) do
= link_to metrics_project_environments_path(@project) do
%strong.fly-out-top-item-name
= _('Operations')
@@ -215,6 +215,8 @@
%span
= _('Metrics')
+ = render_if_exists "layouts/nav/sidebar/tracing_link"
+
= nav_link(controller: :environments, action: [:index, :folder, :show, :new, :edit, :create, :update, :stop, :terminal]) do
= link_to project_environments_path(@project), title: _('Environments'), class: 'shortcuts-environments qa-operations-environments-link' do
%span
@@ -326,6 +328,7 @@
= link_to project_settings_ci_cd_path(@project), title: _('CI / CD') do
%span
= _('CI / CD')
+ = render_if_exists 'projects/sidebar/settings_operations'
- if @project.pages_available?
= nav_link(controller: :pages) do
= link_to project_pages_path(@project), title: _('Pages') do
diff --git a/app/views/shared/notes/_notes_with_form.html.haml b/app/views/shared/notes/_notes_with_form.html.haml
index 9dd1c24fdfa..ec1e10bb0c1 100644
--- a/app/views/shared/notes/_notes_with_form.html.haml
+++ b/app/views/shared/notes/_notes_with_form.html.haml
@@ -7,8 +7,8 @@
= render 'shared/notes/edit_form', project: @project
- if can_create_note?
- %ul.notes.notes-form.timeline
- %li.timeline-entry
+ .notes.notes-form.timeline
+ .timeline-entry
.timeline-entry-inner
.flash-container.timeline-content
diff --git a/app/workers/stuck_import_jobs_worker.rb b/app/workers/stuck_import_jobs_worker.rb
index de92f3eca6a..667a4121131 100644
--- a/app/workers/stuck_import_jobs_worker.rb
+++ b/app/workers/stuck_import_jobs_worker.rb
@@ -7,60 +7,58 @@ class StuckImportJobsWorker
IMPORT_JOBS_EXPIRATION = 15.hours.to_i
def perform
- projects_without_jid_count = mark_projects_without_jid_as_failed!
- projects_with_jid_count = mark_projects_with_jid_as_failed!
+ import_state_without_jid_count = mark_import_states_without_jid_as_failed!
+ import_state_with_jid_count = mark_import_states_with_jid_as_failed!
Gitlab::Metrics.add_event(:stuck_import_jobs,
- projects_without_jid_count: projects_without_jid_count,
- projects_with_jid_count: projects_with_jid_count)
+ projects_without_jid_count: import_state_without_jid_count,
+ projects_with_jid_count: import_state_with_jid_count)
end
private
- def mark_projects_without_jid_as_failed!
- enqueued_projects_without_jid.each do |project|
- project.mark_import_as_failed(error_message)
+ def mark_import_states_without_jid_as_failed!
+ enqueued_import_states_without_jid.each do |import_state|
+ import_state.mark_as_failed(error_message)
end.count
end
# rubocop: disable CodeReuse/ActiveRecord
- def mark_projects_with_jid_as_failed!
- # TODO: Rollback this change to use SQL through #pluck
- jids_and_ids = enqueued_projects_with_jid.map { |project| [project.import_jid, project.id] }.to_h
+ def mark_import_states_with_jid_as_failed!
+ jids_and_ids = enqueued_import_states_with_jid.pluck(:jid, :id).to_h
# Find the jobs that aren't currently running or that exceeded the threshold.
completed_jids = Gitlab::SidekiqStatus.completed_jids(jids_and_ids.keys)
return unless completed_jids.any?
- completed_project_ids = jids_and_ids.values_at(*completed_jids)
+ completed_import_state_ids = jids_and_ids.values_at(*completed_jids)
- # We select the projects again, because they may have transitioned from
+ # We select the import states again, because they may have transitioned from
# scheduled/started to finished/failed while we were looking up their Sidekiq status.
- completed_projects = enqueued_projects_with_jid.where(id: completed_project_ids)
+ completed_import_states = enqueued_import_states_with_jid.where(id: completed_import_state_ids)
- Rails.logger.info("Marked stuck import jobs as failed. JIDs: #{completed_projects.map(&:import_jid).join(', ')}")
+ completed_import_state_jids = completed_import_states.map { |import_state| import_state.jid }.join(', ')
+ Rails.logger.info("Marked stuck import jobs as failed. JIDs: #{completed_import_state_jids}")
- completed_projects.each do |project|
- project.mark_import_as_failed(error_message)
+ completed_import_states.each do |import_state|
+ import_state.mark_as_failed(error_message)
end.count
end
# rubocop: enable CodeReuse/ActiveRecord
- # rubocop: disable CodeReuse/ActiveRecord
- def enqueued_projects
- Project.joins_import_state.where("(import_state.status = 'scheduled' OR import_state.status = 'started') OR (projects.import_status = 'scheduled' OR projects.import_status = 'started')")
+ def enqueued_import_states
+ ProjectImportState.with_status([:scheduled, :started])
end
- # rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
- def enqueued_projects_with_jid
- enqueued_projects.where.not("import_state.jid IS NULL AND projects.import_jid IS NULL")
+ def enqueued_import_states_with_jid
+ enqueued_import_states.where.not(jid: nil)
end
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
- def enqueued_projects_without_jid
- enqueued_projects.where("import_state.jid IS NULL AND projects.import_jid IS NULL")
+ def enqueued_import_states_without_jid
+ enqueued_import_states.where(jid: nil)
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/changelogs/unreleased/51259-ci-cd-tooltips.yml b/changelogs/unreleased/51259-ci-cd-tooltips.yml
new file mode 100644
index 00000000000..fc0010dbeba
--- /dev/null
+++ b/changelogs/unreleased/51259-ci-cd-tooltips.yml
@@ -0,0 +1,6 @@
+---
+title: Replaces tooltip directive with the new gl-tooltip directive for consistency
+ in some ci/cd code
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/53640-follow-up-from-resolve-redesign-activity-feed.yml b/changelogs/unreleased/53640-follow-up-from-resolve-redesign-activity-feed.yml
new file mode 100644
index 00000000000..66301329c52
--- /dev/null
+++ b/changelogs/unreleased/53640-follow-up-from-resolve-redesign-activity-feed.yml
@@ -0,0 +1,4 @@
+title: Adds new icon size to Vue icon component
+merge_request: 22899
+author:
+type: other
diff --git a/changelogs/unreleased/fix-stuck-import-jobs-query-performance-issue.yml b/changelogs/unreleased/fix-stuck-import-jobs-query-performance-issue.yml
new file mode 100644
index 00000000000..d8455a8509f
--- /dev/null
+++ b/changelogs/unreleased/fix-stuck-import-jobs-query-performance-issue.yml
@@ -0,0 +1,5 @@
+---
+title: Improves performance of stuck import jobs detection
+merge_request: 22879
+author:
+type: performance
diff --git a/changelogs/unreleased/gt-align-sign-in-button.yml b/changelogs/unreleased/gt-align-sign-in-button.yml
new file mode 100644
index 00000000000..a51fa319231
--- /dev/null
+++ b/changelogs/unreleased/gt-align-sign-in-button.yml
@@ -0,0 +1,5 @@
+---
+title: Align sign in button
+merge_request: 22888
+author: George Tsiolis
+type: fixed
diff --git a/changelogs/unreleased/osw-comment-on-any-line-on-diffs.yml b/changelogs/unreleased/osw-comment-on-any-line-on-diffs.yml
deleted file mode 100644
index 7b48a94a993..00000000000
--- a/changelogs/unreleased/osw-comment-on-any-line-on-diffs.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow commenting on any diff line in Merge Requests
-merge_request: 22398
-author:
-type: added
diff --git a/changelogs/unreleased/remove-duplicate-primary-button-in-dashboard-snippets.yml b/changelogs/unreleased/remove-duplicate-primary-button-in-dashboard-snippets.yml
new file mode 100644
index 00000000000..3a8b3a0df5d
--- /dev/null
+++ b/changelogs/unreleased/remove-duplicate-primary-button-in-dashboard-snippets.yml
@@ -0,0 +1,5 @@
+---
+title: Remove duplicate primary button in dashboard snippets on small viewports
+merge_request: 22902
+author: George Tsiolis
+type: fixed
diff --git a/changelogs/unreleased/upgrade-workhorse-7-1-0.yml b/changelogs/unreleased/upgrade-workhorse-7-1-0.yml
new file mode 100644
index 00000000000..b6df35e6d10
--- /dev/null
+++ b/changelogs/unreleased/upgrade-workhorse-7-1-0.yml
@@ -0,0 +1,5 @@
+---
+title: Update GitLab-Workhorse to v7.1.0
+merge_request: 22883
+author:
+type: other
diff --git a/doc/administration/auth/okta.md b/doc/administration/auth/okta.md
index 664657650d4..ae38094391b 100644
--- a/doc/administration/auth/okta.md
+++ b/doc/administration/auth/okta.md
@@ -11,7 +11,7 @@ The following documentation enables Okta as a SAML provider.
1. When the app screen comes up you see another button to **Create an App** and
choose SAML 2.0 on the next screen.
1. Now, very important, add a logo
- (you can choose it from https://about.gitlab.com/press/). You'll have to
+ (you can choose it from <https://about.gitlab.com/press/>). You'll have to
crop and resize it.
1. Next, you'll need the to fill in the SAML general config. Here's an example
image.
diff --git a/doc/administration/container_registry.md b/doc/administration/container_registry.md
index 890b780fe80..cfe7b0e05e3 100644
--- a/doc/administration/container_registry.md
+++ b/doc/administration/container_registry.md
@@ -71,7 +71,7 @@ A Registry init file is not shipped with GitLab if you install it from source.
Hence, [restarting GitLab][restart gitlab] will not restart the Registry should
you modify its settings. Read the upstream documentation on how to achieve that.
-At the absolute minimum, make sure your [Registry configuration][registry-auth]
+At the **absolute** minimum, make sure your [Registry configuration][registry-auth]
has `container_registry` as the service and `https://gitlab.example.com/jwt/auth`
as the realm:
@@ -84,6 +84,9 @@ auth:
rootcertbundle: /root/certs/certbundle
```
+CAUTION: **Caution:**
+If `auth` is not set up, users will be able to pull docker images without authentication.
+
## Container Registry domain configuration
There are two ways you can configure the Registry's external domain.
diff --git a/doc/administration/monitoring/prometheus/gitlab_metrics.md b/doc/administration/monitoring/prometheus/gitlab_metrics.md
index 5700f640e4c..c9a2778b3a4 100644
--- a/doc/administration/monitoring/prometheus/gitlab_metrics.md
+++ b/doc/administration/monitoring/prometheus/gitlab_metrics.md
@@ -45,7 +45,7 @@ The following metrics are available:
| redis_ping_success | Gauge | 9.4 | Whether or not the last redis ping succeeded |
| redis_ping_latency_seconds | Gauge | 9.4 | Round trip time of the redis ping |
| user_session_logins_total | Counter | 9.4 | Counter of how many users have logged in |
-| upload_file_does_not_exist | Counter | 10.7 | Number of times an upload record could not find its file |
+| upload_file_does_not_exist | Counter | 10.7 in EE, 11.5 in CE | Number of times an upload record could not find its file |
| failed_login_captcha_total | Gauge | 11.0 | Counter of failed CAPTCHA attempts during login |
| successful_login_captcha_total | Gauge | 11.0 | Counter of successful CAPTCHA attempts during login |
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index a613884dd39..5deeb2b0133 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -1510,7 +1510,7 @@ Possible values for `when` are:
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22631) in GitLab 11.5.
`parallel` allows you to configure how many instances of a job to run in
-parallel. This value has to be greater than or equal to two (2).
+parallel. This value has to be greater than or equal to two (2) and less or equal than 50.
This creates N instances of the same job that run in parallel. They're named
sequentially from `job_name 1/N` to `job_name N/N`.
diff --git a/doc/install/database_mysql.md b/doc/install/database_mysql.md
index acaed53e052..4cb8ca4f3e7 100644
--- a/doc/install/database_mysql.md
+++ b/doc/install/database_mysql.md
@@ -1,15 +1,20 @@
# Database MySQL
-> **Note:**
-> - We do not recommend using MySQL due to various issues. For example, case
- [(in)sensitivity](https://dev.mysql.com/doc/refman/5.0/en/case-sensitivity.html)
- and [problems](https://bugs.mysql.com/bug.php?id=65830) that
- [suggested](https://bugs.mysql.com/bug.php?id=50909)
- [fixes](https://bugs.mysql.com/bug.php?id=65830) [have](https://bugs.mysql.com/bug.php?id=63164).
+NOTE: **Note:**
+We do not recommend using MySQL due to various issues.
+For example, there have been bugs with case
+[(in)sensitivity](https://dev.mysql.com/doc/refman/5.7/en/case-sensitivity.html).
+
+Bugs relating to case sensitivity:
+
+- <https://bugs.mysql.com/bug.php?id=65830>
+- <https://bugs.mysql.com/bug.php?id=50909>
+- <https://bugs.mysql.com/bug.php?id=65830>
+- <https://bugs.mysql.com/bug.php?id=63164>
## Initial database setup
-```
+```sh
# Install the database packages
sudo apt-get install -y mysql-server mysql-client libmysqlclient-dev
@@ -84,8 +89,9 @@ GitLab 8.14 has introduced [a feature](https://gitlab.com/gitlab-org/gitlab-ce/m
Follow the below instructions to ensure you use the most up to date requirements for your GitLab MySQL Database.
**We are about to do the following:**
+
- Ensure you can enable `utf8mb4` encoding and `utf8mb4_general_ci` collation for your GitLab DB, tables and data.
-- Convert your GitLab tables and data from `utf8`/`utf8_general_ci` to `utf8mb4`/`utf8mb4_general_ci`
+- Convert your GitLab tables and data from `utf8`/`utf8_general_ci` to `utf8mb4`/`utf8mb4_general_ci`.
### Check for utf8mb4 support
@@ -130,7 +136,8 @@ We need to check, enable and maybe convert your existing GitLab DB tables to the
Whatever the results of your checks above, we now need to check if your GitLab database has been created using [InnoDB File-Per-Table Tablespaces](http://dev.mysql.com/doc/refman/5.7/en/innodb-multiple-tablespaces.html) (i.e. `innodb_file_per_table` was set to **1** at initial setup time).
-> Note: This setting is [enabled by default](http://dev.mysql.com/doc/refman/5.7/en/innodb-parameters.html#sysvar_innodb_file_per_table) since MySQL 5.6.6.
+NOTE: **Note:**
+This setting is [enabled by default](http://dev.mysql.com/doc/refman/5.7/en/innodb-parameters.html#sysvar_innodb_file_per_table) since MySQL 5.6.6.
# Run this command with root privileges, replace the data dir if different:
sudo ls -lh /var/lib/mysql/gitlabhq_production/*.ibd | wc -l
@@ -138,20 +145,19 @@ Whatever the results of your checks above, we now need to check if your GitLab d
# Run this command with root privileges, replace the data dir if different:
sudo ls -lh /var/lib/mysql/gitlabhq_production/*.frm | wc -l
-
- **Case 1: a result > 0 for both commands**
-Congrats, your GitLab database uses the right InnoDB tablespace format.
+Congratulations, your GitLab database uses the right InnoDB tablespace format.
However, you must still ensure that any **future tables** created by GitLab will still use the right format:
- If `SELECT @@innodb_file_per_table` returned **1** previously, your server is running correctly.
- > It's however a requirement to check *now* that this setting is indeed persisted in your [my.cnf](https://dev.mysql.com/doc/refman/5.7/en/tablespace-enabling.html) file!
+ > It's however a requirement to check *now* that this setting is indeed persisted in your [`my.cnf`](https://dev.mysql.com/doc/refman/5.7/en/innodb-multiple-tablespaces.html) file!
- If `SELECT @@innodb_file_per_table` returned **0** previously, your server is not running correctly.
- > [Enable innodb_file_per_table](https://dev.mysql.com/doc/refman/5.7/en/tablespace-enabling.html) by running in a MySQL session as root the command `SET GLOBAL innodb_file_per_table=1, innodb_file_format=Barracuda;` and persist the two settings in your [my.cnf](https://dev.mysql.com/doc/refman/5.7/en/tablespace-enabling.html) file
+ > [Enable innodb_file_per_table](https://dev.mysql.com/doc/refman/5.7/en/innodb-multiple-tablespaces.html) by running in a MySQL session as root the command `SET GLOBAL innodb_file_per_table=1, innodb_file_format=Barracuda;` and persist the two settings in your [`my.cnf`](https://dev.mysql.com/doc/refman/5.7/en/innodb-multiple-tablespaces.html) file.
Now, if you have a **different result** returned by the 2 commands above, it means you have a **mix of tables format** uses in your GitLab database. This can happen if your MySQL server had different values for `innodb_file_per_table` in its life and you updated GitLab at different moments with those inconsistent values. So keep reading.
@@ -172,7 +178,7 @@ Let's enable what we need on the running server:
# You can now quit the database session
mysql> \q
-> Now, **persist** [innodb_file_per_table](https://dev.mysql.com/doc/refman/5.6/en/tablespace-enabling.html) and [innodb_file_format](https://dev.mysql.com/doc/refman/5.6/en/innodb-file-format-enabling.html) in your `my.cnf` file.
+> Now, **persist** [innodb_file_per_table](https://dev.mysql.com/doc/refman/5.7/en/innodb-multiple-tablespaces.html) and [innodb_file_format](https://dev.mysql.com/doc/refman/5.7/en/innodb-file-format-enabling.html) in your `my.cnf` file.
Ensure at this stage that your GitLab instance is indeed **stopped**.
@@ -184,7 +190,7 @@ Now, let's convert all the GitLab database tables to the new tablespace format:
# Type the MySQL root password
mysql > use gitlabhq_production;
- # Safety check: you should still have those values set as follow:
+ # Safety check: you should still have those values set as follows:
mysql> SELECT @@innodb_file_per_table, @@innodb_file_format;
+-------------------------+----------------------+
| @@innodb_file_per_table | @@innodb_file_format |
@@ -203,7 +209,7 @@ Now, let's convert all the GitLab database tables to the new tablespace format:
#### Check for proper InnoDB File Format, Row Format, Large Prefix and tables conversion
-We need to check, enable and probably convert your existing GitLab DB tables to use the [Barracuda InnoDB file format](https://dev.mysql.com/doc/refman/5.6/en/innodb-file-format.html), the [DYNAMIC row format](https://dev.mysql.com/doc/refman/5.6/en/glossary.html#glos_dynamic_row_format) and [innodb_large_prefix](http://dev.mysql.com/doc/refman/5.7/en/innodb-parameters.html#sysvar_innodb_large_prefix) as a second prerequisite for supporting **utfb8mb4 with long indexes** used by recent GitLab databases.
+We need to check, enable and probably convert your existing GitLab DB tables to use the [Barracuda InnoDB file format](https://dev.mysql.com/doc/refman/5.7/en/innodb-file-format.html), the [DYNAMIC row format](https://dev.mysql.com/doc/refman/5.7/en/glossary.html#glos_dynamic_row_format) and [innodb_large_prefix](http://dev.mysql.com/doc/refman/5.7/en/innodb-parameters.html#sysvar_innodb_large_prefix) as a second prerequisite for supporting **utfb8mb4 with long indexes** used by recent GitLab databases.
# Login to MySQL
mysql -u root -p
@@ -229,7 +235,7 @@ We need to check, enable and probably convert your existing GitLab DB tables to
| utf8 | utf8_general_ci |
+--------------------------+----------------------+
-> Now, ensure that [innodb_file_format](https://dev.mysql.com/doc/refman/5.6/en/tablespace-enabling.html) and [innodb_large_prefix](http://dev.mysql.com/doc/refman/5.7/en/innodb-parameters.html#sysvar_innodb_large_prefix) are **persisted** in your `my.cnf` file.
+> Now, ensure that [innodb_file_format](https://dev.mysql.com/doc/refman/5.7/en/innodb-multiple-tablespaces.html) and [innodb_large_prefix](http://dev.mysql.com/doc/refman/5.7/en/innodb-parameters.html#sysvar_innodb_large_prefix) are **persisted** in your `my.cnf` file.
#### Tables and data conversion to utf8mb4
@@ -257,7 +263,7 @@ Now that you have a persistent MySQL setup, you can safely upgrade tables after
Ensure your GitLab database configuration file uses a proper connection encoding and collation:
-```sudo -u git -H editor config/database.yml```
+`sudo -u git -H editor config/database.yml`
production:
adapter: mysql2
@@ -266,19 +272,19 @@ Ensure your GitLab database configuration file uses a proper connection encoding
[Restart your GitLab instance](../administration/restart_gitlab.md).
-
## MySQL strings limits
After installation or upgrade, remember to run the `add_limits_mysql` Rake task:
**Omnibus GitLab installations**
-```
+
+```sh
sudo gitlab-rake add_limits_mysql
```
**Installations from source**
-```
+```sh
bundle exec rake add_limits_mysql RAILS_ENV=production
```
diff --git a/doc/install/kubernetes/gitlab_chart.md b/doc/install/kubernetes/gitlab_chart.md
index 6d1bc4aedc4..3f5b36f7254 100644
--- a/doc/install/kubernetes/gitlab_chart.md
+++ b/doc/install/kubernetes/gitlab_chart.md
@@ -31,7 +31,7 @@ to deploy.
TIP: **Tip:**
For production deployments, we strongly recommend using the
-[detailed installation instructions](https://gitlab.com/charts/gitlab/blob/master/doc/installation/README.md)
+[detailed installation instructions](https://gitlab.com/charts/gitlab/blob/master/doc/installation/index.md)
utilizing [external Postgres, Redis, and object storage](https://gitlab.com/charts/gitlab/tree/master/doc/advanced) services.
### Requirements
diff --git a/doc/raketasks/import.md b/doc/raketasks/import.md
index 97e9b36d1a6..bb316df5b9a 100644
--- a/doc/raketasks/import.md
+++ b/doc/raketasks/import.md
@@ -6,6 +6,7 @@
- The groups will be created as needed, including subgroups
- The owner of the group will be the first admin
- Existing projects will be skipped
+- Projects in hashed storage may be skipped (see [Importing bare repositories from hashed storage](#importing-bare-repositories-from-hashed-storage))
- The existing Git repos will be moved from disk (removed from the original path)
## How to use
@@ -26,7 +27,6 @@ sudo -u git mkdir /var/opt/gitlab/git-data/repository-import-<date>/new_group
If we copy the repos to `/var/opt/gitlab/git-data/repository-import-<date>`, and repo A needs to be under the groups G1 and G2, it will
have to be created under those folders: `/var/opt/gitlab/git-data/repository-import-<date>/G1/G2/A.git`.
-
```
sudo cp -r /old/git/foo.git /var/opt/gitlab/git-data/repository-import-<date>/new_group/
@@ -70,3 +70,73 @@ Processing /var/opt/gitlab/git-data/repository-import-1/group/xyz.git
* Skipping repo /var/opt/gitlab/git-data/repository-import-1/@shared/a/b/abcd.git
[...]
```
+
+## Importing bare repositories from hashed storage
+
+### Background
+
+Projects in legacy storage have a directory structure that mirrors their full
+project path in GitLab, including their namespace structure. This information is
+leveraged by the bare repository importer to import projects into their proper
+locations. Each project and its parent namespaces are meaningfully named.
+
+However, the directory structure of projects in hashed storage do not contain
+this information. This is beneficial for a variety of reasons, especially
+improved performance and data integrity. See
+[Repository Storage Types](../administration/repository_storage_types.md) for
+more details.
+
+### Which repositories are importable?
+
+#### GitLab 10.3 or earlier
+
+Importing bare repositories from hashed storage is unsupported.
+
+#### GitLab 10.4 and later
+
+To support importing bare repositories from hashed storage, GitLab 10.4 and
+later stores the full project path with each repository, in a special section of
+the git repository's config file. This section is formatted as follows:
+
+```
+[gitlab]
+ fullpath = gitlab-org/gitlab-ce
+```
+
+However, existing repositories were not migrated to include this path.
+
+Bare repositories are importable if the following events occurred to the
+repository in GitLab 10.4 and later:
+
+- Created
+- Migrated to hashed storage
+- Renamed
+- Transferred to another namespace
+- Ancestor renamed
+- Ancestor transferred to another namespace
+
+Bare repositories are **not** importable by GitLab 10.4 and later when all the following are true about the repository:
+
+- It was created in GitLab 10.3 or earlier.
+- It was not renamed, transferred, or migrated to hashed storage in GitLab 10.4 and later.
+- Its ancestor namespaces were not renamed or transferred in GitLab 10.4 and later.
+
+There is an [open issue to add a migration to make all bare repositories
+importable](https://gitlab.com/gitlab-org/gitlab-ce/issues/41776).
+
+Until then, you may wish to manually migrate repositories yourself. You can use
+[Rails console](https://docs.gitlab.com/omnibus/maintenance/#starting-a-rails-console-session)
+to do so. In a Rails console session, run the following to migrate a project:
+
+```
+project = Project.find_by_full_path('gitlab-org/gitlab-ce')
+project.write_repository_config
+```
+
+In a Rails console session, run the following to migrate all of a namespace's
+projects (this may take a while if there are 1000s of projects in a namespace):
+
+```
+namespace = Namespace.find_by_full_path('gitlab-org')
+namespace.send(:write_projects_repository_config)
+```
diff --git a/doc/university/training/topics/additional_resources.md b/doc/university/training/topics/additional_resources.md
index d01634df744..4871372d105 100644
--- a/doc/university/training/topics/additional_resources.md
+++ b/doc/university/training/topics/additional_resources.md
@@ -4,9 +4,9 @@ comments: false
# Additional Resources
-1. GitLab Documentation [http://docs.gitlab.com](http://docs.gitlab.com/)
-2. GUI Clients [http://git-scm.com/downloads/guis](http://git-scm.com/downloads/guis)
-3. Pro git book [http://git-scm.com/book](http://git-scm.com/book)
-4. Platzi Course [https://courses.platzi.com/courses/git-gitlab/](https://courses.platzi.com/courses/git-gitlab/)
-5. Code School tutorial [http://try.github.io/](http://try.github.io/)
-6. Contact Us at `subscribers@gitlab.com`
+1. GitLab Documentation: <http://docs.gitlab.com>.
+1. GUI Clients: <http://git-scm.com/downloads/guis>.
+1. Pro Git book: <http://git-scm.com/book>.
+1. Platzi Course: <https://courses.platzi.com/courses/git-gitlab/>.
+1. Code School tutorial: <http://try.github.io/>.
+1. Contact us at `subscribers@gitlab.com`.
diff --git a/doc/university/training/topics/tags.md b/doc/university/training/topics/tags.md
index 6333ceedbd7..9526bcbfb82 100644
--- a/doc/university/training/topics/tags.md
+++ b/doc/university/training/topics/tags.md
@@ -22,7 +22,7 @@ comments: false
**Additional resources**
-[http://git-scm.com/book/en/Git-Basics-Tagging](http://git-scm.com/book/en/Git-Basics-Tagging)
+<http://git-scm.com/book/en/Git-Basics-Tagging>
----------
diff --git a/doc/university/training/user_training.md b/doc/university/training/user_training.md
index f3a4d766522..ca3f777f403 100644
--- a/doc/university/training/user_training.md
+++ b/doc/university/training/user_training.md
@@ -385,7 +385,7 @@ Thank you for your hard work!
- GitLab Documentation: <http://docs.gitlab.com/>.
- GUI Clients: <http://git-scm.com/downloads/guis>.
-- Pro git book: <http://git-scm.com/book>.
+- Pro Git book: <http://git-scm.com/book>.
- Platzi Course: <https://courses.platzi.com/courses/git-gitlab/>.
- Code School tutorial: <http://try.github.io/>.
- Contact us at `subscribers@gitlab.com`.
diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md
index 94744cf8500..233ed205790 100644
--- a/doc/user/project/clusters/index.md
+++ b/doc/user/project/clusters/index.md
@@ -132,59 +132,62 @@ functionalities needed to successfully build and deploy a containerized
application. Bare in mind that the same credentials are used for all the
applications running on the cluster.
-When GitLab creates the cluster, it enables and uses the legacy
-[Attribute-based access control (ABAC)](https://kubernetes.io/docs/admin/authorization/abac/).
-The newer [RBAC](https://kubernetes.io/docs/admin/authorization/rbac/)
-authorization is [experimental](#role-based-access-control-rbac).
-
-### Role-based access control (RBAC) **[CORE ONLY]**
-
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/21401) in GitLab 11.4.
-
-CAUTION: **Warning:**
-The RBAC authorization is experimental.
-
-Once RBAC is enabled for a cluster, GitLab will create the necessary service accounts
-and privileges in order to install and run [GitLab managed applications](#installing-applications).
-
-If you are creating a [new GKE cluster via
-GitLab](#adding-and-creating-a-new-gke-cluster-via-gitlab), you will be
-asked if you would like to create an RBAC-enabled cluster. Enabling this
-setting will create a `gitlab` service account which will be used by
-GitLab to manage the newly created cluster. To enable this, this service
-account will have the `cluster-admin` privilege.
-
-If you are [adding an existing Kubernetes
-cluster](#adding-an-existing-kubernetes-cluster), you will be asked if
-the cluster you are adding is a RBAC-enabled cluster. Ensure the
-token of the account has administrator privileges for the cluster.
-
-In both cases above, when you install Helm Tiller into your cluster, an
-RBAC-enabled cluster will create a `tiller` service account, with `cluster-admin`
-privileges in the `gitlab-managed-apps` namespace. This service account will be
-added to the installed Helm Tiller and will be used by Helm to install and run
-[GitLab managed applications](#installing-applications).
-
-The table below summarizes which resources will be created in a
-RBAC-enabled cluster :
-
-| Name | Kind | Details | Created when |
-| --- | --- | --- | --- |
-| `gitlab` | `ServiceAccount` | `default` namespace | Creating a new GKE Cluster |
-| `gitlab-admin` | `ClusterRoleBinding` | `cluster-admin` roleRef | Creating a new GKE Cluster |
-| `gitlab-token` | `Secret` | Token for `gitlab` ServiceAccount | Creating a new GKE Cluster |
-| `tiller` | `ServiceAccount` | `gitlab-managed-apps` namespace | Installing Helm Tiller |
-| `tiller-admin` | `ClusterRoleBinding` | `cluster-admin` roleRef | Installing Helm Tiller |
-
-
-Helm Tiller will also create additional service accounts and other RBAC
-resources for each installed application. Consult the documentation for the
-Helm charts for each application for details.
-
-NOTE: **Note:**
-Auto DevOps will not successfully complete in a cluster that only has RBAC
-authorization enabled. RBAC support for Auto DevOps is planned in a
-[future release](https://gitlab.com/gitlab-org/gitlab-ce/issues/44597).
+## Access controls
+
+When creating a cluster in GitLab, you will be asked if you would like to create an
+[Attribute-based access control (ABAC)](https://kubernetes.io/docs/admin/authorization/abac/) cluster, or
+a [Role-based access control (RBAC)](https://kubernetes.io/docs/admin/authorization/rbac/) one.
+
+Whether ABAC or RBAC is enabled, GitLab will create the necessary
+service accounts and privileges in order to install and run
+[GitLab managed applications](#installing-applications):
+
+- A `gitlab` service account with `cluster-admin` privileges will be created in the
+ `default` namespace, which will be used by GitLab to manage the newly created cluster.
+
+- A project service account with `edit` privileges will be created in
+ the project namespace (also created by GitLab), which will be used in
+ [deployment jobs](#deployment-variables).
+
+ NOTE: **Note:**
+ Restricted service account for deployment was [introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/51716) in GitLab 11.5.
+
+- When you install Helm Tiller into your cluster, the `tiller` service account
+ will be created with `cluster-admin` privileges in the `gitlab-managed-apps`
+ namespace. This service account will be added to the installed Helm Tiller and will
+ be used by Helm to install and run [GitLab managed applications](#installing-applications).
+ Helm Tiller will also create additional service accounts and other resources for each
+ installed application. Consult the documentation of the Helm charts for each application
+ for details.
+
+If you are [adding an existing Kubernetes cluster](#adding-an-existing-kubernetes-cluster),
+ensure the token of the account has administrator privileges for the cluster.
+
+The following sections summarize which resources will be created on ABAC/RBAC clusters.
+
+### Attribute-based access control (ABAC)
+
+| Name | Kind | Details | Created when |
+| --- | --- | --- | --- |
+| `gitlab` | `ServiceAccount` | `default` namespace | Creating a new GKE Cluster |
+| `gitlab-token` | `Secret` | Token for `gitlab` ServiceAccount | Creating a new GKE Cluster |
+| `tiller` | `ServiceAccount` | `gitlab-managed-apps` namespace | Installing Helm Tiller |
+| `tiller-admin` | `ClusterRoleBinding` | `cluster-admin` roleRef | Installing Helm Tiller |
+| Project namespace | `ServiceAccount` | Uses namespace of Project | Creating/Adding a new GKE Cluster |
+| Project namespace | `Secret` | Token for project ServiceAccount | Creating/Adding a new GKE Cluster |
+
+### Role-based access control (RBAC)
+
+| Name | Kind | Details | Created when |
+| --- | --- | --- | --- |
+| `gitlab` | `ServiceAccount` | `default` namespace | Creating a new GKE Cluster |
+| `gitlab-admin` | `ClusterRoleBinding` | [`cluster-admin`](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#user-facing-roles) roleRef | Creating a new GKE Cluster |
+| `gitlab-token` | `Secret` | Token for `gitlab` ServiceAccount | Creating a new GKE Cluster |
+| `tiller` | `ServiceAccount` | `gitlab-managed-apps` namespace | Installing Helm Tiller |
+| `tiller-admin` | `ClusterRoleBinding` | `cluster-admin` roleRef | Installing Helm Tiller |
+| Project namespace | `ServiceAccount` | Uses namespace of Project | Creating/Adding a new GKE Cluster |
+| Project namespace | `Secret` | Token for project ServiceAccount | Creating/Adding a new GKE Cluster |
+| Project namespace | `RoleBinding` | [`edit`](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#user-facing-roles) roleRef | Creating/Adding a new GKE Cluster |
### Security of GitLab Runners
@@ -387,12 +390,16 @@ GitLab CI/CD build environment.
| Variable | Description |
| -------- | ----------- |
| `KUBE_URL` | Equal to the API URL. |
-| `KUBE_TOKEN` | The Kubernetes token. |
+| `KUBE_TOKEN` | The Kubernetes token of the [project service account](#access-controls). |
| `KUBE_NAMESPACE` | The Kubernetes namespace is auto-generated if not specified. The default value is `<project_name>-<project_id>`. You can overwrite it to use different one if needed, otherwise the `KUBE_NAMESPACE` variable will receive the default value. |
-| `KUBE_CA_PEM_FILE` | Only present if a custom CA bundle was specified. Path to a file containing PEM data. |
-| `KUBE_CA_PEM` | (**deprecated**) Only if a custom CA bundle was specified. Raw PEM data. |
+| `KUBE_CA_PEM_FILE` | Path to a file containing PEM data. Only present if a custom CA bundle was specified. |
+| `KUBE_CA_PEM` | (**deprecated**) Raw PEM data. Only if a custom CA bundle was specified. |
| `KUBECONFIG` | Path to a file containing `kubeconfig` for this deployment. CA bundle would be embedded if specified. |
+NOTE: **NOTE:**
+Prior to GitLab 11.5, `KUBE_TOKEN` was the Kubernetes token of the main
+service account of the cluster integration.
+
## Enabling or disabling the Kubernetes cluster integration
After you have successfully added your cluster information, you can enable the
diff --git a/lib/gitlab/ci/config/entry/job.rb b/lib/gitlab/ci/config/entry/job.rb
index 8e8c979f973..c8cb3248fa7 100644
--- a/lib/gitlab/ci/config/entry/job.rb
+++ b/lib/gitlab/ci/config/entry/job.rb
@@ -27,7 +27,8 @@ module Gitlab
validates :tags, array_of_strings: true
validates :allow_failure, boolean: true
validates :parallel, numericality: { only_integer: true,
- greater_than_or_equal_to: 2 }
+ greater_than_or_equal_to: 2,
+ less_than_or_equal_to: 50 }
validates :when,
inclusion: { in: %w[on_success on_failure always manual delayed],
message: 'should be on_success, on_failure, ' \
diff --git a/lib/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb
index 84595f8afd7..fb117baca9e 100644
--- a/lib/gitlab/diff/file.rb
+++ b/lib/gitlab/diff/file.rb
@@ -26,7 +26,6 @@ module Gitlab
@repository = repository
@diff_refs = diff_refs
@fallback_diff_refs = fallback_diff_refs
- @unfolded = false
# Ensure items are collected in the the batch
new_blob_lazy
@@ -136,24 +135,6 @@ module Gitlab
Gitlab::Diff::Parser.new.parse(raw_diff.each_line, diff_file: self).to_a
end
- # Changes diff_lines according to the given position. That is,
- # it checks whether the position requires blob lines into the diff
- # in order to be presented.
- def unfold_diff_lines(position)
- return unless position
-
- unfolder = Gitlab::Diff::LinesUnfolder.new(self, position)
-
- if unfolder.unfold_required?
- @diff_lines = unfolder.unfolded_diff_lines
- @unfolded = true
- end
- end
-
- def unfolded?
- @unfolded
- end
-
def highlighted_diff_lines
@highlighted_diff_lines ||=
Gitlab::Diff::Highlight.new(self, repository: self.repository).highlight
diff --git a/lib/gitlab/diff/line.rb b/lib/gitlab/diff/line.rb
index 70063071ee7..5b67cd46c48 100644
--- a/lib/gitlab/diff/line.rb
+++ b/lib/gitlab/diff/line.rb
@@ -3,9 +3,9 @@ module Gitlab
class Line
SERIALIZE_KEYS = %i(line_code rich_text text type index old_pos new_pos).freeze
- attr_reader :line_code, :type, :old_pos, :new_pos
+ attr_reader :line_code, :type, :index, :old_pos, :new_pos
attr_writer :rich_text
- attr_accessor :text, :index
+ attr_accessor :text
def initialize(text, type, index, old_pos, new_pos, parent_file: nil, line_code: nil, rich_text: nil)
@text, @type, @index = text, type, index
@@ -19,14 +19,7 @@ module Gitlab
end
def self.init_from_hash(hash)
- new(hash[:text],
- hash[:type],
- hash[:index],
- hash[:old_pos],
- hash[:new_pos],
- parent_file: hash[:parent_file],
- line_code: hash[:line_code],
- rich_text: hash[:rich_text])
+ new(hash[:text], hash[:type], hash[:index], hash[:old_pos], hash[:new_pos], line_code: hash[:line_code], rich_text: hash[:rich_text])
end
def to_hash
diff --git a/lib/gitlab/diff/lines_unfolder.rb b/lib/gitlab/diff/lines_unfolder.rb
deleted file mode 100644
index 9306b7e16a2..00000000000
--- a/lib/gitlab/diff/lines_unfolder.rb
+++ /dev/null
@@ -1,235 +0,0 @@
-# frozen_string_literal: true
-
-# Given a position, calculates which Blob lines should be extracted, treated and
-# injected in the current diff file lines in order to present a "unfolded" diff.
-module Gitlab
- module Diff
- class LinesUnfolder
- include Gitlab::Utils::StrongMemoize
-
- UNFOLD_CONTEXT_SIZE = 3
-
- def initialize(diff_file, position)
- @diff_file = diff_file
- @blob = diff_file.old_blob
- @position = position
- @generate_top_match_line = true
- @generate_bottom_match_line = true
-
- # These methods update `@generate_top_match_line` and
- # `@generate_bottom_match_line`.
- @from_blob_line = calculate_from_blob_line!
- @to_blob_line = calculate_to_blob_line!
- end
-
- # Returns merged diff lines with required blob lines with correct
- # positions.
- def unfolded_diff_lines
- strong_memoize(:unfolded_diff_lines) do
- next unless unfold_required?
-
- merged_diff_with_blob_lines
- end
- end
-
- # Returns the extracted lines from the old blob which should be merged
- # with the current diff lines.
- def blob_lines
- strong_memoize(:blob_lines) do
- # Blob lines, unlike diffs, doesn't start with an empty space for
- # unchanged line, so the parsing and highlighting step can get fuzzy
- # without the following change.
- line_prefix = ' '
- blob_as_diff_lines = @blob.data.each_line.map { |line| "#{line_prefix}#{line}" }
-
- lines = Gitlab::Diff::Parser.new.parse(blob_as_diff_lines, diff_file: @diff_file).to_a
-
- from = from_blob_line - 1
- to = to_blob_line - 1
-
- lines[from..to]
- end
- end
-
- def unfold_required?
- strong_memoize(:unfold_required) do
- next false unless @diff_file.text?
- next false unless @position.unchanged?
- next false if @diff_file.new_file? || @diff_file.deleted_file?
- next false unless @position.old_line
- # Invalid position (MR import scenario)
- next false if @position.old_line > @blob.lines.size
- next false if @diff_file.diff_lines.empty?
- next false if @diff_file.line_for_position(@position)
- next false unless unfold_line
-
- true
- end
- end
-
- private
-
- attr_reader :from_blob_line, :to_blob_line
-
- def merged_diff_with_blob_lines
- lines = @diff_file.diff_lines
- match_line = unfold_line
- insert_index = bottom? ? -1 : match_line.index
-
- lines -= [match_line] unless bottom?
-
- lines.insert(insert_index, *blob_lines_with_matches)
-
- # The inserted blob lines have invalid indexes, so we need
- # to reindex them.
- reindex(lines)
-
- lines
- end
-
- # Returns 'unchanged' blob lines with recalculated `old_pos` and
- # `new_pos` and the recalculated new match line (needed if we for instance
- # we unfolded once, but there are still folded lines).
- def blob_lines_with_matches
- old_pos = from_blob_line
- new_pos = from_blob_line + offset
-
- new_blob_lines = []
-
- new_blob_lines.push(top_blob_match_line) if top_blob_match_line
-
- blob_lines.each do |line|
- new_blob_lines << Gitlab::Diff::Line.new(line.text, line.type, nil, old_pos, new_pos,
- parent_file: @diff_file)
-
- old_pos += 1
- new_pos += 1
- end
-
- new_blob_lines.push(bottom_blob_match_line) if bottom_blob_match_line
-
- new_blob_lines
- end
-
- def reindex(lines)
- lines.each_with_index { |line, i| line.index = i }
- end
-
- def top_blob_match_line
- strong_memoize(:top_blob_match_line) do
- next unless @generate_top_match_line
-
- old_pos = from_blob_line
- new_pos = from_blob_line + offset
-
- build_match_line(old_pos, new_pos)
- end
- end
-
- def bottom_blob_match_line
- strong_memoize(:bottom_blob_match_line) do
- # The bottom line match addition is already handled on
- # Diff::File#diff_lines_for_serializer
- next if bottom?
- next unless @generate_bottom_match_line
-
- position = line_after_unfold_position.old_pos
-
- old_pos = position
- new_pos = position + offset
-
- build_match_line(old_pos, new_pos)
- end
- end
-
- def build_match_line(old_pos, new_pos)
- blob_lines_length = blob_lines.length
- old_line_ref = [old_pos, blob_lines_length].join(',')
- new_line_ref = [new_pos, blob_lines_length].join(',')
- new_match_line_str = "@@ -#{old_line_ref}+#{new_line_ref} @@"
-
- Gitlab::Diff::Line.new(new_match_line_str, 'match', nil, old_pos, new_pos)
- end
-
- # Returns the first line position that should be extracted
- # from `blob_lines`.
- def calculate_from_blob_line!
- return unless unfold_required?
-
- from = comment_position - UNFOLD_CONTEXT_SIZE
-
- # There's no line before the match if it's in the top-most
- # position.
- prev_line_number = line_before_unfold_position&.old_pos || 0
-
- if from <= prev_line_number + 1
- @generate_top_match_line = false
- from = prev_line_number + 1
- end
-
- from
- end
-
- # Returns the last line position that should be extracted
- # from `blob_lines`.
- def calculate_to_blob_line!
- return unless unfold_required?
-
- to = comment_position + UNFOLD_CONTEXT_SIZE
-
- return to if bottom?
-
- next_line_number = line_after_unfold_position.old_pos
-
- if to >= next_line_number - 1
- @generate_bottom_match_line = false
- to = next_line_number - 1
- end
-
- to
- end
-
- def offset
- unfold_line.new_pos - unfold_line.old_pos
- end
-
- def line_before_unfold_position
- return unless index = unfold_line&.index
-
- @diff_file.diff_lines[index - 1] if index > 0
- end
-
- def line_after_unfold_position
- return unless index = unfold_line&.index
-
- @diff_file.diff_lines[index + 1] if index >= 0
- end
-
- def bottom?
- strong_memoize(:bottom) do
- @position.old_line > last_line.old_pos
- end
- end
-
- # Returns the line which needed to be expanded in order to send a comment
- # in `@position`.
- def unfold_line
- strong_memoize(:unfold_line) do
- next last_line if bottom?
-
- @diff_file.diff_lines.find do |line|
- line.old_pos > comment_position && line.type == 'match'
- end
- end
- end
-
- def comment_position
- @position.old_line
- end
-
- def last_line
- @diff_file.diff_lines.last
- end
- end
- end
-end
diff --git a/lib/gitlab/diff/position.rb b/lib/gitlab/diff/position.rb
index 7bfab2d808f..f967494199e 100644
--- a/lib/gitlab/diff/position.rb
+++ b/lib/gitlab/diff/position.rb
@@ -101,10 +101,6 @@ module Gitlab
@diff_refs ||= DiffRefs.new(base_sha: base_sha, start_sha: start_sha, head_sha: head_sha)
end
- def unfolded_diff?(repository)
- diff_file(repository)&.unfolded?
- end
-
def diff_file(repository)
return @diff_file if defined?(@diff_file)
@@ -138,13 +134,7 @@ module Gitlab
return unless diff_refs.complete?
return unless comparison = diff_refs.compare_in(repository.project)
- file = comparison.diffs(diff_options).diff_files.first
-
- # We need to unfold diff lines according to the position in order
- # to correctly calculate the line code and trace position changes.
- file&.unfold_diff_lines(self)
-
- file
+ comparison.diffs(diff_options).diff_files.first
end
def get_formatter_class(type)
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 3182ffb27b9..3d66b092143 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -3591,6 +3591,9 @@ msgstr ""
msgid "Last edited by %{name}"
msgstr ""
+msgid "Last reply by"
+msgstr ""
+
msgid "Last update"
msgstr ""
@@ -4192,6 +4195,9 @@ msgstr ""
msgid "Notes|Are you sure you want to cancel creating this comment?"
msgstr ""
+msgid "Notes|Collapse replies"
+msgstr ""
+
msgid "Notes|Show all activity"
msgstr ""
@@ -7568,6 +7574,11 @@ msgstr ""
msgid "remove due date"
msgstr ""
+msgid "reply"
+msgid_plural "replies"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "source"
msgstr ""
diff --git a/qa/qa/git/repository.rb b/qa/qa/git/repository.rb
index 27a88534258..7f959441dac 100644
--- a/qa/qa/git/repository.rb
+++ b/qa/qa/git/repository.rb
@@ -109,6 +109,28 @@ module QA
known_hosts_file.close(true)
end
+ def push_with_git_protocol(version, file_name, file_content, commit_message = 'Initial commit')
+ self.git_protocol = version
+ add_file(file_name, file_content)
+ commit(commit_message)
+ push_changes
+
+ fetch_supported_git_protocol
+ end
+
+ def git_protocol=(value)
+ raise ArgumentError, "Please specify the protocol you would like to use: 0, 1, or 2" unless %w[0 1 2].include?(value.to_s)
+
+ run("git config protocol.version #{value}")
+ end
+
+ def fetch_supported_git_protocol
+ # ls-remote is one command known to respond to Git protocol v2 so we use
+ # it to get output including the version reported via Git tracing
+ output = run("git ls-remote #{uri}", "GIT_TRACE_PACKET=1")
+ output[/git< version (\d+)/, 1] || 'unknown'
+ end
+
private
attr_reader :uri, :username, :password, :known_hosts_file, :private_key_file
@@ -117,8 +139,8 @@ module QA
!private_key_file.nil?
end
- def run(command_str)
- command = [env_vars, command_str, '2>&1'].compact.join(' ')
+ def run(command_str, *extra_env)
+ command = [env_vars, *extra_env, command_str, '2>&1'].compact.join(' ')
Runtime::Logger.debug "Git: command=[#{command}]"
output, _ = Open3.capture2(command)
diff --git a/qa/qa/page/project/issue/show.rb b/qa/qa/page/project/issue/show.rb
index de18b9cefa6..23def93c7dd 100644
--- a/qa/qa/page/project/issue/show.rb
+++ b/qa/qa/page/project/issue/show.rb
@@ -37,6 +37,11 @@ module QA
def select_comments_only_filter
click_element :discussion_filter
+ all_elements(:filter_options)[1].click
+ end
+
+ def select_history_only_filter
+ click_element :discussion_filter
all_elements(:filter_options).last.click
end
diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb
index c7052a9f300..c4500f9be90 100644
--- a/qa/qa/runtime/env.rb
+++ b/qa/qa/runtime/env.rb
@@ -7,6 +7,16 @@ module QA
attr_writer :personal_access_token
+ # The environment variables used to indicate if the environment under test
+ # supports the given feature
+ SUPPORTED_FEATURES = {
+ git_protocol_v2: 'QA_CAN_TEST_GIT_PROTOCOL_V2'
+ }.freeze
+
+ def supported_features
+ SUPPORTED_FEATURES
+ end
+
def debug?
enabled?(ENV['QA_DEBUG'], default: false)
end
@@ -104,6 +114,15 @@ module QA
raise ArgumentError, "Please provide GITHUB_ACCESS_TOKEN"
end
+ # Returns true if there is an environment variable that indicates that
+ # the feature is supported in the environment under test.
+ # All features are supported by default.
+ def can_test?(feature)
+ raise ArgumentError, %Q(Unknown feature "#{feature}") unless SUPPORTED_FEATURES.include? feature
+
+ enabled?(ENV[SUPPORTED_FEATURES[feature]], default: true)
+ end
+
private
def enabled?(value, default: true)
diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb
index f5002c8032f..7145b950b6c 100644
--- a/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb
@@ -31,6 +31,7 @@ module QA
create_issue
Page::Project::Issue::Show.perform do |show|
+ show.select_all_activities_filter
show.comment('See attached banana for scale', attachment: file_to_attach)
show.refresh
diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb
index 83603f1cda7..ac34f72bb8f 100644
--- a/qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb
@@ -27,6 +27,11 @@ module QA
expect(show_page).to have_content("made the issue confidential")
expect(show_page).to have_content("My own comment")
+
+ show_page.select_history_only_filter
+
+ expect(show_page).to have_content("made the issue confidential")
+ expect(show_page).not_to have_content("My own comment")
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_ssh_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_ssh_spec.rb
new file mode 100644
index 00000000000..bc88e6450f5
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_ssh_spec.rb
@@ -0,0 +1,84 @@
+# frozen_string_literal: true
+
+module QA
+ context 'Create' do
+ describe 'Push over SSH using Git protocol version 2', :requires_git_protocol_v2 do
+ # Note: If you run this test against GDK make sure you've enabled sshd and
+ # enabled setting the Git protocol by adding `AcceptEnv GIT_PROTOCOL` to
+ # `sshd_config`
+ # See: https://gitlab.com/gitlab-org/gitlab-qa/blob/master/docs/run_qa_against_gdk.md
+
+ let(:key_title) { "key for ssh tests #{Time.now.to_f}" }
+ let(:ssh_key) do
+ Factory::Resource::SSHKey.fabricate! do |resource|
+ resource.title = key_title
+ end
+ end
+
+ def login
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
+ Page::Main::Login.perform(&:sign_in_using_credentials)
+ end
+
+ around do |example|
+ # Create an SSH key to be used with Git
+ login
+ ssh_key
+
+ example.run
+
+ # Remove the SSH key
+ login
+ Page::Main::Menu.perform(&:go_to_profile_settings)
+ Page::Profile::Menu.perform(&:click_ssh_keys)
+ Page::Profile::SSHKeys.perform do |ssh_keys|
+ ssh_keys.remove_key(key_title)
+ end
+ end
+
+ it 'user pushes to the repository' do
+ # Create a project to push to
+ project = Factory::Resource::Project.fabricate! do |project|
+ project.name = 'git-protocol-project'
+ end
+
+ file_name = 'README.md'
+ file_content = 'Test Git protocol v2'
+ git_protocol = '2'
+ git_protocol_reported = nil
+
+ # Use Git to clone the project, push a file to it, and then check the
+ # supported Git protocol
+ Git::Repository.perform do |repository|
+ username = 'GitLab QA'
+ email = 'root@gitlab.com'
+
+ repository.uri = project.repository_ssh_location.uri
+
+ begin
+ repository.use_ssh_key(ssh_key)
+ repository.clone
+ repository.configure_identity(username, email)
+
+ git_protocol_reported = repository.push_with_git_protocol(
+ git_protocol,
+ file_name,
+ file_content)
+ ensure
+ repository.delete_ssh_key
+ end
+ end
+
+ project.visit!
+ Page::Project::Show.perform(&:wait_for_push)
+
+ # Check that the push worked
+ expect(page).to have_content(file_name)
+ expect(page).to have_content(file_content)
+
+ # And check that the correct Git protocol was used
+ expect(git_protocol_reported).to eq(git_protocol)
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/runner.rb b/qa/qa/specs/runner.rb
index ad397c13f0c..1bd8101c36d 100644
--- a/qa/qa/specs/runner.rb
+++ b/qa/qa/specs/runner.rb
@@ -25,6 +25,10 @@ module QA
args.push(%w[--tag ~skip_signup_disabled]) if QA::Runtime::Env.signup_disabled?
+ QA::Runtime::Env.supported_features.each_key do |key|
+ args.push(["--tag", "~requires_#{key}"]) unless QA::Runtime::Env.can_test? key
+ end
+
args.push(options)
args.push(DEFAULT_TEST_PATH_ARGS) unless options.any? { |opt| opt =~ %r{/features/} }
diff --git a/qa/spec/git/repository_spec.rb b/qa/spec/git/repository_spec.rb
index c629f802aa4..faa154c78da 100644
--- a/qa/spec/git/repository_spec.rb
+++ b/qa/spec/git/repository_spec.rb
@@ -26,6 +26,36 @@ describe QA::Git::Repository do
end
end
+ describe '#git_protocol=' do
+ [0, 1, 2].each do |version|
+ it "configures git to use protocol version #{version}" do
+ expect(repository).to receive(:run).with("git config protocol.version #{version}")
+ repository.git_protocol = version
+ end
+ end
+
+ it 'raises an error if the version is unsupported' do
+ expect { repository.git_protocol = 'foo' }.to raise_error(ArgumentError, "Please specify the protocol you would like to use: 0, 1, or 2")
+ end
+ end
+
+ describe '#fetch_supported_git_protocol' do
+ it "reports the detected version" do
+ expect(repository).to receive(:run).and_return("packet: git< version 2")
+ expect(repository.fetch_supported_git_protocol).to eq('2')
+ end
+
+ it 'reports unknown if version is unknown' do
+ expect(repository).to receive(:run).and_return("packet: git< version -1")
+ expect(repository.fetch_supported_git_protocol).to eq('unknown')
+ end
+
+ it 'reports unknown if content does not identify a version' do
+ expect(repository).to receive(:run).and_return("foo")
+ expect(repository.fetch_supported_git_protocol).to eq('unknown')
+ end
+ end
+
def cd_empty_temp_directory
tmp_dir = 'tmp/git-repository-spec/'
FileUtils.rm_rf(tmp_dir) if ::File.exist?(tmp_dir)
diff --git a/qa/spec/runtime/env_spec.rb b/qa/spec/runtime/env_spec.rb
index c59c415c148..ded51d5bb7c 100644
--- a/qa/spec/runtime/env_spec.rb
+++ b/qa/spec/runtime/env_spec.rb
@@ -3,49 +3,62 @@
describe QA::Runtime::Env do
include Support::StubENV
- shared_examples 'boolean method' do |method, env_key, default|
+ shared_examples 'boolean method' do |**kwargs|
+ it_behaves_like 'boolean method with parameter', kwargs
+ end
+
+ shared_examples 'boolean method with parameter' do |method:, param: nil, env_key:, default:|
context 'when there is an env variable set' do
it 'returns false when falsey values specified' do
stub_env(env_key, 'false')
- expect(described_class.public_send(method)).to be_falsey
+ expect(described_class.public_send(method, *param)).to be_falsey
stub_env(env_key, 'no')
- expect(described_class.public_send(method)).to be_falsey
+ expect(described_class.public_send(method, *param)).to be_falsey
stub_env(env_key, '0')
- expect(described_class.public_send(method)).to be_falsey
+ expect(described_class.public_send(method, *param)).to be_falsey
end
it 'returns true when anything else specified' do
stub_env(env_key, 'true')
- expect(described_class.public_send(method)).to be_truthy
+ expect(described_class.public_send(method, *param)).to be_truthy
stub_env(env_key, '1')
- expect(described_class.public_send(method)).to be_truthy
+ expect(described_class.public_send(method, *param)).to be_truthy
stub_env(env_key, 'anything')
- expect(described_class.public_send(method)).to be_truthy
+ expect(described_class.public_send(method, *param)).to be_truthy
end
end
context 'when there is no env variable set' do
it "returns the default, #{default}" do
stub_env(env_key, nil)
- expect(described_class.public_send(method)).to be(default)
+ expect(described_class.public_send(method, *param)).to be(default)
end
end
end
describe '.signup_disabled?' do
- it_behaves_like 'boolean method', :signup_disabled?, 'SIGNUP_DISABLED', false
+ it_behaves_like 'boolean method',
+ method: :signup_disabled?,
+ env_key: 'SIGNUP_DISABLED',
+ default: false
end
describe '.debug?' do
- it_behaves_like 'boolean method', :debug?, 'QA_DEBUG', false
+ it_behaves_like 'boolean method',
+ method: :debug?,
+ env_key: 'QA_DEBUG',
+ default: false
end
describe '.chrome_headless?' do
- it_behaves_like 'boolean method', :chrome_headless?, 'CHROME_HEADLESS', true
+ it_behaves_like 'boolean method',
+ method: :chrome_headless?,
+ env_key: 'CHROME_HEADLESS',
+ default: true
end
describe '.running_in_ci?' do
@@ -182,4 +195,16 @@ describe QA::Runtime::Env do
expect(described_class.log_destination).to eq('path/to_file')
end
end
+
+ describe '.can_test?' do
+ it_behaves_like 'boolean method with parameter',
+ method: :can_test?,
+ param: :git_protocol_v2,
+ env_key: 'QA_CAN_TEST_GIT_PROTOCOL_V2',
+ default: true
+
+ it 'raises ArgumentError if feature is unknown' do
+ expect { described_class.can_test? :foo }.to raise_error(ArgumentError, 'Unknown feature "foo"')
+ end
+ end
end
diff --git a/qa/spec/specs/runner_spec.rb b/qa/spec/specs/runner_spec.rb
index 9ddaf7ab1b3..741821ddf8c 100644
--- a/qa/spec/specs/runner_spec.rb
+++ b/qa/spec/specs/runner_spec.rb
@@ -76,6 +76,20 @@ describe QA::Specs::Runner do
end
end
+ context 'when git protocol v2 is not supported' do
+ before do
+ allow(QA::Runtime::Env).to receive(:can_test?).with(:git_protocol_v2).and_return(false)
+ end
+
+ subject { described_class.new }
+
+ it 'it includes default args and excludes the requires_git_protocol_v2 tag' do
+ expect_rspec_runner_arguments(['--tag', '~orchestrated', '--tag', '~requires_git_protocol_v2', *described_class::DEFAULT_TEST_PATH_ARGS])
+
+ subject.perform
+ end
+ end
+
def expect_rspec_runner_arguments(arguments)
expect(RSpec::Core::Runner).to receive(:run)
.with(arguments, $stderr, $stdout)
diff --git a/scripts/trigger-build b/scripts/trigger-build
index dd0425b6472..873c41db456 100755
--- a/scripts/trigger-build
+++ b/scripts/trigger-build
@@ -107,7 +107,8 @@ module Trigger
{
'GITLAB_VERSION' => ENV['CI_COMMIT_SHA'],
'ALTERNATIVE_SOURCES' => 'true',
- 'ee' => Trigger.ee? ? 'true' : 'false'
+ 'ee' => Trigger.ee? ? 'true' : 'false',
+ 'QA_BRANCH' => ENV['QA_BRANCH'] || 'master'
}
end
end
diff --git a/spec/controllers/projects/blob_controller_spec.rb b/spec/controllers/projects/blob_controller_spec.rb
index 5fdf7f1229d..28f7e4634a5 100644
--- a/spec/controllers/projects/blob_controller_spec.rb
+++ b/spec/controllers/projects/blob_controller_spec.rb
@@ -157,7 +157,7 @@ describe Projects::BlobController do
match_line = JSON.parse(response.body).first
- expect(match_line['type']).to be_nil
+ expect(match_line['type']).to eq('context')
end
it 'adds bottom match line when "t"o is less than blob size' do
@@ -177,7 +177,7 @@ describe Projects::BlobController do
match_line = JSON.parse(response.body).last
- expect(match_line['type']).to be_nil
+ expect(match_line['type']).to eq('context')
end
end
end
diff --git a/spec/features/merge_request/user_posts_diff_notes_spec.rb b/spec/features/merge_request/user_posts_diff_notes_spec.rb
index 51b78d3e7d1..fa148715855 100644
--- a/spec/features/merge_request/user_posts_diff_notes_spec.rb
+++ b/spec/features/merge_request/user_posts_diff_notes_spec.rb
@@ -85,13 +85,12 @@ describe 'Merge request > User posts diff notes', :js do
# `.line_holder` will be an unfolded line.
let(:line_holder) { first('#a5cc2925ca8258af241be7e5b0381edf30266302 .line_holder') }
- it 'allows commenting on the left side' do
- should_allow_commenting(line_holder, 'left')
+ it 'does not allow commenting on the left side' do
+ should_not_allow_commenting(line_holder, 'left')
end
- it 'allows commenting on the right side' do
- # Automatically shifts comment box to left side.
- should_allow_commenting(line_holder, 'right')
+ it 'does not allow commenting on the right side' do
+ should_not_allow_commenting(line_holder, 'right')
end
end
end
@@ -148,8 +147,8 @@ describe 'Merge request > User posts diff notes', :js do
# `.line_holder` will be an unfolded line.
let(:line_holder) { first('.line_holder[id="a5cc2925ca8258af241be7e5b0381edf30266302_1_1"]') }
- it 'allows commenting' do
- should_allow_commenting line_holder
+ it 'does not allow commenting' do
+ should_not_allow_commenting line_holder
end
end
diff --git a/spec/javascripts/diffs/store/mutations_spec.js b/spec/javascripts/diffs/store/mutations_spec.js
index 8821cde76f4..fed04cbaed8 100644
--- a/spec/javascripts/diffs/store/mutations_spec.js
+++ b/spec/javascripts/diffs/store/mutations_spec.js
@@ -98,7 +98,7 @@ describe('DiffsStoreMutations', () => {
it('should call utils.addContextLines with proper params', () => {
const options = {
lineNumbers: { oldLineNumber: 1, newLineNumber: 2 },
- contextLines: [{ oldLine: 1, newLine: 1, lineCode: 'ff9200_1_1', discussions: [] }],
+ contextLines: [{ oldLine: 1 }],
fileHash: 'ff9200',
params: {
bottom: true,
@@ -110,7 +110,7 @@ describe('DiffsStoreMutations', () => {
parallelDiffLines: [],
};
const state = { diffFiles: [diffFile] };
- const lines = [{ oldLine: 1, newLine: 1 }];
+ const lines = [{ oldLine: 1 }];
const findDiffFileSpy = spyOnDependency(mutations, 'findDiffFile').and.returnValue(diffFile);
const removeMatchLineSpy = spyOnDependency(mutations, 'removeMatchLine');
diff --git a/spec/javascripts/notes/components/noteable_discussion_spec.js b/spec/javascripts/notes/components/noteable_discussion_spec.js
index b447e79b0df..81cb3e1f74d 100644
--- a/spec/javascripts/notes/components/noteable_discussion_spec.js
+++ b/spec/javascripts/notes/components/noteable_discussion_spec.js
@@ -3,6 +3,7 @@ import createStore from '~/notes/stores';
import noteableDiscussion from '~/notes/components/noteable_discussion.vue';
import '~/behaviors/markdown/render_gfm';
import { noteableDataMock, discussionMock, notesDataMock } from '../mock_data';
+import mockDiffFile from '../../diffs/mock_data/diff_file';
const discussionWithTwoUnresolvedNotes = 'merge_requests/resolved_diff_discussion.json';
@@ -33,9 +34,20 @@ describe('noteable_discussion component', () => {
expect(vm.$el.querySelector('.user-avatar-link')).not.toBeNull();
});
+ it('should not render discussion header for non diff discussions', () => {
+ expect(vm.$el.querySelector('.discussion-header')).toBeNull();
+ });
+
it('should render discussion header', () => {
- expect(vm.$el.querySelector('.discussion-header')).not.toBeNull();
- expect(vm.$el.querySelector('.notes').children.length).toEqual(discussionMock.notes.length);
+ const discussion = { ...discussionMock };
+ discussion.diff_file = mockDiffFile;
+ discussion.diff_discussion = true;
+ const diffDiscussionVm = new Component({
+ store,
+ propsData: { discussion },
+ }).$mount();
+
+ expect(diffDiscussionVm.$el.querySelector('.discussion-header')).not.toBeNull();
});
describe('actions', () => {
diff --git a/spec/javascripts/notes/components/toggle_replies_widget_spec.js b/spec/javascripts/notes/components/toggle_replies_widget_spec.js
new file mode 100644
index 00000000000..2ead8cc6e6a
--- /dev/null
+++ b/spec/javascripts/notes/components/toggle_replies_widget_spec.js
@@ -0,0 +1,78 @@
+import Vue from 'vue';
+import toggleRepliesWidget from '~/notes/components/toggle_replies_widget.vue';
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
+import { note } from '../mock_data';
+
+const deepCloneObject = obj => JSON.parse(JSON.stringify(obj));
+
+describe('toggle replies widget for notes', () => {
+ let vm;
+ let ToggleRepliesWidget;
+ const noteFromOtherUser = deepCloneObject(note);
+ noteFromOtherUser.author.username = 'fatihacet';
+
+ const noteFromAnotherUser = deepCloneObject(note);
+ noteFromAnotherUser.author.username = 'mgreiling';
+ noteFromAnotherUser.author.name = 'Mike Greiling';
+
+ const replies = [note, note, note, noteFromOtherUser, noteFromAnotherUser];
+
+ beforeEach(() => {
+ ToggleRepliesWidget = Vue.extend(toggleRepliesWidget);
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('collapsed state', () => {
+ beforeEach(() => {
+ vm = mountComponent(ToggleRepliesWidget, {
+ replies,
+ collapsed: true,
+ });
+ });
+
+ it('should render the collapsed', () => {
+ const vmTextContent = vm.$el.textContent.replace(/\s\s+/g, ' ');
+
+ expect(vm.$el.classList.contains('collapsed')).toEqual(true);
+ expect(vm.$el.querySelectorAll('.user-avatar-link').length).toEqual(3);
+ expect(vm.$el.querySelector('time')).not.toBeNull();
+ expect(vmTextContent).toContain('5 replies');
+ expect(vmTextContent).toContain(`Last reply by ${noteFromAnotherUser.author.name}`);
+ });
+
+ it('should emit toggle event when the replies text clicked', () => {
+ const spy = spyOn(vm, '$emit');
+
+ vm.$el.querySelector('.js-replies-text').click();
+
+ expect(spy).toHaveBeenCalledWith('toggle');
+ });
+ });
+
+ describe('expanded state', () => {
+ beforeEach(() => {
+ vm = mountComponent(ToggleRepliesWidget, {
+ replies,
+ collapsed: false,
+ });
+ });
+
+ it('should render expanded state', () => {
+ const vmTextContent = vm.$el.textContent.replace(/\s\s+/g, ' ');
+
+ expect(vm.$el.querySelector('.collapse-replies-btn')).not.toBeNull();
+ expect(vmTextContent).toContain('Collapse replies');
+ });
+
+ it('should emit toggle event when the collapse replies text called', () => {
+ const spy = spyOn(vm, '$emit');
+
+ vm.$el.querySelector('.js-collapse-replies').click();
+
+ expect(spy).toHaveBeenCalledWith('toggle');
+ });
+ });
+});
diff --git a/spec/lib/gitlab/ci/config/entry/job_spec.rb b/spec/lib/gitlab/ci/config/entry/job_spec.rb
index ac9b0c674a5..57d4577a90c 100644
--- a/spec/lib/gitlab/ci/config/entry/job_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb
@@ -118,6 +118,16 @@ describe Gitlab::Ci::Config::Entry::Job do
end
end
+ context 'when it is bigger than 50' do
+ let(:config) { { parallel: 51 } }
+
+ it 'returns error about value too high' do
+ expect(entry).not_to be_valid
+ expect(entry.errors)
+ .to include 'job parallel must be less than or equal to 50'
+ end
+ end
+
context 'when it is not an integer' do
let(:config) { { parallel: 1.5 } }
diff --git a/spec/lib/gitlab/diff/file_spec.rb b/spec/lib/gitlab/diff/file_spec.rb
index 3417896e259..2f51642b58e 100644
--- a/spec/lib/gitlab/diff/file_spec.rb
+++ b/spec/lib/gitlab/diff/file_spec.rb
@@ -41,52 +41,6 @@ describe Gitlab::Diff::File do
end
end
- describe '#unfold_diff_lines' do
- let(:unfolded_lines) { double('expanded-lines') }
- let(:unfolder) { instance_double(Gitlab::Diff::LinesUnfolder) }
- let(:position) { instance_double(Gitlab::Diff::Position, old_line: 10) }
-
- before do
- allow(Gitlab::Diff::LinesUnfolder).to receive(:new) { unfolder }
- end
-
- context 'when unfold required' do
- before do
- allow(unfolder).to receive(:unfold_required?) { true }
- allow(unfolder).to receive(:unfolded_diff_lines) { unfolded_lines }
- end
-
- it 'changes @unfolded to true' do
- diff_file.unfold_diff_lines(position)
-
- expect(diff_file).to be_unfolded
- end
-
- it 'updates @diff_lines' do
- diff_file.unfold_diff_lines(position)
-
- expect(diff_file.diff_lines).to eq(unfolded_lines)
- end
- end
-
- context 'when unfold not required' do
- before do
- allow(unfolder).to receive(:unfold_required?) { false }
- end
-
- it 'keeps @unfolded false' do
- diff_file.unfold_diff_lines(position)
-
- expect(diff_file).not_to be_unfolded
- end
-
- it 'does not update @diff_lines' do
- expect { diff_file.unfold_diff_lines(position) }
- .not_to change(diff_file, :diff_lines)
- end
- end
- end
-
describe '#mode_changed?' do
it { expect(diff_file.mode_changed?).to be_falsey }
end
diff --git a/spec/lib/gitlab/diff/lines_unfolder_spec.rb b/spec/lib/gitlab/diff/lines_unfolder_spec.rb
deleted file mode 100644
index 8e00c8e0e30..00000000000
--- a/spec/lib/gitlab/diff/lines_unfolder_spec.rb
+++ /dev/null
@@ -1,750 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe Gitlab::Diff::LinesUnfolder do
- let(:raw_diff) do
- <<-DIFF.strip_heredoc
- @@ -7,9 +7,6 @@
- "tags": ["devel", "development", "nightly"],
- "desktop-file-name-prefix": "(Development) ",
- "finish-args": [
- - "--share=ipc", "--socket=x11",
- - "--socket=wayland",
- - "--talk-name=org.gnome.OnlineAccounts",
- "--talk-name=org.freedesktop.Tracker1",
- "--filesystem=home",
- "--talk-name=org.gtk.vfs", "--talk-name=org.gtk.vfs.*",
- @@ -62,7 +59,7 @@
- },
- {
- "name": "gnome-desktop",
- - "config-opts": ["--disable-debug-tools", "--disable-udev"],
- + "config-opts": ["--disable-debug-tools", "--disable-"],
- "sources": [
- {
- "type": "git",
- @@ -83,11 +80,6 @@
- "buildsystem": "meson",
- "builddir": true,
- "name": "nautilus",
- - "config-opts": [
- - "-Denable-desktop=false",
- - "-Denable-selinux=false",
- - "--libdir=/app/lib"
- - ],
- "sources": [
- {
- "type": "git",
- DIFF
- end
-
- let(:raw_old_blob) do
- <<-BLOB.strip_heredoc
- {
- "app-id": "org.gnome.Nautilus",
- "runtime": "org.gnome.Platform",
- "runtime-version": "master",
- "sdk": "org.gnome.Sdk",
- "command": "nautilus",
- "tags": ["devel", "development", "nightly"],
- "desktop-file-name-prefix": "(Development) ",
- "finish-args": [
- "--share=ipc", "--socket=x11",
- "--socket=wayland",
- "--talk-name=org.gnome.OnlineAccounts",
- "--talk-name=org.freedesktop.Tracker1",
- "--filesystem=home",
- "--talk-name=org.gtk.vfs", "--talk-name=org.gtk.vfs.*",
- "--filesystem=xdg-run/dconf", "--filesystem=~/.config/dconf:ro",
- "--talk-name=ca.desrt.dconf", "--env=DCONF_USER_CONFIG_DIR=.config/dconf"
- ],
- "cleanup": [ "/include", "/share/bash-completion" ],
- "modules": [
- {
- "name": "exiv2",
- "sources": [
- {
- "type": "archive",
- "url": "http://exiv2.org/builds/exiv2-0.26-trunk.tar.gz",
- "sha256": "c75e3c4a0811bf700d92c82319373b7a825a2331c12b8b37d41eb58e4f18eafb"
- },
- {
- "type": "shell",
- "commands": [
- "cp -f /usr/share/gnu-config/config.sub ./config/",
- "cp -f /usr/share/gnu-config/config.guess ./config/"
- ]
- }
- ]
- },
- {
- "name": "gexiv2",
- "config-opts": [ "--disable-introspection" ],
- "sources": [
- {
- "type": "git",
- "url": "https://git.gnome.org/browse/gexiv2"
- }
- ]
- },
- {
- "name": "tracker",
- "cleanup": [ "/bin", "/etc", "/libexec" ],
- "config-opts": [ "--disable-miner-apps", "--disable-static",
- "--disable-tracker-extract", "--disable-tracker-needle",
- "--disable-tracker-preferences", "--disable-artwork",
- "--disable-tracker-writeback", "--disable-miner-user-guides",
- "--with-bash-completion-dir=no" ],
- "sources": [
- {
- "type": "git",
- "url": "https://git.gnome.org/browse/tracker"
- }
- ]
- },
- {
- "name": "gnome-desktop",
- "config-opts": ["--disable-debug-tools", "--disable-udev"],
- "sources": [
- {
- "type": "git",
- "url": "https://git.gnome.org/browse/gnome-desktop"
- }
- ]
- },
- {
- "name": "gnome-autoar",
- "sources": [
- {
- "type": "git",
- "url": "https://git.gnome.org/browse/gnome-autoar"
- }
- ]
- },
- {
- "buildsystem": "meson",
- "builddir": true,
- "name": "nautilus",
- "config-opts": [
- "-Denable-desktop=false",
- "-Denable-selinux=false",
- "--libdir=/app/lib"
- ],
- "sources": [
- {
- "type": "git",
- "url": "https://gitlab.gnome.org/GNOME/nautilus.git"
- }
- ]
- }
- ]
- },
- {
- "app-id": "foo",
- "runtime": "foo",
- "runtime-version": "foo",
- "sdk": "foo",
- "command": "foo",
- "tags": ["foo", "bar", "kux"],
- "desktop-file-name-prefix": "(Foo) ",
- {
- "buildsystem": "meson",
- "builddir": true,
- "name": "nautilus",
- "sources": [
- {
- "type": "git",
- "url": "https://gitlab.gnome.org/GNOME/nautilus.git"
- }
- ]
- }
- },
- {
- "app-id": "foo",
- "runtime": "foo",
- "runtime-version": "foo",
- "sdk": "foo",
- "command": "foo",
- "tags": ["foo", "bar", "kux"],
- "desktop-file-name-prefix": "(Foo) ",
- {
- "buildsystem": "meson",
- "builddir": true,
- "name": "nautilus",
- "sources": [
- {
- "type": "git",
- "url": "https://gitlab.gnome.org/GNOME/nautilus.git"
- }
- ]
- }
- }
- BLOB
- end
-
- let(:project) { create(:project) }
-
- let(:old_blob) { Gitlab::Git::Blob.new(data: raw_old_blob) }
-
- let(:diff) do
- Gitlab::Git::Diff.new(diff: raw_diff,
- new_path: "build-aux/flatpak/org.gnome.Nautilus.json",
- old_path: "build-aux/flatpak/org.gnome.Nautilus.json",
- a_mode: "100644",
- b_mode: "100644",
- new_file: false,
- renamed_file: false,
- deleted_file: false,
- too_large: false)
- end
-
- let(:diff_file) do
- Gitlab::Diff::File.new(diff, repository: project.repository)
- end
-
- before do
- allow(old_blob).to receive(:load_all_data!)
- allow(diff_file).to receive(:old_blob) { old_blob }
- end
-
- subject { described_class.new(diff_file, position) }
-
- context 'position requires a middle expansion and new match lines' do
- let(:position) do
- Gitlab::Diff::Position.new(base_sha: "1c59dfa64afbea8c721bb09a06a9d326c952ea19",
- start_sha: "1c59dfa64afbea8c721bb09a06a9d326c952ea19",
- head_sha: "1487062132228de836236c522fe52fed4980a46c",
- old_path: "build-aux/flatpak/org.gnome.Nautilus.json",
- new_path: "build-aux/flatpak/org.gnome.Nautilus.json",
- position_type: "text",
- old_line: 43,
- new_line: 40)
- end
-
- context 'blob lines' do
- let(:expected_blob_lines) do
- [[40, 40, " \"config-opts\": [ \"--disable-introspection\" ],"],
- [41, 41, " \"sources\": ["],
- [42, 42, " {"],
- [43, 43, " \"type\": \"git\","],
- [44, 44, " \"url\": \"https://git.gnome.org/browse/gexiv2\""],
- [45, 45, " }"],
- [46, 46, " ]"]]
- end
-
- it 'returns the extracted blob lines correctly' do
- extracted_lines = subject.blob_lines
-
- expect(extracted_lines.size).to eq(7)
-
- extracted_lines.each_with_index do |line, i|
- expect([line.old_line, line.new_line, line.text]).to eq(expected_blob_lines[i])
- end
- end
- end
-
- context 'diff lines' do
- let(:expected_diff_lines) do
- [[7, 7, "@@ -7,9 +7,6 @@"],
- [7, 7, " \"tags\": [\"devel\", \"development\", \"nightly\"],"],
- [8, 8, " \"desktop-file-name-prefix\": \"(Development) \","],
- [9, 9, " \"finish-args\": ["],
- [10, 10, "- \"--share=ipc\", \"--socket=x11\","],
- [11, 10, "- \"--socket=wayland\","],
- [12, 10, "- \"--talk-name=org.gnome.OnlineAccounts\","],
- [13, 10, " \"--talk-name=org.freedesktop.Tracker1\","],
- [14, 11, " \"--filesystem=home\","],
- [15, 12, " \"--talk-name=org.gtk.vfs\", \"--talk-name=org.gtk.vfs.*\","],
-
- # New match line
- [40, 37, "@@ -40,7+37,7 @@"],
-
- # Injected blob lines
- [40, 37, " \"config-opts\": [ \"--disable-introspection\" ],"],
- [41, 38, " \"sources\": ["],
- [42, 39, " {"],
- [43, 40, " \"type\": \"git\","], # comment
- [44, 41, " \"url\": \"https://git.gnome.org/browse/gexiv2\""],
- [45, 42, " }"],
- [46, 43, " ]"],
- # end
-
- # Second match line
- [62, 59, "@@ -62,7+59,7 @@"],
-
- [62, 59, " },"],
- [63, 60, " {"],
- [64, 61, " \"name\": \"gnome-desktop\","],
- [65, 62, "- \"config-opts\": [\"--disable-debug-tools\", \"--disable-udev\"],"],
- [66, 62, "+ \"config-opts\": [\"--disable-debug-tools\", \"--disable-\"],"],
- [66, 63, " \"sources\": ["],
- [67, 64, " {"],
- [68, 65, " \"type\": \"git\","],
- [83, 80, "@@ -83,11 +80,6 @@"],
- [83, 80, " \"buildsystem\": \"meson\","],
- [84, 81, " \"builddir\": true,"],
- [85, 82, " \"name\": \"nautilus\","],
- [86, 83, "- \"config-opts\": ["],
- [87, 83, "- \"-Denable-desktop=false\","],
- [88, 83, "- \"-Denable-selinux=false\","],
- [89, 83, "- \"--libdir=/app/lib\""],
- [90, 83, "- ],"],
- [91, 83, " \"sources\": ["],
- [92, 84, " {"],
- [93, 85, " \"type\": \"git\","]]
- end
-
- it 'return merge of blob lines with diff lines correctly' do
- new_diff_lines = subject.unfolded_diff_lines
-
- expected_diff_lines.each_with_index do |expected_line, i|
- line = new_diff_lines[i]
-
- expect([line.old_pos, line.new_pos, line.text])
- .to eq([expected_line[0], expected_line[1], expected_line[2]])
- end
- end
-
- it 'merged lines have correct line codes' do
- new_diff_lines = subject.unfolded_diff_lines
-
- new_diff_lines.each_with_index do |line, i|
- old_pos, new_pos = expected_diff_lines[i][0], expected_diff_lines[i][1]
-
- unless line.type == 'match'
- expect(line.line_code).to eq(Gitlab::Git.diff_line_code(diff_file.file_path, new_pos, old_pos))
- end
- end
- end
- end
- end
-
- context 'position requires a middle expansion and no top match line' do
- let(:position) do
- Gitlab::Diff::Position.new(base_sha: "1c59dfa64afbea8c721bb09a06a9d326c952ea19",
- start_sha: "1c59dfa64afbea8c721bb09a06a9d326c952ea19",
- head_sha: "1487062132228de836236c522fe52fed4980a46c",
- old_path: "build-aux/flatpak/org.gnome.Nautilus.json",
- new_path: "build-aux/flatpak/org.gnome.Nautilus.json",
- position_type: "text",
- old_line: 16,
- new_line: 17)
- end
-
- context 'blob lines' do
- let(:expected_blob_lines) do
- [[16, 16, " \"--filesystem=xdg-run/dconf\", \"--filesystem=~/.config/dconf:ro\","],
- [17, 17, " \"--talk-name=ca.desrt.dconf\", \"--env=DCONF_USER_CONFIG_DIR=.config/dconf\""],
- [18, 18, " ],"],
- [19, 19, " \"cleanup\": [ \"/include\", \"/share/bash-completion\" ],"]]
- end
-
- it 'returns the extracted blob lines correctly' do
- extracted_lines = subject.blob_lines
-
- expect(extracted_lines.size).to eq(4)
-
- extracted_lines.each_with_index do |line, i|
- expect([line.old_line, line.new_line, line.text]).to eq(expected_blob_lines[i])
- end
- end
- end
-
- context 'diff lines' do
- let(:expected_diff_lines) do
- [[7, 7, "@@ -7,9 +7,6 @@"],
- [7, 7, " \"tags\": [\"devel\", \"development\", \"nightly\"],"],
- [8, 8, " \"desktop-file-name-prefix\": \"(Development) \","],
- [9, 9, " \"finish-args\": ["],
- [10, 10, "- \"--share=ipc\", \"--socket=x11\","],
- [11, 10, "- \"--socket=wayland\","],
- [12, 10, "- \"--talk-name=org.gnome.OnlineAccounts\","],
- [13, 10, " \"--talk-name=org.freedesktop.Tracker1\","],
- [14, 11, " \"--filesystem=home\","],
- [15, 12, " \"--talk-name=org.gtk.vfs\", \"--talk-name=org.gtk.vfs.*\","],
- # No new match needed
-
- # Injected blob lines
- [16, 13, " \"--filesystem=xdg-run/dconf\", \"--filesystem=~/.config/dconf:ro\","],
- [17, 14, " \"--talk-name=ca.desrt.dconf\", \"--env=DCONF_USER_CONFIG_DIR=.config/dconf\""],
- [18, 15, " ],"],
- [19, 16, " \"cleanup\": [ \"/include\", \"/share/bash-completion\" ],"],
- # end
-
- # Second match line
- [62, 59, "@@ -62,4+59,4 @@"],
-
- [62, 59, " },"],
- [63, 60, " {"],
- [64, 61, " \"name\": \"gnome-desktop\","],
- [65, 62, "- \"config-opts\": [\"--disable-debug-tools\", \"--disable-udev\"],"],
- [66, 62, "+ \"config-opts\": [\"--disable-debug-tools\", \"--disable-\"],"],
- [66, 63, " \"sources\": ["],
- [67, 64, " {"],
- [68, 65, " \"type\": \"git\","],
- [83, 80, "@@ -83,11 +80,6 @@"],
- [83, 80, " \"buildsystem\": \"meson\","],
- [84, 81, " \"builddir\": true,"],
- [85, 82, " \"name\": \"nautilus\","],
- [86, 83, "- \"config-opts\": ["],
- [87, 83, "- \"-Denable-desktop=false\","],
- [88, 83, "- \"-Denable-selinux=false\","],
- [89, 83, "- \"--libdir=/app/lib\""],
- [90, 83, "- ],"],
- [91, 83, " \"sources\": ["],
- [92, 84, " {"],
- [93, 85, " \"type\": \"git\","]]
- end
-
- it 'return merge of blob lines with diff lines correctly' do
- new_diff_lines = subject.unfolded_diff_lines
-
- expected_diff_lines.each_with_index do |expected_line, i|
- line = new_diff_lines[i]
-
- expect([line.old_pos, line.new_pos, line.text])
- .to eq([expected_line[0], expected_line[1], expected_line[2]])
- end
- end
-
- it 'merged lines have correct line codes' do
- new_diff_lines = subject.unfolded_diff_lines
-
- new_diff_lines.each_with_index do |line, i|
- old_pos, new_pos = expected_diff_lines[i][0], expected_diff_lines[i][1]
-
- unless line.type == 'match'
- expect(line.line_code).to eq(Gitlab::Git.diff_line_code(diff_file.file_path, new_pos, old_pos))
- end
- end
- end
- end
- end
-
- context 'position requires a middle expansion and no bottom match line' do
- let(:position) do
- Gitlab::Diff::Position.new(base_sha: "1c59dfa64afbea8c721bb09a06a9d326c952ea19",
- start_sha: "1c59dfa64afbea8c721bb09a06a9d326c952ea19",
- head_sha: "1487062132228de836236c522fe52fed4980a46c",
- old_path: "build-aux/flatpak/org.gnome.Nautilus.json",
- new_path: "build-aux/flatpak/org.gnome.Nautilus.json",
- position_type: "text",
- old_line: 82,
- new_line: 79)
- end
-
- context 'blob lines' do
- let(:expected_blob_lines) do
- [[79, 79, " }"],
- [80, 80, " ]"],
- [81, 81, " },"],
- [82, 82, " {"]]
- end
-
- it 'returns the extracted blob lines correctly' do
- extracted_lines = subject.blob_lines
-
- expect(extracted_lines.size).to eq(4)
-
- extracted_lines.each_with_index do |line, i|
- expect([line.old_line, line.new_line, line.text]).to eq(expected_blob_lines[i])
- end
- end
- end
-
- context 'diff lines' do
- let(:expected_diff_lines) do
- [[7, 7, "@@ -7,9 +7,6 @@"],
- [7, 7, " \"tags\": [\"devel\", \"development\", \"nightly\"],"],
- [8, 8, " \"desktop-file-name-prefix\": \"(Development) \","],
- [9, 9, " \"finish-args\": ["],
- [10, 10, "- \"--share=ipc\", \"--socket=x11\","],
- [11, 10, "- \"--socket=wayland\","],
- [12, 10, "- \"--talk-name=org.gnome.OnlineAccounts\","],
- [13, 10, " \"--talk-name=org.freedesktop.Tracker1\","],
- [14, 11, " \"--filesystem=home\","],
- [15, 12, " \"--talk-name=org.gtk.vfs\", \"--talk-name=org.gtk.vfs.*\","],
- [62, 59, "@@ -62,7 +59,7 @@"],
- [62, 59, " },"],
- [63, 60, " {"],
- [64, 61, " \"name\": \"gnome-desktop\","],
- [65, 62, "- \"config-opts\": [\"--disable-debug-tools\", \"--disable-udev\"],"],
- [66, 62, "+ \"config-opts\": [\"--disable-debug-tools\", \"--disable-\"],"],
- [66, 63, " \"sources\": ["],
- [67, 64, " {"],
- [68, 65, " \"type\": \"git\","],
-
- # New top match line
- [79, 76, "@@ -79,4+76,4 @@"],
-
- # Injected blob lines
- [79, 76, " }"],
- [80, 77, " ]"],
- [81, 78, " },"],
- [82, 79, " {"],
- # end
-
- # No new second match line
- [83, 80, " \"buildsystem\": \"meson\","],
- [84, 81, " \"builddir\": true,"],
- [85, 82, " \"name\": \"nautilus\","],
- [86, 83, "- \"config-opts\": ["],
- [87, 83, "- \"-Denable-desktop=false\","],
- [88, 83, "- \"-Denable-selinux=false\","],
- [89, 83, "- \"--libdir=/app/lib\""],
- [90, 83, "- ],"],
- [91, 83, " \"sources\": ["],
- [92, 84, " {"],
- [93, 85, " \"type\": \"git\","]]
- end
-
- it 'return merge of blob lines with diff lines correctly' do
- new_diff_lines = subject.unfolded_diff_lines
-
- expected_diff_lines.each_with_index do |expected_line, i|
- line = new_diff_lines[i]
-
- expect([line.old_pos, line.new_pos, line.text])
- .to eq([expected_line[0], expected_line[1], expected_line[2]])
- end
- end
-
- it 'merged lines have correct line codes' do
- new_diff_lines = subject.unfolded_diff_lines
-
- new_diff_lines.each_with_index do |line, i|
- old_pos, new_pos = expected_diff_lines[i][0], expected_diff_lines[i][1]
-
- unless line.type == 'match'
- expect(line.line_code).to eq(Gitlab::Git.diff_line_code(diff_file.file_path, new_pos, old_pos))
- end
- end
- end
- end
- end
-
- context 'position requires a short top expansion' do
- let(:position) do
- Gitlab::Diff::Position.new(base_sha: "1c59dfa64afbea8c721bb09a06a9d326c952ea19",
- start_sha: "1c59dfa64afbea8c721bb09a06a9d326c952ea19",
- head_sha: "1487062132228de836236c522fe52fed4980a46c",
- old_path: "build-aux/flatpak/org.gnome.Nautilus.json",
- new_path: "build-aux/flatpak/org.gnome.Nautilus.json",
- position_type: "text",
- old_line: 6,
- new_line: 6)
- end
-
- context 'blob lines' do
- let(:expected_blob_lines) do
- [[3, 3, " \"runtime\": \"org.gnome.Platform\","],
- [4, 4, " \"runtime-version\": \"master\","],
- [5, 5, " \"sdk\": \"org.gnome.Sdk\","],
- [6, 6, " \"command\": \"nautilus\","]]
- end
-
- it 'returns the extracted blob lines correctly' do
- extracted_lines = subject.blob_lines
-
- expect(extracted_lines.size).to eq(4)
-
- extracted_lines.each_with_index do |line, i|
- expect([line.old_line, line.new_line, line.text]).to eq(expected_blob_lines[i])
- end
- end
- end
-
- context 'diff lines' do
- let(:expected_diff_lines) do
- # New match line
- [[3, 3, "@@ -3,4+3,4 @@"],
-
- # Injected blob lines
- [3, 3, " \"runtime\": \"org.gnome.Platform\","],
- [4, 4, " \"runtime-version\": \"master\","],
- [5, 5, " \"sdk\": \"org.gnome.Sdk\","],
- [6, 6, " \"command\": \"nautilus\","],
- # end
- [7, 7, " \"tags\": [\"devel\", \"development\", \"nightly\"],"],
- [8, 8, " \"desktop-file-name-prefix\": \"(Development) \","],
- [9, 9, " \"finish-args\": ["],
- [10, 10, "- \"--share=ipc\", \"--socket=x11\","],
- [11, 10, "- \"--socket=wayland\","],
- [12, 10, "- \"--talk-name=org.gnome.OnlineAccounts\","],
- [13, 10, " \"--talk-name=org.freedesktop.Tracker1\","],
- [14, 11, " \"--filesystem=home\","],
- [15, 12, " \"--talk-name=org.gtk.vfs\", \"--talk-name=org.gtk.vfs.*\","],
- [62, 59, "@@ -62,7 +59,7 @@"],
- [62, 59, " },"],
- [63, 60, " {"],
- [64, 61, " \"name\": \"gnome-desktop\","],
- [65, 62, "- \"config-opts\": [\"--disable-debug-tools\", \"--disable-udev\"],"],
- [66, 62, "+ \"config-opts\": [\"--disable-debug-tools\", \"--disable-\"],"],
- [66, 63, " \"sources\": ["],
- [67, 64, " {"],
- [68, 65, " \"type\": \"git\","],
- [83, 80, "@@ -83,11 +80,6 @@"],
- [83, 80, " \"buildsystem\": \"meson\","],
- [84, 81, " \"builddir\": true,"],
- [85, 82, " \"name\": \"nautilus\","],
- [86, 83, "- \"config-opts\": ["],
- [87, 83, "- \"-Denable-desktop=false\","],
- [88, 83, "- \"-Denable-selinux=false\","],
- [89, 83, "- \"--libdir=/app/lib\""],
- [90, 83, "- ],"],
- [91, 83, " \"sources\": ["],
- [92, 84, " {"],
- [93, 85, " \"type\": \"git\","]]
- end
-
- it 'return merge of blob lines with diff lines correctly' do
- new_diff_lines = subject.unfolded_diff_lines
-
- expected_diff_lines.each_with_index do |expected_line, i|
- line = new_diff_lines[i]
-
- expect([line.old_pos, line.new_pos, line.text])
- .to eq([expected_line[0], expected_line[1], expected_line[2]])
- end
- end
-
- it 'merged lines have correct line codes' do
- new_diff_lines = subject.unfolded_diff_lines
-
- new_diff_lines.each_with_index do |line, i|
- old_pos, new_pos = expected_diff_lines[i][0], expected_diff_lines[i][1]
-
- unless line.type == 'match'
- expect(line.line_code).to eq(Gitlab::Git.diff_line_code(diff_file.file_path, new_pos, old_pos))
- end
- end
- end
- end
- end
-
- context 'position sits between two match lines (no expasion needed)' do
- let(:position) do
- Gitlab::Diff::Position.new(base_sha: "1c59dfa64afbea8c721bb09a06a9d326c952ea19",
- start_sha: "1c59dfa64afbea8c721bb09a06a9d326c952ea19",
- head_sha: "1487062132228de836236c522fe52fed4980a46c",
- old_path: "build-aux/flatpak/org.gnome.Nautilus.json",
- new_path: "build-aux/flatpak/org.gnome.Nautilus.json",
- position_type: "text",
- old_line: 64,
- new_line: 61)
- end
-
- context 'diff lines' do
- it 'returns nil' do
- expect(subject.unfolded_diff_lines).to be_nil
- end
- end
- end
-
- context 'position requires bottom expansion and new match lines' do
- let(:position) do
- Gitlab::Diff::Position.new(base_sha: "1c59dfa64afbea8c721bb09a06a9d326c952ea19",
- start_sha: "1c59dfa64afbea8c721bb09a06a9d326c952ea19",
- head_sha: "1487062132228de836236c522fe52fed4980a46c",
- old_path: "build-aux/flatpak/org.gnome.Nautilus.json",
- new_path: "build-aux/flatpak/org.gnome.Nautilus.json",
- position_type: "text",
- old_line: 107,
- new_line: 99)
- end
-
- context 'blob lines' do
- let(:expected_blob_lines) do
- [[104, 104, " \"sdk\": \"foo\","],
- [105, 105, " \"command\": \"foo\","],
- [106, 106, " \"tags\": [\"foo\", \"bar\", \"kux\"],"],
- [107, 107, " \"desktop-file-name-prefix\": \"(Foo) \","],
- [108, 108, " {"],
- [109, 109, " \"buildsystem\": \"meson\","],
- [110, 110, " \"builddir\": true,"]]
- end
-
- it 'returns the extracted blob lines correctly' do
- extracted_lines = subject.blob_lines
-
- expect(extracted_lines.size).to eq(7)
-
- extracted_lines.each_with_index do |line, i|
- expect([line.old_line, line.new_line, line.text]).to eq(expected_blob_lines[i])
- end
- end
- end
-
- context 'diff lines' do
- let(:expected_diff_lines) do
- [[7, 7, "@@ -7,9 +7,6 @@"],
- [7, 7, " \"tags\": [\"devel\", \"development\", \"nightly\"],"],
- [8, 8, " \"desktop-file-name-prefix\": \"(Development) \","],
- [9, 9, " \"finish-args\": ["],
- [10, 10, "- \"--share=ipc\", \"--socket=x11\","],
- [11, 10, "- \"--socket=wayland\","],
- [12, 10, "- \"--talk-name=org.gnome.OnlineAccounts\","],
- [13, 10, " \"--talk-name=org.freedesktop.Tracker1\","],
- [14, 11, " \"--filesystem=home\","],
- [15, 12, " \"--talk-name=org.gtk.vfs\", \"--talk-name=org.gtk.vfs.*\","],
- [62, 59, "@@ -62,7 +59,7 @@"],
- [62, 59, " },"],
- [63, 60, " {"],
- [64, 61, " \"name\": \"gnome-desktop\","],
- [65, 62, "- \"config-opts\": [\"--disable-debug-tools\", \"--disable-udev\"],"],
- [66, 62, "+ \"config-opts\": [\"--disable-debug-tools\", \"--disable-\"],"],
- [66, 63, " \"sources\": ["],
- [67, 64, " {"],
- [68, 65, " \"type\": \"git\","],
- [83, 80, "@@ -83,11 +80,6 @@"],
- [83, 80, " \"buildsystem\": \"meson\","],
- [84, 81, " \"builddir\": true,"],
- [85, 82, " \"name\": \"nautilus\","],
- [86, 83, "- \"config-opts\": ["],
- [87, 83, "- \"-Denable-desktop=false\","],
- [88, 83, "- \"-Denable-selinux=false\","],
- [89, 83, "- \"--libdir=/app/lib\""],
- [90, 83, "- ],"],
- [91, 83, " \"sources\": ["],
- [92, 84, " {"],
- [93, 85, " \"type\": \"git\","],
- # New match line
- [104, 96, "@@ -104,7+96,7 @@"],
-
- # Injected blob lines
- [104, 96, " \"sdk\": \"foo\","],
- [105, 97, " \"command\": \"foo\","],
- [106, 98, " \"tags\": [\"foo\", \"bar\", \"kux\"],"],
- [107, 99, " \"desktop-file-name-prefix\": \"(Foo) \","],
- [108, 100, " {"],
- [109, 101, " \"buildsystem\": \"meson\","],
- [110, 102, " \"builddir\": true,"]]
- # end
- end
-
- it 'return merge of blob lines with diff lines correctly' do
- new_diff_lines = subject.unfolded_diff_lines
-
- expected_diff_lines.each_with_index do |expected_line, i|
- line = new_diff_lines[i]
-
- expect([line.old_pos, line.new_pos, line.text])
- .to eq([expected_line[0], expected_line[1], expected_line[2]])
- end
- end
-
- it 'merged lines have correct line codes' do
- new_diff_lines = subject.unfolded_diff_lines
-
- new_diff_lines.each_with_index do |line, i|
- old_pos, new_pos = expected_diff_lines[i][0], expected_diff_lines[i][1]
-
- unless line.type == 'match'
- expect(line.line_code).to eq(Gitlab::Git.diff_line_code(diff_file.file_path, new_pos, old_pos))
- end
- end
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/import_export/attribute_configuration_spec.rb b/spec/lib/gitlab/import_export/attribute_configuration_spec.rb
index 65f073b2df3..87ab81d8169 100644
--- a/spec/lib/gitlab/import_export/attribute_configuration_spec.rb
+++ b/spec/lib/gitlab/import_export/attribute_configuration_spec.rb
@@ -23,15 +23,23 @@ describe 'Import/Export attribute configuration' do
let(:safe_attributes_file) { 'spec/lib/gitlab/import_export/safe_model_attributes.yml' }
let(:safe_model_attributes) { YAML.load_file(safe_attributes_file) }
+ let(:ee_safe_attributes_file) { 'ee/spec/lib/gitlab/import_export/safe_model_attributes.yml' }
+ let(:ee_safe_model_attributes) { File.exist?(ee_safe_attributes_file) ? YAML.load_file(ee_safe_attributes_file) : {} }
+
it 'has no new columns' do
relation_names.each do |relation_name|
relation_class = relation_class_for_name(relation_name)
relation_attributes = relation_class.new.attributes.keys
- expect(safe_model_attributes[relation_class.to_s]).not_to be_nil, "Expected exported class #{relation_class} to exist in safe_model_attributes"
-
current_attributes = parsed_attributes(relation_name, relation_attributes)
- safe_attributes = safe_model_attributes[relation_class.to_s]
+ safe_attributes = safe_model_attributes[relation_class.to_s].dup || []
+
+ ee_safe_model_attributes[relation_class.to_s].to_a.each do |attribute|
+ safe_attributes << attribute
+ end
+
+ expect(safe_attributes).not_to be_nil, "Expected exported class #{relation_class} to exist in safe_model_attributes"
+
new_attributes = current_attributes - safe_attributes
expect(new_attributes).to be_empty, failure_message(relation_class.to_s, new_attributes)
@@ -43,6 +51,7 @@ describe 'Import/Export attribute configuration' do
It looks like #{relation_class}, which is exported using the project Import/Export, has new attributes: #{new_attributes.join(',')}
Please add the attribute(s) to SAFE_MODEL_ATTRIBUTES if you consider this can be exported.
+ #{"If the model/associations are EE-specific, use `#{File.expand_path(ee_safe_attributes_file)}`.\n" if ee_safe_model_attributes.any?}
Otherwise, please blacklist the attribute(s) in IMPORT_EXPORT_CONFIG by adding it to its correspondent
model in the +excluded_attributes+ section.
diff --git a/spec/services/merge_requests/reload_diffs_service_spec.rb b/spec/services/merge_requests/reload_diffs_service_spec.rb
index 5acd01828cb..546c9f277c5 100644
--- a/spec/services/merge_requests/reload_diffs_service_spec.rb
+++ b/spec/services/merge_requests/reload_diffs_service_spec.rb
@@ -31,11 +31,32 @@ describe MergeRequests::ReloadDiffsService, :use_clean_rails_memory_store_cachin
end
context 'cache clearing' do
+ before do
+ allow_any_instance_of(Gitlab::Diff::File).to receive(:text?).and_return(true)
+ allow_any_instance_of(Gitlab::Diff::File).to receive(:diffable?).and_return(true)
+ end
+
+ it 'retrieves the diff files to cache the highlighted result' do
+ new_diff = merge_request.create_merge_request_diff
+ cache_key = new_diff.diffs_collection.cache_key
+
+ expect(merge_request).to receive(:create_merge_request_diff).and_return(new_diff)
+ expect(Rails.cache).to receive(:read).with(cache_key).and_call_original
+ expect(Rails.cache).to receive(:write).with(cache_key, anything, anything).and_call_original
+
+ subject.execute
+ end
+
it 'clears the cache for older diffs on the merge request' do
old_diff = merge_request.merge_request_diff
old_cache_key = old_diff.diffs_collection.cache_key
+ new_diff = merge_request.create_merge_request_diff
+ new_cache_key = new_diff.diffs_collection.cache_key
+ expect(merge_request).to receive(:create_merge_request_diff).and_return(new_diff)
expect(Rails.cache).to receive(:delete).with(old_cache_key).and_call_original
+ expect(Rails.cache).to receive(:read).with(new_cache_key).and_call_original
+ expect(Rails.cache).to receive(:write).with(new_cache_key, anything, anything).and_call_original
subject.execute
end
diff --git a/spec/services/notes/create_service_spec.rb b/spec/services/notes/create_service_spec.rb
index 80b015d4cd0..b1290fd0d47 100644
--- a/spec/services/notes/create_service_spec.rb
+++ b/spec/services/notes/create_service_spec.rb
@@ -57,57 +57,6 @@ describe Notes::CreateService do
end
end
- context 'noteable highlight cache clearing' do
- let(:project_with_repo) { create(:project, :repository) }
- let(:merge_request) do
- create(:merge_request, source_project: project_with_repo,
- target_project: project_with_repo)
- end
-
- let(:position) do
- Gitlab::Diff::Position.new(old_path: "files/ruby/popen.rb",
- new_path: "files/ruby/popen.rb",
- old_line: nil,
- new_line: 14,
- diff_refs: merge_request.diff_refs)
- end
-
- let(:new_opts) do
- opts.merge(in_reply_to_discussion_id: nil,
- type: 'DiffNote',
- noteable_type: 'MergeRequest',
- noteable_id: merge_request.id,
- position: position.to_h)
- end
-
- before do
- allow_any_instance_of(Gitlab::Diff::Position)
- .to receive(:unfolded_diff?) { true }
- end
-
- it 'clears noteable diff cache when it was unfolded for the note position' do
- expect_any_instance_of(Gitlab::Diff::HighlightCache).to receive(:clear)
-
- described_class.new(project_with_repo, user, new_opts).execute
- end
-
- it 'does not clear cache when note is not the first of the discussion' do
- prev_note =
- create(:diff_note_on_merge_request, noteable: merge_request,
- project: project_with_repo)
- reply_opts =
- opts.merge(in_reply_to_discussion_id: prev_note.discussion_id,
- type: 'DiffNote',
- noteable_type: 'MergeRequest',
- noteable_id: merge_request.id,
- position: position.to_h)
-
- expect(merge_request).not_to receive(:diffs)
-
- described_class.new(project_with_repo, user, reply_opts).execute
- end
- end
-
context 'note diff file' do
let(:project_with_repo) { create(:project, :repository) }
let(:merge_request) do
diff --git a/spec/services/notes/destroy_service_spec.rb b/spec/services/notes/destroy_service_spec.rb
index b1f4e87e8ea..64445be560e 100644
--- a/spec/services/notes/destroy_service_spec.rb
+++ b/spec/services/notes/destroy_service_spec.rb
@@ -21,38 +21,5 @@ describe Notes::DestroyService do
expect { described_class.new(project, user).execute(note) }
.to change { user.todos_pending_count }.from(1).to(0)
end
-
- context 'noteable highlight cache clearing' do
- let(:repo_project) { create(:project, :repository) }
- let(:merge_request) do
- create(:merge_request, source_project: repo_project,
- target_project: repo_project)
- end
-
- let(:note) do
- create(:diff_note_on_merge_request, project: repo_project,
- noteable: merge_request)
- end
-
- before do
- allow(note.position).to receive(:unfolded_diff?) { true }
- end
-
- it 'clears noteable diff cache when it was unfolded for the note position' do
- expect(merge_request).to receive_message_chain(:diffs, :clear_cache)
-
- described_class.new(repo_project, user).execute(note)
- end
-
- it 'does not clear cache when note is not the first of the discussion' do
- reply_note = create(:diff_note_on_merge_request, in_reply_to: note,
- project: repo_project,
- noteable: merge_request)
-
- expect(merge_request).not_to receive(:diffs)
-
- described_class.new(repo_project, user).execute(reply_note)
- end
- end
end
end
diff --git a/spec/support/features/discussion_comments_shared_example.rb b/spec/support/features/discussion_comments_shared_example.rb
index 80604395adf..18cf08f0b9e 100644
--- a/spec/support/features/discussion_comments_shared_example.rb
+++ b/spec/support/features/discussion_comments_shared_example.rb
@@ -150,17 +150,25 @@ shared_examples 'discussion comments' do |resource_name|
end
if resource_name == 'merge request'
- let(:note_id) { find("#{comments_selector} .note", match: :first)['data-note-id'] }
+ let(:note_id) { find("#{comments_selector} .note:first-child", match: :first)['data-note-id'] }
+ let(:reply_id) { find("#{comments_selector} .note:last-child", match: :first)['data-note-id'] }
it 'shows resolved discussion when toggled' do
+ find("#{comments_selector} .js-vue-discussion-reply").click
+ find("#{comments_selector} .note-textarea").send_keys('a')
+
+ click_button "Comment"
+ wait_for_requests
+
click_button "Resolve discussion"
+ wait_for_requests
expect(page).to have_selector(".note-row-#{note_id}", visible: true)
refresh
- click_button "Toggle discussion"
+ click_button "1 reply"
- expect(page).to have_selector(".note-row-#{note_id}", visible: true)
+ expect(page).to have_selector(".note-row-#{reply_id}", visible: true)
end
end
end
diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb
index 9e87b877b93..1f00cdf7e92 100644
--- a/spec/support/helpers/test_env.rb
+++ b/spec/support/helpers/test_env.rb
@@ -162,8 +162,9 @@ module TestEnv
version: Gitlab::GitalyClient.expected_server_version,
task: "gitlab:gitaly:install[#{gitaly_dir},#{repos_path}]") do
- start_gitaly(gitaly_dir)
- end
+ Gitlab::SetupHelper.create_gitaly_configuration(gitaly_dir, { 'default' => repos_path }, force: true)
+ start_gitaly(gitaly_dir)
+ end
end
def start_gitaly(gitaly_dir)
diff --git a/spec/workers/stuck_import_jobs_worker_spec.rb b/spec/workers/stuck_import_jobs_worker_spec.rb
index 2169c14218b..e94d2be9850 100644
--- a/spec/workers/stuck_import_jobs_worker_spec.rb
+++ b/spec/workers/stuck_import_jobs_worker_spec.rb
@@ -8,29 +8,29 @@ describe StuckImportJobsWorker do
context 'when the import status was already updated' do
before do
allow(Gitlab::SidekiqStatus).to receive(:completed_jids) do
- project.import_start
- project.import_finish
+ import_state.start
+ import_state.finish
- [project.import_jid]
+ [import_state.jid]
end
end
it 'does not mark the project as failed' do
worker.perform
- expect(project.reload.import_status).to eq('finished')
+ expect(import_state.reload.status).to eq('finished')
end
end
context 'when the import status was not updated' do
before do
- allow(Gitlab::SidekiqStatus).to receive(:completed_jids).and_return([project.import_jid])
+ allow(Gitlab::SidekiqStatus).to receive(:completed_jids).and_return([import_state.jid])
end
it 'marks the project as failed' do
worker.perform
- expect(project.reload.import_status).to eq('failed')
+ expect(import_state.reload.status).to eq('failed')
end
end
end
@@ -41,27 +41,27 @@ describe StuckImportJobsWorker do
end
it 'does not mark the project as failed' do
- expect { worker.perform }.not_to change { project.reload.import_status }
+ expect { worker.perform }.not_to change { import_state.reload.status }
end
end
end
describe 'with scheduled import_status' do
it_behaves_like 'project import job detection' do
- let(:project) { create(:project, :import_scheduled) }
+ let(:import_state) { create(:project, :import_scheduled).import_state }
before do
- project.import_state.update(jid: '123')
+ import_state.update(jid: '123')
end
end
end
describe 'with started import_status' do
it_behaves_like 'project import job detection' do
- let(:project) { create(:project, :import_started) }
+ let(:import_state) { create(:project, :import_started).import_state }
before do
- project.import_state.update(jid: '123')
+ import_state.update(jid: '123')
end
end
end
diff --git a/vendor/gitignore/Android.gitignore b/vendor/gitignore/Android.gitignore
index 39b6783cef8..69eda01429a 100644
--- a/vendor/gitignore/Android.gitignore
+++ b/vendor/gitignore/Android.gitignore
@@ -1,6 +1,7 @@
# Built application files
*.apk
*.ap_
+*.aab
# Files for the ART/Dalvik VM
*.dex
@@ -43,8 +44,9 @@ captures/
.idea/caches
# Keystore files
-# Uncomment the following line if you do not want to check your keystore files in.
+# Uncomment the following lines if you do not want to check your keystore files in.
#*.jks
+#*.keystore
# External native build folder generated in Android Studio 2.2 and later
.externalNativeBuild
diff --git a/vendor/gitignore/Delphi.gitignore b/vendor/gitignore/Delphi.gitignore
index 000ee5f104b..9532800ba22 100644
--- a/vendor/gitignore/Delphi.gitignore
+++ b/vendor/gitignore/Delphi.gitignore
@@ -64,3 +64,6 @@ __recovery/
# Castalia statistics file (since XE7 Castalia is distributed with Delphi)
*.stat
+
+# Boss dependency manager vendor folder https://github.com/HashLoad/boss
+modules/
diff --git a/vendor/gitignore/Elixir.gitignore b/vendor/gitignore/Elixir.gitignore
index 86e4c3f3905..b263cd10f37 100644
--- a/vendor/gitignore/Elixir.gitignore
+++ b/vendor/gitignore/Elixir.gitignore
@@ -7,3 +7,4 @@ erl_crash.dump
*.ez
*.beam
/config/*.secret.exs
+.elixir_ls/
diff --git a/vendor/gitignore/Global/Images.gitignore b/vendor/gitignore/Global/Images.gitignore
new file mode 100644
index 00000000000..97dcdbe6a95
--- /dev/null
+++ b/vendor/gitignore/Global/Images.gitignore
@@ -0,0 +1,63 @@
+# JPEG
+*.jpg
+*.jpeg
+*.jpe
+*.jif
+*.jfif
+*.jfi
+
+# JPEG 2000
+*.jp2
+*.j2k
+*.jpf
+*.jpx
+*.jpm
+*.mj2
+
+# JPEG XR
+*.jxr
+*.hdp
+*.wdp
+
+# Graphics Interchange Format
+*.gif
+
+# RAW
+*.raw
+
+# Web P
+*.webp
+
+# Portable Network Graphics
+*.png
+
+# Animated Portable Network Graphics
+*.apng
+
+# Multiple-image Network Graphics
+*.mng
+
+# Tagged Image File Format
+*.tiff
+*.tif
+
+# Scalable Vector Graphics
+*.svg
+*.svgz
+
+# Portable Document Format
+*.pdf
+
+# X BitMap
+*.xbm
+
+# BMP
+*.bmp
+*.dib
+
+# ICO
+*.ico
+
+# 3D Images
+*.3dm
+*.max
diff --git a/vendor/gitignore/Global/NetBeans.gitignore b/vendor/gitignore/Global/NetBeans.gitignore
index 254108cd23b..863bc7fa66e 100644
--- a/vendor/gitignore/Global/NetBeans.gitignore
+++ b/vendor/gitignore/Global/NetBeans.gitignore
@@ -1,4 +1,4 @@
-nbproject/private/
+**/nbproject/private/
build/
nbbuild/
dist/
diff --git a/vendor/gitignore/Global/PSoCCreator.gitignore b/vendor/gitignore/Global/PSoCCreator.gitignore
new file mode 100644
index 00000000000..15ae040bcda
--- /dev/null
+++ b/vendor/gitignore/Global/PSoCCreator.gitignore
@@ -0,0 +1,18 @@
+# Project Settings
+*.cywrk.*
+*.cyprj.*
+
+# Generated Assets and Resources
+Debug/
+Release/
+Export/
+*/codegentemp
+*/Generated_Source
+*_datasheet.pdf
+*_timing.html
+*.cycdx
+*.cyfit
+*.rpt
+*.svd
+*.log
+*.zip
diff --git a/vendor/gitignore/Global/Xcode.gitignore b/vendor/gitignore/Global/Xcode.gitignore
index cd0c7d3e45a..b01314d3a64 100644
--- a/vendor/gitignore/Global/Xcode.gitignore
+++ b/vendor/gitignore/Global/Xcode.gitignore
@@ -2,17 +2,11 @@
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
-## User settings
-xcuserdata/
-
-## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
-*.xcscmblueprint
-*.xccheckout
-
-## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
+## Build generated
build/
DerivedData/
-*.moved-aside
+
+## Various settings
*.pbxuser
!default.pbxuser
*.mode1v3
@@ -21,3 +15,65 @@ DerivedData/
!default.mode2v3
*.perspectivev3
!default.perspectivev3
+xcuserdata/
+
+## Other
+*.moved-aside
+*.xccheckout
+*.xcscmblueprint
+
+## Obj-C/Swift specific
+*.hmap
+*.ipa
+*.dSYM.zip
+*.dSYM
+
+## Playgrounds
+timeline.xctimeline
+playground.xcworkspace
+
+# Swift Package Manager
+#
+# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
+# Packages/
+# Package.pins
+# Package.resolved
+.build/
+
+# CocoaPods
+#
+# We recommend against adding the Pods directory to your .gitignore. However
+# you should judge for yourself, the pros and cons are mentioned at:
+# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
+#
+# Pods/
+#
+# Add this line if you want to avoid checking in source code from the Xcode workspace
+# *.xcworkspace
+
+# Carthage
+#
+# Add this line if you want to avoid checking in source code from Carthage dependencies.
+# Carthage/Checkouts
+
+Carthage/Build
+
+# fastlane
+#
+# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
+# screenshots whenever they are needed.
+# For more information about the recommended setup visit:
+# https://docs.fastlane.tools/best-practices/source-control/#source-control
+
+fastlane/report.xml
+fastlane/Preview.html
+fastlane/screenshots/**/*.png
+fastlane/test_output
+
+# Code Injection
+#
+# After new code Injection tools there's a generated folder /iOSInjectionProject
+# https://github.com/johnno1962/injectionforxcode
+
+iOSInjectionProject/
+
diff --git a/vendor/gitignore/Laravel.gitignore b/vendor/gitignore/Laravel.gitignore
index 67e2146f2bc..6552ddf8a06 100644
--- a/vendor/gitignore/Laravel.gitignore
+++ b/vendor/gitignore/Laravel.gitignore
@@ -1,4 +1,4 @@
-vendor/
+/vendor/
node_modules/
npm-debug.log
yarn-error.log
diff --git a/vendor/gitignore/Magento.gitignore b/vendor/gitignore/Magento.gitignore
index 6f1fa223992..abe6d79fedb 100644
--- a/vendor/gitignore/Magento.gitignore
+++ b/vendor/gitignore/Magento.gitignore
@@ -2,6 +2,8 @@
# Magento Default Files #
#--------------------------#
+/PATCH_*.sh
+
/app/etc/local.xml
/media/*
diff --git a/vendor/gitignore/Node.gitignore b/vendor/gitignore/Node.gitignore
index c221276ebae..e1da6ae8ea5 100644
--- a/vendor/gitignore/Node.gitignore
+++ b/vendor/gitignore/Node.gitignore
@@ -71,3 +71,6 @@ typings/
# Serverless directories
.serverless
+
+# FuseBox cache
+.fusebox/
diff --git a/vendor/gitignore/Python.gitignore b/vendor/gitignore/Python.gitignore
index 6f7a6d9c3d7..510c73d0fdb 100644
--- a/vendor/gitignore/Python.gitignore
+++ b/vendor/gitignore/Python.gitignore
@@ -109,3 +109,6 @@ venv.bak/
.mypy_cache/
.dmypy.json
dmypy.json
+
+# Pyre type checker
+.pyre/
diff --git a/vendor/gitignore/Rails.gitignore b/vendor/gitignore/Rails.gitignore
index 78eb74fdc26..38ba1b5b38c 100644
--- a/vendor/gitignore/Rails.gitignore
+++ b/vendor/gitignore/Rails.gitignore
@@ -50,6 +50,7 @@ node_modules/
# Ignore precompiled javascript packs
/public/packs
/public/packs-test
+/public/assets
# Ignore yarn files
/yarn-error.log
diff --git a/vendor/gitignore/Unity.gitignore b/vendor/gitignore/Unity.gitignore
index 0210746b1a5..833e6d4291c 100644
--- a/vendor/gitignore/Unity.gitignore
+++ b/vendor/gitignore/Unity.gitignore
@@ -23,6 +23,7 @@ ExportedObj/
*.svd
*.pdb
*.opendb
+*.VC.db
# Unity3D generated meta files
*.pidb.meta
diff --git a/vendor/licenses.csv b/vendor/licenses.csv
index 5a7f7c0ebd1..ea3d3fd02f9 100644
--- a/vendor/licenses.csv
+++ b/vendor/licenses.csv
@@ -67,8 +67,9 @@
@babel/template,7.1.2,MIT
@babel/traverse,7.1.0,MIT
@babel/types,7.1.2,MIT
+@gitlab-org/gitlab-svgs,1.32.0,MIT
+@gitlab-org/gitlab-ui,1.10.0,MIT
@gitlab/svgs,1.35.0,MIT
-@gitlab-org/gitlab-ui,1.8.0,MIT
@sindresorhus/is,0.7.0,MIT
@types/jquery,2.0.48,MIT
@vue/component-compiler-utils,2.2.0,MIT
@@ -133,7 +134,6 @@ asciidoctor,1.5.6.2,MIT
asciidoctor-plantuml,0.0.8,MIT
asn1.js,4.10.1,MIT
assert,1.4.1,MIT
-asset_sync,2.4.0,MIT
assign-symbols,1.0.0,MIT
async-each,1.0.1,MIT
async-limiter,1.0.0,MIT
@@ -161,7 +161,6 @@ big.js,3.2.0,MIT
binary-extensions,1.11.0,MIT
binaryextensions,2.1.1,MIT
bindata,2.4.3,ruby
-blackst0ne-mermaid,7.1.0-fixed,MIT
bluebird,3.5.1,MIT
bn.js,4.11.8,MIT
body-parser,1.18.2,MIT
@@ -264,8 +263,8 @@ css-selector-tokenizer,0.7.0,MIT
css_parser,1.5.0,MIT
cssesc,0.1.0,MIT
cyclist,0.2.2,MIT*
-d3,3.5.17,New BSD
d3,4.12.2,New BSD
+d3,4.13.0,New BSD
d3-array,1.2.1,New BSD
d3-axis,1.0.8,New BSD
d3-brush,1.0.4,New BSD
@@ -278,6 +277,7 @@ d3-dsv,1.0.8,New BSD
d3-ease,1.0.3,New BSD
d3-force,1.1.0,New BSD
d3-format,1.2.1,New BSD
+d3-format,1.2.2,New BSD
d3-geo,1.9.1,New BSD
d3-hierarchy,1.1.5,New BSD
d3-interpolate,1.1.6,New BSD
@@ -289,6 +289,7 @@ d3-random,1.1.0,New BSD
d3-request,1.0.6,New BSD
d3-scale,1.0.7,New BSD
d3-selection,1.2.0,New BSD
+d3-selection,1.3.0,New BSD
d3-shape,1.2.0,New BSD
d3-time,1.0.8,New BSD
d3-time-format,2.1.1,New BSD
@@ -296,8 +297,8 @@ d3-timer,1.0.7,New BSD
d3-transition,1.1.1,New BSD
d3-voronoi,1.1.2,New BSD
d3-zoom,1.7.1,New BSD
-dagre-d3-renderer,0.4.24,MIT
-dagre-layout,0.8.0,MIT
+dagre-d3-renderer,0.5.8,MIT
+dagre-layout,0.8.8,MIT
date-now,0.1.4,MIT
dateformat,3.0.3,MIT
de-indent,1.0.2,MIT
@@ -328,7 +329,6 @@ device_detector,1.0.0,LGPL
devise,4.4.3,MIT
devise-two-factor,3.0.0,MIT
diff,3.5.0,New BSD
-diff-lcs,1.3,"MIT,Artistic-2.0,GPL-2.0+"
diffie-hellman,5.0.2,MIT
diffy,3.1.0,MIT
document-register-element,1.3.0,MIT
@@ -370,6 +370,7 @@ es6-promise,3.0.2,MIT
escape-html,1.0.3,MIT
escape-string-regexp,1.0.5,MIT
escape_utils,1.1.1,MIT
+escaper,2.5.3,MIT
eslint-scope,4.0.0,Simplified BSD
esrecurse,4.2.1,Simplified BSD
estraverse,4.2.0,Simplified BSD
@@ -447,12 +448,8 @@ get-value,2.0.6,MIT
get_process_mem,0.2.0,MIT
gettext_i18n_rails,1.8.0,MIT
gettext_i18n_rails_js,1.3.0,MIT
-gitaly-proto,0.118.1,MIT
-github-linguist,5.3.3,MIT
+gitaly-proto,0.123.0,MIT
github-markup,1.7.0,MIT
-gitlab-flowdock-git-hook,1.0.1,MIT
-gitlab-gollum-lib,4.2.7.5,MIT
-gitlab-grit,2.8.2,MIT
gitlab-markup,1.6.4,MIT
gitlab-sidekiq-fetcher,0.3.0,LGPL
gitlab_omniauth-ldap,2.0.4,MIT
@@ -461,13 +458,12 @@ glob-parent,3.1.0,ISC
global-modules-path,2.1.0,Apache 2.0
globalid,0.4.1,MIT
globals,11.7.0,MIT
-gollum-grit_adapter,1.0.1,MIT
gon,6.2.0,MIT
good-listener,1.2.2,MIT
google-api-client,0.23.4,Apache 2.0
-google-protobuf,3.5.1,New BSD
-googleapis-common-protos-types,1.0.1,Apache 2.0
-googleauth,0.6.2,Apache 2.0
+google-protobuf,3.6.1,New BSD
+googleapis-common-protos-types,1.0.2,Apache 2.0
+googleauth,0.6.6,Apache 2.0
got,8.3.0,MIT
gpgme,2.0.13,LGPL-2.1+
graceful-fs,4.1.11,ISC
@@ -476,9 +472,9 @@ grape-entity,0.7.1,MIT
grape-path-helpers,1.0.6,MIT
grape_logging,1.7.0,MIT
graphiql-rails,1.4.10,MIT
-graphlib,2.1.1,MIT
+graphlibrary,2.2.0,MIT
graphql,1.8.1,MIT
-grpc,1.11.0,Apache 2.0
+grpc,1.15.0,Apache 2.0
gzip-size,5.0.0,MIT
hamlit,2.8.8,MIT
hangouts-chat,0.0.5,MIT
@@ -572,6 +568,7 @@ is-plain-obj,1.1.0,MIT
is-plain-object,2.0.4,MIT
is-promise,2.1.0,MIT
is-regex,1.0.4,MIT
+is-regexp,1.0.0,MIT
is-retry-allowed,1.1.0,MIT
is-stream,1.1.0,MIT
is-symbol,1.0.2,MIT
@@ -620,7 +617,6 @@ lazy-cache,2.0.2,MIT
lcid,2.0.0,MIT
licensee,8.9.2,MIT
lie,3.1.1,MIT
-little-plugger,1.1.4,MIT
loader-runner,2.3.0,MIT
loader-utils,1.1.0,MIT
locale,2.1.2,"ruby,LGPLv3+"
@@ -635,7 +631,6 @@ lodash.get,4.4.2,MIT
lodash.isequal,4.5.0,MIT
lodash.mergewith,4.6.0,MIT
lodash.startcase,4.4.0,MIT
-logging,2.2.2,MIT
lograge,0.10.0,MIT
loofah,2.2.2,MIT
loose-envify,1.4.0,MIT
@@ -658,6 +653,7 @@ memoist,0.16.0,MIT
memory-fs,0.4.1,MIT
merge-descriptors,1.0.1,MIT
merge-source-map,1.1.0,MIT
+mermaid,8.0.0-rc.8,MIT
method_source,0.9.0,MIT
methods,1.1.2,MIT
micromatch,3.1.10,MIT
@@ -685,11 +681,10 @@ mississippi,2.0.0,Simplified BSD
mississippi,3.0.0,Simplified BSD
mixin-deep,1.3.1,MIT
mkdirp,0.5.1,MIT
-moment,2.19.2,MIT
+moment,2.22.2,MIT
monaco-editor,0.14.3,MIT
monaco-editor-webpack-plugin,1.5.4,MIT
mousetrap,1.4.6,Apache 2.0
-mousetrap-rails,1.4.6,"MIT,Apache"
move-concurrently,1.0.1,ISC
ms,2.0.0,MIT
ms,2.1.1,MIT
@@ -760,7 +755,7 @@ opener,1.5.1,(WTFPL OR MIT)
opn,4.0.2,MIT
org-ruby,0.9.12,MIT
orm_adapter,0.5.0,MIT
-os,0.9.6,MIT
+os,1.0.0,MIT
os-browserify,0.3.0,MIT
os-homedir,1.0.2,MIT
os-locale,3.0.1,MIT
@@ -806,7 +801,6 @@ pkg-dir,3.0.0,MIT
po_to_json,1.0.1,MIT
popper.js,1.14.3,MIT
posix-character-classes,0.1.1,MIT
-posix-spawn,0.3.13,MIT
postcss,6.0.23,MIT
postcss-modules-extract-imports,1.2.0,ISC
postcss-modules-local-by-default,1.2.0,MIT
@@ -830,6 +824,8 @@ prr,1.0.1,MIT
pseudomap,1.0.2,ISC
public-encrypt,4.0.0,MIT
public_suffix,3.0.3,MIT
+puma,3.12.0,New BSD
+puma_worker_killer,0.1.0,MIT
pump,2.0.1,MIT
pump,3.0.0,MIT
pumpify,1.4.0,MIT
@@ -929,7 +925,7 @@ ruby_parser,3.9.0,MIT
rubyntlm,0.6.2,MIT
rubypants,0.2.0,BSD
rufus-scheduler,3.4.0,MIT
-rugged,0.27.4,MIT
+rugged,0.27.5,MIT
run-async,2.3.0,MIT
run-queue,1.0.3,ISC
rw,1.3.3,New BSD
@@ -949,6 +945,7 @@ sawyer,0.8.1,MIT
sax,1.2.4,ISC
schema-utils,0.4.5,MIT
schema-utils,1.0.0,MIT
+scope-css,1.2.1,MIT
seed-fu,2.3.7,MIT
select,1.1.2,MIT
select2,3.5.2-browserify,Apache*
@@ -975,8 +972,9 @@ shebang-regex,1.0.0,MIT
sidekiq,5.2.1,LGPL
sidekiq-cron,0.6.0,MIT
signal-exit,3.0.2,ISC
-signet,0.8.1,Apache 2.0
+signet,0.11.0,Apache 2.0
slack-notifier,1.5.1,MIT
+slugify,1.3.1,MIT
smooshpack,0.0.48,LGPL
snapdragon,0.8.1,MIT
snapdragon-node,2.1.1,MIT
@@ -1012,9 +1010,9 @@ string-width,1.0.2,MIT
string-width,2.1.1,MIT
string_decoder,0.10.31,MIT
string_decoder,1.1.1,MIT
-stringex,2.8.4,MIT
strip-ansi,3.0.1,MIT
strip-ansi,4.0.0,MIT
+strip-css-comments,3.0.0,MIT
strip-eof,1.0.0,MIT
strip-json-comments,2.0.1,MIT
style-loader,0.23.0,MIT