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
diff options
context:
space:
mode:
-rw-r--r--.gitlab/issue_templates/Security developer workflow.md1
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--app/assets/javascripts/environments/components/empty_state.vue4
-rw-r--r--app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue19
-rw-r--r--app/assets/javascripts/persistent_user_callout.js4
-rw-r--r--app/assets/javascripts/persistent_user_callouts.js1
-rw-r--r--app/assets/stylesheets/framework.scss1
-rw-r--r--app/assets/stylesheets/framework/blank.scss118
-rw-r--r--app/assets/stylesheets/framework/variables.scss2
-rw-r--r--app/assets/stylesheets/page_bundles/dashboard_projects.scss35
-rw-r--r--app/assets/stylesheets/page_bundles/merge_requests.scss637
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss632
-rw-r--r--app/controllers/projects_controller.rb1
-rw-r--r--app/helpers/projects_helper.rb1
-rw-r--r--app/helpers/users/group_callouts_helper.rb1
-rw-r--r--app/models/deployment.rb8
-rw-r--r--app/models/project.rb1
-rw-r--r--app/models/users/group_callout.rb3
-rw-r--r--app/policies/project_policy.rb1
-rw-r--r--app/services/ci/after_requeue_job_service.rb10
-rw-r--r--app/views/dashboard/projects/_blank_state_admin_welcome.html.haml52
-rw-r--r--app/views/dashboard/projects/_blank_state_welcome.html.haml65
-rw-r--r--app/views/dashboard/projects/_zero_authorized_projects.html.haml23
-rw-r--r--app/views/dashboard/projects/index.html.haml1
-rw-r--r--app/views/notify/_note_email.html.haml3
-rw-r--r--app/views/notify/_note_email.text.erb2
-rw-r--r--app/views/projects/environments/show.html.haml2
-rw-r--r--config/application.rb1
-rw-r--r--config/feature_flags/development/ci_order_subsequent_jobs_by_stage.yml8
-rw-r--r--config/feature_flags/development/redirect_to_latest_template_jobs_build.yml8
-rw-r--r--db/migrate/20220131192643_add_show_diff_preview_in_email_to_project_settings.rb9
-rw-r--r--db/post_migrate/20220128155251_remove_dangling_running_builds.rb25
-rw-r--r--db/schema_migrations/202201281552511
-rw-r--r--db/schema_migrations/202201311926431
-rw-r--r--db/structure.sql1
-rw-r--r--doc/administration/packages/index.md2
-rw-r--r--lib/gitlab/ci/templates/Jobs/Build.gitlab-ci.yml5
-rw-r--r--lib/gitlab/ci/templates/Jobs/Build.latest.gitlab-ci.yml4
-rw-r--r--lib/gitlab/template/gitlab_ci_yml_template.rb4
-rw-r--r--locale/gitlab.pot57
-rw-r--r--spec/features/projects/environments/environment_spec.rb23
-rw-r--r--spec/features/projects/settings/project_settings_spec.rb30
-rw-r--r--spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js11
-rw-r--r--spec/frontend/persistent_user_callout_spec.js8
-rw-r--r--spec/helpers/projects_helper_spec.rb1
-rw-r--r--spec/migrations/20220128155251_remove_dangling_running_builds_spec.rb52
-rw-r--r--spec/models/deployment_spec.rb32
-rw-r--r--spec/requests/api/project_attributes.yml1
-rw-r--r--spec/services/ci/pipeline_processing/atomic_processing_service_spec.rb16
-rw-r--r--workhorse/internal/upload/rewrite.go14
-rw-r--r--workhorse/internal/upload/rewrite_test.go81
-rw-r--r--workhorse/internal/upload/uploads_test.go86
52 files changed, 1187 insertions, 924 deletions
diff --git a/.gitlab/issue_templates/Security developer workflow.md b/.gitlab/issue_templates/Security developer workflow.md
index 3a210c358e5..5c1b669a88f 100644
--- a/.gitlab/issue_templates/Security developer workflow.md
+++ b/.gitlab/issue_templates/Security developer workflow.md
@@ -15,6 +15,7 @@ MUST be linked for the release bot to know that the associated merge requests sh
- Fill out the [Links section](#links):
- [ ] Next to **Issue on GitLab**, add a link to the `gitlab-org/gitlab` issue that describes the security vulnerability.
- [ ] Add one of the `~severity::x` labels to the issue and all associated merge requests.
+- [ ] If this change affects the public interface (public API or UI) of the product, post in the `#support_gitlab-com` Slack channel to explain the impact and discuss a mitigation plan for users that might be affected. If you need Support feedback or approval, reach out in `#spt_managers` Slack channel or mention `@gitlab-com/support/managers`.
## Development
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 37fe4887872..cadc3650a89 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-e023bdab6cea4ad7124d64b12e77f2cdb0681e21
+cd3f92195e1c23abd22d4dfcb43c6f9ad3d817d4
diff --git a/app/assets/javascripts/environments/components/empty_state.vue b/app/assets/javascripts/environments/components/empty_state.vue
index 977da12e8a9..36b9b647af7 100644
--- a/app/assets/javascripts/environments/components/empty_state.vue
+++ b/app/assets/javascripts/environments/components/empty_state.vue
@@ -12,10 +12,10 @@ export default {
<template>
<div class="empty-state">
<div class="text-content">
- <h4 class="blank-state-title js-blank-state-title">
+ <h4 class="js-blank-state-title">
{{ s__("Environments|You don't have any environments right now") }}
</h4>
- <p class="blank-state-text">
+ <p>
{{
s__(`Environments|Environments are places where
code gets deployed, such as staging or production.`)
diff --git a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
index 184bda4410f..e7bac11a27e 100644
--- a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
+++ b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
@@ -199,6 +199,7 @@ export default {
requestAccessEnabled: true,
highlightChangesClass: false,
emailsDisabled: false,
+ showDiffPreviewInEmail: true,
cveIdRequestEnabled: true,
featureAccessLevelEveryone,
featureAccessLevelMembers,
@@ -762,6 +763,24 @@ export default {
</project-setting-row>
<project-setting-row class="mb-3">
<input
+ :value="showDiffPreviewInEmail"
+ type="hidden"
+ name="project[project_setting_attributes][show_diff_preview_in_email]"
+ />
+ <gl-form-checkbox
+ v-model="showDiffPreviewInEmail"
+ name="project[project_setting_attributes][show_diff_preview_in_email]"
+ >
+ {{ s__('ProjectSettings|Include diff preview in merge request notification emails') }}
+ <template #help>{{
+ s__(
+ 'ProjectSettings|Include the code diff preview on comment threads in merge request notification emails.',
+ )
+ }}</template>
+ </gl-form-checkbox>
+ </project-setting-row>
+ <project-setting-row class="mb-3">
+ <input
:value="showDefaultAwardEmojis"
type="hidden"
name="project[project_setting_attributes][show_default_award_emojis]"
diff --git a/app/assets/javascripts/persistent_user_callout.js b/app/assets/javascripts/persistent_user_callout.js
index bc83844b8b9..b003302ec8e 100644
--- a/app/assets/javascripts/persistent_user_callout.js
+++ b/app/assets/javascripts/persistent_user_callout.js
@@ -7,10 +7,11 @@ const DEFERRED_LINK_CLASS = 'deferred-link';
export default class PersistentUserCallout {
constructor(container, options = container.dataset) {
- const { dismissEndpoint, featureId, deferLinks } = options;
+ const { dismissEndpoint, featureId, groupId, deferLinks } = options;
this.container = container;
this.dismissEndpoint = dismissEndpoint;
this.featureId = featureId;
+ this.groupId = groupId;
this.deferLinks = parseBoolean(deferLinks);
this.init();
@@ -52,6 +53,7 @@ export default class PersistentUserCallout {
axios
.post(this.dismissEndpoint, {
feature_name: this.featureId,
+ group_id: this.groupId,
})
.then(() => {
this.container.remove();
diff --git a/app/assets/javascripts/persistent_user_callouts.js b/app/assets/javascripts/persistent_user_callouts.js
index a7f8704b559..337c204c36a 100644
--- a/app/assets/javascripts/persistent_user_callouts.js
+++ b/app/assets/javascripts/persistent_user_callouts.js
@@ -10,6 +10,7 @@ const PERSISTENT_USER_CALLOUTS = [
'.js-new-user-signups-cap-reached',
'.js-eoa-bronze-plan-banner',
'.js-security-newsletter-callout',
+ '.js-approaching-seats-count-threshold',
];
const initCallouts = () => {
diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss
index c1c8bfffff7..8e43a9b1b0d 100644
--- a/app/assets/stylesheets/framework.scss
+++ b/app/assets/stylesheets/framework.scss
@@ -46,7 +46,6 @@
@import 'framework/toggle';
@import 'framework/typography';
@import 'framework/zen';
-@import 'framework/blank';
@import 'framework/wells';
@import 'framework/page_header';
@import 'framework/page_title';
diff --git a/app/assets/stylesheets/framework/blank.scss b/app/assets/stylesheets/framework/blank.scss
deleted file mode 100644
index 7dd7ab339dd..00000000000
--- a/app/assets/stylesheets/framework/blank.scss
+++ /dev/null
@@ -1,118 +0,0 @@
-.blank-state-parent-container {
- .section-container {
- padding: 10px;
- }
-
- .section-body {
- width: 100%;
- height: 100%;
- padding-bottom: 25px;
- border-radius: $border-radius-default;
- }
-}
-
-.blank-state-row {
- display: flex;
- flex-wrap: wrap;
- justify-content: space-between;
-}
-
-.blank-state-welcome {
- text-align: center;
- padding: $gl-padding 0 ($gl-padding * 2);
-
- .blank-state-welcome-title {
- font-size: 24px;
- }
-
- .blank-state-text {
- margin-bottom: 0;
- }
-}
-
-.blank-state-link {
- color: $gl-text-color;
- margin-bottom: 15px;
-
- &:hover {
- background-color: $gray-light;
- text-decoration: none;
- color: $gl-text-color;
- }
-}
-
-.blank-state-center {
- padding-top: 20px;
- padding-bottom: 20px;
- text-align: center;
-}
-
-.blank-state {
- display: flex;
- align-items: center;
- padding: 20px 50px;
- border: 1px solid $border-color;
- border-radius: $border-radius-default;
- min-height: 240px;
- margin-bottom: $gl-padding;
- width: calc(50% - #{$gl-padding-8});
-
- @include media-breakpoint-down(sm) {
- width: 100%;
- flex-direction: column;
- justify-content: center;
- padding: 50px 20px;
-
- .column-small & {
- width: 100%;
- }
-
- }
-}
-
-.blank-state,
-.blank-state-center {
- .blank-state-icon {
- svg {
- display: block;
- margin: auto;
- }
- }
-
- .blank-state-title {
- margin-top: 0;
- font-size: 18px;
- }
-
- .blank-state-body {
- @include media-breakpoint-down(sm) {
- text-align: center;
- margin-top: 20px;
- }
-
- @include media-breakpoint-up(sm) {
- padding-left: 20px;
- }
- }
-}
-
-@include media-breakpoint-up(lg) {
- .column-large {
- flex: 2;
- }
-
- .column-small {
- flex: 1;
- margin-bottom: 15px;
-
- .blank-state {
- max-width: 400px;
- flex-wrap: wrap;
- margin-left: 15px;
- }
-
- .blank-state-icon {
- margin-bottom: 30px;
- }
- }
-}
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index f9b0f4f3118..9cf435af726 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -929,8 +929,6 @@ Merge requests
*/
$mr-tabs-height: 48px;
$mr-version-controls-height: 56px;
-$mr-widget-margin-left: 40px;
-$mr-review-bar-height: calc(2rem + 13px);
/*
Compare Branches
diff --git a/app/assets/stylesheets/page_bundles/dashboard_projects.scss b/app/assets/stylesheets/page_bundles/dashboard_projects.scss
new file mode 100644
index 00000000000..eb0e1701b7f
--- /dev/null
+++ b/app/assets/stylesheets/page_bundles/dashboard_projects.scss
@@ -0,0 +1,35 @@
+@import 'mixins_and_variables_and_functions';
+
+.blank-state {
+ padding: 20px 50px;
+ min-height: 240px;
+ width: calc(50% - #{$gl-padding-8});
+
+ @include media-breakpoint-down(sm) {
+ width: 100%;
+ flex-direction: column;
+ justify-content: center;
+ padding: 50px 20px;
+ }
+}
+
+.blank-state-link {
+ &:hover {
+ background-color: $gray-light;
+ text-decoration: none;
+ color: $gl-text-color;
+ }
+}
+
+.blank-state-icon {
+ svg {
+ display: block;
+ }
+}
+
+.blank-state-body {
+ @include media-breakpoint-down(sm) {
+ text-align: center;
+ margin-top: 20px;
+ }
+}
diff --git a/app/assets/stylesheets/page_bundles/merge_requests.scss b/app/assets/stylesheets/page_bundles/merge_requests.scss
index 75b953df791..1ee154f9aeb 100644
--- a/app/assets/stylesheets/page_bundles/merge_requests.scss
+++ b/app/assets/stylesheets/page_bundles/merge_requests.scss
@@ -1,5 +1,10 @@
@import 'mixins_and_variables_and_functions';
+$mr-review-bar-height: calc(2rem + 13px);
+$mr-widget-margin-left: 40px;
+$mr-widget-min-height: 69px;
+$tabs-holder-z-index: 250;
+
.compare-versions-container {
min-width: 0;
}
@@ -108,6 +113,638 @@
}
}
+.ci-widget-container {
+ justify-content: space-between;
+ flex: 1;
+ flex-direction: row;
+
+ @include media-breakpoint-down(sm) {
+ flex-direction: column;
+
+ .stage-cell .stage-container {
+ margin-top: 16px;
+ }
+
+ .dropdown .mini-pipeline-graph-dropdown-menu.dropdown-menu {
+ transform: initial;
+ }
+ }
+
+ .coverage {
+ font-size: 12px;
+ color: $gray-500;
+ line-height: initial;
+ }
+}
+
+.deploy-body {
+ display: flex;
+ align-items: center;
+ flex-wrap: wrap;
+
+ @include media-breakpoint-up(xs) {
+ flex-wrap: nowrap;
+ white-space: nowrap;
+ }
+
+ @include media-breakpoint-down(md) {
+ flex-direction: column;
+ align-items: flex-start;
+
+ .deployment-info {
+ margin-bottom: $gl-padding;
+ }
+ }
+
+ > *:not(:last-child) {
+ margin-right: 0.3em;
+ }
+
+ svg {
+ vertical-align: text-top;
+ }
+
+ .deployment-info {
+ flex: 1;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ min-width: 100px;
+
+ @include media-breakpoint-up(xs) {
+ min-width: 0;
+ max-width: 100%;
+ }
+ }
+
+ .dropdown-menu {
+ width: 400px;
+ }
+}
+
+.deploy-heading,
+.merge-train-position-indicator {
+ @include media-breakpoint-up(md) {
+ padding: $gl-padding-8 $gl-padding;
+ }
+
+ .media-body {
+ min-width: 0;
+ font-size: 12px;
+ margin-left: 32px;
+ }
+
+ &:not(:last-child) {
+ border-bottom: 1px solid $border-color;
+ }
+}
+
+.diff-file-row.is-active {
+ background-color: $gray-50;
+}
+
+.mr-conflict-loader {
+ max-width: 334px;
+
+ > svg {
+ vertical-align: middle;
+ }
+}
+
+.mr-info-list {
+ clear: left;
+ position: relative;
+ padding-top: 4px;
+
+ p {
+ margin: 0;
+ position: relative;
+ padding: 4px 0;
+
+ &:last-child {
+ padding-bottom: 0;
+ }
+ }
+
+ &.mr-memory-usage {
+ p {
+ float: left;
+ }
+
+ .memory-graph-container {
+ float: left;
+ margin-left: 5px;
+ }
+ }
+}
+
+.mr-memory-usage {
+ width: 100%;
+
+ p.usage-info-loading .usage-info-load-spinner {
+ margin-right: 10px;
+ font-size: 16px;
+ }
+}
+
+.mr-ready-to-merge-loader {
+ max-width: 418px;
+
+ > svg {
+ vertical-align: middle;
+ }
+}
+
+.mr-section-container {
+ border: 1px solid $border-color;
+ border-radius: $border-radius-default;
+ background: var(--white, $white);
+
+ > .mr-widget-border-top:first-of-type {
+ border-top: 0;
+ }
+}
+
+.mr-source-target {
+ flex-wrap: wrap;
+ padding: $gl-padding;
+ background: var(--white, $white);
+ min-height: $mr-widget-min-height;
+
+ @include media-breakpoint-up(md) {
+ align-items: center;
+ }
+
+ .git-merge-container {
+ justify-content: space-between;
+ flex: 1;
+ flex-direction: row;
+ align-items: center;
+
+ @include media-breakpoint-down(md) {
+ flex-direction: column;
+ align-items: stretch;
+
+ .branch-actions {
+ margin-top: 16px;
+ }
+ }
+
+ @include media-breakpoint-up(lg) {
+ .branch-actions {
+ align-self: center;
+ margin-left: $gl-padding;
+ white-space: nowrap;
+ }
+ }
+ }
+
+ .diverged-commits-count {
+ color: $gl-text-color-secondary;
+ }
+}
+
+.mr-state-widget {
+ color: $gl-text-color;
+
+ .commit-message-edit {
+ border-radius: $border-radius-default;
+ }
+
+ .mr-widget-section:not(:first-child) {
+ border-top: solid 1px $border-color;
+ }
+
+ .mr-widget-alert-container + .mr-widget-section {
+ border-top: 0;
+ }
+
+ .mr-fast-forward-message {
+ padding-left: $gl-padding-50;
+ padding-bottom: $gl-padding;
+ }
+
+ .commits-list {
+ > li {
+ padding: $gl-padding;
+
+ @include media-breakpoint-up(md) {
+ margin-left: $gl-spacing-scale-7;
+ }
+ }
+ }
+
+ .mr-commit-dropdown {
+ .dropdown-menu {
+ @include media-breakpoint-up(md) {
+ width: 150%;
+ }
+ }
+ }
+
+ .mr-report {
+ padding: 0;
+
+ > .media {
+ padding: $gl-padding;
+ }
+ }
+
+ form {
+ margin-bottom: 0;
+
+ .clearfix {
+ margin-bottom: 0;
+ }
+ }
+
+ label {
+ margin-bottom: 0;
+ }
+
+ .btn {
+ font-size: $gl-font-size;
+ }
+
+ .accept-merge-holder {
+ .accept-action {
+ display: inline-block;
+ float: left;
+ }
+ }
+
+ .ci-widget {
+ color: $gl-text-color;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+
+ @include media-breakpoint-down(xs) {
+ flex-wrap: wrap;
+ }
+
+ .ci-widget-content {
+ display: flex;
+ align-items: center;
+ flex: 1;
+ }
+ }
+
+ .mr-widget-icon {
+ font-size: 22px;
+ }
+
+ .mr-loading-icon {
+ margin: 3px 0;
+ }
+
+ .ci-status-icon svg {
+ margin: 3px 0;
+ position: relative;
+ overflow: visible;
+ display: block;
+ }
+
+ .mr-widget-pipeline-graph {
+ .dropdown-menu {
+ z-index: $zindex-dropdown-menu;
+ }
+ }
+
+ .normal {
+ flex: 1;
+ flex-basis: auto;
+ }
+
+ .capitalize {
+ text-transform: capitalize;
+ }
+
+ .label-branch {
+ @include gl-font-monospace;
+ font-size: 95%;
+ color: $gl-text-color;
+ font-weight: normal;
+ overflow: hidden;
+ word-break: break-all;
+ }
+
+ .deploy-link,
+ .label-branch {
+ &.label-truncate {
+ // NOTE: This selector targets its children because some of the HTML comes from
+ // 'source_branch_link'. Once this external HTML is no longer used, we could
+ // simplify this.
+ > a,
+ > span {
+ display: inline-block;
+ max-width: 12.5em;
+ margin-bottom: -6px;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ }
+ }
+ }
+
+ .mr-widget-body {
+ &:not(.mr-widget-body-line-height-1) {
+ line-height: 28px;
+ }
+
+ @include clearfix;
+
+ .approve-btn {
+ margin-right: 5px;
+ }
+
+ h4 {
+ float: left;
+ font-weight: $gl-font-weight-bold;
+ font-size: 14px;
+ line-height: inherit;
+ margin-top: 0;
+ margin-bottom: 0;
+
+ time {
+ font-weight: $gl-font-weight-normal;
+ }
+ }
+
+ .btn-grouped {
+ margin-left: 0;
+ margin-right: 7px;
+ }
+
+ label {
+ font-weight: $gl-font-weight-normal;
+ }
+
+ .spacing {
+ margin: 0 0 0 10px;
+ }
+
+ .bold,
+ .gl-font-weight-bold {
+ font-weight: $gl-font-weight-bold;
+ color: $gray-600;
+ margin-left: 10px;
+ }
+
+ .state-label {
+ font-weight: $gl-font-weight-bold;
+ padding-right: 10px;
+ }
+
+ .danger {
+ color: $red-500;
+ }
+
+ .spacing,
+ .bold,
+ .gl-font-weight-bold {
+ vertical-align: middle;
+ }
+
+ .dropdown-menu {
+ li a {
+ padding: 5px;
+ }
+
+ .merge-opt-icon {
+ line-height: 1.5;
+ }
+
+ .merge-opt-title {
+ margin-left: 8px;
+ }
+ }
+
+ .has-custom-error {
+ display: inline-block;
+ }
+
+ @include media-breakpoint-down(xs) {
+ p {
+ font-size: 13px;
+ }
+
+ .btn-grouped {
+ float: none;
+ margin-right: 0;
+ }
+
+ .accept-action {
+ width: 100%;
+ text-align: center;
+ }
+ }
+
+ .commit-message-editor {
+ label {
+ padding: 0;
+ }
+ }
+
+ &.mr-widget-empty-state {
+ line-height: 20px;
+ padding: $gl-padding;
+
+ .artwork {
+
+ @include media-breakpoint-down(md) {
+ margin-bottom: $gl-padding;
+ }
+ }
+
+ .text {
+ p {
+ margin-top: $gl-padding;
+ }
+
+ .highlight {
+ margin: 0 0 $gl-padding;
+ font-weight: $gl-font-weight-bold;
+ }
+ }
+ }
+
+ &.mr-pipeline-suggest {
+ border-radius: $border-radius-default;
+ line-height: 20px;
+ border: 1px solid $border-color;
+
+ .circle-icon-container {
+ color: $gl-text-color-quaternary;
+ }
+ }
+ }
+
+ .ci-coverage {
+ float: right;
+ }
+
+ .stop-env-container {
+ color: $gl-text-color;
+ float: right;
+
+ a {
+ color: $gl-text-color;
+ }
+ }
+}
+
+.mr-widget-alert-container {
+ $radius: $border-radius-default - 1px;
+
+ border-radius: $radius $radius 0 0;
+
+ .gl-alert:not(:last-child) {
+ margin-bottom: 1px;
+ }
+}
+
+.mr-widget-body,
+.mr-widget-content {
+ padding: $gl-padding;
+}
+
+.mr-widget-border-top {
+ border-top: 1px solid $border-color;
+}
+
+.mr-widget-extension {
+ border-top: 1px solid $border-color;
+ background-color: $gray-50;
+
+ &.clickable:hover {
+ background-color: $gray-100;
+ cursor: pointer;
+ }
+}
+
+.mr-widget-extension-icon::before {
+ @include gl-content-empty;
+ @include gl-absolute;
+ @include gl-left-0;
+ @include gl-top-0;
+ @include gl-opacity-3;
+ @include gl-border-solid;
+ @include gl-border-4;
+ @include gl-rounded-full;
+
+ width: 24px;
+ height: 24px;
+}
+
+.mr-widget-heading {
+ position: relative;
+ border: 1px solid $border-color;
+ border-radius: $border-radius-default;
+ background: var(--white, $white);
+
+ .gl-skeleton-loader {
+ display: block;
+ }
+}
+
+.mr-widget-info {
+ padding-left: $gl-padding;
+ padding-right: $gl-padding;
+}
+
+.mr-widget-margin-left {
+ margin-left: $mr-widget-margin-left;
+}
+
+.mr-widget-section {
+ .code-text {
+ flex: 1;
+ }
+}
+
+.mr-widget-workflow {
+ margin-top: $gl-padding;
+ position: relative;
+
+ &::before {
+ content: '';
+ border-left: 1px solid $gray-100;
+ position: absolute;
+ left: 28px;
+ top: -17px;
+ height: 16px;
+ }
+}
+
+.mr-version-controls {
+ position: relative;
+ z-index: $tabs-holder-z-index + 10;
+ background: $white;
+ color: $gl-text-color;
+ margin-top: -1px;
+
+ .mr-version-menus-container {
+ display: flex;
+ align-items: center;
+ flex-wrap: wrap;
+ padding: 16px;
+ z-index: 199;
+ white-space: nowrap;
+
+ .gl-dropdown-toggle {
+ width: auto;
+ max-width: 170px;
+
+ svg {
+ top: 10px;
+ right: 8px;
+ }
+ }
+ }
+
+ .content-block {
+ padding: $gl-padding;
+ border-bottom: 0;
+ }
+
+ .mr-version-dropdown,
+ .mr-version-compare-dropdown {
+ margin: 0 0.5rem;
+ }
+
+ .dropdown-title {
+ color: $gl-text-color;
+ }
+
+ // Shortening button height by 1px to make compare-versions
+ // header 56px and fit into our 8px design grid
+ .btn {
+ height: 34px;
+ }
+
+ @include media-breakpoint-up(md) {
+ position: -webkit-sticky;
+ position: sticky;
+ top: calc(#{$header-height} + #{$mr-tabs-height});
+
+ .with-system-header & {
+ top: calc(#{$header-height} + #{$mr-tabs-height} + #{$system-header-height});
+ }
+
+ .with-system-header.with-performance-bar & {
+ top: calc(#{$header-height} + #{$mr-tabs-height} + #{$system-header-height} + #{$performance-bar-height});
+ }
+
+ .mr-version-menus-container {
+ flex-wrap: nowrap;
+ }
+
+ .with-performance-bar & {
+ top: calc(#{$header-height} + #{$performance-bar-height} + #{$mr-tabs-height});
+ }
+ }
+}
+
// TODO: Move to GitLab UI
.mr-extenson-scrim {
background: linear-gradient(to bottom, rgba($gray-light, 0), rgba($gray-light, 1));
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index b858d457969..dc01aea7681 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -2,8 +2,6 @@
* MR -> show: Automerge widget
*
*/
-
-$mr-widget-min-height: 69px;
$tabs-holder-z-index: 250;
.space-children {
@@ -18,12 +16,6 @@ $tabs-holder-z-index: 250;
}
}
-.mr-widget-border-top {
- border-top: 1px solid $border-color;
-}
-
-.mr-widget-margin-left { margin-left: $mr-widget-margin-left; }
-
.media-section {
@include media-breakpoint-down(md) {
align-items: flex-start;
@@ -42,140 +34,9 @@ $tabs-holder-z-index: 250;
}
}
-.mr-widget-section {
- .code-text {
- flex: 1;
- }
-}
-
-.mr-widget-heading {
- position: relative;
- border: 1px solid $border-color;
- border-radius: $border-radius-default;
- background: var(--white, $white);
-
- .gl-skeleton-loader {
- display: block;
- }
-}
-
-.mr-widget-extension {
- border-top: 1px solid $border-color;
- background-color: $gray-50;
-
- &.clickable:hover {
- background-color: $gray-100;
- cursor: pointer;
- }
-}
-
-.mr-widget-workflow {
- margin-top: $gl-padding;
- position: relative;
-
- &::before {
- content: '';
- border-left: 1px solid $gray-100;
- position: absolute;
- left: 28px;
- top: -17px;
- height: 16px;
- }
-}
-
-.mr-section-container {
- border: 1px solid $border-color;
- border-radius: $border-radius-default;
- background: var(--white, $white);
-
- > .mr-widget-border-top:first-of-type {
- border-top: 0;
- }
-}
-
-.mr-widget-body,
-.mr-widget-content,
-.mr-widget-footer {
- padding: $gl-padding;
-}
-
-.mr-widget-info {
- padding-left: $gl-padding;
- padding-right: $gl-padding;
-}
-
.mr-state-widget {
- color: $gl-text-color;
-
- .commit-message-edit {
- border-radius: $border-radius-default;
- }
-
- .mr-widget-section:not(:first-child),
- .mr-widget-footer {
- border-top: solid 1px $border-color;
- }
-
- .mr-widget-alert-container + .mr-widget-section {
- border-top: 0;
- }
-
- .mr-fast-forward-message {
- padding-left: $gl-padding-50;
- padding-bottom: $gl-padding;
- }
-
- .commits-list {
- > li {
- padding: $gl-padding;
-
- @include media-breakpoint-up(md) {
- margin-left: $gl-spacing-scale-7;
- }
- }
- }
-
- .mr-commit-dropdown {
- .dropdown-menu {
- @include media-breakpoint-up(md) {
- width: 150%;
- }
- }
- }
-
- .mr-widget-footer {
- padding: 0;
- }
-
- .mr-report {
- padding: 0;
-
- > .media {
- padding: $gl-padding;
- }
- }
-
- form {
- margin-bottom: 0;
-
- .clearfix {
- margin-bottom: 0;
- }
- }
-
- label {
- margin-bottom: 0;
- }
-
- .btn {
- font-size: $gl-font-size;
- }
-
.accept-merge-holder {
.accept-action {
- display: inline-block;
- float: left;
-
.accept-merge-request {
&.ci-preparing,
&.ci-pending,
@@ -192,226 +53,6 @@ $tabs-holder-z-index: 250;
}
}
}
-
- .ci-widget {
- color: $gl-text-color;
- display: flex;
- align-items: center;
- justify-content: space-between;
-
- @include media-breakpoint-down(xs) {
- flex-wrap: wrap;
- }
-
- .ci-widget-content {
- display: flex;
- align-items: center;
- flex: 1;
- }
- }
-
- .mr-widget-icon {
- font-size: 22px;
- }
-
- .mr-loading-icon {
- margin: 3px 0;
- }
-
- .ci-status-icon svg {
- margin: 3px 0;
- position: relative;
- overflow: visible;
- display: block;
- }
-
- .mr-widget-pipeline-graph {
- .dropdown-menu {
- z-index: $zindex-dropdown-menu;
- }
- }
-
- .normal {
- flex: 1;
- flex-basis: auto;
- }
-
- .capitalize {
- text-transform: capitalize;
- }
-
- .label-branch {
- @include gl-font-monospace;
- font-size: 95%;
- color: $gl-text-color;
- font-weight: normal;
- overflow: hidden;
- word-break: break-all;
- }
-
- .deploy-link,
- .label-branch {
- &.label-truncate {
- // NOTE: This selector targets its children because some of the HTML comes from
- // 'source_branch_link'. Once this external HTML is no longer used, we could
- // simplify this.
- > a,
- > span {
- display: inline-block;
- max-width: 12.5em;
- margin-bottom: -6px;
- white-space: nowrap;
- text-overflow: ellipsis;
- overflow: hidden;
- }
- }
- }
-
- .mr-widget-body {
- &:not(.mr-widget-body-line-height-1) {
- line-height: 28px;
- }
-
- @include clearfix;
-
- .approve-btn {
- margin-right: 5px;
- }
-
- h4 {
- float: left;
- font-weight: $gl-font-weight-bold;
- font-size: 14px;
- line-height: inherit;
- margin-top: 0;
- margin-bottom: 0;
-
- time {
- font-weight: $gl-font-weight-normal;
- }
- }
-
- .btn-grouped {
- margin-left: 0;
- margin-right: 7px;
- }
-
- label {
- font-weight: $gl-font-weight-normal;
- }
-
- .spacing {
- margin: 0 0 0 10px;
- }
-
- .bold,
- .gl-font-weight-bold {
- font-weight: $gl-font-weight-bold;
- color: $gray-600;
- margin-left: 10px;
- }
-
- .state-label {
- font-weight: $gl-font-weight-bold;
- padding-right: 10px;
- }
-
- .danger {
- color: $red-500;
- }
-
- .spacing,
- .bold,
- .gl-font-weight-bold {
- vertical-align: middle;
- }
-
- .dropdown-menu {
- li a {
- padding: 5px;
- }
-
- .merge-opt-icon {
- line-height: 1.5;
- }
-
- .merge-opt-title {
- margin-left: 8px;
- }
- }
-
- .has-custom-error {
- display: inline-block;
- }
-
- @include media-breakpoint-down(xs) {
- p {
- font-size: 13px;
- }
-
- .btn-grouped {
- float: none;
- margin-right: 0;
- }
-
- .accept-action {
- width: 100%;
- text-align: center;
- }
- }
-
- .commit-message-editor {
- label {
- padding: 0;
- }
- }
-
- &.mr-widget-empty-state {
- line-height: 20px;
- padding: $gl-padding;
-
- .artwork {
-
- @include media-breakpoint-down(md) {
- margin-bottom: $gl-padding;
- }
- }
-
- .text {
- p {
- margin-top: $gl-padding;
- }
-
- .highlight {
- margin: 0 0 $gl-padding;
- font-weight: $gl-font-weight-bold;
- }
- }
- }
-
- &.mr-pipeline-suggest {
- border-radius: $border-radius-default;
- line-height: 20px;
- border: 1px solid $border-color;
-
- .circle-icon-container {
- color: $gl-text-color-quaternary;
- }
- }
- }
-
- .ci-coverage {
- float: right;
- }
-
- .stop-env-container {
- color: $gl-text-color;
- float: right;
-
- a {
- color: $gl-text-color;
- }
- }
}
.mr_source_commit,
@@ -477,72 +118,6 @@ $tabs-holder-z-index: 250;
}
}
-.mr-info-list {
- clear: left;
- position: relative;
- padding-top: 4px;
-
- p {
- margin: 0;
- position: relative;
- padding: 4px 0;
-
- &:last-child {
- padding-bottom: 0;
- }
- }
-
- &.mr-memory-usage {
- p {
- float: left;
- }
-
- .memory-graph-container {
- float: left;
- margin-left: 5px;
- }
- }
-}
-
-.mr-source-target {
- flex-wrap: wrap;
- padding: $gl-padding;
- background: var(--white, $white);
- min-height: $mr-widget-min-height;
-
- @include media-breakpoint-up(md) {
- align-items: center;
- }
-
- .git-merge-container {
- justify-content: space-between;
- flex: 1;
- flex-direction: row;
- align-items: center;
-
- @include media-breakpoint-down(md) {
- flex-direction: column;
- align-items: stretch;
-
- .branch-actions {
- margin-top: 16px;
- }
- }
-
- @include media-breakpoint-up(lg) {
- .branch-actions {
- align-self: center;
- margin-left: $gl-padding;
- white-space: nowrap;
- }
- }
- }
-
- .diverged-commits-count {
- color: $gl-text-color-secondary;
- }
-}
-
.card-new-merge-request {
.card-header {
padding: 5px 10px;
@@ -639,75 +214,6 @@ $tabs-holder-z-index: 250;
}
}
-.mr-version-controls {
- position: relative;
- z-index: $tabs-holder-z-index + 10;
- background: $white;
- color: $gl-text-color;
- margin-top: -1px;
-
- .mr-version-menus-container {
- display: flex;
- align-items: center;
- flex-wrap: wrap;
- padding: 16px;
- z-index: 199;
- white-space: nowrap;
-
- .gl-dropdown-toggle {
- width: auto;
- max-width: 170px;
-
- svg {
- top: 10px;
- right: 8px;
- }
- }
- }
-
- .content-block {
- padding: $gl-padding;
- border-bottom: 0;
- }
-
- .mr-version-dropdown,
- .mr-version-compare-dropdown {
- margin: 0 0.5rem;
- }
-
- .dropdown-title {
- color: $gl-text-color;
- }
-
- // Shortening button height by 1px to make compare-versions
- // header 56px and fit into our 8px design grid
- .btn {
- height: 34px;
- }
-
- @include media-breakpoint-up(md) {
- position: -webkit-sticky;
- position: sticky;
- top: calc(#{$header-height} + #{$mr-tabs-height});
-
- .with-system-header & {
- top: calc(#{$header-height} + #{$mr-tabs-height} + #{$system-header-height});
- }
-
- .with-system-header.with-performance-bar & {
- top: calc(#{$header-height} + #{$mr-tabs-height} + #{$system-header-height} + #{$performance-bar-height});
- }
-
- .mr-version-menus-container {
- flex-wrap: nowrap;
- }
-
- .with-performance-bar & {
- top: calc(#{$header-height} + #{$performance-bar-height} + #{$mr-tabs-height});
- }
- }
-}
-
.merge-request-tabs-holder,
.epic-tabs-holder {
top: $header-height;
@@ -833,80 +339,10 @@ $tabs-holder-z-index: 250;
}
}
-.mr-memory-usage {
- width: 100%;
-
- p.usage-info-loading .usage-info-load-spinner {
- margin-right: 10px;
- font-size: 16px;
- }
-}
-
.fork-sprite {
margin-right: -5px;
}
-.deploy-heading,
-.merge-train-position-indicator {
- @include media-breakpoint-up(md) {
- padding: $gl-padding-8 $gl-padding;
- }
-
- .media-body {
- min-width: 0;
- font-size: 12px;
- margin-left: 32px;
- }
-
- &:not(:last-child) {
- border-bottom: 1px solid $border-color;
- }
-}
-
-.deploy-body {
- display: flex;
- align-items: center;
- flex-wrap: wrap;
-
- @include media-breakpoint-up(xs) {
- flex-wrap: nowrap;
- white-space: nowrap;
- }
-
- @include media-breakpoint-down(md) {
- flex-direction: column;
- align-items: flex-start;
-
- .deployment-info {
- margin-bottom: $gl-padding;
- }
- }
-
- > *:not(:last-child) {
- margin-right: 0.3em;
- }
-
- svg {
- vertical-align: text-top;
- }
-
- .deployment-info {
- flex: 1;
- white-space: nowrap;
- text-overflow: ellipsis;
- min-width: 100px;
-
- @include media-breakpoint-up(xs) {
- min-width: 0;
- max-width: 100%;
- }
- }
-
- .dropdown-menu {
- width: 400px;
- }
-}
-
// Hack alert: we've rewritten `btn` class in a way that
// we've broken it and it is not possible to use with `btn-link`
// which causes a blank button when it's disabled and hovering
@@ -924,30 +360,6 @@ $tabs-holder-z-index: 250;
}
}
-.ci-widget-container {
- justify-content: space-between;
- flex: 1;
- flex-direction: row;
-
- @include media-breakpoint-down(sm) {
- flex-direction: column;
-
- .stage-cell .stage-container {
- margin-top: 16px;
- }
-
- .dropdown .mini-pipeline-graph-dropdown-menu.dropdown-menu {
- transform: initial;
- }
- }
-
- .coverage {
- font-size: 12px;
- color: $gray-500;
- line-height: initial;
- }
-}
-
.merge-request-details .file-finder-overlay.diff-file-finder {
position: fixed;
z-index: 99999;
@@ -963,47 +375,3 @@ $tabs-holder-z-index: 250;
}
}
}
-
-.diff-file-row.is-active {
- background-color: $gray-50;
-}
-
-.mr-conflict-loader {
- max-width: 334px;
-
- > svg {
- vertical-align: middle;
- }
-}
-
-.mr-ready-to-merge-loader {
- max-width: 418px;
-
- > svg {
- vertical-align: middle;
- }
-}
-
-.mr-widget-alert-container {
- $radius: $border-radius-default - 1px;
-
- border-radius: $radius $radius 0 0;
-
- .gl-alert:not(:last-child) {
- margin-bottom: 1px;
- }
-}
-
-.mr-widget-extension-icon::before {
- @include gl-content-empty;
- @include gl-absolute;
- @include gl-left-0;
- @include gl-top-0;
- @include gl-opacity-3;
- @include gl-border-solid;
- @include gl-border-4;
- @include gl-rounded-full;
-
- width: 24px;
- height: 24px;
-}
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index c84d6256339..d2e3c7d0272 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -419,6 +419,7 @@ class ProjectsController < Projects::ApplicationController
%i[
show_default_award_emojis
squash_option
+ show_diff_preview_in_email
mr_default_target_self
warn_about_potentially_unwanted_characters
]
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index b39cd485fe8..fd0ae40e4dc 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -586,6 +586,7 @@ module ProjectsHelper
metricsDashboardAccessLevel: feature.metrics_dashboard_access_level,
operationsAccessLevel: feature.operations_access_level,
showDefaultAwardEmojis: project.show_default_award_emojis?,
+ showDiffPreviewInEmail: project.show_diff_preview_in_email?,
warnAboutPotentiallyUnwantedCharacters: project.warn_about_potentially_unwanted_characters?,
securityAndComplianceAccessLevel: project.security_and_compliance_access_level,
containerRegistryAccessLevel: feature.container_registry_access_level
diff --git a/app/helpers/users/group_callouts_helper.rb b/app/helpers/users/group_callouts_helper.rb
index b66c7f9f821..0aa4eb89499 100644
--- a/app/helpers/users/group_callouts_helper.rb
+++ b/app/helpers/users/group_callouts_helper.rb
@@ -3,6 +3,7 @@
module Users
module GroupCalloutsHelper
INVITE_MEMBERS_BANNER = 'invite_members_banner'
+ APPROACHING_SEAT_COUNT_THRESHOLD = 'approaching_seat_count_threshold'
def show_invite_banner?(group)
Ability.allowed?(current_user, :admin_group, group) &&
diff --git a/app/models/deployment.rb b/app/models/deployment.rb
index beee1f90198..46409465209 100644
--- a/app/models/deployment.rb
+++ b/app/models/deployment.rb
@@ -294,10 +294,6 @@ class Deployment < ApplicationRecord
@stop_action ||= manual_actions.find { |action| action.name == self.on_stop }
end
- def finished_at
- read_attribute(:finished_at) || legacy_finished_at
- end
-
def deployed_at
return unless success?
@@ -405,10 +401,6 @@ class Deployment < ApplicationRecord
raise ArgumentError, "The status #{status.inspect} is invalid"
end
end
-
- def legacy_finished_at
- self.created_at if success? && !read_attribute(:finished_at)
- end
end
Deployment.prepend_mod_with('Deployment')
diff --git a/app/models/project.rb b/app/models/project.rb
index abe072a3a6d..2b90f176279 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -433,6 +433,7 @@ class Project < ApplicationRecord
alias_method :container_registry_enabled, :container_registry_enabled?
delegate :show_default_award_emojis, :show_default_award_emojis=, :show_default_award_emojis?,
:warn_about_potentially_unwanted_characters, :warn_about_potentially_unwanted_characters=, :warn_about_potentially_unwanted_characters?,
+ :show_diff_preview_in_email, :show_diff_preview_in_email=, :show_diff_preview_in_email?,
to: :project_setting, allow_nil: true
delegate :scheduled?, :started?, :in_progress?, :failed?, :finished?,
prefix: :import, to: :import_state, allow_nil: true
diff --git a/app/models/users/group_callout.rb b/app/models/users/group_callout.rb
index da9b95fd718..faa5130e6ec 100644
--- a/app/models/users/group_callout.rb
+++ b/app/models/users/group_callout.rb
@@ -9,7 +9,8 @@ module Users
belongs_to :group
enum feature_name: {
- invite_members_banner: 1
+ invite_members_banner: 1,
+ approaching_seat_count_threshold: 2 # EE-only
}
validates :group, presence: true
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index 4cc5ed06d61..ca7a095b54b 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -229,6 +229,7 @@ class ProjectPolicy < BasePolicy
enable :set_note_created_at
enable :set_emails_disabled
enable :set_show_default_award_emojis
+ enable :set_show_diff_preview_in_email
enable :set_warn_about_potentially_unwanted_characters
end
diff --git a/app/services/ci/after_requeue_job_service.rb b/app/services/ci/after_requeue_job_service.rb
index ee0ae6651ca..097b29cf143 100644
--- a/app/services/ci/after_requeue_job_service.rb
+++ b/app/services/ci/after_requeue_job_service.rb
@@ -22,13 +22,9 @@ module Ci
end
def dependent_jobs
- if ::Feature.enabled?(:ci_order_subsequent_jobs_by_stage, @processable.pipeline.project, default_enabled: :yaml)
- stage_dependent_jobs
- .or(needs_dependent_jobs.except(:preload))
- .ordered_by_stage
- else
- stage_dependent_jobs | needs_dependent_jobs
- end
+ stage_dependent_jobs
+ .or(needs_dependent_jobs.except(:preload))
+ .ordered_by_stage
end
def process(job)
diff --git a/app/views/dashboard/projects/_blank_state_admin_welcome.html.haml b/app/views/dashboard/projects/_blank_state_admin_welcome.html.haml
index 20bf7d232ce..eba5e7c6e9b 100644
--- a/app/views/dashboard/projects/_blank_state_admin_welcome.html.haml
+++ b/app/views/dashboard/projects/_blank_state_admin_welcome.html.haml
@@ -1,40 +1,42 @@
-.blank-state-row
+- link_classes = "blank-state blank-state-link gl-text-body gl-display-flex gl-align-items-center gl-border-1 gl-border-solid gl-border-gray-100 gl-rounded-base gl-mb-5"
+
+.gl-display-flex.gl-flex-wrap.gl-justify-content-space-between
- if has_start_trial?
= render_if_exists "dashboard/projects/blank_state_ee_trial"
- = link_to new_project_path, class: "blank-state blank-state-link" do
+ = link_to new_project_path, class: link_classes do
.blank-state-icon
= custom_icon("add_new_project", size: 50)
- .blank-state-body
- %h3.blank-state-title
- Create a project
- %p.blank-state-text
- Projects are where you store your code, access issues, wiki and other features of GitLab.
+ .blank-state-body.gl-sm-pl-0.gl-pl-6
+ %h3.gl-font-size-h2.gl-mt-0
+ = _('Create a project')
+ %p
+ = _('Projects are where you store your code, access issues, wiki and other features of GitLab.')
- if current_user.can_create_group?
- = link_to new_group_path, class: "blank-state blank-state-link" do
+ = link_to new_group_path, class: link_classes do
.blank-state-icon
= custom_icon("add_new_group", size: 50)
- .blank-state-body
- %h3.blank-state-title
- Create a group
- %p.blank-state-text
- Groups are a great way to organize projects and people.
+ .blank-state-body.gl-sm-pl-0.gl-pl-6
+ %h3.gl-font-size-h2.gl-mt-0
+ = _('Create a group')
+ %p
+ = _('Groups are a great way to organize projects and people.')
- = link_to new_admin_user_path, class: "blank-state blank-state-link" do
+ = link_to new_admin_user_path, class: link_classes do
.blank-state-icon
= custom_icon("add_new_user", size: 50)
- .blank-state-body
- %h3.blank-state-title
- Add people
- %p.blank-state-text
- Add your team members and others to GitLab.
+ .blank-state-body.gl-sm-pl-0.gl-pl-6
+ %h3.gl-font-size-h2.gl-mt-0
+ = _('Add people')
+ %p
+ = _('Add your team members and others to GitLab.')
- = link_to admin_root_path, class: "blank-state blank-state-link" do
+ = link_to admin_root_path, class: link_classes do
.blank-state-icon
= custom_icon("configure_server", size: 50)
- .blank-state-body
- %h3.blank-state-title
- Configure GitLab
- %p.blank-state-text
- Make adjustments to how your GitLab instance is set up.
+ .blank-state-body.gl-sm-pl-0.gl-pl-6
+ %h3.gl-font-size-h2.gl-mt-0
+ = _('Configure GitLab')
+ %p
+ = _('Make adjustments to how your GitLab instance is set up.')
diff --git a/app/views/dashboard/projects/_blank_state_welcome.html.haml b/app/views/dashboard/projects/_blank_state_welcome.html.haml
index 003e6f18b33..e0b8850357e 100644
--- a/app/views/dashboard/projects/_blank_state_welcome.html.haml
+++ b/app/views/dashboard/projects/_blank_state_welcome.html.haml
@@ -1,48 +1,49 @@
-.blank-state-row
+- link_classes = "blank-state blank-state-link gl-text-body gl-display-flex gl-align-items-center gl-border-1 gl-border-solid gl-border-gray-100 gl-rounded-base gl-mb-5"
+
+.gl-display-flex.gl-flex-wrap.gl-justify-content-space-between
- if current_user.can_create_project?
- = link_to new_project_path, class: "blank-state blank-state-link" do
+ = link_to new_project_path, class: link_classes do
.blank-state-icon
= custom_icon("add_new_project", size: 50)
- .blank-state-body
- %h3.blank-state-title
- Create a project
- %p.blank-state-text
- Projects are where you store your code, access issues, wiki and other features of GitLab.
+ .blank-state-body.gl-sm-pl-0.gl-pl-6
+ %h3.gl-font-size-h2.gl-mt-0
+ = _('Create a project')
+ %p
+ = _('Projects are where you store your code, access issues, wiki and other features of GitLab.')
- else
- .blank-state
+ .blank-state.gl-display-flex.gl-align-items-center.gl-border-1.gl-border-solid.gl-border-gray-100.gl-rounded-base.gl-mb-5
.blank-state-icon
= custom_icon("add_new_project", size: 50)
- .blank-state-body
- %h3.blank-state-title
- Create a project
- %p.blank-state-text
- If you are added to a project, it will be displayed here.
+ .blank-state-body.gl-sm-pl-0.gl-pl-6
+ %h3.gl-font-size-h2.gl-mt-0
+ = _('Create a project')
+ %p
+ = _('If you are added to a project, it will be displayed here.')
- if current_user.can_create_group?
- = link_to new_group_path, class: "blank-state blank-state-link" do
+ = link_to new_group_path, class: link_classes do
.blank-state-icon
= custom_icon("add_new_group", size: 50)
- .blank-state-body
- %h3.blank-state-title
- Create a group
- %p.blank-state-text
- Groups are the best way to manage projects and members.
+ .blank-state-body.gl-sm-pl-0.gl-pl-6
+ %h3.gl-font-size-h2.gl-mt-0
+ = _('Create a group')
+ %p
+ = _('Groups are the best way to manage projects and members.')
- = link_to trending_explore_projects_path, class: "blank-state blank-state-link" do
+ = link_to trending_explore_projects_path, class: link_classes do
.blank-state-icon
= custom_icon("globe", size: 50)
- .blank-state-body
- %h3.blank-state-title
- Explore public projects
- %p.blank-state-text
- Public projects are an easy way to allow
- everyone to have read-only access.
+ .blank-state-body.gl-sm-pl-0.gl-pl-6
+ %h3.gl-font-size-h2.gl-mt-0
+ = _('Explore public projects')
+ %p
+ = _('Public projects are an easy way to allow everyone to have read-only access.')
- = link_to "https://docs.gitlab.com/", class: "blank-state blank-state-link" do
+ = link_to "https://docs.gitlab.com/", class: link_classes do
.blank-state-icon
= custom_icon("lightbulb", size: 50)
- .blank-state-body
- %h3.blank-state-title
- Learn more about GitLab
- %p.blank-state-text
- Take a look at the documentation to discover all of GitLab's capabilities.
+ .blank-state-body.gl-sm-pl-0.gl-pl-6
+ %h3.gl-font-size-h2.gl-mt-0
+ = _('Learn more about GitLab')
+ %p
+ = _('Take a look at the documentation to discover all of GitLab’s capabilities.')
diff --git a/app/views/dashboard/projects/_zero_authorized_projects.html.haml b/app/views/dashboard/projects/_zero_authorized_projects.html.haml
index b5f5025b581..e72762f2ae5 100644
--- a/app/views/dashboard/projects/_zero_authorized_projects.html.haml
+++ b/app/views/dashboard/projects/_zero_authorized_projects.html.haml
@@ -1,13 +1,10 @@
-.blank-state-parent-container
- .section-container.section-welcome{ class: "#{ 'section-admin-welcome' if current_user.admin? }" }
- .container.section-body
- .row
- .blank-state-welcome.w-100
- %h2.blank-state-welcome-title{ data: { qa_selector: 'welcome_title_content' } }
- = _('Welcome to GitLab')
- %p.blank-state-text
- = _('Faster releases. Better code. Less pain.')
- - if current_user.admin?
- = render "blank_state_admin_welcome"
- - else
- = render "blank_state_welcome"
+.container
+ .gl-text-center.gl-pt-6.gl-pb-7
+ %h2.gl-font-size-h1{ data: { qa_selector: 'welcome_title_content' } }
+ = _('Welcome to GitLab')
+ %p.gl-m-0
+ = _('Faster releases. Better code. Less pain.')
+ - if current_user.admin?
+ = render "blank_state_admin_welcome"
+ - else
+ = render "blank_state_welcome"
diff --git a/app/views/dashboard/projects/index.html.haml b/app/views/dashboard/projects/index.html.haml
index 4252b60514a..0d9257e659a 100644
--- a/app/views/dashboard/projects/index.html.haml
+++ b/app/views/dashboard/projects/index.html.haml
@@ -7,6 +7,7 @@
- page_title _("Projects")
- header_title _("Projects"), dashboard_projects_path
+- add_page_specific_style 'page_bundles/dashboard_projects'
= render "projects/last_push"
- if show_projects?(@projects, params)
diff --git a/app/views/notify/_note_email.html.haml b/app/views/notify/_note_email.html.haml
index ad0c873bf56..1e927b5805d 100644
--- a/app/views/notify/_note_email.html.haml
+++ b/app/views/notify/_note_email.html.haml
@@ -20,8 +20,7 @@
discussion on #{link_to(discussion.file_path, target_url)}
- else
= link_to 'discussion', target_url
-
-- if discussion&.diff_discussion? && discussion.on_text?
+- if discussion&.diff_discussion? && discussion.on_text? && @project.show_diff_preview_in_email?
= content_for :head do
= stylesheet_link_tag 'mailers/highlighted_diff_email'
diff --git a/app/views/notify/_note_email.text.erb b/app/views/notify/_note_email.text.erb
index 8e2f7e6f76e..e212b165134 100644
--- a/app/views/notify/_note_email.text.erb
+++ b/app/views/notify/_note_email.text.erb
@@ -20,7 +20,7 @@
<% end -%>
-<% if discussion&.diff_discussion? && discussion.on_text? -%>
+<% if discussion&.diff_discussion? && discussion.on_text? && @project.show_diff_preview_in_email? -%>
<% discussion.truncated_diff_lines(highlight: false, diff_limit: diff_limit).each do |line| -%>
<%= "> #{line.text}\n" -%>
<% end -%>
diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml
index b123b81b89c..6d60ef92d86 100644
--- a/app/views/projects/environments/show.html.haml
+++ b/app/views/projects/environments/show.html.haml
@@ -14,7 +14,7 @@
.text-content
%h4.state-title
= _("You don't have any deployments right now.")
- %p.blank-state-text
+ %p
= html_escape(_("Define environments in the deploy stage(s) in %{code_open}.gitlab-ci.yml%{code_close} to track deployments here.")) % { code_open: '<code>'.html_safe, code_close: '</code>'.html_safe }
.text-center
= link_to _("Read more"), help_page_path("ci/environments/index.md"), class: "gl-button btn btn-confirm"
diff --git a/config/application.rb b/config/application.rb
index c55841b40b9..8d795e6bc4e 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -257,6 +257,7 @@ module Gitlab
config.assets.precompile << "page_bundles/build.css"
config.assets.precompile << "page_bundles/ci_status.css"
config.assets.precompile << "page_bundles/cycle_analytics.css"
+ config.assets.precompile << "page_bundles/dashboard_projects.css"
config.assets.precompile << "page_bundles/dev_ops_reports.css"
config.assets.precompile << "page_bundles/environments.css"
config.assets.precompile << "page_bundles/epics.css"
diff --git a/config/feature_flags/development/ci_order_subsequent_jobs_by_stage.yml b/config/feature_flags/development/ci_order_subsequent_jobs_by_stage.yml
deleted file mode 100644
index babe10764e3..00000000000
--- a/config/feature_flags/development/ci_order_subsequent_jobs_by_stage.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: ci_order_subsequent_jobs_by_stage
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/77528
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/349977
-milestone: '14.7'
-type: development
-group: group::pipeline authoring
-default_enabled: true
diff --git a/config/feature_flags/development/redirect_to_latest_template_jobs_build.yml b/config/feature_flags/development/redirect_to_latest_template_jobs_build.yml
deleted file mode 100644
index df03505afc5..00000000000
--- a/config/feature_flags/development/redirect_to_latest_template_jobs_build.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: redirect_to_latest_template_jobs_build
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/67782
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/337990
-milestone: '14.2'
-type: development
-group: group::configure
-default_enabled: false
diff --git a/db/migrate/20220131192643_add_show_diff_preview_in_email_to_project_settings.rb b/db/migrate/20220131192643_add_show_diff_preview_in_email_to_project_settings.rb
new file mode 100644
index 00000000000..1811bf04ee4
--- /dev/null
+++ b/db/migrate/20220131192643_add_show_diff_preview_in_email_to_project_settings.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class AddShowDiffPreviewInEmailToProjectSettings < Gitlab::Database::Migration[1.0]
+ enable_lock_retries!
+
+ def change
+ add_column :project_settings, :show_diff_preview_in_email, :boolean, default: true, null: false
+ end
+end
diff --git a/db/post_migrate/20220128155251_remove_dangling_running_builds.rb b/db/post_migrate/20220128155251_remove_dangling_running_builds.rb
new file mode 100644
index 00000000000..f86a21ced00
--- /dev/null
+++ b/db/post_migrate/20220128155251_remove_dangling_running_builds.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+class RemoveDanglingRunningBuilds < Gitlab::Database::Migration[1.0]
+ BATCH_SIZE = 100
+
+ disable_ddl_transaction!
+
+ def up
+ each_batch_range('ci_running_builds', of: BATCH_SIZE) do |min, max|
+ execute <<~SQL
+ DELETE FROM ci_running_builds
+ USING ci_builds
+ WHERE ci_builds.id = ci_running_builds.build_id
+ AND ci_builds.status = 'failed'
+ AND ci_builds.type = 'Ci::Build'
+ AND ci_running_builds.id BETWEEN #{min} AND #{max}
+ SQL
+ end
+ end
+
+ def down
+ # no-op
+ # This migration deletes data and it can not be reversed
+ end
+end
diff --git a/db/schema_migrations/20220128155251 b/db/schema_migrations/20220128155251
new file mode 100644
index 00000000000..11fa6807ed5
--- /dev/null
+++ b/db/schema_migrations/20220128155251
@@ -0,0 +1 @@
+0d121aeecdd6ace1516c2e9b84fefd47d963c4cbe22a0448728241d83da3742e \ No newline at end of file
diff --git a/db/schema_migrations/20220131192643 b/db/schema_migrations/20220131192643
new file mode 100644
index 00000000000..e947bf0cfbe
--- /dev/null
+++ b/db/schema_migrations/20220131192643
@@ -0,0 +1 @@
+3b9068f109685dcfa8a0a1fda886cca3909d29cbc280cf70ed9f3d927def12ac \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index b1f0a258634..a1cd7a08125 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -18696,6 +18696,7 @@ CREATE TABLE project_settings (
merge_commit_template text,
has_shimo boolean DEFAULT false NOT NULL,
squash_commit_template text,
+ show_diff_preview_in_email boolean DEFAULT true NOT NULL,
CONSTRAINT check_3a03e7557a CHECK ((char_length(previous_default_branch) <= 4096)),
CONSTRAINT check_b09644994b CHECK ((char_length(squash_commit_template) <= 500)),
CONSTRAINT check_bde223416c CHECK ((show_default_award_emojis IS NOT NULL)),
diff --git a/doc/administration/packages/index.md b/doc/administration/packages/index.md
index eea4964efbe..abf5c46114b 100644
--- a/doc/administration/packages/index.md
+++ b/doc/administration/packages/index.md
@@ -240,7 +240,7 @@ You can optionally track progress and verify that all packages migrated successf
- `sudo gitlab-rails dbconsole` for Omnibus GitLab instances.
- `sudo -u git -H psql -d gitlabhq_production` for source-installed instances.
-Verify `objectstg` below (where `store=2`) has count of all packages:
+Verify `objectstg` below (where `file_store = '2'`) has count of all packages:
```shell
gitlabhq_production=# SELECT count(*) AS total, sum(case when file_store = '1' then 1 else 0 end) AS filesystem, sum(case when file_store = '2' then 1 else 0 end) AS objectstg FROM packages_package_files;
diff --git a/lib/gitlab/ci/templates/Jobs/Build.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Build.gitlab-ci.yml
index 5efa557d7eb..d5ca93a0a3b 100644
--- a/lib/gitlab/ci/templates/Jobs/Build.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Jobs/Build.gitlab-ci.yml
@@ -1,5 +1,5 @@
variables:
- AUTO_BUILD_IMAGE_VERSION: 'v1.0.0'
+ AUTO_BUILD_IMAGE_VERSION: 'v1.5.0'
build:
stage: build
@@ -19,6 +19,9 @@ build:
export CI_APPLICATION_TAG=${CI_APPLICATION_TAG:-$CI_COMMIT_TAG}
fi
- /build/build.sh
+ artifacts:
+ reports:
+ dotenv: gl-auto-build-variables.env
rules:
- if: '$BUILD_DISABLED'
when: never
diff --git a/lib/gitlab/ci/templates/Jobs/Build.latest.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Build.latest.gitlab-ci.yml
index 211adc9bd5b..d5ca93a0a3b 100644
--- a/lib/gitlab/ci/templates/Jobs/Build.latest.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Jobs/Build.latest.gitlab-ci.yml
@@ -1,7 +1,3 @@
-# WARNING: This latest template is for internal FEATURE-FLAG TESTING ONLY.
-# It is not meant to be used with `include:`.
-# This template is scheduled for removal when testing is complete: https://gitlab.com/gitlab-org/gitlab/-/issues/337987
-
variables:
AUTO_BUILD_IMAGE_VERSION: 'v1.5.0'
diff --git a/lib/gitlab/template/gitlab_ci_yml_template.rb b/lib/gitlab/template/gitlab_ci_yml_template.rb
index 35f45c8809f..323f59d3373 100644
--- a/lib/gitlab/template/gitlab_ci_yml_template.rb
+++ b/lib/gitlab/template/gitlab_ci_yml_template.rb
@@ -5,9 +5,7 @@ module Gitlab
class GitlabCiYmlTemplate < BaseTemplate
BASE_EXCLUDED_PATTERNS = [%r{\.latest\.}].freeze
- TEMPLATES_WITH_LATEST_VERSION = {
- 'Jobs/Build' => true
- }.freeze
+ TEMPLATES_WITH_LATEST_VERSION = {}.freeze
def description
"# This file is a template, and might need editing before it works on your project."
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index cbc023e5b46..22ba8da3e14 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -2200,6 +2200,9 @@ msgstr ""
msgid "Add or subtract spent time"
msgstr ""
+msgid "Add people"
+msgstr ""
+
msgid "Add previously merged commits"
msgstr ""
@@ -2266,6 +2269,9 @@ msgstr ""
msgid "Add webhook"
msgstr ""
+msgid "Add your team members and others to GitLab."
+msgstr ""
+
msgid "Add/remove"
msgstr ""
@@ -9099,6 +9105,9 @@ msgstr ""
msgid "Configure Dependency Scanning in `.gitlab-ci.yml`, creating this file if it does not already exist"
msgstr ""
+msgid "Configure GitLab"
+msgstr ""
+
msgid "Configure GitLab runners to start using the Web Terminal. %{helpStart}Learn more.%{helpEnd}"
msgstr ""
@@ -10089,6 +10098,9 @@ msgstr ""
msgid "Create a Mattermost team for this group"
msgstr ""
+msgid "Create a group"
+msgstr ""
+
msgid "Create a merge request"
msgstr ""
@@ -10113,6 +10125,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create a project"
+msgstr ""
+
msgid "Create an account using:"
msgstr ""
@@ -14605,6 +14620,9 @@ msgstr ""
msgid "Explore public groups"
msgstr ""
+msgid "Explore public projects"
+msgstr ""
+
msgid "Explore snippets"
msgstr ""
@@ -16349,6 +16367,9 @@ msgstr ""
msgid "GitLab is a single application for the entire software development lifecycle. From project planning and source code management to CI/CD, monitoring, and security."
msgstr ""
+msgid "GitLab is free to use. Many features for larger teams are part of our %{link_start}paid products%{link_end}. You can try Ultimate for free without any obligation or payment details."
+msgstr ""
+
msgid "GitLab is obtaining a Let's Encrypt SSL certificate for this domain. This process can take some time. Please try again later."
msgstr ""
@@ -17459,6 +17480,12 @@ msgstr ""
msgid "Groups and subgroups"
msgstr ""
+msgid "Groups are a great way to organize projects and people."
+msgstr ""
+
+msgid "Groups are the best way to manage projects and members."
+msgstr ""
+
msgid "Groups to synchronize"
msgstr ""
@@ -18138,6 +18165,9 @@ msgstr ""
msgid "If you add %{codeStart}needs%{codeEnd} to jobs in your pipeline you'll be able to view the %{codeStart}needs%{codeEnd} relationships between jobs in this tab as a %{linkStart}Directed Acyclic Graph (DAG)%{linkEnd}."
msgstr ""
+msgid "If you are added to a project, it will be displayed here."
+msgstr ""
+
msgid "If you did not initiate these sign-in attempts, please reach out to your administrator or enable two-factor authentication (2FA) on your account."
msgstr ""
@@ -21169,6 +21199,9 @@ msgstr ""
msgid "Learn more about Auto DevOps"
msgstr ""
+msgid "Learn more about GitLab"
+msgstr ""
+
msgid "Learn more about Needs relationships"
msgstr ""
@@ -21846,6 +21879,9 @@ msgstr ""
msgid "Maintenance mode"
msgstr ""
+msgid "Make adjustments to how your GitLab instance is set up."
+msgstr ""
+
msgid "Make and review changes in the browser with the Web IDE"
msgstr ""
@@ -28264,6 +28300,12 @@ msgstr ""
msgid "ProjectSettings|If merge trains are enabled, merging is only possible if the branch can be rebased without conflicts."
msgstr ""
+msgid "ProjectSettings|Include diff preview in merge request notification emails"
+msgstr ""
+
+msgid "ProjectSettings|Include the code diff preview on comment threads in merge request notification emails."
+msgstr ""
+
msgid "ProjectSettings|Internal"
msgstr ""
@@ -28594,6 +28636,9 @@ msgstr ""
msgid "Projects are organized into groups"
msgstr ""
+msgid "Projects are where you store your code, access issues, wiki and other features of GitLab."
+msgstr ""
+
msgid "Projects contributed to"
msgstr ""
@@ -29245,6 +29290,9 @@ msgstr ""
msgid "Public projects Minutes cost factor"
msgstr ""
+msgid "Public projects are an easy way to allow everyone to have read-only access."
+msgstr ""
+
msgid "Publish to status page"
msgstr ""
@@ -34242,6 +34290,9 @@ msgstr ""
msgid "Start date"
msgstr ""
+msgid "Start free trial"
+msgstr ""
+
msgid "Start merge train"
msgstr ""
@@ -35316,6 +35367,9 @@ msgstr ""
msgid "TagsPage|protected"
msgstr ""
+msgid "Take a look at the documentation to discover all of GitLab’s capabilities."
+msgstr ""
+
msgid "Target Branch"
msgstr ""
@@ -38488,6 +38542,9 @@ msgstr ""
msgid "Unlock account"
msgstr ""
+msgid "Unlock more features with GitLab Ultimate"
+msgstr ""
+
msgid "Unlock the discussion"
msgstr ""
diff --git a/spec/features/projects/environments/environment_spec.rb b/spec/features/projects/environments/environment_spec.rb
index d88ff5c1aa5..bfd54b9c6da 100644
--- a/spec/features/projects/environments/environment_spec.rb
+++ b/spec/features/projects/environments/environment_spec.rb
@@ -132,6 +132,29 @@ RSpec.describe 'Environment' do
end
end
+ context 'with upcoming deployments' do
+ let(:pipeline) { create(:ci_pipeline, project: project) }
+ let(:build) { create(:ci_build, pipeline: pipeline) }
+
+ let!(:runnind_deployment_1) { create(:deployment, environment: environment, deployable: build, status: :running) }
+ let!(:runnind_deployment_2) { create(:deployment, environment: environment, deployable: build, status: :running) }
+ # Success deployments must have present `finished_at`. We'll backfill in the future.
+ # See https://gitlab.com/gitlab-org/gitlab/-/issues/350618 for more information.
+ let!(:success_without_finished_at) { create(:deployment, environment: environment, deployable: build, status: :success, finished_at: nil) }
+
+ before do
+ visit_environment(environment)
+ end
+
+ # This ordering is unexpected and to be fixed.
+ # See https://gitlab.com/gitlab-org/gitlab/-/issues/350618 for more information.
+ it 'shows upcoming deployments in unordered way' do
+ displayed_ids = find_all('[data-testid="deployment-id"]').map { |e| e.text }
+ internal_ids = [runnind_deployment_1, runnind_deployment_2, success_without_finished_at].map { |d| "##{d.iid}" }
+ expect(displayed_ids).to match_array(internal_ids)
+ end
+ end
+
context 'with related deployable present' do
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:build) { create(:ci_build, pipeline: pipeline) }
diff --git a/spec/features/projects/settings/project_settings_spec.rb b/spec/features/projects/settings/project_settings_spec.rb
index a0d44b579a8..0e1e6bacd16 100644
--- a/spec/features/projects/settings/project_settings_spec.rb
+++ b/spec/features/projects/settings/project_settings_spec.rb
@@ -85,6 +85,36 @@ RSpec.describe 'Projects settings' do
end
end
+ context 'show diffs in emails', :js do
+ it 'does not hide diffs by default' do
+ visit edit_project_path(project)
+
+ show_diff_preview_in_email_input = find('input[name="project[project_setting_attributes][show_diff_preview_in_email]"]', visible: :hidden)
+
+ expect(show_diff_preview_in_email_input.value).to eq('true')
+ end
+
+ it 'hides diffs in emails when toggled' do
+ visit edit_project_path(project)
+
+ show_diff_preview_in_email_input = find('input[name="project[project_setting_attributes][show_diff_preview_in_email]"]', visible: :hidden)
+ show_diff_preview_in_email_checkbox = find('input[name="project[project_setting_attributes][show_diff_preview_in_email]"][type=checkbox]')
+
+ expect(show_diff_preview_in_email_input.value).to eq('true')
+
+ show_diff_preview_in_email_checkbox.click
+
+ expect(show_diff_preview_in_email_input.value).to eq('false')
+
+ page.within('.sharing-permissions') do
+ find('[data-testid="project-features-save-button"]').click
+ end
+ wait_for_requests
+
+ expect(show_diff_preview_in_email_input.value).to eq('false')
+ end
+ end
+
def expect_toggle_state(state)
is_collapsed = state == :collapsed
diff --git a/spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js b/spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js
index 305dce51971..776788da879 100644
--- a/spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js
+++ b/spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js
@@ -28,6 +28,7 @@ const defaultProps = {
emailsDisabled: false,
packagesEnabled: true,
showDefaultAwardEmojis: true,
+ showDiffPreviewInEmail: true,
warnAboutPotentiallyUnwantedCharacters: true,
},
isGitlabCom: true,
@@ -101,6 +102,9 @@ describe('Settings Panel', () => {
const findEmailSettings = () => wrapper.find({ ref: 'email-settings' });
const findShowDefaultAwardEmojis = () =>
wrapper.find('input[name="project[project_setting_attributes][show_default_award_emojis]"]');
+
+ const findShowDiffPreviewInEmail = () =>
+ wrapper.find('input[name="project[project_setting_attributes][show_diff_preview_in_email]"]');
const findWarnAboutPuc = () =>
wrapper.find(
'input[name="project[project_setting_attributes][warn_about_potentially_unwanted_characters]"]',
@@ -585,6 +589,13 @@ describe('Settings Panel', () => {
expect(findShowDefaultAwardEmojis().exists()).toBe(true);
});
});
+ describe('Hide diffs in email', () => {
+ it('should show the "Hide Diffs in email" input', () => {
+ wrapper = mountComponent();
+
+ expect(findShowDiffPreviewInEmail().exists()).toBe(true);
+ });
+ });
describe('Warn about potentially unwanted characters', () => {
it('should have a "Warn about Potentially Unwanted Characters" input', () => {
diff --git a/spec/frontend/persistent_user_callout_spec.js b/spec/frontend/persistent_user_callout_spec.js
index 1db255106ed..4633602de26 100644
--- a/spec/frontend/persistent_user_callout_spec.js
+++ b/spec/frontend/persistent_user_callout_spec.js
@@ -10,6 +10,7 @@ jest.mock('~/flash');
describe('PersistentUserCallout', () => {
const dismissEndpoint = '/dismiss';
const featureName = 'feature';
+ const groupId = '5';
function createFixture() {
const fixture = document.createElement('div');
@@ -18,6 +19,7 @@ describe('PersistentUserCallout', () => {
class="container"
data-dismiss-endpoint="${dismissEndpoint}"
data-feature-id="${featureName}"
+ data-group-id="${groupId}"
>
<button type="button" class="js-close"></button>
</div>
@@ -86,7 +88,9 @@ describe('PersistentUserCallout', () => {
return waitForPromises().then(() => {
expect(persistentUserCallout.container.remove).toHaveBeenCalled();
- expect(mockAxios.history.post[0].data).toBe(JSON.stringify({ feature_name: featureName }));
+ expect(mockAxios.history.post[0].data).toBe(
+ JSON.stringify({ feature_name: featureName, group_id: groupId }),
+ );
});
});
@@ -191,8 +195,8 @@ describe('PersistentUserCallout', () => {
return waitForPromises().then(() => {
expect(window.location.assign).toBeCalledWith(href);
- expect(mockAxios.history.post[0].data).toBe(JSON.stringify({ feature_name: featureName }));
expect(persistentUserCallout.container.remove).not.toHaveBeenCalled();
+ expect(mockAxios.history.post[0].data).toBe(JSON.stringify({ feature_name: featureName }));
});
});
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index 1d78778c757..0d785dcabca 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -964,6 +964,7 @@ RSpec.describe ProjectsHelper do
metricsDashboardAccessLevel: project.project_feature.metrics_dashboard_access_level,
operationsAccessLevel: project.project_feature.operations_access_level,
showDefaultAwardEmojis: project.show_default_award_emojis?,
+ showDiffPreviewInEmail: project.show_diff_preview_in_email?,
securityAndComplianceAccessLevel: project.security_and_compliance_access_level,
containerRegistryAccessLevel: project.project_feature.container_registry_access_level
)
diff --git a/spec/migrations/20220128155251_remove_dangling_running_builds_spec.rb b/spec/migrations/20220128155251_remove_dangling_running_builds_spec.rb
new file mode 100644
index 00000000000..a48464befdf
--- /dev/null
+++ b/spec/migrations/20220128155251_remove_dangling_running_builds_spec.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!('remove_dangling_running_builds')
+
+RSpec.describe RemoveDanglingRunningBuilds do
+ let(:namespace) { table(:namespaces).create!(name: 'user', path: 'user') }
+ let(:project) { table(:projects).create!(namespace_id: namespace.id) }
+ let(:runner) { table(:ci_runners).create!(runner_type: 1) }
+ let(:builds) { table(:ci_builds) }
+ let(:running_builds) { table(:ci_running_builds) }
+
+ let(:running_build) do
+ builds.create!(
+ name: 'test 1',
+ status: 'running',
+ project_id: project.id,
+ type: 'Ci::Build')
+ end
+
+ let(:failed_build) do
+ builds.create!(
+ name: 'test 2',
+ status: 'failed',
+ project_id: project.id,
+ type: 'Ci::Build')
+ end
+
+ let!(:running_metadata) do
+ running_builds.create!(
+ build_id: running_build.id,
+ project_id: project.id,
+ runner_id: runner.id,
+ runner_type:
+ runner.runner_type)
+ end
+
+ let!(:failed_metadata) do
+ running_builds.create!(
+ build_id: failed_build.id,
+ project_id: project.id,
+ runner_id: runner.id,
+ runner_type: runner.runner_type)
+ end
+
+ it 'removes failed builds' do
+ migrate!
+
+ expect(running_metadata.reload).to be_present
+ expect { failed_metadata.reload } .to raise_error(ActiveRecord::RecordNotFound)
+ end
+end
diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb
index 299800c56a3..47c246d12cc 100644
--- a/spec/models/deployment_spec.rb
+++ b/spec/models/deployment_spec.rb
@@ -369,38 +369,6 @@ RSpec.describe Deployment do
end
end
- describe '#finished_at' do
- subject { deployment.finished_at }
-
- context 'when deployment status is created' do
- let(:deployment) { create(:deployment) }
-
- it { is_expected.to be_nil }
- end
-
- context 'when deployment status is success' do
- let(:deployment) { create(:deployment, :success) }
-
- it { is_expected.to eq(deployment.read_attribute(:finished_at)) }
- end
-
- context 'when deployment status is success' do
- let(:deployment) { create(:deployment, :success, finished_at: nil) }
-
- before do
- deployment.update_column(:finished_at, nil)
- end
-
- it { is_expected.to eq(deployment.read_attribute(:created_at)) }
- end
-
- context 'when deployment status is running' do
- let(:deployment) { create(:deployment, :running) }
-
- it { is_expected.to be_nil }
- end
- end
-
describe '#deployed_at' do
subject { deployment.deployed_at }
diff --git a/spec/requests/api/project_attributes.yml b/spec/requests/api/project_attributes.yml
index 01d2fb18f00..ef47263ad78 100644
--- a/spec/requests/api/project_attributes.yml
+++ b/spec/requests/api/project_attributes.yml
@@ -145,6 +145,7 @@ project_setting:
- project_id
- push_rule_id
- show_default_award_emojis
+ - show_diff_preview_in_email
- updated_at
- cve_id_request_enabled
- mr_default_target_self
diff --git a/spec/services/ci/pipeline_processing/atomic_processing_service_spec.rb b/spec/services/ci/pipeline_processing/atomic_processing_service_spec.rb
index 26bc6f747e1..7365ad162d2 100644
--- a/spec/services/ci/pipeline_processing/atomic_processing_service_spec.rb
+++ b/spec/services/ci/pipeline_processing/atomic_processing_service_spec.rb
@@ -1043,22 +1043,6 @@ RSpec.describe Ci::PipelineProcessing::AtomicProcessingService do
expect(all_builds_names).to eq(%w[A1 A2 B])
expect(all_builds_statuses).to eq(%w[pending created created])
end
-
- context 'when the FF ci_order_subsequent_jobs_by_stage is disabled' do
- before do
- stub_feature_flags(ci_order_subsequent_jobs_by_stage: false)
- end
-
- it 'processes subsequent jobs in an incorrect order when playing first job' do
- expect(all_builds_names).to eq(%w[A1 A2 B])
- expect(all_builds_statuses).to eq(%w[manual skipped skipped])
-
- play_manual_action('A1')
-
- expect(all_builds_names).to eq(%w[A1 A2 B])
- expect(all_builds_statuses).to eq(%w[pending created skipped])
- end
- end
end
private
diff --git a/workhorse/internal/upload/rewrite.go b/workhorse/internal/upload/rewrite.go
index b9324ac8b7b..bbabe840ef5 100644
--- a/workhorse/internal/upload/rewrite.go
+++ b/workhorse/internal/upload/rewrite.go
@@ -6,8 +6,10 @@ import (
"fmt"
"io"
"io/ioutil"
+ "mime"
"mime/multipart"
"net/http"
+ "net/textproto"
"os"
"path/filepath"
"strings"
@@ -95,7 +97,8 @@ func rewriteFormFilesFromMultipart(r *http.Request, writer *multipart.Writer, pr
return err
}
- name := p.FormName()
+ name, filename := parseAndNormalizeContentDisposition(p.Header)
+
if name == "" {
continue
}
@@ -104,7 +107,7 @@ func rewriteFormFilesFromMultipart(r *http.Request, writer *multipart.Writer, pr
return ErrInjectedClientParam
}
- if p.FileName() != "" {
+ if filename != "" {
err = rew.handleFilePart(r.Context(), name, p, opts)
} else {
err = rew.copyPart(r.Context(), name, p)
@@ -118,6 +121,13 @@ func rewriteFormFilesFromMultipart(r *http.Request, writer *multipart.Writer, pr
return nil
}
+func parseAndNormalizeContentDisposition(header textproto.MIMEHeader) (string, string) {
+ const key = "Content-Disposition"
+ mediaType, params, _ := mime.ParseMediaType(header.Get(key))
+ header.Set(key, mime.FormatMediaType(mediaType, params))
+ return params["name"], params["filename"]
+}
+
func (rew *rewriter) handleFilePart(ctx context.Context, name string, p *multipart.Part, opts *filestore.SaveFileOpts) error {
if rew.filter.Count() >= maxFilesAllowed {
return ErrTooManyFilesUploaded
diff --git a/workhorse/internal/upload/rewrite_test.go b/workhorse/internal/upload/rewrite_test.go
index e3f33a02489..145f62ee910 100644
--- a/workhorse/internal/upload/rewrite_test.go
+++ b/workhorse/internal/upload/rewrite_test.go
@@ -1,6 +1,7 @@
package upload
import (
+ "net/textproto"
"os"
"runtime"
"testing"
@@ -54,3 +55,83 @@ func TestImageTypeRecongition(t *testing.T) {
})
}
}
+
+func TestParseAndNormalizeContentDisposition(t *testing.T) {
+ tests := []struct {
+ desc string
+ header string
+ name string
+ filename string
+ sanitizedHeader string
+ }{
+ {
+ desc: "without content disposition",
+ header: "",
+ name: "",
+ filename: "",
+ sanitizedHeader: "",
+ }, {
+ desc: "content disposition without filename",
+ header: `form-data; name="filename"`,
+ name: "filename",
+ filename: "",
+ sanitizedHeader: `form-data; name=filename`,
+ }, {
+ desc: "with filename",
+ header: `form-data; name="file"; filename=foobar`,
+ name: "file",
+ filename: "foobar",
+ sanitizedHeader: `form-data; filename=foobar; name=file`,
+ }, {
+ desc: "with filename*",
+ header: `form-data; name="file"; filename*=UTF-8''bar`,
+ name: "file",
+ filename: "bar",
+ sanitizedHeader: `form-data; filename=bar; name=file`,
+ }, {
+ desc: "filename and filename*",
+ header: `form-data; name="file"; filename=foobar; filename*=UTF-8''bar`,
+ name: "file",
+ filename: "bar",
+ sanitizedHeader: `form-data; filename=bar; name=file`,
+ }, {
+ desc: "with empty filename",
+ header: `form-data; name="file"; filename=""`,
+ name: "file",
+ filename: "",
+ sanitizedHeader: `form-data; filename=""; name=file`,
+ }, {
+ desc: "with complex filename*",
+ header: `form-data; name="file"; filename*=UTF-8''viel%20Spa%C3%9F`,
+ name: "file",
+ filename: "viel Spaß",
+ sanitizedHeader: `form-data; filename*=utf-8''viel%20Spa%C3%9F; name=file`,
+ }, {
+ desc: "with unsupported charset",
+ header: `form-data; name="file"; filename*=UTF-16''bar`,
+ name: "file",
+ filename: "",
+ sanitizedHeader: `form-data; name=file`,
+ }, {
+ desc: "with filename and filename* with unsupported charset",
+ header: `form-data; name="file"; filename=foobar; filename*=UTF-16''bar`,
+ name: "file",
+ filename: "foobar",
+ sanitizedHeader: `form-data; filename=foobar; name=file`,
+ },
+ }
+
+ for _, testCase := range tests {
+ t.Run(testCase.desc, func(t *testing.T) {
+ h := make(textproto.MIMEHeader)
+ h.Set("Content-Disposition", testCase.header)
+ h.Set("Content-Type", "application/octet-stream")
+
+ name, filename := parseAndNormalizeContentDisposition(h)
+
+ require.Equal(t, testCase.name, name)
+ require.Equal(t, testCase.filename, filename)
+ require.Equal(t, testCase.sanitizedHeader, h.Get("Content-Disposition"))
+ })
+ }
+}
diff --git a/workhorse/internal/upload/uploads_test.go b/workhorse/internal/upload/uploads_test.go
index 7c22b3b4e28..31fd3126dd4 100644
--- a/workhorse/internal/upload/uploads_test.go
+++ b/workhorse/internal/upload/uploads_test.go
@@ -1,13 +1,16 @@
package upload
import (
+ "bufio"
"bytes"
"context"
"fmt"
+ "io"
"io/ioutil"
"mime/multipart"
"net/http"
"net/http/httptest"
+ "net/textproto"
"os"
"regexp"
"strconv"
@@ -384,6 +387,89 @@ func TestInvalidFileNames(t *testing.T) {
}
}
+func TestContentDispositionRewrite(t *testing.T) {
+ testhelper.ConfigureSecret()
+
+ tempPath, err := ioutil.TempDir("", "uploads")
+ require.NoError(t, err)
+ defer os.RemoveAll(tempPath)
+
+ tests := []struct {
+ desc string
+ header string
+ code int
+ sanitizedHeader string
+ }{
+ {
+ desc: "with name",
+ header: `form-data; name="foo"`,
+ code: 200,
+ sanitizedHeader: `form-data; name=foo`,
+ },
+ {
+ desc: "with name and name*",
+ header: `form-data; name="foo"; name*=UTF-8''bar`,
+ code: 200,
+ sanitizedHeader: `form-data; name=bar`,
+ },
+ {
+ desc: "with name and invalid name*",
+ header: `form-data; name="foo"; name*=UTF-16''bar`,
+ code: 200,
+ sanitizedHeader: `form-data; name=foo`,
+ },
+ }
+
+ for _, testCase := range tests {
+ t.Run(testCase.desc, func(t *testing.T) {
+ h := make(textproto.MIMEHeader)
+ h.Set("Content-Disposition", testCase.header)
+ h.Set("Content-Type", "application/octet-stream")
+
+ buffer := &bytes.Buffer{}
+ writer := multipart.NewWriter(buffer)
+ file, err := writer.CreatePart(h)
+ require.NoError(t, err)
+ fmt.Fprint(file, "test")
+ writer.Close()
+
+ httpRequest := httptest.NewRequest("POST", "/example", buffer)
+ httpRequest.Header.Set("Content-Type", writer.FormDataContentType())
+
+ var upstreamRequestBuffer bytes.Buffer
+ customHandler := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
+ r.Write(&upstreamRequestBuffer)
+ })
+
+ response := httptest.NewRecorder()
+ apiResponse := &api.Response{TempPath: tempPath}
+ preparer := &DefaultPreparer{}
+ opts, _, err := preparer.Prepare(apiResponse)
+ require.NoError(t, err)
+
+ HandleFileUploads(response, httpRequest, customHandler, apiResponse, &SavedFileTracker{Request: httpRequest}, opts)
+
+ upstreamRequest, err := http.ReadRequest(bufio.NewReader(&upstreamRequestBuffer))
+ require.NoError(t, err)
+
+ reader, err := upstreamRequest.MultipartReader()
+ require.NoError(t, err)
+
+ for i := 0; ; i++ {
+ p, err := reader.NextPart()
+ if err == io.EOF {
+ require.Equal(t, i, 1)
+ break
+ }
+ require.NoError(t, err)
+ require.Equal(t, testCase.sanitizedHeader, p.Header.Get("Content-Disposition"))
+ }
+
+ require.Equal(t, testCase.code, response.Code)
+ })
+ }
+}
+
func TestUploadHandlerRemovingExif(t *testing.T) {
content, err := ioutil.ReadFile("exif/testdata/sample_exif.jpg")
require.NoError(t, err)