summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/diffs/components/compare_versions_dropdown.vue2
-rw-r--r--app/assets/javascripts/diffs/components/diff_content.vue8
-rw-r--r--app/assets/javascripts/gfm_auto_complete.js17
-rw-r--r--app/assets/javascripts/notes/components/discussion_filter.vue20
-rw-r--r--app/assets/javascripts/notes/constants.js1
-rw-r--r--app/assets/javascripts/pages/users/user_overview_block.js15
-rw-r--r--app/assets/javascripts/pipelines/components/pipeline_url.vue4
-rw-r--r--app/assets/javascripts/set_status_modal/emoji_menu_in_modal.js3
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue5
-rw-r--r--app/assets/stylesheets/framework/variables.scss1
-rw-r--r--app/assets/stylesheets/pages/pipelines.scss1
-rw-r--r--app/assets/stylesheets/pages/projects.scss6
-rw-r--r--app/controllers/groups/settings/ci_cd_controller.rb6
-rw-r--r--app/controllers/projects/blob_controller.rb2
-rw-r--r--app/controllers/projects/merge_requests_controller.rb6
-rw-r--r--app/controllers/projects/releases_controller.rb6
-rw-r--r--app/controllers/projects/snippets_controller.rb9
-rw-r--r--app/controllers/projects/tags_controller.rb15
-rw-r--r--app/controllers/projects_controller.rb1
-rw-r--r--app/controllers/snippets_controller.rb8
-rw-r--r--app/finders/releases_finder.rb14
-rw-r--r--app/helpers/appearances_helper.rb4
-rw-r--r--app/helpers/blob_helper.rb32
-rw-r--r--app/helpers/diff_helper.rb24
-rw-r--r--app/helpers/emails_helper.rb2
-rw-r--r--app/helpers/snippets_helper.rb10
-rw-r--r--app/helpers/sorting_helper.rb2
-rw-r--r--app/helpers/workhorse_helper.rb5
-rw-r--r--app/models/appearance.rb28
-rw-r--r--app/models/blob.rb10
-rw-r--r--app/models/blob_viewer/base.rb6
-rw-r--r--app/models/ci/build.rb5
-rw-r--r--app/models/ci/pipeline.rb13
-rw-r--r--app/models/clusters/applications/cert_manager.rb2
-rw-r--r--app/models/clusters/applications/runner.rb2
-rw-r--r--app/models/clusters/platforms/kubernetes.rb2
-rw-r--r--app/models/commit.rb4
-rw-r--r--app/models/concerns/blob_like.rb2
-rw-r--r--app/models/concerns/discussion_on_diff.rb20
-rw-r--r--app/models/concerns/has_ref.rb17
-rw-r--r--app/models/concerns/noteable.rb4
-rw-r--r--app/models/diff_viewer/base.rb39
-rw-r--r--app/models/diff_viewer/image.rb2
-rw-r--r--app/models/diff_viewer/rich.rb2
-rw-r--r--app/models/diff_viewer/server_side.rb12
-rw-r--r--app/models/diff_viewer/simple.rb2
-rw-r--r--app/models/merge_request.rb22
-rw-r--r--app/models/milestone.rb2
-rw-r--r--app/models/note_diff_file.rb15
-rw-r--r--app/models/project.rb26
-rw-r--r--app/models/prometheus_metric.rb84
-rw-r--r--app/models/release.rb28
-rw-r--r--app/models/remote_mirror.rb2
-rw-r--r--app/models/repository.rb13
-rw-r--r--app/models/snippet.rb6
-rw-r--r--app/models/todo.rb5
-rw-r--r--app/policies/concerns/clusterable_actions.rb14
-rw-r--r--app/policies/group_policy.rb10
-rw-r--r--app/policies/issuable_policy.rb2
-rw-r--r--app/policies/project_policy.rb14
-rw-r--r--app/policies/release_policy.rb5
-rw-r--r--app/presenters/clusterable_presenter.rb4
-rw-r--r--app/serializers/diff_viewer_entity.rb3
-rw-r--r--app/services/ci/create_pipeline_service.rb3
-rw-r--r--app/services/commits/tag_service.rb3
-rw-r--r--app/services/create_release_service.rb35
-rw-r--r--app/services/git_push_service.rb3
-rw-r--r--app/services/git_tag_push_service.rb3
-rw-r--r--app/services/groups/update_service.rb2
-rw-r--r--app/services/issues/update_service.rb2
-rw-r--r--app/services/members/base_service.rb6
-rw-r--r--app/services/members/destroy_service.rb8
-rw-r--r--app/services/members/update_service.rb9
-rw-r--r--app/services/merge_requests/build_service.rb24
-rw-r--r--app/services/merge_requests/update_service.rb6
-rw-r--r--app/services/notification_service.rb2
-rw-r--r--app/services/projects/lfs_pointers/lfs_download_service.rb35
-rw-r--r--app/services/projects/update_service.rb4
-rw-r--r--app/services/releases/concerns.rb48
-rw-r--r--app/services/releases/create_service.rb54
-rw-r--r--app/services/releases/destroy_service.rb25
-rw-r--r--app/services/releases/update_service.rb32
-rw-r--r--app/services/tags/create_service.rb7
-rw-r--r--app/services/tags/destroy_service.rb10
-rw-r--r--app/services/update_release_service.rb28
-rw-r--r--app/views/admin/appearances/_form.html.haml6
-rw-r--r--app/views/clusters/clusters/_buttons.html.haml3
-rw-r--r--app/views/clusters/clusters/_empty_state.html.haml2
-rw-r--r--app/views/events/_events.html.haml5
-rw-r--r--app/views/layouts/nav/sidebar/_project.html.haml2
-rw-r--r--app/views/projects/diffs/_render_error.html.haml6
-rw-r--r--app/views/shared/snippets/_header.html.haml2
-rw-r--r--app/workers/mail_scheduler/notification_service_worker.rb23
-rw-r--r--app/workers/post_receive.rb20
94 files changed, 773 insertions, 301 deletions
diff --git a/app/assets/javascripts/diffs/components/compare_versions_dropdown.vue b/app/assets/javascripts/diffs/components/compare_versions_dropdown.vue
index 8da02ed0b7c..b9b1ee02697 100644
--- a/app/assets/javascripts/diffs/components/compare_versions_dropdown.vue
+++ b/app/assets/javascripts/diffs/components/compare_versions_dropdown.vue
@@ -129,7 +129,7 @@ export default {
</strong>
</div>
<div>
- <small class="commit-sha"> {{ version.truncated_commit_sha }} </small>
+ <small class="commit-sha"> {{ version.short_commit_sha }} </small>
</div>
<div>
<small>
diff --git a/app/assets/javascripts/diffs/components/diff_content.vue b/app/assets/javascripts/diffs/components/diff_content.vue
index 42d09e44768..ba6dcd63880 100644
--- a/app/assets/javascripts/diffs/components/diff_content.vue
+++ b/app/assets/javascripts/diffs/components/diff_content.vue
@@ -45,6 +45,9 @@ export default {
isTextFile() {
return this.diffFile.viewer.name === 'text';
},
+ errorMessage() {
+ return this.diffFile.viewer.error;
+ },
diffFileCommentForm() {
return this.getCommentFormForDiffFile(this.diffFile.file_hash);
},
@@ -75,7 +78,7 @@ export default {
<template>
<div class="diff-content">
- <div class="diff-viewer">
+ <div v-if="!errorMessage" class="diff-viewer">
<template v-if="isTextFile">
<empty-file-viewer v-if="diffFile.empty" />
<inline-diff-view
@@ -129,5 +132,8 @@ export default {
</div>
</diff-viewer>
</div>
+ <div v-else class="diff-viewer">
+ <div class="nothing-here-block" v-html="errorMessage"></div>
+ </div>
</div>
</template>
diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js
index c14eb936930..8178821be3d 100644
--- a/app/assets/javascripts/gfm_auto_complete.js
+++ b/app/assets/javascripts/gfm_auto_complete.js
@@ -256,7 +256,7 @@ class GfmAutoComplete {
displayTpl(value) {
let tmpl = GfmAutoComplete.Loading.template;
if (value.title != null) {
- tmpl = GfmAutoComplete.Milestones.template;
+ tmpl = GfmAutoComplete.Milestones.templateFunction(value.title);
}
return tmpl;
},
@@ -323,7 +323,7 @@ class GfmAutoComplete {
searchKey: 'search',
data: GfmAutoComplete.defaultLoadingData,
displayTpl(value) {
- let tmpl = GfmAutoComplete.Labels.template;
+ let tmpl = GfmAutoComplete.Labels.templateFunction(value.color, value.title);
if (GfmAutoComplete.isLoading(value)) {
tmpl = GfmAutoComplete.Loading.template;
}
@@ -588,9 +588,11 @@ GfmAutoComplete.Members = {
},
};
GfmAutoComplete.Labels = {
- template:
- // eslint-disable-next-line no-template-curly-in-string
- '<li><span class="dropdown-label-box" style="background: ${color}"></span> ${title}</li>',
+ templateFunction(color, title) {
+ return `<li><span class="dropdown-label-box" style="background: ${_.escape(
+ color,
+ )}"></span> ${_.escape(title)}</li>`;
+ },
};
// Issues, MergeRequests and Snippets
GfmAutoComplete.Issues = {
@@ -600,8 +602,9 @@ GfmAutoComplete.Issues = {
};
// Milestones
GfmAutoComplete.Milestones = {
- // eslint-disable-next-line no-template-curly-in-string
- template: '<li>${title}</li>',
+ templateFunction(title) {
+ return `<li>${_.escape(title)}</li>`;
+ },
};
GfmAutoComplete.Loading = {
template:
diff --git a/app/assets/javascripts/notes/components/discussion_filter.vue b/app/assets/javascripts/notes/components/discussion_filter.vue
index 86c114a761a..f5c410211b6 100644
--- a/app/assets/javascripts/notes/components/discussion_filter.vue
+++ b/app/assets/javascripts/notes/components/discussion_filter.vue
@@ -2,7 +2,11 @@
import $ from 'jquery';
import { mapGetters, mapActions } from 'vuex';
import Icon from '~/vue_shared/components/icon.vue';
-import { DISCUSSION_FILTERS_DEFAULT_VALUE, HISTORY_ONLY_FILTER_VALUE } from '../constants';
+import {
+ DISCUSSION_FILTERS_DEFAULT_VALUE,
+ HISTORY_ONLY_FILTER_VALUE,
+ DISCUSSION_TAB_LABEL,
+} from '../constants';
export default {
components: {
@@ -23,6 +27,7 @@ export default {
return {
currentValue: this.selectedValue,
defaultValue: DISCUSSION_FILTERS_DEFAULT_VALUE,
+ displayFilters: true,
};
},
computed: {
@@ -32,6 +37,14 @@ export default {
return this.filters.find(filter => filter.value === this.currentValue);
},
},
+ created() {
+ if (window.mrTabs) {
+ const { eventHub, currentTab } = window.mrTabs;
+
+ eventHub.$on('MergeRequestTabChange', this.toggleFilters);
+ this.toggleFilters(currentTab);
+ }
+ },
mounted() {
this.toggleCommentsForm();
},
@@ -51,12 +64,15 @@ export default {
toggleCommentsForm() {
this.setCommentsDisabled(this.currentValue === HISTORY_ONLY_FILTER_VALUE);
},
+ toggleFilters(tab) {
+ this.displayFilters = tab === DISCUSSION_TAB_LABEL;
+ },
},
};
</script>
<template>
- <div class="discussion-filter-container d-inline-block align-bottom">
+ <div v-if="displayFilters" class="discussion-filter-container d-inline-block align-bottom">
<button
id="discussion-filter-dropdown"
ref="dropdownToggle"
diff --git a/app/assets/javascripts/notes/constants.js b/app/assets/javascripts/notes/constants.js
index 3147dc64c27..78d365fe94b 100644
--- a/app/assets/javascripts/notes/constants.js
+++ b/app/assets/javascripts/notes/constants.js
@@ -17,6 +17,7 @@ export const RESOLVE_NOTE_METHOD_NAME = 'post';
export const DESCRIPTION_TYPE = 'changed the description';
export const HISTORY_ONLY_FILTER_VALUE = 2;
export const DISCUSSION_FILTERS_DEFAULT_VALUE = 0;
+export const DISCUSSION_TAB_LABEL = 'show';
export const NOTEABLE_TYPE_MAPPING = {
Issue: ISSUE_NOTEABLE_TYPE,
diff --git a/app/assets/javascripts/pages/users/user_overview_block.js b/app/assets/javascripts/pages/users/user_overview_block.js
index eec2b5ca8e5..e9ecec717d6 100644
--- a/app/assets/javascripts/pages/users/user_overview_block.js
+++ b/app/assets/javascripts/pages/users/user_overview_block.js
@@ -29,18 +29,21 @@ export default class UserOverviewBlock {
render(data) {
const { html, count } = data;
- const contentList = document.querySelector(`${this.container} .overview-content-list`);
+ const containerEl = document.querySelector(this.container);
+ const contentList = containerEl.querySelector('.overview-content-list');
contentList.innerHTML += html;
- const loadingEl = document.querySelector(`${this.container} .loading`);
+ const loadingEl = containerEl.querySelector('.loading');
if (count && count > 0) {
- document.querySelector(`${this.container} .js-view-all`).classList.remove('hide');
+ containerEl.querySelector('.js-view-all').classList.remove('hide');
} else {
- document
- .querySelector(`${this.container} .nothing-here-block`)
- .classList.add('text-left', 'p-0');
+ const nothingHereBlock = containerEl.querySelector('.nothing-here-block');
+
+ if (nothingHereBlock) {
+ nothingHereBlock.classList.add('text-left', 'p-0');
+ }
}
loadingEl.classList.add('hide');
diff --git a/app/assets/javascripts/pipelines/components/pipeline_url.vue b/app/assets/javascripts/pipelines/components/pipeline_url.vue
index 30a5bbf92ce..7d8863dff29 100644
--- a/app/assets/javascripts/pipelines/components/pipeline_url.vue
+++ b/app/assets/javascripts/pipelines/components/pipeline_url.vue
@@ -65,7 +65,7 @@ export default {
v-if="pipeline.flags.latest"
v-gl-tooltip
class="js-pipeline-url-latest badge badge-success"
- title="__('Latest pipeline for this branch')"
+ :title="__('Latest pipeline for this branch')"
>
latest
</span>
@@ -100,7 +100,7 @@ export default {
<span
v-if="pipeline.flags.merge_request"
v-gl-tooltip
- title="__('This pipeline is run in a merge request context')"
+ :title="__('This pipeline is run in a merge request context')"
class="js-pipeline-url-mergerequest badge badge-info"
>
merge request
diff --git a/app/assets/javascripts/set_status_modal/emoji_menu_in_modal.js b/app/assets/javascripts/set_status_modal/emoji_menu_in_modal.js
index 14a89ef9293..3a8631a196f 100644
--- a/app/assets/javascripts/set_status_modal/emoji_menu_in_modal.js
+++ b/app/assets/javascripts/set_status_modal/emoji_menu_in_modal.js
@@ -12,9 +12,8 @@ class EmojiMenuInModal extends AwardsHandler {
this.bindEvents();
}
- postEmoji($emojiButton, awardUrl, selectedEmoji, callback) {
+ postEmoji($emojiButton, awardUrl, selectedEmoji) {
this.selectEmojiCallback(selectedEmoji, this.emoji.glEmojiTag(selectedEmoji));
- callback();
}
}
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 d8a75388e84..b7f12076958 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
@@ -106,6 +106,9 @@ export default {
(!this.mr.isNothingToMergeState && !this.mr.isMergedState)
);
},
+ shouldRenderCollaborationStatus() {
+ return this.mr.allowCollaboration && this.mr.isOpen;
+ },
shouldRenderMergedPipeline() {
return this.mr.state === 'merged' && !_.isEmpty(this.mr.mergePipeline);
},
@@ -315,7 +318,7 @@ export default {
<div class="mr-widget-section">
<component :is="componentName" :mr="mr" :service="service" />
- <section v-if="mr.allowCollaboration" class="mr-info-list mr-links">
+ <section v-if="shouldRenderCollaborationStatus" class="mr-info-list mr-links">
{{ s__('mrWidget|Allows commits from members who can merge to the target branch') }}
</section>
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 343c09b4a3e..7e53f1ec48d 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -198,7 +198,6 @@ $well-light-text-color: #5b6169;
$gl-font-size: 14px;
$gl-font-size-xs: 11px;
$gl-font-size-small: 12px;
-$gl-font-size-medium: 20px;
$gl-font-size-large: 16px;
$gl-font-weight-normal: 400;
$gl-font-weight-bold: 600;
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index fdd17af35fb..7a47e0a2836 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -978,7 +978,6 @@ button.mini-pipeline-graph-dropdown-toggle {
* Top arrow in the dropdown in the mini pipeline graph
*/
.mini-pipeline-graph-dropdown-menu {
- z-index: 200;
&::before,
&::after {
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 0ce0db038a7..004c49dd226 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -973,7 +973,7 @@ pre.light-well {
padding: $gl-padding 0;
@include media-breakpoint-up(lg) {
- padding: $gl-padding-24 0;
+ padding: $gl-padding 0;
}
&.no-description {
@@ -990,7 +990,7 @@ pre.light-well {
}
h2 {
- font-size: $gl-font-size-medium;
+ font-size: $gl-font-size-large;
font-weight: $gl-font-weight-bold;
margin-bottom: 0;
@@ -1049,7 +1049,7 @@ pre.light-well {
}
.controls {
- margin-top: $gl-padding;
+ margin-top: $gl-padding-8;
@include media-breakpoint-down(md) {
margin-top: 0;
diff --git a/app/controllers/groups/settings/ci_cd_controller.rb b/app/controllers/groups/settings/ci_cd_controller.rb
index c1dcc463de7..f476f428fdb 100644
--- a/app/controllers/groups/settings/ci_cd_controller.rb
+++ b/app/controllers/groups/settings/ci_cd_controller.rb
@@ -4,7 +4,7 @@ module Groups
module Settings
class CiCdController < Groups::ApplicationController
skip_cross_project_access_check :show
- before_action :authorize_admin_pipeline!
+ before_action :authorize_admin_group!
def show
define_ci_variables
@@ -26,8 +26,8 @@ module Groups
.map { |variable| variable.present(current_user: current_user) }
end
- def authorize_admin_pipeline!
- return render_404 unless can?(current_user, :admin_pipeline, group)
+ def authorize_admin_group!
+ return render_404 unless can?(current_user, :admin_group, group)
end
end
end
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index 60fabd15333..ff286c0ccf0 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -260,7 +260,7 @@ class Projects::BlobController < Projects::ApplicationController
extension: blob.extension,
size: blob.raw_size,
mime_type: blob.mime_type,
- binary: blob.raw_binary?,
+ binary: blob.binary?,
simple_viewer: blob.simple_viewer&.class&.partial_name,
rich_viewer: blob.rich_viewer&.class&.partial_name,
show_viewer_switcher: !!blob.show_viewer_switcher?,
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 8b8eac7ff8e..162c2636641 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -218,6 +218,12 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
head :ok
end
+ def discussions
+ merge_request.preload_discussions_diff_highlight
+
+ super
+ end
+
protected
alias_method :subscribable_resource, :merge_request
diff --git a/app/controllers/projects/releases_controller.rb b/app/controllers/projects/releases_controller.rb
index 58d5ea4762f..62bdc84b41a 100644
--- a/app/controllers/projects/releases_controller.rb
+++ b/app/controllers/projects/releases_controller.rb
@@ -3,7 +3,7 @@
class Projects::ReleasesController < Projects::ApplicationController
# Authorize
before_action :require_non_empty_project
- before_action :authorize_download_code!
+ before_action :authorize_read_release!
before_action :check_releases_page_feature_flag
def index
@@ -12,8 +12,8 @@ class Projects::ReleasesController < Projects::ApplicationController
private
def check_releases_page_feature_flag
- return render_404 unless Feature.enabled?(:releases_page)
+ return render_404 unless Feature.enabled?(:releases_page, @project)
- push_frontend_feature_flag(:releases_page)
+ push_frontend_feature_flag(:releases_page, @project)
end
end
diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb
index a44acb12bdf..255f1f3569a 100644
--- a/app/controllers/projects/snippets_controller.rb
+++ b/app/controllers/projects/snippets_controller.rb
@@ -75,7 +75,14 @@ class Projects::SnippetsController < Projects::ApplicationController
format.json do
render_blob_json(blob)
end
- format.js { render 'shared/snippets/show'}
+
+ format.js do
+ if @snippet.embeddable?
+ render 'shared/snippets/show'
+ else
+ head :not_found
+ end
+ end
end
end
diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb
index a50a1475eb2..a17c050b696 100644
--- a/app/controllers/projects/tags_controller.rb
+++ b/app/controllers/projects/tags_controller.rb
@@ -43,9 +43,22 @@ class Projects::TagsController < Projects::ApplicationController
def create
result = ::Tags::CreateService.new(@project, current_user)
- .execute(params[:tag_name], params[:ref], params[:message], params[:release_description])
+ .execute(params[:tag_name], params[:ref], params[:message])
if result[:status] == :success
+ # Release creation with Tags was deprecated in GitLab 11.7
+ if params[:release_description].present?
+ release_params = {
+ tag: params[:tag_name],
+ name: params[:tag_name],
+ description: params[:release_description]
+ }
+
+ Releases::CreateService
+ .new(@project, current_user, release_params)
+ .execute
+ end
+
@tag = result[:tag]
redirect_to project_tag_path(@project, @tag.name)
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 8bf93bfd68d..878816475b2 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -19,6 +19,7 @@ class ProjectsController < Projects::ApplicationController
before_action :lfs_blob_ids, only: [:show], if: [:repo_exists?, :project_view_files?]
before_action :project_export_enabled, only: [:export, :download_export, :remove_export, :generate_new_export]
before_action :present_project, only: [:edit]
+ before_action :authorize_download_code!, only: [:refs]
# Authorize
before_action :authorize_admin_project!, only: [:edit, :update, :housekeeping, :download_export, :export, :remove_export, :generate_new_export]
diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb
index dd9bf17cf0c..8ea5450b4e8 100644
--- a/app/controllers/snippets_controller.rb
+++ b/app/controllers/snippets_controller.rb
@@ -80,7 +80,13 @@ class SnippetsController < ApplicationController
render_blob_json(blob)
end
- format.js { render 'shared/snippets/show' }
+ format.js do
+ if @snippet.embeddable?
+ render 'shared/snippets/show'
+ else
+ head :not_found
+ end
+ end
end
end
diff --git a/app/finders/releases_finder.rb b/app/finders/releases_finder.rb
new file mode 100644
index 00000000000..59e84198fde
--- /dev/null
+++ b/app/finders/releases_finder.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+class ReleasesFinder
+ def initialize(project, current_user = nil)
+ @project = project
+ @current_user = current_user
+ end
+
+ def execute
+ return Release.none unless Ability.allowed?(@current_user, :read_release, @project)
+
+ @project.releases.sorted
+ end
+end
diff --git a/app/helpers/appearances_helper.rb b/app/helpers/appearances_helper.rb
index 3f69af50f25..473c90c882c 100644
--- a/app/helpers/appearances_helper.rb
+++ b/app/helpers/appearances_helper.rb
@@ -11,7 +11,7 @@ module AppearancesHelper
end
def brand_image
- image_tag(current_appearance.logo) if current_appearance&.logo?
+ image_tag(current_appearance.logo_path) if current_appearance&.logo?
end
def brand_text
@@ -28,7 +28,7 @@ module AppearancesHelper
def brand_header_logo
if current_appearance&.header_logo?
- image_tag current_appearance.header_logo
+ image_tag current_appearance.header_logo_path
else
render 'shared/logo.svg'
end
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index bd42f00944f..3dea0975beb 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -140,36 +140,6 @@ module BlobHelper
Gitlab::Sanitizers::SVG.clean(data)
end
- # Remove once https://gitlab.com/gitlab-org/gitlab-ce/issues/36103 is closed
- # and :workhorse_set_content_type flag is removed
- # If we blindly set the 'real' content type when serving a Git blob we
- # are enabling XSS attacks. An attacker could upload e.g. a Javascript
- # file to a Git repository, trick the browser of a victim into
- # downloading the blob, and then the 'application/javascript' content
- # type would tell the browser to execute the attacker's Javascript. By
- # overriding the content type and setting it to 'text/plain' (in the
- # example of Javascript) we tell the browser of the victim not to
- # execute untrusted data.
- def safe_content_type(blob)
- if blob.extension == 'svg'
- blob.mime_type
- elsif blob.text?
- 'text/plain; charset=utf-8'
- elsif blob.image?
- blob.content_type
- else
- 'application/octet-stream'
- end
- end
-
- def content_disposition(blob, inline)
- # Remove the following line when https://gitlab.com/gitlab-org/gitlab-ce/issues/36103
- # is closed and :workhorse_set_content_type flag is removed
- return 'attachment' if blob.extension == 'svg'
-
- inline ? 'inline' : 'attachment'
- end
-
def ref_project
@ref_project ||= @target_project || @project
end
@@ -223,7 +193,7 @@ module BlobHelper
def open_raw_blob_button(blob)
return if blob.empty?
- return if blob.raw_binary? || blob.stored_externally?
+ return if blob.binary? || blob.stored_externally?
title = 'Open raw'
link_to icon('file-code-o'), blob_raw_path, class: 'btn btn-sm has-tooltip', target: '_blank', rel: 'noopener noreferrer', title: title, data: { container: 'body' }
diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb
index b6844d36052..32431959851 100644
--- a/app/helpers/diff_helper.rb
+++ b/app/helpers/diff_helper.rb
@@ -138,30 +138,6 @@ module DiffHelper
!diff_file.deleted_file? && @merge_request && @merge_request.source_project
end
- def diff_render_error_reason(viewer)
- case viewer.render_error
- when :too_large
- "it is too large"
- when :server_side_but_stored_externally
- case viewer.diff_file.external_storage
- when :lfs
- 'it is stored in LFS'
- else
- 'it is stored externally'
- end
- end
- end
-
- def diff_render_error_options(viewer)
- diff_file = viewer.diff_file
- options = []
-
- blob_url = project_blob_path(@project, tree_join(diff_file.content_sha, diff_file.file_path))
- options << link_to('view the blob', blob_url)
-
- options
- end
-
def diff_file_changed_icon(diff_file)
if diff_file.deleted_file?
"file-deletion"
diff --git a/app/helpers/emails_helper.rb b/app/helpers/emails_helper.rb
index e4c46ceeaa2..fa5d3ae474a 100644
--- a/app/helpers/emails_helper.rb
+++ b/app/helpers/emails_helper.rb
@@ -58,7 +58,7 @@ module EmailsHelper
def header_logo
if current_appearance&.header_logo?
image_tag(
- current_appearance.header_logo,
+ current_appearance.header_logo_path,
style: 'height: 50px'
)
else
diff --git a/app/helpers/snippets_helper.rb b/app/helpers/snippets_helper.rb
index c7d31f3469d..ecb2b2d707b 100644
--- a/app/helpers/snippets_helper.rb
+++ b/app/helpers/snippets_helper.rb
@@ -110,7 +110,7 @@ module SnippetsHelper
def embedded_snippet_raw_button
blob = @snippet.blob
- return if blob.empty? || blob.raw_binary? || blob.stored_externally?
+ return if blob.empty? || blob.binary? || blob.stored_externally?
snippet_raw_url = if @snippet.is_a?(PersonalSnippet)
raw_snippet_url(@snippet)
@@ -130,12 +130,4 @@ module SnippetsHelper
link_to external_snippet_icon('download'), download_url, class: 'btn', target: '_blank', title: 'Download', rel: 'noopener noreferrer'
end
-
- def public_snippet?
- if @snippet.project_id?
- can?(nil, :read_project_snippet, @snippet)
- else
- can?(nil, :read_personal_snippet, @snippet)
- end
- end
end
diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb
index 6ac1f42c321..02762897c89 100644
--- a/app/helpers/sorting_helper.rb
+++ b/app/helpers/sorting_helper.rb
@@ -234,7 +234,7 @@ module SortingHelper
end
def sort_title_milestone
- s_('SortOptions|Milestone')
+ s_('SortOptions|Milestone due date')
end
def sort_title_milestone_later
diff --git a/app/helpers/workhorse_helper.rb b/app/helpers/workhorse_helper.rb
index e9fc39e451b..bb5b1555dc4 100644
--- a/app/helpers/workhorse_helper.rb
+++ b/app/helpers/workhorse_helper.rb
@@ -7,8 +7,7 @@ module WorkhorseHelper
def send_git_blob(repository, blob, inline: true)
headers.store(*Gitlab::Workhorse.send_git_blob(repository, blob))
- headers['Content-Disposition'] = content_disposition(blob, inline)
- headers['Content-Type'] = safe_content_type(blob)
+ headers['Content-Disposition'] = inline ? 'inline' : 'attachment'
# If enabled, this will override the values set above
workhorse_set_content_type!
@@ -47,6 +46,6 @@ module WorkhorseHelper
end
def workhorse_set_content_type!
- headers[Gitlab::Workhorse::DETECT_HEADER] = "true" if Feature.enabled?(:workhorse_set_content_type)
+ headers[Gitlab::Workhorse::DETECT_HEADER] = "true"
end
end
diff --git a/app/models/appearance.rb b/app/models/appearance.rb
index bffba3e13fa..e114c435b67 100644
--- a/app/models/appearance.rb
+++ b/app/models/appearance.rb
@@ -28,4 +28,32 @@ class Appearance < ActiveRecord::Base
errors.add(:single_appearance_row, 'Only 1 appearances row can exist')
end
end
+
+ def logo_path
+ logo_system_path(logo, 'logo')
+ end
+
+ def header_logo_path
+ logo_system_path(header_logo, 'header_logo')
+ end
+
+ def favicon_path
+ logo_system_path(favicon, 'favicon')
+ end
+
+ private
+
+ def logo_system_path(logo, mount_type)
+ return unless logo&.upload
+
+ # If we're using a CDN, we need to use the full URL
+ asset_host = ActionController::Base.asset_host
+ local_path = Gitlab::Routing.url_helpers.appearance_upload_path(
+ filename: logo.filename,
+ id: logo.upload.model_id,
+ model: 'appearance',
+ mounted_as: mount_type)
+
+ Gitlab::Utils.append_path(asset_host, local_path)
+ end
end
diff --git a/app/models/blob.rb b/app/models/blob.rb
index 66a0925c495..c5766eb0327 100644
--- a/app/models/blob.rb
+++ b/app/models/blob.rb
@@ -102,7 +102,7 @@ class Blob < SimpleDelegator
# If the blob is a text based blob the content is converted to UTF-8 and any
# invalid byte sequences are replaced.
def data
- if binary?
+ if binary_in_repo?
super
else
@data ||= super.encode(Encoding::UTF_8, invalid: :replace, undef: :replace)
@@ -149,7 +149,7 @@ class Blob < SimpleDelegator
# an LFS pointer, we assume the file stored in LFS is binary, unless a
# text-based rich blob viewer matched on the file's extension. Otherwise, this
# depends on the type of the blob itself.
- def raw_binary?
+ def binary?
if stored_externally?
if rich_viewer
rich_viewer.binary?
@@ -161,7 +161,7 @@ class Blob < SimpleDelegator
true
end
else
- binary?
+ binary_in_repo?
end
end
@@ -180,7 +180,7 @@ class Blob < SimpleDelegator
end
def readable_text?
- text? && !stored_externally? && !truncated?
+ text_in_repo? && !stored_externally? && !truncated?
end
def simple_viewer
@@ -220,7 +220,7 @@ class Blob < SimpleDelegator
def simple_viewer_class
if empty?
BlobViewer::Empty
- elsif raw_binary?
+ elsif binary?
BlobViewer::Download
else # text
BlobViewer::Text
diff --git a/app/models/blob_viewer/base.rb b/app/models/blob_viewer/base.rb
index eaaf9af1330..df6b9bb2f0b 100644
--- a/app/models/blob_viewer/base.rb
+++ b/app/models/blob_viewer/base.rb
@@ -16,7 +16,7 @@ module BlobViewer
def initialize(blob)
@blob = blob
- @initially_binary = blob.binary?
+ @initially_binary = blob.binary_in_repo?
end
def self.partial_path
@@ -52,7 +52,7 @@ module BlobViewer
end
def self.can_render?(blob, verify_binary: true)
- return false if verify_binary && binary? != blob.binary?
+ return false if verify_binary && binary? != blob.binary_in_repo?
return true if extensions&.include?(blob.extension)
return true if file_types&.include?(blob.file_type)
@@ -72,7 +72,7 @@ module BlobViewer
end
def binary_detected_after_load?
- !@initially_binary && blob.binary?
+ !@initially_binary && blob.binary_in_repo?
end
# This method is used on the server side to check whether we can attempt to
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 16a72c680fa..b128a254b96 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -10,6 +10,7 @@ module Ci
include Importable
include Gitlab::Utils::StrongMemoize
include Deployable
+ include HasRef
belongs_to :project, inverse_of: :builds
belongs_to :runner
@@ -640,11 +641,11 @@ module Ci
def secret_group_variables
return [] unless project.group
- project.group.ci_variables_for(ref, project)
+ project.group.ci_variables_for(git_ref, project)
end
def secret_project_variables(environment: persisted_environment)
- project.ci_variables_for(ref: ref, environment: environment)
+ project.ci_variables_for(ref: git_ref, environment: environment)
end
def steps
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 25937065011..1f5017cc3c3 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -11,6 +11,7 @@ module Ci
include Gitlab::Utils::StrongMemoize
include AtomicInternalId
include EnumWithNil
+ include HasRef
belongs_to :project, inverse_of: :all_pipelines
belongs_to :user
@@ -380,7 +381,7 @@ module Ci
end
def branch?
- !tag? && !merge_request?
+ super && !merge_request?
end
def stuck?
@@ -580,7 +581,7 @@ module Ci
end
def protected_ref?
- strong_memoize(:protected_ref) { project.protected_for?(ref) }
+ strong_memoize(:protected_ref) { project.protected_for?(git_ref) }
end
def legacy_trigger
@@ -712,14 +713,10 @@ module Ci
end
def git_ref
- if branch?
+ if merge_request?
Gitlab::Git::BRANCH_REF_PREFIX + ref.to_s
- elsif merge_request?
- Gitlab::Git::BRANCH_REF_PREFIX + ref.to_s
- elsif tag?
- Gitlab::Git::TAG_REF_PREFIX + ref.to_s
else
- raise ArgumentError, 'Invalid pipeline type!'
+ super
end
end
diff --git a/app/models/clusters/applications/cert_manager.rb b/app/models/clusters/applications/cert_manager.rb
index 74ef7c7e145..c758577815a 100644
--- a/app/models/clusters/applications/cert_manager.rb
+++ b/app/models/clusters/applications/cert_manager.rb
@@ -3,7 +3,7 @@
module Clusters
module Applications
class CertManager < ActiveRecord::Base
- VERSION = 'v0.5.0'.freeze
+ VERSION = 'v0.5.2'.freeze
self.table_name = 'clusters_applications_cert_managers'
diff --git a/app/models/clusters/applications/runner.rb b/app/models/clusters/applications/runner.rb
index c931b340b24..0c0247da1fb 100644
--- a/app/models/clusters/applications/runner.rb
+++ b/app/models/clusters/applications/runner.rb
@@ -3,7 +3,7 @@
module Clusters
module Applications
class Runner < ActiveRecord::Base
- VERSION = '0.1.39'.freeze
+ VERSION = '0.1.43'.freeze
self.table_name = 'clusters_applications_runners'
diff --git a/app/models/clusters/platforms/kubernetes.rb b/app/models/clusters/platforms/kubernetes.rb
index 6cec497b4e4..0dc0c4f80d6 100644
--- a/app/models/clusters/platforms/kubernetes.rb
+++ b/app/models/clusters/platforms/kubernetes.rb
@@ -106,7 +106,7 @@ module Clusters
def terminals(environment)
with_reactive_cache do |data|
pods = filter_by_label(data[:pods], app: environment.slug)
- terminals = pods.flat_map { |pod| terminals_for_pod(api_url, actual_namespace, pod) }
+ terminals = pods.flat_map { |pod| terminals_for_pod(api_url, actual_namespace, pod) }.compact
terminals.each { |terminal| add_terminal_auth(terminal, terminal_auth) }
end
end
diff --git a/app/models/commit.rb b/app/models/commit.rb
index a422a0995ff..01f4c58daa1 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -469,6 +469,10 @@ class Commit
!!merged_merge_request(user)
end
+ def cache_key
+ "commit:#{sha}"
+ end
+
private
def commit_reference(from, referable_commit_id, full: false)
diff --git a/app/models/concerns/blob_like.rb b/app/models/concerns/blob_like.rb
index f20f01486a5..dc80f8d62f4 100644
--- a/app/models/concerns/blob_like.rb
+++ b/app/models/concerns/blob_like.rb
@@ -28,7 +28,7 @@ module BlobLike
nil
end
- def binary?
+ def binary_in_repo?
false
end
diff --git a/app/models/concerns/discussion_on_diff.rb b/app/models/concerns/discussion_on_diff.rb
index 86b61248534..e4e5928f5cf 100644
--- a/app/models/concerns/discussion_on_diff.rb
+++ b/app/models/concerns/discussion_on_diff.rb
@@ -9,7 +9,7 @@ module DiscussionOnDiff
included do
delegate :line_code,
:original_line_code,
- :diff_file,
+ :note_diff_file,
:diff_line,
:active?,
:created_at_diff?,
@@ -60,6 +60,13 @@ module DiscussionOnDiff
prev_lines
end
+ def diff_file
+ strong_memoize(:diff_file) do
+ # Falling back here is important as `note_diff_files` are created async.
+ fetch_preloaded_diff_file || first_note.diff_file
+ end
+ end
+
def line_code_in_diffs(diff_refs)
if active?(diff_refs)
line_code
@@ -67,4 +74,15 @@ module DiscussionOnDiff
original_line_code
end
end
+
+ private
+
+ def fetch_preloaded_diff_file
+ fetch_preloaded_diff =
+ context_noteable &&
+ context_noteable.preloads_discussion_diff_highlighting? &&
+ note_diff_file
+
+ context_noteable.discussions_diffs.find_by_id(note_diff_file.id) if fetch_preloaded_diff
+ end
end
diff --git a/app/models/concerns/has_ref.rb b/app/models/concerns/has_ref.rb
new file mode 100644
index 00000000000..d7089294efc
--- /dev/null
+++ b/app/models/concerns/has_ref.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module HasRef
+ extend ActiveSupport::Concern
+
+ def branch?
+ !tag?
+ end
+
+ def git_ref
+ if branch?
+ Gitlab::Git::BRANCH_REF_PREFIX + ref.to_s
+ elsif tag?
+ Gitlab::Git::TAG_REF_PREFIX + ref.to_s
+ end
+ end
+end
diff --git a/app/models/concerns/noteable.rb b/app/models/concerns/noteable.rb
index f2cad09e779..29476654bf7 100644
--- a/app/models/concerns/noteable.rb
+++ b/app/models/concerns/noteable.rb
@@ -34,6 +34,10 @@ module Noteable
false
end
+ def preloads_discussion_diff_highlighting?
+ false
+ end
+
def discussion_notes
notes
end
diff --git a/app/models/diff_viewer/base.rb b/app/models/diff_viewer/base.rb
index 1176861a827..527ee33b83b 100644
--- a/app/models/diff_viewer/base.rb
+++ b/app/models/diff_viewer/base.rb
@@ -18,7 +18,7 @@ module DiffViewer
def initialize(diff_file)
@diff_file = diff_file
- @initially_binary = diff_file.binary?
+ @initially_binary = diff_file.binary_in_repo?
end
def self.partial_path
@@ -48,7 +48,7 @@ module DiffViewer
def self.can_render_blob?(blob, verify_binary: true)
return true if blob.nil?
- return false if verify_binary && binary? != blob.binary?
+ return false if verify_binary && binary? != blob.binary_in_repo?
return true if extensions&.include?(blob.extension)
return true if file_types&.include?(blob.file_type)
@@ -70,20 +70,49 @@ module DiffViewer
end
def binary_detected_after_load?
- !@initially_binary && diff_file.binary?
+ !@initially_binary && diff_file.binary_in_repo?
end
# This method is used on the server side to check whether we can attempt to
- # render the diff_file at all. Human-readable error messages are found in the
- # `BlobHelper#diff_render_error_reason` helper.
+ # render the diff_file at all. The human-readable error message can be
+ # retrieved by #render_error_message.
def render_error
if too_large?
:too_large
end
end
+ def render_error_message
+ return unless render_error
+
+ _("This %{viewer} could not be displayed because %{reason}. You can %{options} instead.") %
+ {
+ viewer: switcher_title,
+ reason: render_error_reason,
+ options: render_error_options.to_sentence(two_words_connector: _(' or '), last_word_connector: _(', or '))
+ }
+ end
+
def prepare!
# To be overridden by subclasses
end
+
+ private
+
+ def render_error_options
+ options = []
+
+ blob_url = Gitlab::Routing.url_helpers.project_blob_path(diff_file.repository.project,
+ File.join(diff_file.content_sha, diff_file.file_path))
+ options << ActionController::Base.helpers.link_to(_('view the blob'), blob_url)
+
+ options
+ end
+
+ def render_error_reason
+ if render_error == :too_large
+ _("it is too large")
+ end
+ end
end
end
diff --git a/app/models/diff_viewer/image.rb b/app/models/diff_viewer/image.rb
index c356c2ca50e..350bef1d42a 100644
--- a/app/models/diff_viewer/image.rb
+++ b/app/models/diff_viewer/image.rb
@@ -9,6 +9,6 @@ module DiffViewer
self.extensions = UploaderHelper::IMAGE_EXT
self.binary = true
self.switcher_icon = 'picture-o'
- self.switcher_title = 'image diff'
+ self.switcher_title = _('image diff')
end
end
diff --git a/app/models/diff_viewer/rich.rb b/app/models/diff_viewer/rich.rb
index 2faa1be6567..5caefa2031c 100644
--- a/app/models/diff_viewer/rich.rb
+++ b/app/models/diff_viewer/rich.rb
@@ -7,7 +7,7 @@ module DiffViewer
included do
self.type = :rich
self.switcher_icon = 'file-text-o'
- self.switcher_title = 'rendered diff'
+ self.switcher_title = _('rendered diff')
end
end
end
diff --git a/app/models/diff_viewer/server_side.rb b/app/models/diff_viewer/server_side.rb
index 977204e6c97..0877c9dddec 100644
--- a/app/models/diff_viewer/server_side.rb
+++ b/app/models/diff_viewer/server_side.rb
@@ -24,5 +24,17 @@ module DiffViewer
super
end
+
+ private
+
+ def render_error_reason
+ return super unless render_error == :server_side_but_stored_externally
+
+ if diff_file.external_storage == :lfs
+ _('it is stored in LFS')
+ else
+ _('it is stored externally')
+ end
+ end
end
end
diff --git a/app/models/diff_viewer/simple.rb b/app/models/diff_viewer/simple.rb
index 8d28ca5239a..929d8ad5a7e 100644
--- a/app/models/diff_viewer/simple.rb
+++ b/app/models/diff_viewer/simple.rb
@@ -7,7 +7,7 @@ module DiffViewer
included do
self.type = :simple
self.switcher_icon = 'code'
- self.switcher_title = 'source diff'
+ self.switcher_title = _('source diff')
end
end
end
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 944b9f72396..b937bef100b 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -408,6 +408,28 @@ class MergeRequest < ActiveRecord::Base
merge_request_diffs.where.not(id: merge_request_diff.id)
end
+ def preloads_discussion_diff_highlighting?
+ true
+ end
+
+ def preload_discussions_diff_highlight
+ preloadable_files = note_diff_files.for_commit_or_unresolved
+
+ discussions_diffs.load_highlight(preloadable_files.pluck(:id))
+ end
+
+ def discussions_diffs
+ strong_memoize(:discussions_diffs) do
+ Gitlab::DiscussionsDiff::FileCollection.new(note_diff_files.to_a)
+ end
+ end
+
+ def note_diff_files
+ NoteDiffFile
+ .where(diff_note: discussion_notes)
+ .includes(diff_note: :project)
+ end
+
def diff_size
# Calling `merge_request_diff.diffs.real_size` will also perform
# highlighting, which we don't need here.
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index 3cc8e2c44bb..6dc0fca68e6 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -212,7 +212,7 @@ class Milestone < ActiveRecord::Base
end
def reference_link_text(from = nil)
- self.title
+ self.class.reference_prefix + self.title
end
def milestoneish_ids
diff --git a/app/models/note_diff_file.rb b/app/models/note_diff_file.rb
index 27aef7adc48..e369122003e 100644
--- a/app/models/note_diff_file.rb
+++ b/app/models/note_diff_file.rb
@@ -3,7 +3,22 @@
class NoteDiffFile < ActiveRecord::Base
include DiffFile
+ scope :for_commit_or_unresolved, -> do
+ joins(:diff_note).where("resolved_at IS NULL OR noteable_type = 'Commit'")
+ end
+
+ delegate :original_position, :project, to: :diff_note
+
belongs_to :diff_note, inverse_of: :note_diff_file
validates :diff_note, presence: true
+
+ def raw_diff_file
+ raw_diff = Gitlab::Git::Diff.new(to_hash)
+
+ Gitlab::Diff::File.new(raw_diff,
+ repository: project.repository,
+ diff_refs: original_position.diff_refs,
+ unique_identifier: id)
+ end
end
diff --git a/app/models/project.rb b/app/models/project.rb
index 09e2a6114fe..cd558752080 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -324,10 +324,9 @@ class Project < ActiveRecord::Base
validates :namespace, presence: true
validates :name, uniqueness: { scope: :namespace_id }
- validates :import_url, url: { protocols: ->(project) { project.persisted? ? VALID_MIRROR_PROTOCOLS : VALID_IMPORT_PROTOCOLS },
- ports: ->(project) { project.persisted? ? VALID_MIRROR_PORTS : VALID_IMPORT_PORTS },
- allow_localhost: false,
- enforce_user: true }, if: [:external_import?, :import_url_changed?]
+ validates :import_url, public_url: { protocols: ->(project) { project.persisted? ? VALID_MIRROR_PROTOCOLS : VALID_IMPORT_PROTOCOLS },
+ ports: ->(project) { project.persisted? ? VALID_MIRROR_PORTS : VALID_IMPORT_PORTS },
+ enforce_user: true }, if: [:external_import?, :import_url_changed?]
validates :star_count, numericality: { greater_than_or_equal_to: 0 }
validate :check_limit, on: :create
validate :check_repository_path_availability, on: :update, if: ->(project) { project.renamed? }
@@ -1734,10 +1733,21 @@ class Project < ActiveRecord::Base
end
def protected_for?(ref)
- if repository.branch_exists?(ref)
- ProtectedBranch.protected?(self, ref)
- elsif repository.tag_exists?(ref)
- ProtectedTag.protected?(self, ref)
+ raise Repository::AmbiguousRefError if repository.ambiguous_ref?(ref)
+
+ resolved_ref = repository.expand_ref(ref) || ref
+ return false unless Gitlab::Git.tag_ref?(resolved_ref) || Gitlab::Git.branch_ref?(resolved_ref)
+
+ ref_name = if resolved_ref == ref
+ Gitlab::Git.ref_name(resolved_ref)
+ else
+ ref
+ end
+
+ if Gitlab::Git.branch_ref?(resolved_ref)
+ ProtectedBranch.protected?(self, ref_name)
+ elsif Gitlab::Git.tag_ref?(resolved_ref)
+ ProtectedTag.protected?(self, ref_name)
end
end
diff --git a/app/models/prometheus_metric.rb b/app/models/prometheus_metric.rb
index defbade1ed6..5594594a48d 100644
--- a/app/models/prometheus_metric.rb
+++ b/app/models/prometheus_metric.rb
@@ -18,6 +18,54 @@ class PrometheusMetric < ActiveRecord::Base
system: 2
}
+ GROUP_DETAILS = {
+ # built-in groups
+ nginx_ingress_vts: {
+ group_title: _('Response metrics (NGINX Ingress VTS)'),
+ required_metrics: %w(nginx_upstream_responses_total nginx_upstream_response_msecs_avg),
+ priority: 10
+ }.freeze,
+ nginx_ingress: {
+ group_title: _('Response metrics (NGINX Ingress)'),
+ required_metrics: %w(nginx_ingress_controller_requests nginx_ingress_controller_ingress_upstream_latency_seconds_sum),
+ priority: 10
+ }.freeze,
+ ha_proxy: {
+ group_title: _('Response metrics (HA Proxy)'),
+ required_metrics: %w(haproxy_frontend_http_requests_total haproxy_frontend_http_responses_total),
+ priority: 10
+ }.freeze,
+ aws_elb: {
+ group_title: _('Response metrics (AWS ELB)'),
+ required_metrics: %w(aws_elb_request_count_sum aws_elb_latency_average aws_elb_httpcode_backend_5_xx_sum),
+ priority: 10
+ }.freeze,
+ nginx: {
+ group_title: _('Response metrics (NGINX)'),
+ required_metrics: %w(nginx_server_requests nginx_server_requestMsec),
+ priority: 10
+ }.freeze,
+ kubernetes: {
+ group_title: _('System metrics (Kubernetes)'),
+ required_metrics: %w(container_memory_usage_bytes container_cpu_usage_seconds_total),
+ priority: 5
+ }.freeze,
+
+ # custom/user groups
+ business: {
+ group_title: _('Business metrics (Custom)'),
+ priority: 0
+ }.freeze,
+ response: {
+ group_title: _('Response metrics (Custom)'),
+ priority: -5
+ }.freeze,
+ system: {
+ group_title: _('System metrics (Custom)'),
+ priority: -10
+ }.freeze
+ }.freeze
+
validates :title, presence: true
validates :query, presence: true
validates :group, presence: true
@@ -29,36 +77,16 @@ class PrometheusMetric < ActiveRecord::Base
scope :common, -> { where(common: true) }
- GROUP_TITLES = {
- # built-in groups
- nginx_ingress_vts: _('Response metrics (NGINX Ingress VTS)'),
- nginx_ingress: _('Response metrics (NGINX Ingress)'),
- ha_proxy: _('Response metrics (HA Proxy)'),
- aws_elb: _('Response metrics (AWS ELB)'),
- nginx: _('Response metrics (NGINX)'),
- kubernetes: _('System metrics (Kubernetes)'),
-
- # custom/user groups
- business: _('Business metrics (Custom)'),
- response: _('Response metrics (Custom)'),
- system: _('System metrics (Custom)')
- }.freeze
-
- REQUIRED_METRICS = {
- nginx_ingress_vts: %w(nginx_upstream_responses_total nginx_upstream_response_msecs_avg),
- nginx_ingress: %w(nginx_ingress_controller_requests nginx_ingress_controller_ingress_upstream_latency_seconds_sum),
- ha_proxy: %w(haproxy_frontend_http_requests_total haproxy_frontend_http_responses_total),
- aws_elb: %w(aws_elb_request_count_sum aws_elb_latency_average aws_elb_httpcode_backend_5_xx_sum),
- nginx: %w(nginx_server_requests nginx_server_requestMsec),
- kubernetes: %w(container_memory_usage_bytes container_cpu_usage_seconds_total)
- }.freeze
+ def priority
+ group_details(group).fetch(:priority)
+ end
def group_title
- GROUP_TITLES[group.to_sym]
+ group_details(group).fetch(:group_title)
end
def required_metrics
- REQUIRED_METRICS[group.to_sym].to_a.map(&:to_s)
+ group_details(group).fetch(:required_metrics, []).map(&:to_s)
end
def to_query_metric
@@ -89,4 +117,10 @@ class PrometheusMetric < ActiveRecord::Base
}]
end
end
+
+ private
+
+ def group_details(group)
+ GROUP_DETAILS.fetch(group.to_sym)
+ end
end
diff --git a/app/models/release.rb b/app/models/release.rb
index 7a09ee459a6..df3dfe1cf2f 100644
--- a/app/models/release.rb
+++ b/app/models/release.rb
@@ -2,11 +2,39 @@
class Release < ActiveRecord::Base
include CacheMarkdownField
+ include Gitlab::Utils::StrongMemoize
cache_markdown_field :description
belongs_to :project
+ # releases prior to 11.7 have no author
belongs_to :author, class_name: 'User'
validates :description, :project, :tag, presence: true
+
+ scope :sorted, -> { order(created_at: :desc) }
+
+ delegate :repository, to: :project
+
+ def commit
+ strong_memoize(:commit) do
+ repository.commit(actual_sha)
+ end
+ end
+
+ def tag_missing?
+ actual_tag.nil?
+ end
+
+ private
+
+ def actual_sha
+ sha || actual_tag&.dereferenced_target
+ end
+
+ def actual_tag
+ strong_memoize(:actual_tag) do
+ repository.find_tag(tag)
+ end
+ end
end
diff --git a/app/models/remote_mirror.rb b/app/models/remote_mirror.rb
index 5a6895aefab..a3fa67c72bf 100644
--- a/app/models/remote_mirror.rb
+++ b/app/models/remote_mirror.rb
@@ -17,7 +17,7 @@ class RemoteMirror < ActiveRecord::Base
belongs_to :project, inverse_of: :remote_mirrors
- validates :url, presence: true, url: { protocols: %w(ssh git http https), allow_blank: true, enforce_user: true }
+ validates :url, presence: true, public_url: { protocols: %w(ssh git http https), allow_blank: true, enforce_user: true }
before_save :set_new_remote_name, if: :mirror_url_changed?
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 015a179f374..b19ae2e0e6a 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -25,6 +25,7 @@ class Repository
delegate :bundle_to_disk, to: :raw_repository
CreateTreeError = Class.new(StandardError)
+ AmbiguousRefError = Class.new(StandardError)
# Methods that cache data from the Git repository.
#
@@ -181,6 +182,18 @@ class Repository
tags.find { |tag| tag.name == name }
end
+ def ambiguous_ref?(ref)
+ tag_exists?(ref) && branch_exists?(ref)
+ end
+
+ def expand_ref(ref)
+ if tag_exists?(ref)
+ Gitlab::Git::TAG_REF_PREFIX + ref
+ elsif branch_exists?(ref)
+ Gitlab::Git::BRANCH_REF_PREFIX + ref
+ end
+ end
+
def add_branch(user, branch_name, ref)
branch = raw_repository.add_branch(branch_name, user: user, target: ref)
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index 11856b55902..f9b23bbbf6c 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -175,6 +175,12 @@ class Snippet < ActiveRecord::Base
:visibility_level
end
+ def embeddable?
+ ability = project_id? ? :read_project_snippet : :read_personal_snippet
+
+ Ability.allowed?(nil, ability, self)
+ end
+
def notes_with_associations
notes.includes(:author)
end
diff --git a/app/models/todo.rb b/app/models/todo.rb
index 7b64615f699..d9b86d941b6 100644
--- a/app/models/todo.rb
+++ b/app/models/todo.rb
@@ -4,6 +4,11 @@ class Todo < ActiveRecord::Base
include Sortable
include FromUnion
+ # Time to wait for todos being removed when not visible for user anymore.
+ # Prevents TODOs being removed by mistake, for example, removing access from a user
+ # and giving it back again.
+ WAIT_FOR_DELETE = 1.hour
+
ASSIGNED = 1
MENTIONED = 2
BUILD_FAILED = 3
diff --git a/app/policies/concerns/clusterable_actions.rb b/app/policies/concerns/clusterable_actions.rb
new file mode 100644
index 00000000000..08ddd742ea9
--- /dev/null
+++ b/app/policies/concerns/clusterable_actions.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module ClusterableActions
+ private
+
+ # Overridden on EE module
+ def multiple_clusters_available?
+ false
+ end
+
+ def clusterable_has_clusters?
+ !subject.clusters.empty?
+ end
+end
diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb
index f07bb188265..c25766a5af8 100644
--- a/app/policies/group_policy.rb
+++ b/app/policies/group_policy.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
class GroupPolicy < BasePolicy
+ include ClusterableActions
+
desc "Group is public"
with_options scope: :subject, score: 0
condition(:public_group) { @subject.public? }
@@ -27,6 +29,9 @@ class GroupPolicy < BasePolicy
GroupProjectsFinder.new(group: @subject, current_user: @user, options: { include_subgroups: true }).execute.any?
end
+ condition(:has_clusters, scope: :subject) { clusterable_has_clusters? }
+ condition(:can_have_multiple_clusters) { multiple_clusters_available? }
+
with_options scope: :subject, score: 0
condition(:request_access_enabled) { @subject.request_access_enabled }
@@ -45,7 +50,7 @@ class GroupPolicy < BasePolicy
enable :read_label
end
- rule { admin } .enable :read_group
+ rule { admin }.enable :read_group
rule { has_projects }.policy do
enable :read_group
@@ -67,6 +72,7 @@ class GroupPolicy < BasePolicy
enable :admin_pipeline
enable :admin_build
enable :read_cluster
+ enable :add_cluster
enable :create_cluster
enable :update_cluster
enable :admin_cluster
@@ -106,6 +112,8 @@ class GroupPolicy < BasePolicy
rule { owner & (~share_with_group_locked | ~has_parent | ~parent_share_with_group_locked | can_change_parent_share_with_group_lock) }.enable :change_share_with_group_lock
+ rule { ~can_have_multiple_clusters & has_clusters }.prevent :add_cluster
+
def access_level
return GroupMember::NO_ACCESS if @user.nil?
diff --git a/app/policies/issuable_policy.rb b/app/policies/issuable_policy.rb
index 6d8b575102e..ecb2797d1d9 100644
--- a/app/policies/issuable_policy.rb
+++ b/app/policies/issuable_policy.rb
@@ -11,7 +11,7 @@ class IssuablePolicy < BasePolicy
@user && @subject.assignee_or_author?(@user)
end
- rule { assignee_or_author }.policy do
+ rule { can?(:guest_access) & assignee_or_author }.policy do
enable :read_issue
enable :update_issue
enable :reopen_issue
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index 1c082945299..3146f26bed5 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -2,6 +2,7 @@
class ProjectPolicy < BasePolicy
extend ClassMethods
+ include ClusterableActions
READONLY_FEATURES_WHEN_ARCHIVED = %i[
issue
@@ -22,6 +23,7 @@ class ProjectPolicy < BasePolicy
container_image
pages
cluster
+ release
].freeze
desc "User is a project owner"
@@ -103,6 +105,9 @@ class ProjectPolicy < BasePolicy
@subject.feature_available?(:merge_requests, @user)
end
+ condition(:has_clusters, scope: :subject) { clusterable_has_clusters? }
+ condition(:can_have_multiple_clusters) { multiple_clusters_available? }
+
features = %w[
merge_requests
issues
@@ -169,6 +174,7 @@ class ProjectPolicy < BasePolicy
enable :read_cycle_analytics
enable :award_emoji
enable :read_pages_content
+ enable :read_release
end
# These abilities are not allowed to admins that are not members of the project,
@@ -235,6 +241,8 @@ class ProjectPolicy < BasePolicy
enable :update_container_image
enable :create_environment
enable :create_deployment
+ enable :create_release
+ enable :update_release
end
rule { can?(:maintainer_access) }.policy do
@@ -257,10 +265,12 @@ class ProjectPolicy < BasePolicy
enable :read_pages
enable :update_pages
enable :read_cluster
+ enable :add_cluster
enable :create_cluster
enable :update_cluster
enable :admin_cluster
enable :create_environment_terminal
+ enable :destroy_release
end
rule { (mirror_available & can?(:admin_project)) | admin }.enable :admin_remote_mirror
@@ -320,6 +330,7 @@ class ProjectPolicy < BasePolicy
prevent :download_code
prevent :fork_project
prevent :read_commit_status
+ prevent(*create_read_update_admin_destroy(:release))
end
rule { container_registry_disabled }.policy do
@@ -349,6 +360,7 @@ class ProjectPolicy < BasePolicy
enable :read_commit_status
enable :read_container_image
enable :download_code
+ enable :read_release
enable :download_wiki_code
enable :read_cycle_analytics
enable :read_pages_content
@@ -381,6 +393,8 @@ class ProjectPolicy < BasePolicy
(can?(:read_project_for_iids) & merge_requests_visible_to_user) | can?(:read_merge_request)
end.enable :read_merge_request_iid
+ rule { ~can_have_multiple_clusters & has_clusters }.prevent :add_cluster
+
private
def team_member?
diff --git a/app/policies/release_policy.rb b/app/policies/release_policy.rb
new file mode 100644
index 00000000000..d7f9e5d7445
--- /dev/null
+++ b/app/policies/release_policy.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+class ReleasePolicy < BasePolicy
+ delegate { @subject.project }
+end
diff --git a/app/presenters/clusterable_presenter.rb b/app/presenters/clusterable_presenter.rb
index 9cc137aa3bd..d94d9118eee 100644
--- a/app/presenters/clusterable_presenter.rb
+++ b/app/presenters/clusterable_presenter.rb
@@ -12,6 +12,10 @@ class ClusterablePresenter < Gitlab::View::Presenter::Delegated
.fabricate!
end
+ def can_add_cluster?
+ can?(current_user, :add_cluster, clusterable)
+ end
+
def can_create_cluster?
can?(current_user, :create_cluster, clusterable)
end
diff --git a/app/serializers/diff_viewer_entity.rb b/app/serializers/diff_viewer_entity.rb
index 27fba03cb3f..587fa2347fd 100644
--- a/app/serializers/diff_viewer_entity.rb
+++ b/app/serializers/diff_viewer_entity.rb
@@ -4,4 +4,7 @@ class DiffViewerEntity < Grape::Entity
# Partial name refers directly to a Rails feature, let's avoid
# using this on the frontend.
expose :partial_name, as: :name
+ expose :error do |diff_viewer|
+ diff_viewer.render_error_message
+ end
end
diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb
index 19b5552887f..f8d8ef04001 100644
--- a/app/services/ci/create_pipeline_service.rb
+++ b/app/services/ci/create_pipeline_service.rb
@@ -31,7 +31,8 @@ module Ci
seeds_block: block,
variables_attributes: params[:variables_attributes],
project: project,
- current_user: current_user)
+ current_user: current_user,
+ push_options: params[:push_options])
sequence = Gitlab::Ci::Pipeline::Chain::Sequence
.new(pipeline, command, SEQUENCE)
diff --git a/app/services/commits/tag_service.rb b/app/services/commits/tag_service.rb
index 7961ba4d3c4..bb8cfb63f98 100644
--- a/app/services/commits/tag_service.rb
+++ b/app/services/commits/tag_service.rb
@@ -9,11 +9,10 @@ module Commits
tag_name = params[:tag_name]
message = params[:tag_message]
- release_description = nil
result = Tags::CreateService
.new(commit.project, current_user)
- .execute(tag_name, commit.sha, message, release_description)
+ .execute(tag_name, commit.sha, message)
if result[:status] == :success
tag = result[:tag]
diff --git a/app/services/create_release_service.rb b/app/services/create_release_service.rb
deleted file mode 100644
index ab2dc5337aa..00000000000
--- a/app/services/create_release_service.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-# frozen_string_literal: true
-
-class CreateReleaseService < BaseService
- # rubocop: disable CodeReuse/ActiveRecord
- def execute(tag_name, release_description)
- repository = project.repository
- existing_tag = repository.find_tag(tag_name)
-
- # Only create a release if the tag exists
- if existing_tag
- release = project.releases.find_by(tag: tag_name)
-
- if release
- error('Release already exists', 409)
- else
- release = project.releases.create!(
- tag: tag_name,
- name: tag_name,
- sha: existing_tag.dereferenced_target.sha,
- author: current_user,
- description: release_description
- )
-
- success(release)
- end
- else
- error('Tag does not exist', 404)
- end
- end
- # rubocop: enable CodeReuse/ActiveRecord
-
- def success(release)
- super().merge(release: release)
- end
-end
diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb
index f1883877d56..9ecee7c6156 100644
--- a/app/services/git_push_service.rb
+++ b/app/services/git_push_service.rb
@@ -174,7 +174,8 @@ class GitPushService < BaseService
params[:newrev],
params[:ref],
@push_commits,
- commits_count: commits_count)
+ commits_count: commits_count,
+ push_options: params[:push_options] || [])
end
def push_to_existing_branch?
diff --git a/app/services/git_tag_push_service.rb b/app/services/git_tag_push_service.rb
index dbadafc0f52..03fcf614c64 100644
--- a/app/services/git_tag_push_service.rb
+++ b/app/services/git_tag_push_service.rb
@@ -45,7 +45,8 @@ class GitTagPushService < BaseService
params[:newrev],
params[:ref],
commits,
- message)
+ message,
+ push_options: params[:push_options] || [])
end
def build_system_push_data
diff --git a/app/services/groups/update_service.rb b/app/services/groups/update_service.rb
index 31d3c844ad5..de78a3f7b27 100644
--- a/app/services/groups/update_service.rb
+++ b/app/services/groups/update_service.rb
@@ -31,7 +31,7 @@ module Groups
def after_update
if group.previous_changes.include?(:visibility_level) && group.private?
# don't enqueue immediately to prevent todos removal in case of a mistake
- TodosDestroyer::GroupPrivateWorker.perform_in(1.hour, group.id)
+ TodosDestroyer::GroupPrivateWorker.perform_in(Todo::WAIT_FOR_DELETE, group.id)
end
end
diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb
index a1d0cc0e568..e992d682c79 100644
--- a/app/services/issues/update_service.rb
+++ b/app/services/issues/update_service.rb
@@ -44,7 +44,7 @@ module Issues
if issue.previous_changes.include?('confidential')
# don't enqueue immediately to prevent todos removal in case of a mistake
- TodosDestroyer::ConfidentialIssueWorker.perform_in(1.hour, issue.id) if issue.confidential?
+ TodosDestroyer::ConfidentialIssueWorker.perform_in(Todo::WAIT_FOR_DELETE, issue.id) if issue.confidential?
create_confidentiality_note(issue)
end
diff --git a/app/services/members/base_service.rb b/app/services/members/base_service.rb
index d734571f835..e78affff797 100644
--- a/app/services/members/base_service.rb
+++ b/app/services/members/base_service.rb
@@ -47,5 +47,11 @@ module Members
raise "Unknown action '#{action}' on #{member}!"
end
end
+
+ def enqueue_delete_todos(member)
+ type = member.is_a?(GroupMember) ? 'Group' : 'Project'
+ # don't enqueue immediately to prevent todos removal in case of a mistake
+ TodosDestroyer::EntityLeaveWorker.perform_in(Todo::WAIT_FOR_DELETE, member.user_id, member.source_id, type)
+ end
end
end
diff --git a/app/services/members/destroy_service.rb b/app/services/members/destroy_service.rb
index c186a5971dc..ae0c644e6c0 100644
--- a/app/services/members/destroy_service.rb
+++ b/app/services/members/destroy_service.rb
@@ -15,7 +15,7 @@ module Members
notification_service.decline_access_request(member)
end
- enqeue_delete_todos(member)
+ enqueue_delete_todos(member)
after_execute(member: member)
@@ -24,12 +24,6 @@ module Members
private
- def enqeue_delete_todos(member)
- type = member.is_a?(GroupMember) ? 'Group' : 'Project'
- # don't enqueue immediately to prevent todos removal in case of a mistake
- TodosDestroyer::EntityLeaveWorker.perform_in(1.hour, member.user_id, member.source_id, type)
- end
-
def can_destroy_member?(member)
can?(current_user, destroy_member_permission(member), member)
end
diff --git a/app/services/members/update_service.rb b/app/services/members/update_service.rb
index 1f5618dae53..ff8d5c1d8c9 100644
--- a/app/services/members/update_service.rb
+++ b/app/services/members/update_service.rb
@@ -10,9 +10,18 @@ module Members
if member.update(params)
after_execute(action: permission, old_access_level: old_access_level, member: member)
+
+ # Deletes only confidential issues todos for guests
+ enqueue_delete_todos(member) if downgrading_to_guest?
end
member
end
+
+ private
+
+ def downgrading_to_guest?
+ params[:access_level] == Gitlab::Access::GUEST
+ end
end
end
diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb
index 36767621d74..48419da98ad 100644
--- a/app/services/merge_requests/build_service.rb
+++ b/app/services/merge_requests/build_service.rb
@@ -18,7 +18,7 @@ module MergeRequests
merge_request.source_project = find_source_project
merge_request.target_project = find_target_project
merge_request.target_branch = find_target_branch
- merge_request.can_be_created = branches_valid?
+ merge_request.can_be_created = projects_and_branches_valid?
# compare branches only if branches are valid, otherwise
# compare_branches may raise an error
@@ -49,15 +49,19 @@ module MergeRequests
to: :merge_request
def find_source_project
- return source_project if source_project.present? && can?(current_user, :read_project, source_project)
+ return source_project if source_project.present? && can?(current_user, :create_merge_request_from, source_project)
project
end
def find_target_project
- return target_project if target_project.present? && can?(current_user, :read_project, target_project)
+ return target_project if target_project.present? && can?(current_user, :create_merge_request_in, target_project)
- project.default_merge_request_target
+ target_project = project.default_merge_request_target
+
+ return target_project if target_project.present? && can?(current_user, :create_merge_request_in, target_project)
+
+ project
end
def find_target_branch
@@ -72,10 +76,11 @@ module MergeRequests
params[:target_branch].present?
end
- def branches_valid?
+ def projects_and_branches_valid?
+ return false if source_project.nil? || target_project.nil?
return false unless source_branch_specified? || target_branch_specified?
- validate_branches
+ validate_projects_and_branches
errors.blank?
end
@@ -94,7 +99,12 @@ module MergeRequests
end
end
- def validate_branches
+ def validate_projects_and_branches
+ merge_request.validate_target_project
+ merge_request.validate_fork
+
+ return if errors.any?
+
add_error('You must select source and target branch') unless branches_present?
add_error('You must select different branches') if same_source_and_target?
add_error("Source branch \"#{source_branch}\" does not exist") unless source_branch_exists?
diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb
index 33d8299c8b6..86a04587f79 100644
--- a/app/services/merge_requests/update_service.rb
+++ b/app/services/merge_requests/update_service.rb
@@ -46,11 +46,13 @@ module MergeRequests
end
if merge_request.previous_changes.include?('assignee_id')
+ reassigned_merge_request_args = [merge_request, current_user]
+
old_assignee_id = merge_request.previous_changes['assignee_id'].first
- old_assignee = User.find(old_assignee_id) if old_assignee_id
+ reassigned_merge_request_args << User.find(old_assignee_id) if old_assignee_id
create_assignee_note(merge_request)
- notification_service.async.reassigned_merge_request(merge_request, current_user, old_assignee)
+ notification_service.async.reassigned_merge_request(*reassigned_merge_request_args)
todo_service.reassigned_merge_request(merge_request, current_user)
end
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index ff035fea216..e1cf327209b 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -188,7 +188,7 @@ class NotificationService
# * merge_request assignee if their notification level is not Disabled
# * users with custom level checked with "reassign merge request"
#
- def reassigned_merge_request(merge_request, current_user, previous_assignee)
+ def reassigned_merge_request(merge_request, current_user, previous_assignee = nil)
recipients = NotificationRecipientService.build_recipients(
merge_request,
current_user,
diff --git a/app/services/projects/lfs_pointers/lfs_download_service.rb b/app/services/projects/lfs_pointers/lfs_download_service.rb
index f9b9781ad5f..b5128443435 100644
--- a/app/services/projects/lfs_pointers/lfs_download_service.rb
+++ b/app/services/projects/lfs_pointers/lfs_download_service.rb
@@ -12,28 +12,43 @@ module Projects
return if LfsObject.exists?(oid: oid)
- sanitized_uri = Gitlab::UrlSanitizer.new(url)
- Gitlab::UrlBlocker.validate!(sanitized_uri.sanitized_url, protocols: VALID_PROTOCOLS)
+ sanitized_uri = sanitize_url!(url)
with_tmp_file(oid) do |file|
- size = download_and_save_file(file, sanitized_uri)
- lfs_object = LfsObject.new(oid: oid, size: size, file: file)
+ download_and_save_file(file, sanitized_uri)
+ lfs_object = LfsObject.new(oid: oid, size: file.size, file: file)
project.all_lfs_objects << lfs_object
end
+ rescue Gitlab::UrlBlocker::BlockedUrlError => e
+ Rails.logger.error("LFS file with oid #{oid} couldn't be downloaded: #{e.message}")
rescue StandardError => e
- Rails.logger.error("LFS file with oid #{oid} could't be downloaded from #{sanitized_uri.sanitized_url}: #{e.message}")
+ Rails.logger.error("LFS file with oid #{oid} couldn't be downloaded from #{sanitized_uri.sanitized_url}: #{e.message}")
end
# rubocop: enable CodeReuse/ActiveRecord
private
+ def sanitize_url!(url)
+ Gitlab::UrlSanitizer.new(url).tap do |sanitized_uri|
+ # Just validate that HTTP/HTTPS protocols are used. The
+ # subsequent Gitlab::HTTP.get call will do network checks
+ # based on the settings.
+ Gitlab::UrlBlocker.validate!(sanitized_uri.sanitized_url,
+ protocols: VALID_PROTOCOLS)
+ end
+ end
+
def download_and_save_file(file, sanitized_uri)
- IO.copy_stream(open(sanitized_uri.sanitized_url, headers(sanitized_uri)), file) # rubocop:disable Security/Open
+ response = Gitlab::HTTP.get(sanitized_uri.sanitized_url, headers(sanitized_uri)) do |fragment|
+ file.write(fragment)
+ end
+
+ raise StandardError, "Received error code #{response.code}" unless response.success?
end
def headers(sanitized_uri)
- {}.tap do |headers|
+ query_options.tap do |headers|
credentials = sanitized_uri.credentials
if credentials[:user].present? || credentials[:password].present?
@@ -43,10 +58,14 @@ module Projects
end
end
+ def query_options
+ { stream_body: true }
+ end
+
def with_tmp_file(oid)
create_tmp_storage_dir
- File.open(File.join(tmp_storage_dir, oid), 'w') { |file| yield file }
+ File.open(File.join(tmp_storage_dir, oid), 'wb') { |file| yield file }
end
def create_tmp_storage_dir
diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb
index 93e48fc0199..dd1b9680ece 100644
--- a/app/services/projects/update_service.rb
+++ b/app/services/projects/update_service.rb
@@ -61,9 +61,9 @@ module Projects
if project.previous_changes.include?(:visibility_level) && project.private?
# don't enqueue immediately to prevent todos removal in case of a mistake
- TodosDestroyer::ProjectPrivateWorker.perform_in(1.hour, project.id)
+ TodosDestroyer::ProjectPrivateWorker.perform_in(Todo::WAIT_FOR_DELETE, project.id)
elsif (project_changed_feature_keys & todos_features_changes).present?
- TodosDestroyer::PrivateFeaturesWorker.perform_in(1.hour, project.id)
+ TodosDestroyer::PrivateFeaturesWorker.perform_in(Todo::WAIT_FOR_DELETE, project.id)
end
if project.previous_changes.include?('path')
diff --git a/app/services/releases/concerns.rb b/app/services/releases/concerns.rb
new file mode 100644
index 00000000000..a04bb8f9e14
--- /dev/null
+++ b/app/services/releases/concerns.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+module Releases
+ module Concerns
+ extend ActiveSupport::Concern
+ include Gitlab::Utils::StrongMemoize
+
+ included do
+ def tag_name
+ params[:tag]
+ end
+
+ def ref
+ params[:ref]
+ end
+
+ def name
+ params[:name]
+ end
+
+ def description
+ params[:description]
+ end
+
+ def release
+ strong_memoize(:release) do
+ project.releases.find_by_tag(tag_name)
+ end
+ end
+
+ def existing_tag
+ strong_memoize(:existing_tag) do
+ repository.find_tag(tag_name)
+ end
+ end
+
+ def tag_exist?
+ existing_tag.present?
+ end
+
+ def repository
+ strong_memoize(:repository) do
+ project.repository
+ end
+ end
+ end
+ end
+end
diff --git a/app/services/releases/create_service.rb b/app/services/releases/create_service.rb
new file mode 100644
index 00000000000..73fcebf79af
--- /dev/null
+++ b/app/services/releases/create_service.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+module Releases
+ class CreateService < BaseService
+ include Releases::Concerns
+
+ def execute
+ return error('Access Denied', 403) unless allowed?
+ return error('Release already exists', 409) if release
+
+ tag = ensure_tag
+
+ return tag unless tag.is_a?(Gitlab::Git::Tag)
+
+ create_release(tag)
+ end
+
+ private
+
+ def ensure_tag
+ existing_tag || create_tag
+ end
+
+ def create_tag
+ return error('Ref is not specified', 422) unless ref
+
+ result = Tags::CreateService
+ .new(project, current_user)
+ .execute(tag_name, ref, nil)
+
+ return result unless result[:status] == :success
+
+ result[:tag]
+ end
+
+ def allowed?
+ Ability.allowed?(current_user, :create_release, project)
+ end
+
+ def create_release(tag)
+ release = project.releases.create!(
+ name: name,
+ description: description,
+ author: current_user,
+ tag: tag.name,
+ sha: tag.dereferenced_target.sha
+ )
+
+ success(tag: tag, release: release)
+ rescue ActiveRecord::RecordInvalid => e
+ error(e.message, 400)
+ end
+ end
+end
diff --git a/app/services/releases/destroy_service.rb b/app/services/releases/destroy_service.rb
new file mode 100644
index 00000000000..8c2bc3b4e6e
--- /dev/null
+++ b/app/services/releases/destroy_service.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Releases
+ class DestroyService < BaseService
+ include Releases::Concerns
+
+ def execute
+ return error('Tag does not exist', 404) unless existing_tag
+ return error('Release does not exist', 404) unless release
+ return error('Access Denied', 403) unless allowed?
+
+ if release.destroy
+ success(tag: existing_tag, release: release)
+ else
+ error(release.errors.messages || '400 Bad request', 400)
+ end
+ end
+
+ private
+
+ def allowed?
+ Ability.allowed?(current_user, :destroy_release, release)
+ end
+ end
+end
diff --git a/app/services/releases/update_service.rb b/app/services/releases/update_service.rb
new file mode 100644
index 00000000000..fabfa398c59
--- /dev/null
+++ b/app/services/releases/update_service.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+module Releases
+ class UpdateService < BaseService
+ include Releases::Concerns
+
+ def execute
+ return error('Tag does not exist', 404) unless existing_tag
+ return error('Release does not exist', 404) unless release
+ return error('Access Denied', 403) unless allowed?
+ return error('params is empty', 400) if empty_params?
+
+ if release.update(params)
+ success(tag: existing_tag, release: release)
+ else
+ error(release.errors.messages || '400 Bad request', 400)
+ end
+ end
+
+ private
+
+ def allowed?
+ Ability.allowed?(current_user, :update_release, release)
+ end
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def empty_params?
+ params.except(:tag).empty?
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+ end
+end
diff --git a/app/services/tags/create_service.rb b/app/services/tags/create_service.rb
index 6bb9bb3988e..4de6b2d2774 100644
--- a/app/services/tags/create_service.rb
+++ b/app/services/tags/create_service.rb
@@ -2,7 +2,7 @@
module Tags
class CreateService < BaseService
- def execute(tag_name, target, message, release_description = nil)
+ def execute(tag_name, target, message)
valid_tag = Gitlab::GitRefValidator.validate(tag_name)
return error('Tag name invalid') unless valid_tag
@@ -20,10 +20,7 @@ module Tags
end
if new_tag
- if release_description.present?
- CreateReleaseService.new(@project, @current_user)
- .execute(tag_name, release_description)
- end
+ repository.expire_tags_cache
success.merge(tag: new_tag)
else
diff --git a/app/services/tags/destroy_service.rb b/app/services/tags/destroy_service.rb
index 6bfef09ac54..cab507946b4 100644
--- a/app/services/tags/destroy_service.rb
+++ b/app/services/tags/destroy_service.rb
@@ -2,7 +2,6 @@
module Tags
class DestroyService < BaseService
- # rubocop: disable CodeReuse/ActiveRecord
def execute(tag_name)
repository = project.repository
tag = repository.find_tag(tag_name)
@@ -12,8 +11,12 @@ module Tags
end
if repository.rm_tag(current_user, tag_name)
- release = project.releases.find_by(tag: tag_name)
- release&.destroy
+ ##
+ # When a tag in a repository is destroyed,
+ # release assets will be destroyed too.
+ Releases::DestroyService
+ .new(project, current_user, tag: tag_name)
+ .execute
push_data = build_push_data(tag)
EventCreateService.new.push(project, current_user, push_data)
@@ -27,7 +30,6 @@ module Tags
rescue Gitlab::Git::PreReceiveError => ex
error(ex.message)
end
- # rubocop: enable CodeReuse/ActiveRecord
def error(message, return_code = 400)
super(message).merge(return_code: return_code)
diff --git a/app/services/update_release_service.rb b/app/services/update_release_service.rb
deleted file mode 100644
index e2228ca026c..00000000000
--- a/app/services/update_release_service.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-# frozen_string_literal: true
-
-class UpdateReleaseService < BaseService
- # rubocop: disable CodeReuse/ActiveRecord
- def execute(tag_name, release_description)
- repository = project.repository
- existing_tag = repository.find_tag(tag_name)
-
- if existing_tag
- release = project.releases.find_by(tag: tag_name)
-
- if release
- release.update(description: release_description)
-
- success(release)
- else
- error('Release does not exist', 404)
- end
- else
- error('Tag does not exist', 404)
- end
- end
- # rubocop: enable CodeReuse/ActiveRecord
-
- def success(release)
- super().merge(release: release)
- end
-end
diff --git a/app/views/admin/appearances/_form.html.haml b/app/views/admin/appearances/_form.html.haml
index cb67079853e..544f09048f5 100644
--- a/app/views/admin/appearances/_form.html.haml
+++ b/app/views/admin/appearances/_form.html.haml
@@ -8,7 +8,7 @@
= f.label :header_logo, 'Header logo', class: 'col-sm-2 col-form-label pt-0'
.col-sm-10
- if @appearance.header_logo?
- = image_tag @appearance.header_logo_url, class: 'appearance-light-logo-preview'
+ = image_tag @appearance.header_logo_path, class: 'appearance-light-logo-preview'
- if @appearance.persisted?
%br
= link_to 'Remove header logo', header_logos_admin_appearances_path, data: { confirm: "Header logo will be removed. Are you sure?"}, method: :delete, class: "btn btn-inverted btn-remove btn-sm remove-logo"
@@ -25,7 +25,7 @@
= f.label :favicon, 'Favicon', class: 'col-sm-2 col-form-label pt-0'
.col-sm-10
- if @appearance.favicon?
- = image_tag @appearance.favicon_url, class: 'appearance-light-logo-preview'
+ = image_tag @appearance.favicon_path, class: 'appearance-light-logo-preview'
- if @appearance.persisted?
%br
= link_to 'Remove favicon', favicon_admin_appearances_path, data: { confirm: "Favicon will be removed. Are you sure?"}, method: :delete, class: "btn btn-inverted btn-remove btn-sm remove-logo"
@@ -54,7 +54,7 @@
= f.label :logo, class: 'col-sm-2 col-form-label pt-0'
.col-sm-10
- if @appearance.logo?
- = image_tag @appearance.logo_url, class: 'appearance-logo-preview'
+ = image_tag @appearance.logo_path, class: 'appearance-logo-preview'
- if @appearance.persisted?
%br
= link_to 'Remove logo', logo_admin_appearances_path, data: { confirm: "Logo will be removed. Are you sure?"}, method: :delete, class: "btn btn-inverted btn-remove btn-sm remove-logo"
diff --git a/app/views/clusters/clusters/_buttons.html.haml b/app/views/clusters/clusters/_buttons.html.haml
index 9238903aa10..c81d1d5b05a 100644
--- a/app/views/clusters/clusters/_buttons.html.haml
+++ b/app/views/clusters/clusters/_buttons.html.haml
@@ -1,6 +1,5 @@
--# This partial is overridden in EE
.nav-controls
- - if clusterable.can_create_cluster? && clusterable.clusters.empty?
+ - if clusterable.can_add_cluster?
= link_to s_('ClusterIntegration|Add Kubernetes cluster'), clusterable.new_path, class: 'btn btn-success js-add-cluster'
- else
%span.btn.btn-add-cluster.disabled.js-add-cluster
diff --git a/app/views/clusters/clusters/_empty_state.html.haml b/app/views/clusters/clusters/_empty_state.html.haml
index c926ec258f0..cfdbfe2dea1 100644
--- a/app/views/clusters/clusters/_empty_state.html.haml
+++ b/app/views/clusters/clusters/_empty_state.html.haml
@@ -9,6 +9,6 @@
= clusterable.empty_state_help_text
= clusterable.learn_more_link
- - if clusterable.can_create_cluster?
+ - if clusterable.can_add_cluster?
.text-center
= link_to s_('ClusterIntegration|Add Kubernetes cluster'), clusterable.new_path, class: 'btn btn-success'
diff --git a/app/views/events/_events.html.haml b/app/views/events/_events.html.haml
index 68c19df092d..6ae4c334f7f 100644
--- a/app/views/events/_events.html.haml
+++ b/app/views/events/_events.html.haml
@@ -1 +1,4 @@
-= render partial: 'events/event', collection: @events
+- if @events.present?
+ = render partial: 'events/event', collection: @events
+- else
+ .nothing-here-block= _("No activities found")
diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml
index 59557c70904..d8017742c90 100644
--- a/app/views/layouts/nav/sidebar/_project.html.haml
+++ b/app/views/layouts/nav/sidebar/_project.html.haml
@@ -29,7 +29,7 @@
= link_to activity_project_path(@project), title: _('Activity'), class: 'shortcuts-project-activity' do
%span= _('Activity')
- - if project_nav_tab?(:releases) && Feature.enabled?(:releases_page)
+ - if project_nav_tab?(:releases) && Feature.enabled?(:releases_page, @project)
= nav_link(controller: :releases) do
= link_to project_releases_path(@project), title: _('Releases'), class: 'shortcuts-project-releases' do
%span= _('Releases')
diff --git a/app/views/projects/diffs/_render_error.html.haml b/app/views/projects/diffs/_render_error.html.haml
index c3dc47a56a7..7dbd9897e83 100644
--- a/app/views/projects/diffs/_render_error.html.haml
+++ b/app/views/projects/diffs/_render_error.html.haml
@@ -1,6 +1,2 @@
.nothing-here-block
- = _("This %{viewer} could not be displayed because %{reason}.") % { viewer: viewer.switcher_title, reason: diff_render_error_reason(viewer) }
-
- You can
- = diff_render_error_options(viewer).to_sentence(two_words_connector: ' or ', last_word_connector: ', or ').html_safe
- instead.
+ = viewer.render_error_message.html_safe
diff --git a/app/views/shared/snippets/_header.html.haml b/app/views/shared/snippets/_header.html.haml
index 10bfc30492a..a43296aa806 100644
--- a/app/views/shared/snippets/_header.html.haml
+++ b/app/views/shared/snippets/_header.html.haml
@@ -30,7 +30,7 @@
- if @snippet.updated_at != @snippet.created_at
= edited_time_ago_with_tooltip(@snippet, placement: 'bottom', html_class: 'snippet-edited-ago', exclude_author: true)
- - if public_snippet?
+ - if @snippet.embeddable?
.embed-snippet
.input-group
.input-group-prepend
diff --git a/app/workers/mail_scheduler/notification_service_worker.rb b/app/workers/mail_scheduler/notification_service_worker.rb
index 4726e416182..c8ccaf0c487 100644
--- a/app/workers/mail_scheduler/notification_service_worker.rb
+++ b/app/workers/mail_scheduler/notification_service_worker.rb
@@ -8,14 +8,35 @@ module MailScheduler
include MailSchedulerQueue
def perform(meth, *args)
- deserialized_args = ActiveJob::Arguments.deserialize(args)
+ check_arguments!(args)
+ deserialized_args = ActiveJob::Arguments.deserialize(args)
notification_service.public_send(meth, *deserialized_args) # rubocop:disable GitlabSecurity/PublicSend
rescue ActiveJob::DeserializationError
+ # No-op.
+ # This exception gets raised when an argument
+ # is correct (deserializeable), but it still cannot be deserialized.
+ # This can happen when an object has been deleted after
+ # rails passes this job to sidekiq, but before
+ # sidekiq gets it for execution.
+ # In this case just do nothing.
end
def self.perform_async(*args)
super(*ActiveJob::Arguments.serialize(args))
end
+
+ private
+
+ # If an argument is in the ActiveJob::Arguments::TYPE_WHITELIST list,
+ # it means the argument cannot be deserialized.
+ # Which means there's something wrong with our code.
+ def check_arguments!(args)
+ args.each do |arg|
+ if arg.class.in?(ActiveJob::Arguments::TYPE_WHITELIST)
+ raise(ArgumentError, "Argument `#{arg}` cannot be deserialized because of its type")
+ end
+ end
+ end
end
end
diff --git a/app/workers/post_receive.rb b/app/workers/post_receive.rb
index 72a1733a2a8..bbd4ab159e4 100644
--- a/app/workers/post_receive.rb
+++ b/app/workers/post_receive.rb
@@ -3,7 +3,7 @@
class PostReceive
include ApplicationWorker
- def perform(gl_repository, identifier, changes)
+ def perform(gl_repository, identifier, changes, push_options = [])
project, is_wiki = Gitlab::GlRepository.parse(gl_repository)
if project.nil?
@@ -15,7 +15,7 @@ class PostReceive
# Use Sidekiq.logger so arguments can be correlated with execution
# time and thread ID's.
Sidekiq.logger.info "changes: #{changes.inspect}" if ENV['SIDEKIQ_LOG_ARGUMENTS']
- post_received = Gitlab::GitPostReceive.new(project, identifier, changes)
+ post_received = Gitlab::GitPostReceive.new(project, identifier, changes, push_options)
if is_wiki
process_wiki_changes(post_received)
@@ -38,9 +38,21 @@ class PostReceive
post_received.changes_refs do |oldrev, newrev, ref|
if Gitlab::Git.tag_ref?(ref)
- GitTagPushService.new(post_received.project, @user, oldrev: oldrev, newrev: newrev, ref: ref).execute
+ GitTagPushService.new(
+ post_received.project,
+ @user,
+ oldrev: oldrev,
+ newrev: newrev,
+ ref: ref,
+ push_options: post_received.push_options).execute
elsif Gitlab::Git.branch_ref?(ref)
- GitPushService.new(post_received.project, @user, oldrev: oldrev, newrev: newrev, ref: ref).execute
+ GitPushService.new(
+ post_received.project,
+ @user,
+ oldrev: oldrev,
+ newrev: newrev,
+ ref: ref,
+ push_options: post_received.push_options).execute
end
changes << Gitlab::DataBuilder::Repository.single_change(oldrev, newrev, ref)