Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/behaviors/markdown/copy_as_gfm.js33
-rw-r--r--app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js42
-rw-r--r--app/assets/javascripts/diffs/components/compare_versions.vue18
-rw-r--r--app/assets/javascripts/diffs/components/diff_file_header.vue3
-rw-r--r--app/assets/javascripts/diffs/components/diff_stats.vue52
-rw-r--r--app/assets/javascripts/diffs/components/tree_list.vue11
-rw-r--r--app/assets/javascripts/diffs/store/modules/diff_state.js2
-rw-r--r--app/assets/javascripts/due_date_select.js2
-rw-r--r--app/assets/javascripts/issuable_form.js1
-rw-r--r--app/assets/javascripts/issue_show/components/description.vue14
-rw-r--r--app/assets/javascripts/lib/utils/file_upload.js3
-rw-r--r--app/assets/javascripts/member_expiration_date.js1
-rw-r--r--app/assets/javascripts/merge_request.js8
-rw-r--r--app/assets/javascripts/monitoring/components/charts/area.vue4
-rw-r--r--app/assets/javascripts/monitoring/components/dashboard.vue6
-rw-r--r--app/assets/javascripts/pages/dashboard/projects/index.js2
-rw-r--r--app/assets/javascripts/pages/explore/projects/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/environments/metrics/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/graphs/charts/index.js18
-rw-r--r--app/assets/javascripts/pages/users/activity_calendar.js10
-rw-r--r--app/assets/javascripts/pages/users/user_tabs.js2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/commit_edit.vue40
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/commit_message_dropdown.vue38
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue91
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue278
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js2
-rw-r--r--app/assets/javascripts/vue_shared/components/pikaday.vue1
-rw-r--r--app/assets/stylesheets/framework/common.scss9
-rw-r--r--app/assets/stylesheets/framework/files.scss20
-rw-r--r--app/assets/stylesheets/framework/variables.scss8
-rw-r--r--app/assets/stylesheets/pages/diff.scss52
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss43
-rw-r--r--app/assets/stylesheets/pages/projects.scss17
-rw-r--r--app/controllers/clusters/clusters_controller.rb2
-rw-r--r--app/controllers/omniauth_callbacks_controller.rb6
-rw-r--r--app/controllers/profiles/preferences_controller.rb12
-rw-r--r--app/controllers/projects/environments_controller.rb5
-rw-r--r--app/controllers/projects/error_tracking_controller.rb40
-rw-r--r--app/controllers/projects/merge_requests/application_controller.rb3
-rw-r--r--app/finders/issuable_finder.rb2
-rw-r--r--app/helpers/application_settings_helper.rb4
-rw-r--r--app/helpers/auto_devops_helper.rb37
-rw-r--r--app/helpers/preferences_helper.rb15
-rw-r--r--app/models/application_record.rb6
-rw-r--r--app/models/application_setting.rb8
-rw-r--r--app/models/clusters/cluster.rb39
-rw-r--r--app/models/clusters/platforms/kubernetes.rb2
-rw-r--r--app/models/concerns/cache_markdown_field.rb23
-rw-r--r--app/models/gpg_signature.rb7
-rw-r--r--app/models/pool_repository.rb4
-rw-r--r--app/models/project.rb2
-rw-r--r--app/models/project_auto_devops.rb5
-rw-r--r--app/models/project_wiki.rb4
-rw-r--r--app/models/repository.rb5
-rw-r--r--app/models/user.rb3
-rw-r--r--app/serializers/error_tracking/project_serializer.rb2
-rw-r--r--app/services/error_tracking/list_projects_service.rb44
-rw-r--r--app/services/labels/update_service.rb1
-rw-r--r--app/services/merge_requests/update_service.rb7
-rw-r--r--app/services/projects/import_service.rb2
-rw-r--r--app/services/task_list_toggle_service.rb3
-rw-r--r--app/views/admin/application_settings/_localization.html.haml11
-rw-r--r--app/views/admin/application_settings/preferences.html.haml11
-rw-r--r--app/views/clusters/clusters/_form.html.haml17
-rw-r--r--app/views/layouts/nav/sidebar/_group.html.haml2
-rw-r--r--app/views/profiles/preferences/show.html.haml21
-rw-r--r--app/views/projects/commit/show.html.haml2
-rw-r--r--app/views/projects/compare/show.html.haml2
-rw-r--r--app/views/projects/diffs/_diffs.html.haml4
-rw-r--r--app/views/projects/diffs/_file.html.haml4
-rw-r--r--app/views/projects/settings/ci_cd/_autodevops_form.html.haml19
-rw-r--r--app/views/shared/projects/_project.html.haml41
-rw-r--r--app/workers/repository_fork_worker.rb10
74 files changed, 919 insertions, 355 deletions
diff --git a/app/assets/javascripts/behaviors/markdown/copy_as_gfm.js b/app/assets/javascripts/behaviors/markdown/copy_as_gfm.js
index 947d019c725..52d9f2f0322 100644
--- a/app/assets/javascripts/behaviors/markdown/copy_as_gfm.js
+++ b/app/assets/javascripts/behaviors/markdown/copy_as_gfm.js
@@ -1,8 +1,5 @@
import $ from 'jquery';
-import { DOMParser } from 'prosemirror-model';
import { getSelectedFragment } from '~/lib/utils/common_utils';
-import schema from './schema';
-import markdownSerializer from './serializer';
export class CopyAsGFM {
constructor() {
@@ -39,9 +36,13 @@ export class CopyAsGFM {
div.appendChild(el.cloneNode(true));
const html = div.innerHTML;
- clipboardData.setData('text/plain', el.textContent);
- clipboardData.setData('text/x-gfm', this.nodeToGFM(el));
- clipboardData.setData('text/html', html);
+ CopyAsGFM.nodeToGFM(el)
+ .then(res => {
+ clipboardData.setData('text/plain', el.textContent);
+ clipboardData.setData('text/x-gfm', res);
+ clipboardData.setData('text/html', html);
+ })
+ .catch(() => {});
}
static pasteGFM(e) {
@@ -137,11 +138,21 @@ export class CopyAsGFM {
}
static nodeToGFM(node) {
- const wrapEl = document.createElement('div');
- wrapEl.appendChild(node.cloneNode(true));
- const doc = DOMParser.fromSchema(schema).parse(wrapEl);
-
- return markdownSerializer.serialize(doc);
+ return Promise.all([
+ import(/* webpackChunkName: 'gfm_copy_extra' */ 'prosemirror-model'),
+ import(/* webpackChunkName: 'gfm_copy_extra' */ './schema'),
+ import(/* webpackChunkName: 'gfm_copy_extra' */ './serializer'),
+ ])
+ .then(([prosemirrorModel, schema, markdownSerializer]) => {
+ const { DOMParser } = prosemirrorModel;
+ const wrapEl = document.createElement('div');
+ wrapEl.appendChild(node.cloneNode(true));
+ const doc = DOMParser.fromSchema(schema.default).parse(wrapEl);
+
+ const res = markdownSerializer.default.serialize(doc);
+ return res;
+ })
+ .catch(() => {});
}
}
diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js
index 0eb067d4963..680f2031409 100644
--- a/app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js
+++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js
@@ -64,26 +64,30 @@ export default class ShortcutsIssuable extends Shortcuts {
const el = CopyAsGFM.transformGFMSelection(documentFragment.cloneNode(true));
const blockquoteEl = document.createElement('blockquote');
blockquoteEl.appendChild(el);
- const text = CopyAsGFM.nodeToGFM(blockquoteEl);
-
- if (text.trim() === '') {
- return false;
- }
-
- // If replyField already has some content, add a newline before our quote
- const separator = ($replyField.val().trim() !== '' && '\n\n') || '';
- $replyField
- .val((a, current) => `${current}${separator}${text}\n\n`)
- .trigger('input')
- .trigger('change');
-
- // Trigger autosize
- const event = document.createEvent('Event');
- event.initEvent('autosize:update', true, false);
- $replyField.get(0).dispatchEvent(event);
+ CopyAsGFM.nodeToGFM(blockquoteEl)
+ .then(text => {
+ if (text.trim() === '') {
+ return false;
+ }
+
+ // If replyField already has some content, add a newline before our quote
+ const separator = ($replyField.val().trim() !== '' && '\n\n') || '';
+ $replyField
+ .val((a, current) => `${current}${separator}${text}\n\n`)
+ .trigger('input')
+ .trigger('change');
+
+ // Trigger autosize
+ const event = document.createEvent('Event');
+ event.initEvent('autosize:update', true, false);
+ $replyField.get(0).dispatchEvent(event);
+
+ // Focus the input field
+ $replyField.focus();
- // Focus the input field
- $replyField.focus();
+ return false;
+ })
+ .catch(() => {});
return false;
}
diff --git a/app/assets/javascripts/diffs/components/compare_versions.vue b/app/assets/javascripts/diffs/components/compare_versions.vue
index 3ef54752436..0bf2dde8b96 100644
--- a/app/assets/javascripts/diffs/components/compare_versions.vue
+++ b/app/assets/javascripts/diffs/components/compare_versions.vue
@@ -6,6 +6,7 @@ import { polyfillSticky } from '~/lib/utils/sticky';
import Icon from '~/vue_shared/components/icon.vue';
import CompareVersionsDropdown from './compare_versions_dropdown.vue';
import SettingsDropdown from './settings_dropdown.vue';
+import DiffStats from './diff_stats.vue';
export default {
components: {
@@ -14,6 +15,7 @@ export default {
GlLink,
GlButton,
SettingsDropdown,
+ DiffStats,
},
directives: {
GlTooltip: GlTooltipDirective,
@@ -35,8 +37,15 @@ export default {
},
},
computed: {
- ...mapState('diffs', ['commit', 'showTreeList', 'startVersion', 'latestVersionPath']),
- ...mapGetters('diffs', ['hasCollapsedFile']),
+ ...mapGetters('diffs', ['hasCollapsedFile', 'diffFilesLength']),
+ ...mapState('diffs', [
+ 'commit',
+ 'showTreeList',
+ 'startVersion',
+ 'latestVersionPath',
+ 'addedLines',
+ 'removedLines',
+ ]),
comparableDiffs() {
return this.mergeRequestDiffs.slice(1);
},
@@ -104,6 +113,11 @@ export default {
<gl-link :href="commit.commit_url" class="monospace">{{ commit.short_id }}</gl-link>
</div>
<div class="inline-parallel-buttons d-none d-md-flex ml-auto">
+ <diff-stats
+ :diff-files-length="diffFilesLength"
+ :added-lines="addedLines"
+ :removed-lines="removedLines"
+ />
<gl-button
v-if="commit || startVersion"
:href="latestVersionPath"
diff --git a/app/assets/javascripts/diffs/components/diff_file_header.vue b/app/assets/javascripts/diffs/components/diff_file_header.vue
index b58f704bebb..60586d4a607 100644
--- a/app/assets/javascripts/diffs/components/diff_file_header.vue
+++ b/app/assets/javascripts/diffs/components/diff_file_header.vue
@@ -9,6 +9,7 @@ import { GlTooltipDirective } from '@gitlab/ui';
import { truncateSha } from '~/lib/utils/text_utility';
import { __, s__, sprintf } from '~/locale';
import EditButton from './edit_button.vue';
+import DiffStats from './diff_stats.vue';
export default {
components: {
@@ -16,6 +17,7 @@ export default {
EditButton,
Icon,
FileIcon,
+ DiffStats,
},
directives: {
GlTooltip: GlTooltipDirective,
@@ -202,6 +204,7 @@ export default {
v-if="!diffFile.submodule && addMergeRequestButtons"
class="file-actions d-none d-sm-block"
>
+ <diff-stats :added-lines="diffFile.added_lines" :removed-lines="diffFile.removed_lines" />
<template v-if="diffFile.blob && diffFile.blob.readable_text">
<button
:disabled="!diffHasDiscussions(diffFile)"
diff --git a/app/assets/javascripts/diffs/components/diff_stats.vue b/app/assets/javascripts/diffs/components/diff_stats.vue
new file mode 100644
index 00000000000..2e5855380af
--- /dev/null
+++ b/app/assets/javascripts/diffs/components/diff_stats.vue
@@ -0,0 +1,52 @@
+<script>
+import Icon from '~/vue_shared/components/icon.vue';
+import { n__ } from '~/locale';
+
+export default {
+ components: { Icon },
+ props: {
+ addedLines: {
+ type: Number,
+ required: true,
+ },
+ removedLines: {
+ type: Number,
+ required: true,
+ },
+ diffFilesLength: {
+ type: Number,
+ required: false,
+ default: null,
+ },
+ },
+ computed: {
+ filesText() {
+ return n__('File', 'Files', this.diffFilesLength);
+ },
+ isCompareVersionsHeader() {
+ return Boolean(this.diffFilesLength);
+ },
+ },
+};
+</script>
+
+<template>
+ <div
+ class="diff-stats"
+ :class="{
+ 'is-compare-versions-header d-none d-lg-inline-flex': isCompareVersionsHeader,
+ 'd-inline-flex': !isCompareVersionsHeader,
+ }"
+ >
+ <div v-if="diffFilesLength !== null" class="diff-stats-group">
+ <icon name="doc-code" class="diff-stats-icon text-secondary" />
+ <strong>{{ diffFilesLength }} {{ filesText }}</strong>
+ </div>
+ <div class="diff-stats-group cgreen">
+ <icon name="file-addition" class="diff-stats-icon" /> <strong>{{ addedLines }}</strong>
+ </div>
+ <div class="diff-stats-group cred">
+ <icon name="file-deletion" class="diff-stats-icon" /> <strong>{{ removedLines }}</strong>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/diffs/components/tree_list.vue b/app/assets/javascripts/diffs/components/tree_list.vue
index a0f09932593..96ae197d8b8 100644
--- a/app/assets/javascripts/diffs/components/tree_list.vue
+++ b/app/assets/javascripts/diffs/components/tree_list.vue
@@ -14,8 +14,8 @@ export default {
FileRow,
},
computed: {
- ...mapState('diffs', ['tree', 'addedLines', 'removedLines', 'renderTreeList']),
- ...mapGetters('diffs', ['allBlobs', 'diffFilesLength']),
+ ...mapState('diffs', ['tree', 'renderTreeList']),
+ ...mapGetters('diffs', ['allBlobs']),
filteredTreeList() {
return this.renderTreeList ? this.tree : this.allBlobs;
},
@@ -64,13 +64,6 @@ export default {
{{ s__('MergeRequest|No files found') }}
</p>
</div>
- <div v-once class="pt-3 pb-3 text-center">
- {{ n__('%d changed file', '%d changed files', diffFilesLength) }}
- <div>
- <span class="cgreen"> {{ n__('%d addition', '%d additions', addedLines) }} </span>
- <span class="cred"> {{ n__('%d deleted', '%d deletions', removedLines) }} </span>
- </div>
- </div>
</div>
</template>
diff --git a/app/assets/javascripts/diffs/store/modules/diff_state.js b/app/assets/javascripts/diffs/store/modules/diff_state.js
index 6ee33d9fc6d..47f78a5db54 100644
--- a/app/assets/javascripts/diffs/store/modules/diff_state.js
+++ b/app/assets/javascripts/diffs/store/modules/diff_state.js
@@ -11,6 +11,8 @@ const storedTreeShow = localStorage.getItem(MR_TREE_SHOW_KEY);
export default () => ({
isLoading: true,
+ addedLines: null,
+ removedLines: null,
endpoint: '',
basePath: '',
commit: null,
diff --git a/app/assets/javascripts/due_date_select.js b/app/assets/javascripts/due_date_select.js
index dbfcf8cc921..cb1b1173190 100644
--- a/app/assets/javascripts/due_date_select.js
+++ b/app/assets/javascripts/due_date_select.js
@@ -64,6 +64,7 @@ class DueDateSelect {
this.saveDueDate(true);
}
},
+ firstDay: gon.first_day_of_week,
});
calendar.setDate(parsePikadayDate($dueDateInput.val()));
@@ -183,6 +184,7 @@ export default class DueDateSelectors {
onSelect(dateText) {
$datePicker.val(calendar.toString(dateText));
},
+ firstDay: gon.first_day_of_week,
});
calendar.setDate(parsePikadayDate(datePickerVal));
diff --git a/app/assets/javascripts/issuable_form.js b/app/assets/javascripts/issuable_form.js
index 4d2533d01f1..9336b71cfd7 100644
--- a/app/assets/javascripts/issuable_form.js
+++ b/app/assets/javascripts/issuable_form.js
@@ -44,6 +44,7 @@ export default class IssuableForm {
parse: dateString => parsePikadayDate(dateString),
toString: date => pikadayToString(date),
onSelect: dateText => $issuableDueDate.val(calendar.toString(dateText)),
+ firstDay: gon.first_day_of_week,
});
calendar.setDate(parsePikadayDate($issuableDueDate.val()));
}
diff --git a/app/assets/javascripts/issue_show/components/description.vue b/app/assets/javascripts/issue_show/components/description.vue
index e664269b199..58f14bac8c8 100644
--- a/app/assets/javascripts/issue_show/components/description.vue
+++ b/app/assets/javascripts/issue_show/components/description.vue
@@ -1,6 +1,7 @@
<script>
import $ from 'jquery';
-import { __ } from '~/locale';
+import { s__, sprintf } from '~/locale';
+import createFlash from '~/flash';
import animateMixin from '../mixins/animate';
import TaskList from '../../task_list';
import recaptchaModalImplementor from '../../vue_shared/mixins/recaptcha_modal_implementor';
@@ -91,9 +92,14 @@ export default {
},
taskListUpdateError() {
- window.Flash(
- __(
- 'Someone edited this issue at the same time you did. The description has been updated and you will need to make your changes again.',
+ createFlash(
+ sprintf(
+ s__(
+ 'Someone edited this %{issueType} at the same time you did. The description has been updated and you will need to make your changes again.',
+ ),
+ {
+ issueType: this.issuableType,
+ },
),
);
diff --git a/app/assets/javascripts/lib/utils/file_upload.js b/app/assets/javascripts/lib/utils/file_upload.js
index b41ffb44971..82ee83e4348 100644
--- a/app/assets/javascripts/lib/utils/file_upload.js
+++ b/app/assets/javascripts/lib/utils/file_upload.js
@@ -1,6 +1,9 @@
export default (buttonSelector, fileSelector) => {
const btn = document.querySelector(buttonSelector);
const fileInput = document.querySelector(fileSelector);
+
+ if (!btn || !fileInput) return;
+
const form = btn.closest('form');
btn.addEventListener('click', () => {
diff --git a/app/assets/javascripts/member_expiration_date.js b/app/assets/javascripts/member_expiration_date.js
index 0beedcacf33..0dabb28ea66 100644
--- a/app/assets/javascripts/member_expiration_date.js
+++ b/app/assets/javascripts/member_expiration_date.js
@@ -33,6 +33,7 @@ export default function memberExpirationDate(selector = '.js-access-expiration-d
toggleClearInput.call($input);
},
+ firstDay: gon.first_day_of_week,
});
calendar.setDate(parsePikadayDate($input.val()));
diff --git a/app/assets/javascripts/merge_request.js b/app/assets/javascripts/merge_request.js
index ac3b47cd218..3b42a154af8 100644
--- a/app/assets/javascripts/merge_request.js
+++ b/app/assets/javascripts/merge_request.js
@@ -2,6 +2,7 @@
import $ from 'jquery';
import { __ } from '~/locale';
+import createFlash from '~/flash';
import TaskList from './task_list';
import MergeRequestTabs from './merge_request_tabs';
import IssuablesHelper from './helpers/issuables_helper';
@@ -40,6 +41,13 @@ function MergeRequest(opts) {
document.querySelector('#task_status').innerText = result.task_status;
document.querySelector('#task_status_short').innerText = result.task_status_short;
},
+ onError: () => {
+ createFlash(
+ __(
+ 'Someone edited this merge request at the same time you did. Please refresh the page to see changes.',
+ ),
+ );
+ },
});
}
}
diff --git a/app/assets/javascripts/monitoring/components/charts/area.vue b/app/assets/javascripts/monitoring/components/charts/area.vue
index ec0e33a1927..14c02db7bcc 100644
--- a/app/assets/javascripts/monitoring/components/charts/area.vue
+++ b/app/assets/javascripts/monitoring/components/charts/area.vue
@@ -189,8 +189,8 @@ export default {
<template>
<div class="prometheus-graph col-12 col-lg-6">
<div class="prometheus-graph-header">
- <h5 class="prometheus-graph-title">{{ graphData.title }}</h5>
- <div class="prometheus-graph-widgets"><slot></slot></div>
+ <h5 ref="graphTitle" class="prometheus-graph-title">{{ graphData.title }}</h5>
+ <div ref="graphWidgets" class="prometheus-graph-widgets"><slot></slot></div>
</div>
<gl-area-chart
ref="areaChart"
diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue
index 9c5fd93f7d1..895a57785bc 100644
--- a/app/assets/javascripts/monitoring/components/dashboard.vue
+++ b/app/assets/javascripts/monitoring/components/dashboard.vue
@@ -160,7 +160,8 @@ export default {
{{ s__('Metrics|Environment') }}
<div class="dropdown prepend-left-10">
<button class="dropdown-menu-toggle" data-toggle="dropdown" type="button">
- <span> {{ currentEnvironmentName }} </span> <icon name="chevron-down" />
+ <span>{{ currentEnvironmentName }}</span>
+ <icon name="chevron-down" />
</button>
<div
v-if="store.environmentsData.length > 0"
@@ -172,9 +173,8 @@ export default {
:href="environment.metrics_path"
:class="{ 'is-active': environment.name == currentEnvironmentName }"
class="dropdown-item"
+ >{{ environment.name }}</a
>
- {{ environment.name }}
- </a>
</li>
</ul>
</div>
diff --git a/app/assets/javascripts/pages/dashboard/projects/index.js b/app/assets/javascripts/pages/dashboard/projects/index.js
index 8f98be79640..01001d4f3ff 100644
--- a/app/assets/javascripts/pages/dashboard/projects/index.js
+++ b/app/assets/javascripts/pages/dashboard/projects/index.js
@@ -1,7 +1,5 @@
import ProjectsList from '~/projects_list';
-import Star from '../../../star';
document.addEventListener('DOMContentLoaded', () => {
new ProjectsList(); // eslint-disable-line no-new
- new Star('.project-row'); // eslint-disable-line no-new
});
diff --git a/app/assets/javascripts/pages/explore/projects/index.js b/app/assets/javascripts/pages/explore/projects/index.js
index 8f98be79640..01001d4f3ff 100644
--- a/app/assets/javascripts/pages/explore/projects/index.js
+++ b/app/assets/javascripts/pages/explore/projects/index.js
@@ -1,7 +1,5 @@
import ProjectsList from '~/projects_list';
-import Star from '../../../star';
document.addEventListener('DOMContentLoaded', () => {
new ProjectsList(); // eslint-disable-line no-new
- new Star('.project-row'); // eslint-disable-line no-new
});
diff --git a/app/assets/javascripts/pages/projects/environments/metrics/index.js b/app/assets/javascripts/pages/projects/environments/metrics/index.js
index 0b644780ad4..0d69a689316 100644
--- a/app/assets/javascripts/pages/projects/environments/metrics/index.js
+++ b/app/assets/javascripts/pages/projects/environments/metrics/index.js
@@ -1,3 +1,3 @@
-import monitoringBundle from '~/monitoring/monitoring_bundle';
+import monitoringBundle from 'ee_else_ce/monitoring/monitoring_bundle';
document.addEventListener('DOMContentLoaded', monitoringBundle);
diff --git a/app/assets/javascripts/pages/projects/graphs/charts/index.js b/app/assets/javascripts/pages/projects/graphs/charts/index.js
index 3ccad513c05..26d7fa7371d 100644
--- a/app/assets/javascripts/pages/projects/graphs/charts/index.js
+++ b/app/assets/javascripts/pages/projects/graphs/charts/index.js
@@ -43,10 +43,26 @@ document.addEventListener('DOMContentLoaded', () => {
],
});
+ const reorderWeekDays = (weekDays, firstDayOfWeek = 0) => {
+ if (firstDayOfWeek === 0) {
+ return weekDays;
+ }
+
+ return Object.keys(weekDays).reduce((acc, dayName, idx, arr) => {
+ const reorderedDayName = arr[(idx + firstDayOfWeek) % arr.length];
+
+ return {
+ ...acc,
+ [reorderedDayName]: weekDays[reorderedDayName],
+ };
+ }, {});
+ };
+
const hourData = chartData(projectChartData.hour);
responsiveChart($('#hour-chart'), hourData);
- const dayData = chartData(projectChartData.weekDays);
+ const weekDays = reorderWeekDays(projectChartData.weekDays, gon.first_day_of_week);
+ const dayData = chartData(weekDays);
responsiveChart($('#weekday-chart'), dayData);
const monthData = chartData(projectChartData.month);
diff --git a/app/assets/javascripts/pages/users/activity_calendar.js b/app/assets/javascripts/pages/users/activity_calendar.js
index 8a84ac37dab..afa099d0e0b 100644
--- a/app/assets/javascripts/pages/users/activity_calendar.js
+++ b/app/assets/javascripts/pages/users/activity_calendar.js
@@ -159,7 +159,7 @@ export default class ActivityCalendar {
.append('g')
.attr('transform', (group, i) => {
_.each(group, (stamp, a) => {
- if (a === 0 && stamp.day === 0) {
+ if (a === 0 && stamp.day === this.firstDayOfWeek) {
const month = stamp.date.getMonth();
const x = this.daySizeWithSpace * i + 1 + this.daySizeWithSpace;
const lastMonth = _.last(this.months);
@@ -205,6 +205,14 @@ export default class ActivityCalendar {
y: 29 + this.dayYPos(5),
},
];
+
+ if (this.firstDayOfWeek === 1) {
+ days.push({
+ text: 'S',
+ y: 29 + this.dayYPos(7),
+ });
+ }
+
this.svg
.append('g')
.selectAll('text')
diff --git a/app/assets/javascripts/pages/users/user_tabs.js b/app/assets/javascripts/pages/users/user_tabs.js
index 1c3fd58ca74..39cd891c111 100644
--- a/app/assets/javascripts/pages/users/user_tabs.js
+++ b/app/assets/javascripts/pages/users/user_tabs.js
@@ -234,7 +234,7 @@ export default class UserTabs {
data,
calendarActivitiesPath,
utcOffset,
- 0,
+ gon.first_day_of_week,
monthsAgo,
);
}
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/commit_edit.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/commit_edit.vue
new file mode 100644
index 00000000000..a38f25cce35
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/commit_edit.vue
@@ -0,0 +1,40 @@
+<script>
+export default {
+ props: {
+ value: {
+ type: String,
+ required: true,
+ },
+ label: {
+ type: String,
+ required: true,
+ },
+ inputId: {
+ type: String,
+ required: true,
+ },
+ },
+};
+</script>
+
+<template>
+ <li>
+ <div class="commit-message-editor">
+ <div class="d-flex flex-wrap align-items-center justify-content-between">
+ <label class="col-form-label" :for="inputId">
+ <strong>{{ label }}</strong>
+ </label>
+ <slot name="header"></slot>
+ </div>
+ <textarea
+ :id="inputId"
+ :value="value"
+ class="form-control js-gfm-input append-bottom-default commit-message-edit"
+ required="required"
+ rows="7"
+ @input="$emit('input', $event.target.value)"
+ ></textarea>
+ <slot name="checkbox"></slot>
+ </div>
+ </li>
+</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/commit_message_dropdown.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/commit_message_dropdown.vue
new file mode 100644
index 00000000000..b3c1c0e329d
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/commit_message_dropdown.vue
@@ -0,0 +1,38 @@
+<script>
+import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
+
+export default {
+ components: {
+ GlDropdown,
+ GlDropdownItem,
+ },
+ props: {
+ commits: {
+ type: Array,
+ required: true,
+ default: () => [],
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <gl-dropdown
+ right
+ no-caret
+ text="Use an existing commit message"
+ variant="link"
+ class="mr-commit-dropdown"
+ >
+ <gl-dropdown-item
+ v-for="commit in commits"
+ :key="commit.short_id"
+ class="text-nowrap text-truncate"
+ @click="$emit('input', commit.message)"
+ >
+ <span class="monospace mr-2">{{ commit.short_id }}</span> {{ commit.title }}
+ </gl-dropdown-item>
+ </gl-dropdown>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue
new file mode 100644
index 00000000000..a1d3a09cca4
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue
@@ -0,0 +1,91 @@
+<script>
+import { GlButton } from '@gitlab/ui';
+import _ from 'underscore';
+import { __, n__, sprintf, s__ } from '~/locale';
+import Icon from '~/vue_shared/components/icon.vue';
+
+export default {
+ components: {
+ Icon,
+ GlButton,
+ },
+ props: {
+ isSquashEnabled: {
+ type: Boolean,
+ required: true,
+ },
+ commitsCount: {
+ type: Number,
+ required: false,
+ default: 0,
+ },
+ targetBranch: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ expanded: false,
+ };
+ },
+ computed: {
+ collapseIcon() {
+ return this.expanded ? 'chevron-down' : 'chevron-right';
+ },
+ commitsCountMessage() {
+ return n__(__('%d commit'), __('%d commits'), this.isSquashEnabled ? 1 : this.commitsCount);
+ },
+ modifyLinkMessage() {
+ return this.isSquashEnabled ? __('Modify commit messages') : __('Modify merge commit');
+ },
+ ariaLabel() {
+ return this.expanded ? __('Collapse') : __('Expand');
+ },
+ message() {
+ return sprintf(
+ s__(
+ 'mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}.',
+ ),
+ {
+ commitCount: `<strong class="commits-count-message">${this.commitsCountMessage}</strong>`,
+ mergeCommitCount: `<strong>${s__('mrWidgetCommitsAdded|1 merge commit')}</strong>`,
+ targetBranch: `<span class="label-branch">${_.escape(this.targetBranch)}</span>`,
+ },
+ false,
+ );
+ },
+ },
+ methods: {
+ toggle() {
+ this.expanded = !this.expanded;
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <div
+ class="js-mr-widget-commits-count mr-widget-extension clickable d-flex align-items-center px-3 py-2"
+ @click="toggle()"
+ >
+ <gl-button
+ :aria-label="ariaLabel"
+ variant="blank"
+ class="commit-edit-toggle mr-2"
+ @click.stop="toggle()"
+ >
+ <icon :name="collapseIcon" :size="16" />
+ </gl-button>
+ <span v-if="expanded">{{ __('Collapse') }}</span>
+ <span v-else>
+ <span v-html="message"></span>
+ <gl-button variant="link" class="modify-message-button">
+ {{ modifyLinkMessage }}
+ </gl-button>
+ </span>
+ </div>
+ <div v-show="expanded"><slot></slot></div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
index b8f29649eb5..ce4207864ea 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
@@ -2,17 +2,24 @@
import successSvg from 'icons/_icon_status_success.svg';
import warningSvg from 'icons/_icon_status_warning.svg';
import simplePoll from '~/lib/utils/simple_poll';
+import { __ } from '~/locale';
import MergeRequest from '../../../merge_request';
import Flash from '../../../flash';
import statusIcon from '../mr_widget_status_icon.vue';
import eventHub from '../../event_hub';
import SquashBeforeMerge from './squash_before_merge.vue';
+import CommitsHeader from './commits_header.vue';
+import CommitEdit from './commit_edit.vue';
+import CommitMessageDropdown from './commit_message_dropdown.vue';
export default {
name: 'ReadyToMerge',
components: {
statusIcon,
SquashBeforeMerge,
+ CommitsHeader,
+ CommitEdit,
+ CommitMessageDropdown,
},
props: {
mr: { type: Object, required: true },
@@ -22,27 +29,20 @@ export default {
return {
removeSourceBranch: this.mr.shouldRemoveSourceBranch,
mergeWhenBuildSucceeds: false,
- useCommitMessageWithDescription: false,
setToMergeWhenPipelineSucceeds: false,
- showCommitMessageEditor: false,
isMakingRequest: false,
isMergingImmediately: false,
commitMessage: this.mr.commitMessage,
squashBeforeMerge: this.mr.squash,
successSvg,
warningSvg,
+ squashCommitMessage: this.mr.squashCommitMessage,
};
},
computed: {
shouldShowMergeWhenPipelineSucceedsText() {
return this.mr.isPipelineActive;
},
- commitMessageLinkTitle() {
- const withDesc = 'Include description in commit message';
- const withoutDesc = "Don't include description in commit message";
-
- return this.useCommitMessageWithDescription ? withoutDesc : withDesc;
- },
status() {
const { pipeline, isPipelineActive, isPipelineFailed, hasCI, ciStatus } = this.mr;
@@ -84,9 +84,9 @@ export default {
},
mergeButtonText() {
if (this.isMergingImmediately) {
- return 'Merge in progress';
+ return __('Merge in progress');
} else if (this.shouldShowMergeWhenPipelineSucceedsText) {
- return 'Merge when pipeline succeeds';
+ return __('Merge when pipeline succeeds');
}
return 'Merge';
@@ -98,7 +98,7 @@ export default {
const { commitMessage } = this;
return Boolean(
!commitMessage.length ||
- !this.shouldShowMergeControls() ||
+ !this.shouldShowMergeControls ||
this.isMakingRequest ||
this.mr.preventMerge,
);
@@ -110,18 +110,14 @@ export default {
const { commitsCount, enableSquashBeforeMerge } = this.mr;
return enableSquashBeforeMerge && commitsCount > 1;
},
- },
- methods: {
shouldShowMergeControls() {
return this.mr.isMergeAllowed || this.shouldShowMergeWhenPipelineSucceedsText;
},
- updateCommitMessage() {
- const cmwd = this.mr.commitMessageWithDescription;
- this.useCommitMessageWithDescription = !this.useCommitMessageWithDescription;
- this.commitMessage = this.useCommitMessageWithDescription ? cmwd : this.mr.commitMessage;
- },
- toggleCommitMessageEditor() {
- this.showCommitMessageEditor = !this.showCommitMessageEditor;
+ },
+ methods: {
+ updateMergeCommitMessage(includeDescription) {
+ const { commitMessageWithDescription, commitMessage } = this.mr;
+ this.commitMessage = includeDescription ? commitMessageWithDescription : commitMessage;
},
handleMergeButtonClick(mergeWhenBuildSucceeds, mergeImmediately) {
// TODO: Remove no-param-reassign
@@ -139,6 +135,7 @@ export default {
merge_when_pipeline_succeeds: this.setToMergeWhenPipelineSucceeds,
should_remove_source_branch: this.removeSourceBranch === true,
squash: this.squashBeforeMerge,
+ squash_commit_message: this.squashCommitMessage,
};
this.isMakingRequest = true;
@@ -158,7 +155,7 @@ export default {
})
.catch(() => {
this.isMakingRequest = false;
- new Flash('Something went wrong. Please try again.'); // eslint-disable-line
+ new Flash(__('Something went wrong. Please try again.')); // eslint-disable-line
});
},
initiateMergePolling() {
@@ -194,7 +191,7 @@ export default {
}
})
.catch(() => {
- new Flash('Something went wrong while merging this merge request. Please try again.'); // eslint-disable-line
+ new Flash(__('Something went wrong while merging this merge request. Please try again.')); // eslint-disable-line
});
},
initiateRemoveSourceBranchPolling() {
@@ -223,7 +220,7 @@ export default {
}
})
.catch(() => {
- new Flash('Something went wrong while deleting the source branch. Please try again.'); // eslint-disable-line
+ new Flash(__('Something went wrong while deleting the source branch. Please try again.')); // eslint-disable-line
});
},
},
@@ -231,127 +228,136 @@ export default {
</script>
<template>
- <div class="mr-widget-body media">
- <status-icon :status="iconClass" />
- <div class="media-body">
- <div class="mr-widget-body-controls media space-children">
- <span class="btn-group">
- <button
- :disabled="isMergeButtonDisabled"
- :class="mergeButtonClass"
- type="button"
- class="qa-merge-button"
- @click="handleMergeButtonClick()"
- >
- <i v-if="isMakingRequest" class="fa fa-spinner fa-spin" aria-hidden="true"></i>
- {{ mergeButtonText }}
- </button>
- <button
- v-if="shouldShowMergeOptionsDropdown"
- :disabled="isMergeButtonDisabled"
- type="button"
- class="btn btn-sm btn-info dropdown-toggle js-merge-moment"
- data-toggle="dropdown"
- aria-label="Select merge moment"
- >
- <i class="fa fa-chevron-down qa-merge-moment-dropdown" aria-hidden="true"></i>
- </button>
- <ul
- v-if="shouldShowMergeOptionsDropdown"
- class="dropdown-menu dropdown-menu-right"
- role="menu"
- >
- <li>
- <a
- class="merge_when_pipeline_succeeds qa-merge-when-pipeline-succeeds-option"
- href="#"
- @click.prevent="handleMergeButtonClick(true)"
- >
- <span class="media">
- <span class="merge-opt-icon" aria-hidden="true" v-html="successSvg"></span>
- <span class="media-body merge-opt-title">Merge when pipeline succeeds</span>
- </span>
- </a>
- </li>
- <li>
- <a
- class="accept-merge-request qa-merge-immediately-option"
- href="#"
- @click.prevent="handleMergeButtonClick(false, true)"
- >
- <span class="media">
- <span class="merge-opt-icon" aria-hidden="true" v-html="warningSvg"></span>
- <span class="media-body merge-opt-title">Merge immediately</span>
- </span>
- </a>
- </li>
- </ul>
- </span>
- <div class="media-body-wrap space-children">
- <template v-if="shouldShowMergeControls()">
- <label v-if="mr.canRemoveSourceBranch">
- <input
- id="remove-source-branch-input"
- v-model="removeSourceBranch"
- :disabled="isRemoveSourceBranchButtonDisabled"
- class="js-remove-source-branch-checkbox"
- type="checkbox"
- />
- Delete source branch
- </label>
-
- <!-- Placeholder for EE extension of this component -->
- <squash-before-merge
- v-if="shouldShowSquashBeforeMerge"
- v-model="squashBeforeMerge"
- :help-path="mr.squashBeforeMergeHelpPath"
- :is-disabled="isMergeButtonDisabled"
- />
-
- <span v-if="mr.ffOnlyEnabled" class="js-fast-forward-message">
- Fast-forward merge without a merge commit
- </span>
+ <div>
+ <div class="mr-widget-body media">
+ <status-icon :status="iconClass" />
+ <div class="media-body">
+ <div class="mr-widget-body-controls media space-children">
+ <span class="btn-group">
<button
- v-else
:disabled="isMergeButtonDisabled"
- class="js-modify-commit-message-button btn btn-default btn-sm"
+ :class="mergeButtonClass"
type="button"
- @click="toggleCommitMessageEditor"
+ class="qa-merge-button"
+ @click="handleMergeButtonClick()"
>
- Modify commit message
+ <i v-if="isMakingRequest" class="fa fa-spinner fa-spin" aria-hidden="true"></i>
+ {{ mergeButtonText }}
</button>
- </template>
- <template v-else>
- <span class="bold js-resolve-mr-widget-items-message">
- You can only merge once the items above are resolved
- </span>
- </template>
- </div>
- </div>
- <div v-if="showCommitMessageEditor" class="prepend-top-default commit-message-editor">
- <div class="form-group clearfix">
- <label class="col-form-label" for="commit-message"> Commit message </label>
- <div class="col-sm-10">
- <div class="commit-message-container">
- <div class="max-width-marker"></div>
- <textarea
- id="commit-message"
- v-model="commitMessage"
- class="form-control js-commit-message"
- required="required"
- rows="14"
- name="Commit message"
- ></textarea>
- </div>
- <p class="hint">
- Try to keep the first line under 52 characters and the others under 72
- </p>
- <div class="hint">
- <a href="#" @click.prevent="updateCommitMessage"> {{ commitMessageLinkTitle }} </a>
- </div>
+ <button
+ v-if="shouldShowMergeOptionsDropdown"
+ :disabled="isMergeButtonDisabled"
+ type="button"
+ class="btn btn-sm btn-info dropdown-toggle js-merge-moment"
+ data-toggle="dropdown"
+ aria-label="Select merge moment"
+ >
+ <i class="fa fa-chevron-down qa-merge-moment-dropdown" aria-hidden="true"></i>
+ </button>
+ <ul
+ v-if="shouldShowMergeOptionsDropdown"
+ class="dropdown-menu dropdown-menu-right"
+ role="menu"
+ >
+ <li>
+ <a
+ class="merge_when_pipeline_succeeds qa-merge-when-pipeline-succeeds-option"
+ href="#"
+ @click.prevent="handleMergeButtonClick(true)"
+ >
+ <span class="media">
+ <span class="merge-opt-icon" aria-hidden="true" v-html="successSvg"></span>
+ <span class="media-body merge-opt-title">{{
+ __('Merge when pipeline succeeds')
+ }}</span>
+ </span>
+ </a>
+ </li>
+ <li>
+ <a
+ class="accept-merge-request qa-merge-immediately-option"
+ href="#"
+ @click.prevent="handleMergeButtonClick(false, true)"
+ >
+ <span class="media">
+ <span class="merge-opt-icon" aria-hidden="true" v-html="warningSvg"></span>
+ <span class="media-body merge-opt-title">{{ __('Merge immediately') }}</span>
+ </span>
+ </a>
+ </li>
+ </ul>
+ </span>
+ <div class="media-body-wrap space-children">
+ <template v-if="shouldShowMergeControls">
+ <label v-if="mr.canRemoveSourceBranch">
+ <input
+ id="remove-source-branch-input"
+ v-model="removeSourceBranch"
+ :disabled="isRemoveSourceBranchButtonDisabled"
+ class="js-remove-source-branch-checkbox"
+ type="checkbox"
+ />
+ {{ __('Delete source branch') }}
+ </label>
+
+ <!-- Placeholder for EE extension of this component -->
+ <squash-before-merge
+ v-if="shouldShowSquashBeforeMerge"
+ v-model="squashBeforeMerge"
+ :help-path="mr.squashBeforeMergeHelpPath"
+ :is-disabled="isMergeButtonDisabled"
+ />
+ </template>
+ <template v-else>
+ <span class="bold js-resolve-mr-widget-items-message">
+ {{ __('You can only merge once the items above are resolved') }}
+ </span>
+ </template>
</div>
</div>
</div>
</div>
+ <template v-if="shouldShowMergeControls">
+ <div v-if="mr.ffOnlyEnabled" class="mr-fast-forward-message">
+ {{ __('Fast-forward merge without a merge commit') }}
+ </div>
+ <template v-else>
+ <commits-header
+ :is-squash-enabled="squashBeforeMerge"
+ :commits-count="mr.commitsCount"
+ :target-branch="mr.targetBranch"
+ >
+ <ul class="border-top content-list commits-list flex-list">
+ <commit-edit
+ v-if="squashBeforeMerge"
+ v-model="squashCommitMessage"
+ :label="__('Squash commit message')"
+ input-id="squash-message-edit"
+ squash
+ >
+ <commit-message-dropdown
+ slot="header"
+ v-model="squashCommitMessage"
+ :commits="mr.commits"
+ />
+ </commit-edit>
+ <commit-edit
+ v-model="commitMessage"
+ :label="__('Merge commit message')"
+ input-id="merge-message-edit"
+ >
+ <label slot="checkbox">
+ <input
+ id="include-description"
+ type="checkbox"
+ @change="updateMergeCommitMessage($event.target.checked)"
+ />
+ {{ __('Include merge request description') }}
+ </label>
+ </commit-edit>
+ </ul>
+ </commits-header>
+ </template>
+ </template>
</div>
</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
index 57c4dfbe3b7..abbbe19c5ef 100644
--- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
@@ -315,7 +315,7 @@ export default {
:endpoint="mr.testResultsPath"
/>
- <div class="mr-widget-section">
+ <div class="mr-widget-section p-0">
<component :is="componentName" :mr="mr" :service="service" />
<section v-if="shouldRenderCollaborationStatus" class="mr-info-list mr-links">
diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
index 36cac230d9d..58363f632a9 100644
--- a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
+++ b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
@@ -42,6 +42,8 @@ export default class MergeRequestStore {
this.mergePipeline = data.merge_pipeline || {};
this.deployments = this.deployments || data.deployments || [];
this.postMergeDeployments = this.postMergeDeployments || [];
+ this.commits = data.commits_without_merge_commits || [];
+ this.squashCommitMessage = data.default_squash_commit_message;
this.initRebase(data);
if (data.issues_links) {
diff --git a/app/assets/javascripts/vue_shared/components/pikaday.vue b/app/assets/javascripts/vue_shared/components/pikaday.vue
index 8bdb5bf22c2..13eb46437dd 100644
--- a/app/assets/javascripts/vue_shared/components/pikaday.vue
+++ b/app/assets/javascripts/vue_shared/components/pikaday.vue
@@ -40,6 +40,7 @@ export default {
toString: date => pikadayToString(date),
onSelect: this.selected.bind(this),
onClose: this.toggled.bind(this),
+ firstDay: gon.first_day_of_week,
});
this.$el.append(this.calendar.el);
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index cb449b642e7..c5c3b66438c 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -391,6 +391,11 @@ img.emoji {
.flex-no-shrink { flex-shrink: 0; }
.ws-initial { white-space: initial; }
.overflow-auto { overflow: auto; }
+.d-flex-center {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
/** COMMON SIZING CLASSES **/
.w-0 { width: 0; }
@@ -402,6 +407,10 @@ img.emoji {
.min-height-0 { min-height: 0; }
+.w-3 { width: #{3 * $grid-size}; }
+
+.h-3 { width: #{3 * $grid-size}; }
+
/** COMMON SPACING CLASSES **/
.gl-pl-0 { padding-left: 0; }
.gl-pl-1 { padding-left: #{0.5 * $grid-size}; }
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss
index 037a5adfb7e..6108eaa1ad0 100644
--- a/app/assets/stylesheets/framework/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
@@ -4,6 +4,7 @@
*/
.file-holder {
border: 1px solid $border-color;
+ border-top: 0;
border-radius: $border-radius-default;
&.file-holder-no-border {
@@ -51,6 +52,7 @@
position: absolute;
top: 5px;
right: 15px;
+ margin-left: auto;
.btn {
padding: 0 10px;
@@ -324,10 +326,12 @@ span.idiff {
&,
.file-holder & {
display: flex;
+ flex-wrap: wrap;
align-items: center;
justify-content: space-between;
background-color: $gray-light;
border-bottom: 1px solid $border-color;
+ border-top: 1px solid $border-color;
padding: 5px $gl-padding;
margin: 0;
border-radius: $border-radius-default $border-radius-default 0 0;
@@ -365,16 +369,12 @@ span.idiff {
margin: 0 10px 0 0;
}
- .file-actions {
- white-space: nowrap;
-
- .btn {
- padding: 0 10px;
- font-size: 13px;
- line-height: 28px;
- display: inline-block;
- float: none;
- }
+ .file-actions .btn {
+ padding: 0 10px;
+ font-size: 13px;
+ line-height: 28px;
+ display: inline-block;
+ float: none;
}
@include media-breakpoint-down(xs) {
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 9eae9a831fa..96dab609a13 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -243,6 +243,7 @@ $gl-padding-8: 8px;
$gl-padding: 16px;
$gl-padding-24: 24px;
$gl-padding-32: 32px;
+$gl-padding-50: 50px;
$gl-col-padding: 15px;
$gl-input-padding: 10px;
$gl-vert-padding: 6px;
@@ -490,6 +491,7 @@ $builds-trace-bg: #111;
*/
$commit-max-width-marker-color: rgba(0, 0, 0, 0);
$commit-message-text-area-bg: rgba(0, 0, 0, 0);
+$commit-stat-summary-height: 36px;
/*
* Common
@@ -664,8 +666,14 @@ $priority-label-empty-state-width: 114px;
Issues Analytics
*/
$issues-analytics-popover-boarder-color: rgba(0, 0, 0, 0.15);
+
/*
Merge Requests
*/
$mr-tabs-height: 51px;
$mr-version-controls-height: 56px;
+
+/*
+Compare Branches
+*/
+$compare-branches-sticky-header-height: 68px;
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index 02aac58a475..e3b98b26a11 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -7,22 +7,13 @@
cursor: pointer;
@media (min-width: map-get($grid-breakpoints, md)) {
+ $mr-file-header-top: $mr-version-controls-height + $header-height + $mr-tabs-height;
+
position: -webkit-sticky;
position: sticky;
- top: $mr-version-controls-height + $header-height + $mr-tabs-height;
- margin-left: -1px;
- border-left: 1px solid $border-color;
+ top: $mr-file-header-top;
z-index: 102;
- &.is-commit {
- top: $header-height + 36px;
-
- .with-performance-bar & {
- top: $header-height + 36px + $performance-bar-height;
-
- }
- }
-
&::before {
content: '';
position: absolute;
@@ -35,7 +26,23 @@
}
.with-performance-bar & {
- top: $header-height + $performance-bar-height + $mr-version-controls-height + $mr-tabs-height;
+ top: $mr-file-header-top + $performance-bar-height;
+ }
+
+ &.is-commit {
+ top: $header-height + $commit-stat-summary-height;
+
+ .with-performance-bar & {
+ top: $header-height + $commit-stat-summary-height + $performance-bar-height;
+ }
+ }
+
+ &.is-compare {
+ top: $header-height + $compare-branches-sticky-header-height;
+
+ .with-performance-bar & {
+ top: $performance-bar-height + $header-height + $compare-branches-sticky-header-height;
+ }
}
}
@@ -501,6 +508,25 @@
}
}
+.diff-stats {
+ align-items: center;
+ padding: 0 .25rem;
+
+ .diff-stats-group {
+ padding: 0 .25rem;
+ }
+
+ svg.diff-stats-icon {
+ vertical-align: text-bottom;
+ }
+
+ &.is-compare-versions-header {
+ .diff-stats-group {
+ padding: 0 .5rem;
+ }
+ }
+}
+
.file-content .diff-file {
margin: 0;
border: 0;
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 38a7e199c6a..135730d71e9 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -38,9 +38,7 @@
}
.mr-widget-section {
- .media {
- align-items: center;
- }
+ border-radius: $border-radius-default $border-radius-default 0 0;
.code-text {
flex: 1;
@@ -56,6 +54,11 @@
.mr-widget-extension {
border-top: 1px solid $border-color;
background-color: $gray-light;
+
+ &.clickable:hover {
+ background-color: $gl-gray-200;
+ cursor: pointer;
+ }
}
.mr-widget-workflow {
@@ -78,6 +81,7 @@
border-top: 0;
}
+.mr-widget-body,
.mr-widget-section,
.mr-widget-content,
.mr-widget-footer {
@@ -87,11 +91,38 @@
.mr-state-widget {
color: $gl-text-color;
+ .commit-message-edit {
+ border-radius: $border-radius-default;
+ }
+
.mr-widget-section,
.mr-widget-footer {
border-top: solid 1px $border-color;
}
+ .mr-fast-forward-message {
+ padding-left: $gl-padding-50;
+ padding-bottom: $gl-padding;
+ }
+
+ .commits-list {
+ > li {
+ padding: $gl-padding;
+
+ @include media-breakpoint-up(md) {
+ padding-left: $gl-padding-50;
+ }
+ }
+ }
+
+ .mr-commit-dropdown {
+ .dropdown-menu {
+ @include media-breakpoint-up(md) {
+ width: 150%;
+ }
+ }
+ }
+
.mr-widget-footer {
padding: 0;
}
@@ -405,7 +436,7 @@
}
.mr-widget-help {
- padding: 10px 16px 10px 48px;
+ padding: 10px 16px 10px $gl-padding-50;
font-style: italic;
}
@@ -423,10 +454,6 @@
}
}
-.mr-widget-body-controls {
- flex-wrap: wrap;
-}
-
.mr_source_commit,
.mr_target_commit {
margin-bottom: 0;
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 2342c284a5e..3eb02cd4358 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -946,6 +946,11 @@ pre.light-well {
.flex-wrapper {
min-width: 0;
margin-top: -$gl-padding-8; // negative margin required for flex-wrap
+ flex: 1 1 100%;
+
+ .project-title {
+ line-height: 20px;
+ }
}
p,
@@ -984,14 +989,16 @@ pre.light-well {
}
.controls {
- margin-top: $gl-padding-8;
+ @include media-breakpoint-down(xs) {
+ margin-top: $gl-padding-8;
+ }
- @include media-breakpoint-down(md) {
+ @include media-breakpoint-up(sm) {
margin-top: 0;
}
- @include media-breakpoint-down(xs) {
- margin-top: $gl-padding-8;
+ @include media-breakpoint-up(lg) {
+ flex: 1 1 40%;
}
.icon-wrapper {
@@ -1041,7 +1048,7 @@ pre.light-well {
min-height: 40px;
min-width: 40px;
- .identicon.s64 {
+ .identicon.s48 {
font-size: 16px;
}
}
diff --git a/app/controllers/clusters/clusters_controller.rb b/app/controllers/clusters/clusters_controller.rb
index b9717b97640..3bd91b71d92 100644
--- a/app/controllers/clusters/clusters_controller.rb
+++ b/app/controllers/clusters/clusters_controller.rb
@@ -127,6 +127,7 @@ class Clusters::ClustersController < Clusters::BaseController
params.require(:cluster).permit(
:enabled,
:environment_scope,
+ :base_domain,
platform_kubernetes_attributes: [
:namespace
]
@@ -136,6 +137,7 @@ class Clusters::ClustersController < Clusters::BaseController
:enabled,
:name,
:environment_scope,
+ :base_domain,
platform_kubernetes_attributes: [
:api_url,
:token,
diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb
index 97120273d6b..cc2bb99f55b 100644
--- a/app/controllers/omniauth_callbacks_controller.rb
+++ b/app/controllers/omniauth_callbacks_controller.rb
@@ -116,8 +116,12 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
session[:service_tickets][provider] = ticket
end
+ def build_auth_user(auth_user_class)
+ auth_user_class.new(oauth)
+ end
+
def sign_in_user_flow(auth_user_class)
- auth_user = auth_user_class.new(oauth)
+ auth_user = build_auth_user(auth_user_class)
user = auth_user.find_and_update!
if auth_user.valid_sign_in?
diff --git a/app/controllers/profiles/preferences_controller.rb b/app/controllers/profiles/preferences_controller.rb
index 37ac11dc6a1..94002095739 100644
--- a/app/controllers/profiles/preferences_controller.rb
+++ b/app/controllers/profiles/preferences_controller.rb
@@ -33,12 +33,10 @@ class Profiles::PreferencesController < Profiles::ApplicationController
end
def preferences_params
- params.require(:user).permit(
- :color_scheme_id,
- :layout,
- :dashboard,
- :project_view,
- :theme_id
- )
+ params.require(:user).permit(preferences_param_names)
+ end
+
+ def preferences_param_names
+ [:color_scheme_id, :layout, :dashboard, :project_view, :theme_id, :first_day_of_week]
end
end
diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb
index 79685e8b675..e9cd475a199 100644
--- a/app/controllers/projects/environments_controller.rb
+++ b/app/controllers/projects/environments_controller.rb
@@ -11,11 +11,6 @@ class Projects::EnvironmentsController < Projects::ApplicationController
before_action :verify_api_request!, only: :terminal_websocket_authorize
before_action :expire_etag_cache, only: [:index]
- before_action do
- push_frontend_feature_flag(:area_chart, project)
- end
-
- # Returns all environments or all folders based on the :nested param
def index
@environments = project.environments
.with_state(params[:scope] || :available)
diff --git a/app/controllers/projects/error_tracking_controller.rb b/app/controllers/projects/error_tracking_controller.rb
index 9e403e1d25b..88d0755f41f 100644
--- a/app/controllers/projects/error_tracking_controller.rb
+++ b/app/controllers/projects/error_tracking_controller.rb
@@ -15,6 +15,14 @@ class Projects::ErrorTrackingController < Projects::ApplicationController
end
end
+ def list_projects
+ respond_to do |format|
+ format.json do
+ render_project_list_json
+ end
+ end
+ end
+
private
def render_index_json
@@ -32,6 +40,32 @@ class Projects::ErrorTrackingController < Projects::ApplicationController
}
end
+ def render_project_list_json
+ service = ErrorTracking::ListProjectsService.new(
+ project,
+ current_user,
+ list_projects_params
+ )
+ result = service.execute
+
+ if result[:status] == :success
+ render json: {
+ projects: serialize_projects(result[:projects])
+ }
+ else
+ return render(
+ status: result[:http_status] || :bad_request,
+ json: {
+ message: result[:message]
+ }
+ )
+ end
+ end
+
+ def list_projects_params
+ params.require(:error_tracking_setting).permit([:api_host, :token])
+ end
+
def set_polling_interval
Gitlab::PollingInterval.set_header(response, interval: POLLING_INTERVAL)
end
@@ -41,4 +75,10 @@ class Projects::ErrorTrackingController < Projects::ApplicationController
.new(project: project, user: current_user)
.represent(errors)
end
+
+ def serialize_projects(projects)
+ ErrorTracking::ProjectSerializer
+ .new(project: project, user: current_user)
+ .represent(projects)
+ end
end
diff --git a/app/controllers/projects/merge_requests/application_controller.rb b/app/controllers/projects/merge_requests/application_controller.rb
index 54ff7ded8e5..6045ee4e171 100644
--- a/app/controllers/projects/merge_requests/application_controller.rb
+++ b/app/controllers/projects/merge_requests/application_controller.rb
@@ -34,7 +34,8 @@ class Projects::MergeRequests::ApplicationController < Projects::ApplicationCont
:task_num,
:title,
:discussion_locked,
- label_ids: []
+ label_ids: [],
+ update_task: [:index, :checked, :line_number, :line_source]
]
end
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index 3e08c0ccd8f..23af2e0521c 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -305,7 +305,7 @@ class IssuableFinder
def use_subquery_for_search?
strong_memoize(:use_subquery_for_search) do
attempt_group_search_optimizations? &&
- Feature.enabled?(:use_subquery_for_group_issues_search, default_enabled: false)
+ Feature.enabled?(:use_subquery_for_group_issues_search, default_enabled: true)
end
end
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index c8e4e2e3df9..e635f608237 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -150,6 +150,7 @@ module ApplicationSettingsHelper
:email_author_in_body,
:enabled_git_access_protocol,
:enforce_terms,
+ :first_day_of_week,
:gitaly_timeout_default,
:gitaly_timeout_medium,
:gitaly_timeout_fast,
@@ -231,7 +232,8 @@ module ApplicationSettingsHelper
:web_ide_clientside_preview_enabled,
:diff_max_patch_bytes,
:commit_email_hostname,
- :protected_ci_variables
+ :protected_ci_variables,
+ :local_markdown_version
]
end
diff --git a/app/helpers/auto_devops_helper.rb b/app/helpers/auto_devops_helper.rb
index 516c8a353ea..67e7e475920 100644
--- a/app/helpers/auto_devops_helper.rb
+++ b/app/helpers/auto_devops_helper.rb
@@ -9,41 +9,4 @@ module AutoDevopsHelper
!project.repository.gitlab_ci_yml &&
!project.ci_service
end
-
- def auto_devops_warning_message(project)
- if missing_auto_devops_service?(project)
- params = {
- kubernetes: link_to('Kubernetes cluster', project_clusters_path(project))
- }
-
- if missing_auto_devops_domain?(project)
- _('Auto Review Apps and Auto Deploy need a domain name and a %{kubernetes} to work correctly.') % params
- else
- _('Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly.') % params
- end
- elsif missing_auto_devops_domain?(project)
- _('Auto Review Apps and Auto Deploy need a domain name to work correctly.')
- end
- end
-
- # rubocop: disable CodeReuse/ActiveRecord
- def cluster_ingress_ip(project)
- project
- .cluster_ingresses
- .where("external_ip is not null")
- .limit(1)
- .pluck(:external_ip)
- .first
- end
- # rubocop: enable CodeReuse/ActiveRecord
-
- private
-
- def missing_auto_devops_domain?(project)
- !(project.auto_devops || project.build_auto_devops)&.has_domain?
- end
-
- def missing_auto_devops_service?(project)
- !project.deployment_platform&.active?
- end
end
diff --git a/app/helpers/preferences_helper.rb b/app/helpers/preferences_helper.rb
index f4f46b0fe96..bc1742e8167 100644
--- a/app/helpers/preferences_helper.rb
+++ b/app/helpers/preferences_helper.rb
@@ -43,6 +43,17 @@ module PreferencesHelper
]
end
+ def first_day_of_week_choices
+ [
+ [_('Sunday'), 0],
+ [_('Monday'), 1]
+ ]
+ end
+
+ def first_day_of_week_choices_with_default
+ first_day_of_week_choices.unshift([_('System default (%{default})') % { default: default_first_day_of_week }, nil])
+ end
+
def user_application_theme
@user_application_theme ||= Gitlab::Themes.for_user(current_user).css_class
end
@@ -66,4 +77,8 @@ module PreferencesHelper
def excluded_dashboard_choices
['operations']
end
+
+ def default_first_day_of_week
+ first_day_of_week_choices.rassoc(Gitlab::CurrentSettings.first_day_of_week).first
+ end
end
diff --git a/app/models/application_record.rb b/app/models/application_record.rb
index c4e310e638d..a3d662d8250 100644
--- a/app/models/application_record.rb
+++ b/app/models/application_record.rb
@@ -7,6 +7,12 @@ class ApplicationRecord < ActiveRecord::Base
where(id: ids)
end
+ def self.safe_find_or_create_by!(*args)
+ safe_find_or_create_by(*args).tap do |record|
+ record.validate! unless record.persisted?
+ end
+ end
+
def self.safe_find_or_create_by(*args)
transaction(requires_new: true) do
find_or_create_by(*args)
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 88746375c67..daadf9427ba 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -193,6 +193,10 @@ class ApplicationSetting < ActiveRecord::Base
allow_nil: true,
numericality: { only_integer: true, greater_than_or_equal_to: 1.day.seconds }
+ validates :local_markdown_version,
+ allow_nil: true,
+ numericality: { only_integer: true, greater_than_or_equal_to: 0, less_than: 65536 }
+
SUPPORTED_KEY_TYPES.each do |type|
validates :"#{type}_key_restriction", presence: true, key_restriction: { type: type }
end
@@ -246,6 +250,7 @@ class ApplicationSetting < ActiveRecord::Base
dsa_key_restriction: 0,
ecdsa_key_restriction: 0,
ed25519_key_restriction: 0,
+ first_day_of_week: 0,
gitaly_timeout_default: 55,
gitaly_timeout_fast: 10,
gitaly_timeout_medium: 30,
@@ -303,7 +308,8 @@ class ApplicationSetting < ActiveRecord::Base
usage_stats_set_by_user_id: nil,
diff_max_patch_bytes: Gitlab::Git::Diff::DEFAULT_MAX_PATCH_BYTES,
commit_email_hostname: default_commit_email_hostname,
- protected_ci_variables: false
+ protected_ci_variables: false,
+ local_markdown_version: 0
}
end
diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb
index a2c48973fa5..f2f5b89e3bb 100644
--- a/app/models/clusters/cluster.rb
+++ b/app/models/clusters/cluster.rb
@@ -18,6 +18,7 @@ module Clusters
Applications::Knative.application_name => Applications::Knative
}.freeze
DEFAULT_ENVIRONMENT = '*'.freeze
+ KUBE_INGRESS_BASE_DOMAIN = 'KUBE_INGRESS_BASE_DOMAIN'.freeze
belongs_to :user
@@ -49,7 +50,7 @@ module Clusters
validates :name, cluster_name: true
validates :cluster_type, presence: true
- validates :domain, allow_nil: true, hostname: { allow_numeric_hostname: true, require_valid_tld: true }
+ validates :domain, allow_blank: true, hostname: { allow_numeric_hostname: true, require_valid_tld: true }
validate :restrict_modification, on: :update
validate :no_groups, unless: :group_type?
@@ -65,6 +66,9 @@ module Clusters
delegate :available?, to: :application_ingress, prefix: true, allow_nil: true
delegate :available?, to: :application_prometheus, prefix: true, allow_nil: true
delegate :available?, to: :application_knative, prefix: true, allow_nil: true
+ delegate :external_ip, to: :application_ingress, prefix: true, allow_nil: true
+
+ alias_attribute :base_domain, :domain
enum cluster_type: {
instance_type: 1,
@@ -193,8 +197,41 @@ module Clusters
project_type?
end
+ def kube_ingress_domain
+ @kube_ingress_domain ||= domain.presence || instance_domain || legacy_auto_devops_domain
+ end
+
+ def predefined_variables
+ Gitlab::Ci::Variables::Collection.new.tap do |variables|
+ break variables unless kube_ingress_domain
+
+ variables.append(key: KUBE_INGRESS_BASE_DOMAIN, value: kube_ingress_domain)
+ end
+ end
+
private
+ def instance_domain
+ @instance_domain ||= Gitlab::CurrentSettings.auto_devops_domain
+ end
+
+ # To keep backward compatibility with AUTO_DEVOPS_DOMAIN
+ # environment variable, we need to ensure KUBE_INGRESS_BASE_DOMAIN
+ # is set if AUTO_DEVOPS_DOMAIN is set on any of the following options:
+ # ProjectAutoDevops#Domain, project variables or group variables,
+ # as the AUTO_DEVOPS_DOMAIN is needed for CI_ENVIRONMENT_URL
+ #
+ # This method should be removed on 12.0
+ def legacy_auto_devops_domain
+ if project_type?
+ project&.auto_devops&.domain.presence ||
+ project.variables.find_by(key: 'AUTO_DEVOPS_DOMAIN')&.value.presence ||
+ project.group&.variables&.find_by(key: 'AUTO_DEVOPS_DOMAIN')&.value.presence
+ elsif group_type?
+ group.variables.find_by(key: 'AUTO_DEVOPS_DOMAIN')&.value.presence
+ end
+ end
+
def restrict_modification
if provider&.on_creation?
errors.add(:base, "cannot modify during creation")
diff --git a/app/models/clusters/platforms/kubernetes.rb b/app/models/clusters/platforms/kubernetes.rb
index 8f3424db295..c8969351ed9 100644
--- a/app/models/clusters/platforms/kubernetes.rb
+++ b/app/models/clusters/platforms/kubernetes.rb
@@ -98,6 +98,8 @@ module Clusters
.append(key: 'KUBE_NAMESPACE', value: actual_namespace)
.append(key: 'KUBECONFIG', value: kubeconfig, public: false, file: true)
end
+
+ variables.concat(cluster.predefined_variables)
end
end
diff --git a/app/models/concerns/cache_markdown_field.rb b/app/models/concerns/cache_markdown_field.rb
index 5fa6f79bdaa..1a8570b80c3 100644
--- a/app/models/concerns/cache_markdown_field.rb
+++ b/app/models/concerns/cache_markdown_field.rb
@@ -115,7 +115,28 @@ module CacheMarkdownField
end
def latest_cached_markdown_version
- CacheMarkdownField::CACHE_COMMONMARK_VERSION
+ @latest_cached_markdown_version ||= (CacheMarkdownField::CACHE_COMMONMARK_VERSION << 16) | local_version
+ end
+
+ def local_version
+ # because local_markdown_version is stored in application_settings which
+ # uses cached_markdown_version too, we check explicitly to avoid
+ # endless loop
+ return local_markdown_version if has_attribute?(:local_markdown_version)
+
+ settings = Gitlab::CurrentSettings.current_application_settings
+
+ # Following migrations are not properly isolated and
+ # use real models (by calling .ghost method), in these migrations
+ # local_markdown_version attribute doesn't exist yet, so we
+ # use a default value:
+ # db/migrate/20170825104051_migrate_issues_to_ghost_user.rb
+ # db/migrate/20171114150259_merge_requests_author_id_foreign_key.rb
+ if settings.respond_to?(:local_markdown_version)
+ settings.local_markdown_version
+ else
+ 0
+ end
end
included do
diff --git a/app/models/gpg_signature.rb b/app/models/gpg_signature.rb
index 0816778deae..7f9ff7bbda6 100644
--- a/app/models/gpg_signature.rb
+++ b/app/models/gpg_signature.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class GpgSignature < ActiveRecord::Base
+class GpgSignature < ApplicationRecord
include ShaAttribute
sha_attribute :commit_sha
@@ -33,6 +33,11 @@ class GpgSignature < ActiveRecord::Base
)
end
+ def self.safe_create!(attributes)
+ create_with(attributes)
+ .safe_find_or_create_by!(commit_sha: attributes[:commit_sha])
+ end
+
def gpg_key=(model)
case model
when GpgKey
diff --git a/app/models/pool_repository.rb b/app/models/pool_repository.rb
index 34220c1b450..4635fc72dc7 100644
--- a/app/models/pool_repository.rb
+++ b/app/models/pool_repository.rb
@@ -96,7 +96,9 @@ class PoolRepository < ActiveRecord::Base
@object_pool ||= Gitlab::Git::ObjectPool.new(
shard.name,
disk_path + '.git',
- source_project.repository.raw)
+ source_project.repository.raw,
+ source_project.full_path
+ )
end
def inspect
diff --git a/app/models/project.rb b/app/models/project.rb
index d4e2ed883bc..8f746f6e094 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -1288,7 +1288,7 @@ class Project < ActiveRecord::Base
# Forked import is handled asynchronously
return if forked? && !force
- if gitlab_shell.create_repository(repository_storage, disk_path)
+ if gitlab_shell.create_project_repository(self)
repository.after_create
true
else
diff --git a/app/models/project_auto_devops.rb b/app/models/project_auto_devops.rb
index 2253ad7b543..b6c5c7c4c87 100644
--- a/app/models/project_auto_devops.rb
+++ b/app/models/project_auto_devops.rb
@@ -24,6 +24,11 @@ class ProjectAutoDevops < ActiveRecord::Base
domain.present? || instance_domain.present?
end
+ # From 11.8, AUTO_DEVOPS_DOMAIN has been replaced by KUBE_INGRESS_BASE_DOMAIN.
+ # See Clusters::Cluster#predefined_variables and https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/24580
+ # for more info.
+ # Support for AUTO_DEVOPS_DOMAIN support will be dropped on 12.0 on
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/52363
def predefined_variables
Gitlab::Ci::Variables::Collection.new.tap do |variables|
if has_domain?
diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb
index 559e4f99294..c43bd45a62f 100644
--- a/app/models/project_wiki.rb
+++ b/app/models/project_wiki.rb
@@ -60,7 +60,7 @@ class ProjectWiki
def wiki
@wiki ||= begin
gl_repository = Gitlab::GlRepository.gl_repository(project, true)
- raw_repository = Gitlab::Git::Repository.new(project.repository_storage, disk_path + '.git', gl_repository)
+ raw_repository = Gitlab::Git::Repository.new(project.repository_storage, disk_path + '.git', gl_repository, full_path)
create_repo!(raw_repository) unless raw_repository.exists?
@@ -175,7 +175,7 @@ class ProjectWiki
private
def create_repo!(raw_repository)
- gitlab_shell.create_repository(project.repository_storage, disk_path)
+ gitlab_shell.create_wiki_repository(project)
raise CouldNotCreateWikiError unless raw_repository.exists?
diff --git a/app/models/repository.rb b/app/models/repository.rb
index bfd2608bed4..7c50b4488e5 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -1104,6 +1104,9 @@ class Repository
end
def initialize_raw_repository
- Gitlab::Git::Repository.new(project.repository_storage, disk_path + '.git', Gitlab::GlRepository.gl_repository(project, is_wiki))
+ Gitlab::Git::Repository.new(project.repository_storage,
+ disk_path + '.git',
+ Gitlab::GlRepository.gl_repository(project, is_wiki),
+ project.full_path)
end
end
diff --git a/app/models/user.rb b/app/models/user.rb
index 9c091ac366c..24101eda0b1 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -228,6 +228,9 @@ class User < ApplicationRecord
delegate :path, to: :namespace, allow_nil: true, prefix: true
delegate :notes_filter_for, to: :user_preference
delegate :set_notes_filter, to: :user_preference
+ delegate :first_day_of_week, :first_day_of_week=, to: :user_preference
+
+ accepts_nested_attributes_for :user_preference, update_only: true
state_machine :state, initial: :active do
event :block do
diff --git a/app/serializers/error_tracking/project_serializer.rb b/app/serializers/error_tracking/project_serializer.rb
index b2406f4d631..68724088fff 100644
--- a/app/serializers/error_tracking/project_serializer.rb
+++ b/app/serializers/error_tracking/project_serializer.rb
@@ -2,6 +2,6 @@
module ErrorTracking
class ProjectSerializer < BaseSerializer
- entity ProjectEntity
+ entity ErrorTracking::ProjectEntity
end
end
diff --git a/app/services/error_tracking/list_projects_service.rb b/app/services/error_tracking/list_projects_service.rb
new file mode 100644
index 00000000000..c6e8be0f2be
--- /dev/null
+++ b/app/services/error_tracking/list_projects_service.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+module ErrorTracking
+ class ListProjectsService < ::BaseService
+ def execute
+ return error('access denied') unless can_read?
+
+ setting = project_error_tracking_setting
+
+ unless setting.valid?
+ return error(setting.errors.full_messages.join(', '), :bad_request)
+ end
+
+ begin
+ result = setting.list_sentry_projects
+ rescue Sentry::Client::Error => e
+ return error(e.message, :bad_request)
+ rescue Sentry::Client::SentryError => e
+ return error(e.message, :unprocessable_entity)
+ end
+
+ success(projects: result[:projects])
+ end
+
+ private
+
+ def project_error_tracking_setting
+ (project.error_tracking_setting || project.build_error_tracking_setting).tap do |setting|
+ setting.api_url = ErrorTracking::ProjectErrorTrackingSetting.build_api_url_from(
+ api_host: params[:api_host],
+ organization_slug: nil,
+ project_slug: nil
+ )
+
+ setting.token = params[:token]
+ setting.enabled = true
+ end
+ end
+
+ def can_read?
+ can?(current_user, :read_sentry_issue, project)
+ end
+ end
+end
diff --git a/app/services/labels/update_service.rb b/app/services/labels/update_service.rb
index e563447c64c..be33947d0eb 100644
--- a/app/services/labels/update_service.rb
+++ b/app/services/labels/update_service.rb
@@ -8,6 +8,7 @@ module Labels
# returns the updated label
def execute(label)
+ params[:name] = params.delete(:new_name) if params.key?(:new_name)
params[:color] = convert_color_name_to_hex if params[:color].present?
label.update(params)
diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb
index 86a04587f79..8112c2a4299 100644
--- a/app/services/merge_requests/update_service.rb
+++ b/app/services/merge_requests/update_service.rb
@@ -21,7 +21,7 @@ module MergeRequests
end
handle_wip_event(merge_request)
- update(merge_request)
+ update_task_event(merge_request) || update(merge_request)
end
# rubocop:disable Metrics/AbcSize
@@ -83,6 +83,11 @@ module MergeRequests
end
# rubocop:enable Metrics/AbcSize
+ def handle_task_changes(merge_request)
+ todo_service.mark_pending_todos_as_done(merge_request, current_user)
+ todo_service.update_merge_request(merge_request, current_user)
+ end
+
def merge_from_quick_action(merge_request)
last_diff_sha = params.delete(:merge)
return unless merge_request.mergeable_with_quick_action?(current_user, last_diff_sha: last_diff_sha)
diff --git a/app/services/projects/import_service.rb b/app/services/projects/import_service.rb
index 5861b803996..7214e9efaf6 100644
--- a/app/services/projects/import_service.rb
+++ b/app/services/projects/import_service.rb
@@ -73,7 +73,7 @@ module Projects
project.ensure_repository
project.repository.fetch_as_mirror(project.import_url, refmap: refmap)
else
- gitlab_shell.import_repository(project.repository_storage, project.disk_path, project.import_url)
+ gitlab_shell.import_project_repository(project)
end
rescue Gitlab::Shell::Error => e
# Expire cache to prevent scenarios such as:
diff --git a/app/services/task_list_toggle_service.rb b/app/services/task_list_toggle_service.rb
index b5c4cd3235d..cfe187d9b12 100644
--- a/app/services/task_list_toggle_service.rb
+++ b/app/services/task_list_toggle_service.rb
@@ -32,7 +32,8 @@ class TaskListToggleService
source_line_index = line_number - 1
markdown_task = source_lines[source_line_index]
- return unless markdown_task == line_source
+ # The source in the DB could be using either \n or \r\n line endings
+ return unless markdown_task == line_source || markdown_task == line_source + "\r"
return unless source_checkbox = Taskable::ITEM_PATTERN.match(markdown_task)
currently_checked = TaskList::Item.new(source_checkbox[1]).complete?
diff --git a/app/views/admin/application_settings/_localization.html.haml b/app/views/admin/application_settings/_localization.html.haml
new file mode 100644
index 00000000000..95d016a94a5
--- /dev/null
+++ b/app/views/admin/application_settings/_localization.html.haml
@@ -0,0 +1,11 @@
+= form_for @application_setting, url: admin_application_settings_path(anchor: 'js-localization-settings'), html: { class: 'fieldset-form' } do |f|
+ = form_errors(@application_setting)
+
+ %fieldset
+ .form-group
+ = f.label :first_day_of_week, _('Default first day of the week'), class: 'label-bold'
+ = f.select :first_day_of_week, first_day_of_week_choices, {}, class: 'form-control'
+ .form-text.text-muted
+ = _('Default first day of the week in calendars and date pickers.')
+
+ = f.submit _('Save changes'), class: "btn btn-success"
diff --git a/app/views/admin/application_settings/preferences.html.haml b/app/views/admin/application_settings/preferences.html.haml
index 00000b86ab7..c468d69d7b8 100644
--- a/app/views/admin/application_settings/preferences.html.haml
+++ b/app/views/admin/application_settings/preferences.html.haml
@@ -56,3 +56,14 @@
= _('Configure Gitaly timeouts.')
.settings-content
= render 'gitaly'
+
+%section.settings.as-localization.no-animate#js-localization-settings{ class: ('expanded' if expanded_by_default?) }
+ .settings-header
+ %h4
+ = _('Localization')
+ %button.btn.btn-default.js-settings-toggle{ type: 'button' }
+ = expanded_by_default? ? _('Collapse') : _('Expand')
+ %p
+ = _('Various localization settings.')
+ .settings-content
+ = render 'localization'
diff --git a/app/views/clusters/clusters/_form.html.haml b/app/views/clusters/clusters/_form.html.haml
index 4c47e11927e..7acd9ce0562 100644
--- a/app/views/clusters/clusters/_form.html.haml
+++ b/app/views/clusters/clusters/_form.html.haml
@@ -20,12 +20,27 @@
.form-text.text-muted= s_("ClusterIntegration|Choose which of your environments will use this cluster.")
- else
= text_field_tag :environment_scope, '*', class: 'col-md-6 form-control disabled', placeholder: s_('ClusterIntegration|Environment scope'), disabled: true
- - environment_scope_url = 'https://docs.gitlab.com/ee/user/project/clusters/#setting-the-environment-scope-premium'
+ - environment_scope_url = help_page_path('user/project/clusters/index', anchor: 'base-domain')
- environment_scope_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: environment_scope_url }
.form-text.text-muted
%code *
= s_("ClusterIntegration| is the default environment scope for this cluster. This means that all jobs, regardless of their environment, will use this cluster. %{environment_scope_start}More information%{environment_scope_end}").html_safe % { environment_scope_start: environment_scope_start, environment_scope_end: '</a>'.html_safe }
+ .form-group
+ %h5= s_('ClusterIntegration|Base domain')
+ = field.text_field :base_domain, class: 'col-md-6 form-control js-select-on-focus'
+ .form-text.text-muted
+ - auto_devops_url = help_page_path('topics/autodevops/index')
+ - auto_devops_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: auto_devops_url }
+ = s_('ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain.').html_safe % { auto_devops_start: auto_devops_start, auto_devops_end: '</a>'.html_safe }
+ - if @cluster.application_ingress_external_ip.present?
+ = s_('ClusterIntegration|Alternatively')
+ %code #{@cluster.application_ingress_external_ip}.nip.io
+ = s_('ClusterIntegration| can be used instead of a custom domain.')
+ - custom_domain_url = help_page_path('user/project/clusters/index', anchor: 'pointing-your-dns-at-the-cluster-ip')
+ - custom_domain_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: custom_domain_url }
+ = s_('ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}.').html_safe % { custom_domain_start: custom_domain_start, custom_domain_end: '</a>'.html_safe }
+
- if can?(current_user, :update_cluster, @cluster)
.form-group
= field.submit _('Save changes'), class: 'btn btn-success'
diff --git a/app/views/layouts/nav/sidebar/_group.html.haml b/app/views/layouts/nav/sidebar/_group.html.haml
index bf475c07711..3fbaaafe89e 100644
--- a/app/views/layouts/nav/sidebar/_group.html.haml
+++ b/app/views/layouts/nav/sidebar/_group.html.haml
@@ -36,7 +36,7 @@
%span
= _('Activity')
- = render_if_exists 'groups/sidebar/security_dashboard'
+ = render_if_exists 'groups/sidebar/security_dashboard' # EE-specific
- if group_sidebar_link?(:contribution_analytics)
= nav_link(path: 'analytics#show') do
diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml
index 7c378633667..1a9aca1f6bf 100644
--- a/app/views/profiles/preferences/show.html.haml
+++ b/app/views/profiles/preferences/show.html.haml
@@ -51,11 +51,30 @@
= f.label :dashboard, class: 'label-bold' do
Default dashboard
= f.select :dashboard, dashboard_choices, {}, class: 'form-control'
+
+ = render_if_exists 'profiles/preferences/group_overview_selector', f: f # EE-specific
+
.form-group
= f.label :project_view, class: 'label-bold' do
Project overview content
= f.select :project_view, project_view_choices, {}, class: 'form-control'
.form-text.text-muted
Choose what content you want to see on a project’s overview page.
+
+ .col-sm-12
+ %hr
+
+ .col-lg-4.profile-settings-sidebar
+ %h4.prepend-top-0
+ = _('Localization')
+ %p
+ = _('Customize language and region related settings.')
+ = succeed '.' do
+ = link_to _('Learn more'), help_page_path('user/profile/preferences', anchor: 'localization'), target: '_blank'
+ .col-lg-8
+ .form-group
+ = f.label :first_day_of_week, class: 'label-bold' do
+ = _('First day of the week')
+ = f.select :first_day_of_week, first_day_of_week_choices_with_default, {}, class: 'form-control'
.form-group
- = f.submit 'Save changes', class: 'btn btn-success'
+ = f.submit _('Save changes'), class: 'btn btn-success'
diff --git a/app/views/projects/commit/show.html.haml b/app/views/projects/commit/show.html.haml
index 06f0cd9675e..fe9a8ac4182 100644
--- a/app/views/projects/commit/show.html.haml
+++ b/app/views/projects/commit/show.html.haml
@@ -10,7 +10,7 @@
.container-fluid{ class: [limited_container_width, container_class] }
= render "commit_box"
= render "ci_menu"
- = render "projects/diffs/diffs", diffs: @diffs, environment: @environment, is_commit: true
+ = render "projects/diffs/diffs", diffs: @diffs, environment: @environment, diff_page_context: "is-commit"
.limited-width-notes
= render "shared/notes/notes_with_form", :autocomplete => true
diff --git a/app/views/projects/compare/show.html.haml b/app/views/projects/compare/show.html.haml
index b6bebbabed0..5774b48a054 100644
--- a/app/views/projects/compare/show.html.haml
+++ b/app/views/projects/compare/show.html.haml
@@ -8,7 +8,7 @@
- if @commits.present?
= render "projects/commits/commit_list"
- = render "projects/diffs/diffs", diffs: @diffs, environment: @environment
+ = render "projects/diffs/diffs", diffs: @diffs, environment: @environment, diff_page_context: "is-compare"
- else
.card.bg-light
.center
diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml
index cc2d0d3b2d8..2dba3fcd664 100644
--- a/app/views/projects/diffs/_diffs.html.haml
+++ b/app/views/projects/diffs/_diffs.html.haml
@@ -2,7 +2,7 @@
- show_whitespace_toggle = local_assigns.fetch(:show_whitespace_toggle, true)
- can_create_note = !@diff_notes_disabled && can?(current_user, :create_note, diffs.project)
- diff_files = diffs.diff_files
-- is_commit = local_assigns.fetch(:is_commit, false)
+- diff_page_context = local_assigns.fetch(:diff_page_context, nil)
.content-block.oneline-block.files-changed.diff-files-changed.js-diff-files-changed
.files-changed-inner
@@ -25,4 +25,4 @@
= render 'projects/diffs/warning', diff_files: diffs
.files{ data: { can_create_note: can_create_note } }
- = render partial: 'projects/diffs/file', collection: diff_files, as: :diff_file, locals: { project: diffs.project, environment: environment, is_commit: is_commit }
+ = render partial: 'projects/diffs/file', collection: diff_files, as: :diff_file, locals: { project: diffs.project, environment: environment, diff_page_context: diff_page_context }
diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml
index 5565ae1d98b..855b719dc45 100644
--- a/app/views/projects/diffs/_file.html.haml
+++ b/app/views/projects/diffs/_file.html.haml
@@ -1,11 +1,11 @@
- environment = local_assigns.fetch(:environment, nil)
-- is_commit = local_assigns.fetch(:is_commit, false)
+- diff_page_context = local_assigns.fetch(:diff_page_context, nil)
- file_hash = hexdigest(diff_file.file_path)
- image_diff = diff_file.rich_viewer && diff_file.rich_viewer.partial_name == 'image'
- image_replaced = diff_file.old_content_sha && diff_file.old_content_sha != diff_file.content_sha
.diff-file.file-holder{ id: file_hash, data: diff_file_html_data(project, diff_file.file_path, diff_file.content_sha) }
- .js-file-title.file-title-flex-parent{ class: is_commit ? "is-commit" : "" }
+ .js-file-title.file-title-flex-parent{ class: diff_page_context }
.file-header-content
= render "projects/diffs/file_header", diff_file: diff_file, url: "##{file_hash}"
diff --git a/app/views/projects/settings/ci_cd/_autodevops_form.html.haml b/app/views/projects/settings/ci_cd/_autodevops_form.html.haml
index 5ec5a06396e..8c4d1c32ebe 100644
--- a/app/views/projects/settings/ci_cd/_autodevops_form.html.haml
+++ b/app/views/projects/settings/ci_cd/_autodevops_form.html.haml
@@ -4,10 +4,6 @@
= form_errors(@project)
%fieldset.builds-feature.js-auto-devops-settings
.form-group
- - message = auto_devops_warning_message(@project)
- - if message
- %p.auto-devops-warning-message.settings-message.text-center
- = message.html_safe
= f.fields_for :auto_devops_attributes, @auto_devops do |form|
.card.auto-devops-card
.card-body
@@ -21,19 +17,12 @@
= s_('CICD|The Auto DevOps pipeline will run if no alternative CI configuration file is found.')
= link_to _('More information'), help_page_path('topics/autodevops/index.md'), target: '_blank'
.card-footer.js-extra-settings{ class: @project.auto_devops_enabled? || 'hidden' }
- = form.label :domain do
- %strong= _('Domain')
- = form.text_field :domain, class: 'form-control', placeholder: 'domain.com'
- .form-text.text-muted
- = s_('CICD|You need to specify a domain if you want to use Auto Review Apps and Auto Deploy stages.')
- - if cluster_ingress_ip = cluster_ingress_ip(@project)
- = s_('%{nip_domain} can be used as an alternative to a custom domain.').html_safe % { nip_domain: "<code>#{cluster_ingress_ip}.nip.io</code>".html_safe }
- = link_to icon('question-circle'), help_page_path('topics/autodevops/index.md', anchor: 'auto-devops-base-domain'), target: '_blank'
-
+ %p.settings-message.text-center
+ - kubernetes_cluster_link = help_page_path('user/project/clusters/index')
+ - kubernetes_cluster_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: kubernetes_cluster_link }
+ = s_('CICD|You must add a %{kubernetes_cluster_start}Kubernetes cluster integration%{kubernetes_cluster_end} to this project with a domain in order for your deployment strategy to work correctly.').html_safe % { kubernetes_cluster_start: kubernetes_cluster_start, kubernetes_cluster_end: '</a>'.html_safe }
%label.prepend-top-10
%strong= s_('CICD|Deployment strategy')
- %p.settings-message.text-center
- = s_('CICD|Deployment strategy needs a domain name to work correctly.')
.form-check
= form.radio_button :deploy_strategy, 'continuous', class: 'form-check-input'
= form.label :deploy_strategy_continuous, class: 'form-check-label' do
diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml
index e1564d57426..df17ae95e2a 100644
--- a/app/views/shared/projects/_project.html.haml
+++ b/app/views/shared/projects/_project.html.haml
@@ -12,21 +12,20 @@
- css_class += " no-description" if project.description.blank? && !show_last_commit_as_description
- cache_key = project_list_cache_key(project)
- updated_tooltip = time_ago_with_tooltip(project.last_activity_date)
-- css_details_class = compact_mode ? "d-flex flex-column flex-sm-row flex-md-row align-items-sm-center" : "align-items-center flex-md-fill flex-lg-column d-sm-flex d-lg-block"
-- css_controls_class = compact_mode ? "" : "align-items-md-end align-items-lg-center flex-lg-row"
+- css_controls_class = compact_mode ? "" : "flex-lg-row justify-content-lg-between"
%li.project-row.d-flex{ class: css_class }
= cache(cache_key) do
- if avatar
- .avatar-container.s64.flex-grow-0.flex-shrink-0
+ .avatar-container.s48.flex-grow-0.flex-shrink-0
= link_to project_path(project), class: dom_class(project) do
- if project.creator && use_creator_avatar
- = image_tag avatar_icon_for_user(project.creator, 64), class: "avatar s65", alt:''
+ = image_tag avatar_icon_for_user(project.creator, 48), class: "avatar s65", alt:''
- else
- = project_icon(project, alt: '', class: 'avatar project-avatar s64', width: 64, height: 64)
- .project-details.flex-sm-fill{ class: css_details_class }
- .flex-wrapper.flex-fill
- .d-flex.align-items-center.flex-wrap
+ = project_icon(project, alt: '', class: 'avatar project-avatar s48', width: 48, height: 48)
+ .project-details.d-sm-flex.flex-sm-fill.align-items-center
+ .flex-wrapper
+ .d-flex.align-items-center.flex-wrap.project-title
%h2.d-flex.prepend-top-8
= link_to project_path(project), class: 'text-plain' do
%span.project-full-name.append-right-8><
@@ -52,13 +51,13 @@
%span.user-access-role.d-block= Gitlab::Access.human_access(access)
- if show_last_commit_as_description
- .description.d-none.d-sm-block.prepend-top-8.append-right-default
+ .description.d-none.d-sm-block.append-right-default
= link_to_markdown(project.commit.title, project_commit_path(project, project.commit), class: "commit-row-message")
- elsif project.description.present?
- .description.d-none.d-sm-block.prepend-top-8.append-right-default
+ .description.d-none.d-sm-block.append-right-default
= markdown_field(project, :description)
- .controls.d-flex.flex-row.flex-sm-column.flex-md-column.align-items-center.align-items-sm-end.flex-wrap.flex-shrink-0{ class: css_controls_class }
+ .controls.d-flex.flex-sm-column.align-items-center.align-items-sm-end.flex-wrap.flex-shrink-0{ class: css_controls_class }
.icon-container.d-flex.align-items-center
- if project.archived
%span.d-flex.icon-wrapper.badge.badge-warning archived
@@ -74,13 +73,13 @@
= number_with_delimiter(project.forks_count)
- if show_merge_request_count?(disabled: !merge_requests, compact_mode: compact_mode)
= link_to project_merge_requests_path(project),
- class: "d-none d-lg-flex align-items-center icon-wrapper merge-requests has-tooltip",
+ class: "d-none d-xl-flex align-items-center icon-wrapper merge-requests has-tooltip",
title: _('Merge Requests'), data: { container: 'body', placement: 'top' } do
= sprite_icon('git-merge', size: 14, css_class: 'append-right-4')
= number_with_delimiter(project.open_merge_requests_count)
- if show_issue_count?(disabled: !issues, compact_mode: compact_mode)
= link_to project_issues_path(project),
- class: "d-none d-lg-flex align-items-center icon-wrapper issues has-tooltip",
+ class: "d-none d-xl-flex align-items-center icon-wrapper issues has-tooltip",
title: _('Issues'), data: { container: 'body', placement: 'top' } do
= sprite_icon('issues', size: 14, css_class: 'append-right-4')
= number_with_delimiter(project.open_issues_count)
@@ -89,19 +88,3 @@
= render_project_pipeline_status(project.pipeline_status, tooltip_placement: 'top')
.updated-note
%span Updated #{updated_tooltip}
-
- .d-none.d-lg-flex.align-item-stretch
- - unless compact_mode
- - if current_user
- %button.star-button.btn.btn-default.d-flex.align-items-center.star-btn.toggle-star{ type: "button", data: { endpoint: toggle_star_project_path(project, :json) } }
- - if current_user.starred?(project)
- = sprite_icon('star', { css_class: 'icon' })
- %span.starred= s_('ProjectOverview|Unstar')
- - else
- = sprite_icon('star-o', { css_class: 'icon' })
- %span= s_('ProjectOverview|Star')
-
- - else
- = link_to new_user_session_path, class: 'btn btn-default has-tooltip count-badge-button d-flex align-items-center star-btn', title: s_('ProjectOverview|You must sign in to star a project') do
- = sprite_icon('star-o', { css_class: 'icon' })
- %span= s_('ProjectOverview|Star')
diff --git a/app/workers/repository_fork_worker.rb b/app/workers/repository_fork_worker.rb
index 7eae07d3f6b..a9b88a133be 100644
--- a/app/workers/repository_fork_worker.rb
+++ b/app/workers/repository_fork_worker.rb
@@ -15,19 +15,19 @@ class RepositoryForkWorker
return target_project.import_state.mark_as_failed(_('Source project cannot be found.'))
end
- fork_repository(target_project, source_project.repository_storage, source_project.disk_path)
+ fork_repository(target_project, source_project)
end
private
- def fork_repository(target_project, source_repository_storage_name, source_disk_path)
+ def fork_repository(target_project, source_project)
return unless start_fork(target_project)
Gitlab::Metrics.add_event(:fork_repository)
- result = gitlab_shell.fork_repository(source_repository_storage_name, source_disk_path,
- target_project.repository_storage, target_project.disk_path)
- raise "Unable to fork project #{target_project.id} for repository #{source_disk_path} -> #{target_project.disk_path}" unless result
+ result = gitlab_shell.fork_repository(source_project, target_project)
+
+ raise "Unable to fork project #{target_project.id} for repository #{source_project.disk_path} -> #{target_project.disk_path}" unless result
target_project.after_import
end