diff options
Diffstat (limited to 'app')
61 files changed, 664 insertions, 83 deletions
diff --git a/app/assets/javascripts/boards/components/board_list.vue b/app/assets/javascripts/boards/components/board_list.vue index 3e610a4088c..bfc8d9b03ad 100644 --- a/app/assets/javascripts/boards/components/board_list.vue +++ b/app/assets/javascripts/boards/components/board_list.vue @@ -203,7 +203,7 @@ export default { this.showIssueForm = !this.showIssueForm; }, onScroll() { - if (!this.loadingMore && (this.scrollTop() > this.scrollHeight() - this.scrollOffset)) { + if (!this.list.loadingMore && (this.scrollTop() > this.scrollHeight() - this.scrollOffset)) { this.loadNextPage(); } }, diff --git a/app/assets/javascripts/commons/polyfills.js b/app/assets/javascripts/commons/polyfills.js index 589eeee9695..742cf490ad2 100644 --- a/app/assets/javascripts/commons/polyfills.js +++ b/app/assets/javascripts/commons/polyfills.js @@ -8,6 +8,7 @@ import 'core-js/fn/object/assign'; import 'core-js/fn/promise'; import 'core-js/fn/string/code-point-at'; import 'core-js/fn/string/from-code-point'; +import 'core-js/fn/string/includes'; import 'core-js/fn/symbol'; import 'core-js/es6/map'; import 'core-js/es6/weak-map'; diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue index 7cc4e6a2c3a..b5b05df4d34 100644 --- a/app/assets/javascripts/diffs/components/app.vue +++ b/app/assets/javascripts/diffs/components/app.vue @@ -114,11 +114,15 @@ export default { this.adjustView(); }, methods: { - ...mapActions('diffs', ['setBaseConfig', 'fetchDiffFiles']), + ...mapActions('diffs', ['setBaseConfig', 'fetchDiffFiles', 'startRenderDiffsQueue']), fetchData() { - this.fetchDiffFiles().catch(() => { - createFlash(__('Something went wrong on our end. Please try again!')); - }); + this.fetchDiffFiles() + .then(() => { + requestIdleCallback(this.startRenderDiffsQueue, { timeout: 1000 }); + }) + .catch(() => { + createFlash(__('Something went wrong on our end. Please try again!')); + }); if (!this.isNotesFetched) { eventHub.$emit('fetchNotesData'); diff --git a/app/assets/javascripts/diffs/components/diff_file.vue b/app/assets/javascripts/diffs/components/diff_file.vue index 7e7058d8d08..59e9ba08b8b 100644 --- a/app/assets/javascripts/diffs/components/diff_file.vue +++ b/app/assets/javascripts/diffs/components/diff_file.vue @@ -46,16 +46,25 @@ export default { showExpandMessage() { return this.isCollapsed && !this.isLoadingCollapsedDiff && !this.file.tooLarge; }, + showLoadingIcon() { + return this.isLoadingCollapsedDiff || (!this.file.renderIt && !this.isCollapsed); + }, }, methods: { ...mapActions('diffs', ['loadCollapsedDiff']), handleToggle() { const { collapsed, highlightedDiffLines, parallelDiffLines } = this.file; - if (collapsed && !highlightedDiffLines && !parallelDiffLines.length) { + if ( + collapsed && + !highlightedDiffLines && + parallelDiffLines !== undefined && + !parallelDiffLines.length + ) { this.handleLoadCollapsedDiff(); } else { this.file.collapsed = !this.file.collapsed; + this.file.renderIt = true; } }, handleLoadCollapsedDiff() { @@ -65,6 +74,7 @@ export default { .then(() => { this.isLoadingCollapsedDiff = false; this.file.collapsed = false; + this.file.renderIt = true; }) .catch(() => { this.isLoadingCollapsedDiff = false; @@ -121,12 +131,12 @@ export default { </div> <diff-content - v-if="!isCollapsed" + v-if="!isCollapsed && file.renderIt" :class="{ hidden: isCollapsed || file.tooLarge }" :diff-file="file" /> <loading-icon - v-if="isLoadingCollapsedDiff" + v-else-if="showLoadingIcon" class="diff-content loading" /> <div diff --git a/app/assets/javascripts/diffs/constants.js b/app/assets/javascripts/diffs/constants.js index 2fa8367f528..f68afa44837 100644 --- a/app/assets/javascripts/diffs/constants.js +++ b/app/assets/javascripts/diffs/constants.js @@ -25,3 +25,6 @@ export const CONTEXT_LINE_CLASS_NAME = 'diff-expanded'; export const UNFOLD_COUNT = 20; export const COUNT_OF_AVATARS_IN_GUTTER = 3; export const LENGTH_OF_AVATAR_TOOLTIP = 17; + +export const LINES_TO_BE_RENDERED_DIRECTLY = 100; +export const MAX_LINES_TO_BE_RENDERED = 2000; diff --git a/app/assets/javascripts/diffs/store/actions.js b/app/assets/javascripts/diffs/store/actions.js index 27001142257..4ab6ceb249a 100644 --- a/app/assets/javascripts/diffs/store/actions.js +++ b/app/assets/javascripts/diffs/store/actions.js @@ -29,6 +29,27 @@ export const fetchDiffFiles = ({ state, commit }) => { .then(handleLocationHash); }; +export const startRenderDiffsQueue = ({ state, commit }) => { + const checkItem = () => { + const nextFile = state.diffFiles.find( + file => !file.renderIt && (!file.collapsed || !file.text), + ); + if (nextFile) { + requestAnimationFrame(() => { + commit(types.RENDER_FILE, nextFile); + }); + requestIdleCallback( + () => { + checkItem(); + }, + { timeout: 1000 }, + ); + } + }; + + checkItem(); +}; + export const setInlineDiffViewType = ({ commit }) => { commit(types.SET_DIFF_VIEW_TYPE, INLINE_DIFF_VIEW_TYPE); diff --git a/app/assets/javascripts/diffs/store/mutation_types.js b/app/assets/javascripts/diffs/store/mutation_types.js index 2c8e1a1466f..c999d637d50 100644 --- a/app/assets/javascripts/diffs/store/mutation_types.js +++ b/app/assets/javascripts/diffs/store/mutation_types.js @@ -8,3 +8,4 @@ export const REMOVE_COMMENT_FORM_LINE = 'REMOVE_COMMENT_FORM_LINE'; export const ADD_CONTEXT_LINES = 'ADD_CONTEXT_LINES'; export const ADD_COLLAPSED_DIFFS = 'ADD_COLLAPSED_DIFFS'; export const EXPAND_ALL_FILES = 'EXPAND_ALL_FILES'; +export const RENDER_FILE = 'RENDER_FILE'; diff --git a/app/assets/javascripts/diffs/store/mutations.js b/app/assets/javascripts/diffs/store/mutations.js index a98b2be89a3..0522e32c410 100644 --- a/app/assets/javascripts/diffs/store/mutations.js +++ b/app/assets/javascripts/diffs/store/mutations.js @@ -2,6 +2,7 @@ import Vue from 'vue'; import _ from 'underscore'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import { findDiffFile, addLineReferences, removeMatchLine, addContextLines } from './utils'; +import { LINES_TO_BE_RENDERED_DIRECTLY, MAX_LINES_TO_BE_RENDERED } from '../constants'; import * as types from './mutation_types'; export default { @@ -15,8 +16,48 @@ export default { }, [types.SET_DIFF_DATA](state, data) { + const diffData = convertObjectPropsToCamelCase(data, { deep: true }); + let showingLines = 0; + const filesLength = diffData.diffFiles.length; + let i; + for (i = 0; i < filesLength; i += 1) { + const file = diffData.diffFiles[i]; + if (file.parallelDiffLines) { + const linesLength = file.parallelDiffLines.length; + let u = 0; + for (u = 0; u < linesLength; u += 1) { + const line = file.parallelDiffLines[u]; + if (line.left) delete line.left.text; + if (line.right) delete line.right.text; + } + } + + if (file.highlightedDiffLines) { + const linesLength = file.highlightedDiffLines.length; + let u; + for (u = 0; u < linesLength; u += 1) { + const line = file.highlightedDiffLines[u]; + delete line.text; + } + } + + if (file.highlightedDiffLines) { + showingLines += file.parallelDiffLines.length; + } + Object.assign(file, { + renderIt: showingLines < LINES_TO_BE_RENDERED_DIRECTLY, + collapsed: file.text && showingLines > MAX_LINES_TO_BE_RENDERED, + }); + } + Object.assign(state, { - ...convertObjectPropsToCamelCase(data, { deep: true }), + ...diffData, + }); + }, + + [types.RENDER_FILE](state, file) { + Object.assign(file, { + renderIt: true, }); }, diff --git a/app/assets/javascripts/ide/stores/mutations.js b/app/assets/javascripts/ide/stores/mutations.js index 1eda5768709..56a8d9430c7 100644 --- a/app/assets/javascripts/ide/stores/mutations.js +++ b/app/assets/javascripts/ide/stores/mutations.js @@ -200,6 +200,7 @@ export default { }, [types.DELETE_ENTRY](state, path) { const entry = state.entries[path]; + const { tempFile = false } = entry; const parent = entry.parentPath ? state.entries[entry.parentPath] : state.trees[`${state.currentProjectId}/${state.currentBranchId}`]; @@ -209,7 +210,11 @@ export default { parent.tree = parent.tree.filter(f => f.path !== entry.path); if (entry.type === 'blob') { - state.changedFiles = state.changedFiles.concat(entry); + if (tempFile) { + state.changedFiles = state.changedFiles.filter(f => f.path !== path); + } else { + state.changedFiles = state.changedFiles.concat(entry); + } } }, [types.RENAME_ENTRY](state, { path, name, entryPath = null }) { diff --git a/app/assets/javascripts/jobs/components/artifacts_block.vue b/app/assets/javascripts/jobs/components/artifacts_block.vue new file mode 100644 index 00000000000..525c5eec91a --- /dev/null +++ b/app/assets/javascripts/jobs/components/artifacts_block.vue @@ -0,0 +1,98 @@ +<script> + import TimeagoTooltiop from '~/vue_shared/components/time_ago_tooltip.vue'; + + export default { + components: { + TimeagoTooltiop, + }, + props: { + // @build.artifacts_expired? + haveArtifactsExpired: { + type: Boolean, + required: true, + }, + // @build.has_expiring_artifacts? + willArtifactsExpire: { + type: Boolean, + required: true, + }, + expireAt: { + type: String, + required: false, + default: null, + }, + keepArtifactsPath: { + type: String, + required: false, + default: null, + }, + downloadArtifactsPath: { + type: String, + required: false, + default: null, + }, + browseArtifactsPath: { + type: String, + required: false, + default: null, + }, + }, + }; +</script> +<template> + <div class="block"> + <div class="title"> + {{ s__('Job|Job artifacts') }} + </div> + + <p + v-if="haveArtifactsExpired" + class="js-artifacts-removed build-detail-row" + > + {{ s__('Job|The artifacts were removed') }} + </p> + <p + v-else-if="willArtifactsExpire" + class="js-artifacts-will-be-removed build-detail-row" + > + {{ s__('Job|The artifacts will be removed') }} + </p> + + <timeago-tooltiop + v-if="expireAt" + :time="expireAt" + /> + + <div + class="btn-group d-flex" + role="group" + > + <a + v-if="keepArtifactsPath" + :href="keepArtifactsPath" + class="js-keep-artifacts btn btn-sm btn-default" + data-method="post" + > + {{ s__('Job|Keep') }} + </a> + + <a + v-if="downloadArtifactsPath" + :href="downloadArtifactsPath" + class="js-download-artifacts btn btn-sm btn-default" + download + rel="nofollow" + > + {{ s__('Job|Download') }} + </a> + + <a + v-if="browseArtifactsPath" + :href="browseArtifactsPath" + class="js-browse-artifacts btn btn-sm btn-default" + > + {{ s__('Job|Browse') }} + </a> + </div> + </div> +</template> diff --git a/app/assets/javascripts/jobs/components/empty_state.vue b/app/assets/javascripts/jobs/components/empty_state.vue new file mode 100644 index 00000000000..4faf08387fb --- /dev/null +++ b/app/assets/javascripts/jobs/components/empty_state.vue @@ -0,0 +1,76 @@ +<script> + export default { + props: { + illustrationPath: { + type: String, + required: true, + }, + illustrationSizeClass: { + type: String, + required: true, + }, + title: { + type: String, + required: true, + }, + content: { + type: String, + required: false, + default: null, + }, + action: { + type: Object, + required: false, + default: null, + validator(value) { + return ( + value === null || + (Object.prototype.hasOwnProperty.call(value, 'link') && + Object.prototype.hasOwnProperty.call(value, 'method') && + Object.prototype.hasOwnProperty.call(value, 'title')) + ); + }, + }, + }, + }; +</script> +<template> + <div class="row empty-state"> + <div class="col-12"> + <div + :class="illustrationSizeClass" + class="svg-content" + > + <img :src="illustrationPath" /> + </div> + </div> + + <div class="col-12"> + <div class="text-content"> + <h4 class="js-job-empty-state-title text-center"> + {{ title }} + </h4> + + <p + v-if="content" + class="js-job-empty-state-content" + > + {{ content }} + </p> + + <div + v-if="action" + class="text-center" + > + <a + :href="action.link" + :data-method="action.method" + class="js-job-empty-state-action btn btn-primary" + > + {{ action.title }} + </a> + </div> + </div> + </div> + </div> +</template> diff --git a/app/assets/javascripts/jobs/components/job_log_controllers.vue b/app/assets/javascripts/jobs/components/job_log_controllers.vue new file mode 100644 index 00000000000..513851e376f --- /dev/null +++ b/app/assets/javascripts/jobs/components/job_log_controllers.vue @@ -0,0 +1,139 @@ +<script> + import Icon from '~/vue_shared/components/icon.vue'; + import tooltip from '~/vue_shared/directives/tooltip'; + import { numberToHumanSize } from '~/lib/utils/number_utils'; + import { s__, sprintf } from '~/locale'; + + export default { + components: { + Icon, + }, + directives: { + tooltip, + }, + props: { + canEraseJob: { + type: Boolean, + required: true, + }, + size: { + type: Number, + required: true, + }, + rawTracePath: { + type: String, + required: false, + default: null, + }, + canScrollToTop: { + type: Boolean, + required: true, + }, + canScrollToBottom: { + type: Boolean, + required: true, + }, + }, + computed: { + jobLogSize() { + return sprintf('Showing last %{startSpanTag} %{size} %{endSpanTag} of log -', { + startSpanTag: '<span class="s-truncated-info-size truncated-info-size">', + endSpanTag: '</span>', + size: numberToHumanSize(this.size), + }); + }, + }, + methods: { + handleEraseJobClick() { + // eslint-disable-next-line no-alert + if (window.confirm(s__('Job|Are you sure you want to erase this job?'))) { + this.$emit('eraseJob'); + } + }, + handleScrollToTop() { + this.$emit('scrollJobLogTop'); + }, + handleScrollToBottom() { + this.$emit('scrollJobLogBottom'); + }, + }, + }; +</script> +<template> + <div class="top-bar"> + <!-- truncate information --> + <div class="js-truncated-info truncated-info d-none d-sm-block float-left"> + <p v-html="jobLogSize"></p> + + <a + v-if="rawTracePath" + :href="rawTracePath" + class="js-raw-link raw-link" + > + {{ s__("Job|Complete Raw") }} + </a> + </div> + <!-- eo truncate information --> + + <div class="controllers float-right"> + <!-- links --> + <a + v-tooltip + v-if="rawTracePath" + :title="s__('Job|Show complete raw')" + :href="rawTracePath" + class="js-raw-link-controller controllers-buttons" + data-container="body" + > + <icon name="doc-text" /> + </a> + + <button + v-tooltip + v-if="canEraseJob" + :title="s__('Job|Erase job log')" + type="button" + class="js-erase-link controllers-buttons" + data-container="body" + @click="handleEraseJobClick" + > + <icon name="remove" /> + </button> + <!-- eo links --> + + <!-- scroll buttons --> + <div + v-tooltip + :title="s__('Job|Scroll to top')" + class="controllers-buttons" + data-container="body" + > + <button + :disabled="!canScrollToTop" + type="button" + class="js-scroll-top btn-scroll btn-transparent btn-blank" + @click="handleScrollToTop" + > + <icon name="scroll_up"/> + </button> + </div> + + <div + v-tooltip + :title="s__('Job|Scroll to bottom')" + class="controllers-buttons" + data-container="body" + > + <button + :disabled="!canScrollToBottom" + type="button" + class="js-scroll-bottom btn-scroll btn-transparent btn-blank" + @click="handleScrollToBottom" + > + <icon name="scroll_down"/> + </button> + </div> + <!-- eo scroll buttons --> + </div> + </div> +</template> diff --git a/app/assets/javascripts/pages/admin/application_settings/index.js b/app/assets/javascripts/pages/admin/application_settings/index.js index 48d75f5443b..47bd70537f1 100644 --- a/app/assets/javascripts/pages/admin/application_settings/index.js +++ b/app/assets/javascripts/pages/admin/application_settings/index.js @@ -1,6 +1,8 @@ import initSettingsPanels from '~/settings_panels'; +import projectSelect from '~/project_select'; document.addEventListener('DOMContentLoaded', () => { // Initialize expandable settings panels initSettingsPanels(); + projectSelect(); }); diff --git a/app/assets/javascripts/project_select.js b/app/assets/javascripts/project_select.js index bce7556bd40..6f3b32f8eea 100644 --- a/app/assets/javascripts/project_select.js +++ b/app/assets/javascripts/project_select.js @@ -14,6 +14,7 @@ export default function projectSelect() { this.orderBy = $(select).data('orderBy') || 'id'; this.withIssuesEnabled = $(select).data('withIssuesEnabled'); this.withMergeRequestsEnabled = $(select).data('withMergeRequestsEnabled'); + this.allowClear = $(select).data('allowClear') || false; placeholder = "Search for project"; if (this.includeGroups) { @@ -71,6 +72,13 @@ export default function projectSelect() { text: function (project) { return project.name_with_namespace || project.name; }, + + initSelection: function(el, callback) { + return Api.project(el.val()).then(({ data }) => callback(data)); + }, + + allowClear: this.allowClear, + dropdownCssClass: "ajax-project-dropdown" }); if (simpleFilter) return select; diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss index 268e68dbb15..48a87ea8616 100644 --- a/app/assets/stylesheets/framework/common.scss +++ b/app/assets/stylesheets/framework/common.scss @@ -117,6 +117,10 @@ hr { color: $blue-600; } +.author-link:hover { + text-decoration: none; +} + .back-link { font-size: 14px; } diff --git a/app/assets/stylesheets/page_bundles/ide.scss b/app/assets/stylesheets/page_bundles/ide.scss index af91497d0ea..eac1345742d 100644 --- a/app/assets/stylesheets/page_bundles/ide.scss +++ b/app/assets/stylesheets/page_bundles/ide.scss @@ -1293,6 +1293,7 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding; &.build-page .top-bar { top: 0; + height: auto; font-size: 12px; border-top-right-radius: $border-radius-default; } diff --git a/app/finders/license_template_finder.rb b/app/finders/license_template_finder.rb new file mode 100644 index 00000000000..fad33f0eca2 --- /dev/null +++ b/app/finders/license_template_finder.rb @@ -0,0 +1,36 @@ +# LicenseTemplateFinder +# +# Used to find license templates, which may come from a variety of external +# sources +# +# Arguments: +# popular: boolean. When set to true, only "popular" licenses are shown. When +# false, all licenses except popular ones are shown. When nil (the +# default), *all* licenses will be shown. +class LicenseTemplateFinder + attr_reader :params + + def initialize(params = {}) + @params = params + end + + def execute + Licensee::License.all(featured: popular_only?).map do |license| + LicenseTemplate.new( + id: license.key, + name: license.name, + nickname: license.nickname, + category: (license.featured? ? :Popular : :Other), + content: license.content, + url: license.url, + meta: license.meta + ) + end + end + + private + + def popular_only? + params.fetch(:popular, nil) + end +end diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index 2bdf2c2c120..1e05f07e676 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -254,6 +254,7 @@ module ApplicationSettingsHelper :usage_ping_enabled, :instance_statistics_visibility_private, :user_default_external, + :user_show_add_ssh_key_message, :user_oauth_applications, :version_check_enabled, :web_ide_clientside_preview_enabled diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb index 7eb45ddd117..b61cbd5418a 100644 --- a/app/helpers/blob_helper.rb +++ b/app/helpers/blob_helper.rb @@ -182,12 +182,14 @@ module BlobHelper def licenses_for_select return @licenses_for_select if defined?(@licenses_for_select) - licenses = Licensee::License.all + grouped_licenses = LicenseTemplateFinder.new.execute.group_by(&:category) + categories = grouped_licenses.keys - @licenses_for_select = { - Popular: licenses.select(&:featured).map { |license| { name: license.name, id: license.key } }, - Other: licenses.reject(&:featured).map { |license| { name: license.name, id: license.key } } - } + @licenses_for_select = categories.each_with_object({}) do |category, hash| + hash[category] = grouped_licenses[category].map do |license| + { name: license.name, id: license.id } + end + end end def ref_project diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb index 0171a880164..7adc882bc47 100644 --- a/app/helpers/button_helper.rb +++ b/app/helpers/button_helper.rb @@ -73,7 +73,11 @@ module ButtonHelper end def ssh_clone_button(project, append_link: true) - dropdown_description = _("You won't be able to pull or push project code via SSH until you add an SSH key to your profile") if current_user.try(:require_ssh_key?) + if Gitlab::CurrentSettings.user_show_add_ssh_key_message? && + current_user.try(:require_ssh_key?) + dropdown_description = _("You won't be able to pull or push project code via SSH until you add an SSH key to your profile") + end + append_url = project.ssh_url_to_repo if append_link dropdown_item_with_description('SSH', dropdown_description, href: append_url) diff --git a/app/helpers/icons_helper.rb b/app/helpers/icons_helper.rb index 41084ec686f..a8a10c98d69 100644 --- a/app/helpers/icons_helper.rb +++ b/app/helpers/icons_helper.rb @@ -62,6 +62,8 @@ module IconsHelper names = "key" when "two-factor" names = "key" + when "google_oauth2" + names = "google" end options.include?(:base) ? fa_stacked_icon(names, options) : fa_icon(names, options) diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index aaf9dff43ee..6b4079b4113 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -192,7 +192,10 @@ module ProjectsHelper end def show_no_ssh_key_message? - cookies[:hide_no_ssh_message].blank? && !current_user.hide_no_ssh_key && current_user.require_ssh_key? + Gitlab::CurrentSettings.user_show_add_ssh_key_message? && + cookies[:hide_no_ssh_message].blank? && + !current_user.hide_no_ssh_key && + current_user.require_ssh_key? end def show_no_password_message? diff --git a/app/mailers/abuse_report_mailer.rb b/app/mailers/abuse_report_mailer.rb index fe5f68ba3d5..e032f568913 100644 --- a/app/mailers/abuse_report_mailer.rb +++ b/app/mailers/abuse_report_mailer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class AbuseReportMailer < BaseMailer def notify(abuse_report_id) return unless deliverable? diff --git a/app/mailers/base_mailer.rb b/app/mailers/base_mailer.rb index 654468bc7fe..5fd209c4761 100644 --- a/app/mailers/base_mailer.rb +++ b/app/mailers/base_mailer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class BaseMailer < ActionMailer::Base around_action :render_with_default_locale diff --git a/app/mailers/devise_mailer.rb b/app/mailers/devise_mailer.rb index 962570a0efd..7aa75ee30e6 100644 --- a/app/mailers/devise_mailer.rb +++ b/app/mailers/devise_mailer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class DeviseMailer < Devise::Mailer default from: "#{Gitlab.config.gitlab.email_display_name} <#{Gitlab.config.gitlab.email_from}>" default reply_to: Gitlab.config.gitlab.email_reply_to @@ -9,8 +11,9 @@ class DeviseMailer < Devise::Mailer protected def subject_for(key) - subject = super - subject << " | #{Gitlab.config.gitlab.email_subject_suffix}" if Gitlab.config.gitlab.email_subject_suffix.present? - subject + subject = [super] + subject << Gitlab.config.gitlab.email_subject_suffix if Gitlab.config.gitlab.email_subject_suffix.present? + + subject.join(' | ') end end diff --git a/app/mailers/email_rejection_mailer.rb b/app/mailers/email_rejection_mailer.rb index 76db31a4c45..45fc5a6c383 100644 --- a/app/mailers/email_rejection_mailer.rb +++ b/app/mailers/email_rejection_mailer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class EmailRejectionMailer < BaseMailer def rejection(reason, original_raw, can_retry = false) @reason = reason diff --git a/app/mailers/emails/issues.rb b/app/mailers/emails/issues.rb index 392cc0bee03..c8b1ab5033a 100644 --- a/app/mailers/emails/issues.rb +++ b/app/mailers/emails/issues.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Emails module Issues def new_issue_email(recipient_id, issue_id, reason = nil) diff --git a/app/mailers/emails/members.rb b/app/mailers/emails/members.rb index 75cf56a51f2..91dfdf58982 100644 --- a/app/mailers/emails/members.rb +++ b/app/mailers/emails/members.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Emails module Members extend ActiveSupport::Concern diff --git a/app/mailers/emails/merge_requests.rb b/app/mailers/emails/merge_requests.rb index 70509e9066d..70f65d4e58d 100644 --- a/app/mailers/emails/merge_requests.rb +++ b/app/mailers/emails/merge_requests.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Emails module MergeRequests def new_merge_request_email(recipient_id, merge_request_id, reason = nil) diff --git a/app/mailers/emails/notes.rb b/app/mailers/emails/notes.rb index d9a6fe2a41e..d3284e90568 100644 --- a/app/mailers/emails/notes.rb +++ b/app/mailers/emails/notes.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Emails module Notes def note_commit_email(recipient_id, note_id) diff --git a/app/mailers/emails/pages_domains.rb b/app/mailers/emails/pages_domains.rb index 0027dfdc36b..ce449237ef6 100644 --- a/app/mailers/emails/pages_domains.rb +++ b/app/mailers/emails/pages_domains.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Emails module PagesDomains def pages_domain_enabled_email(domain, recipient) diff --git a/app/mailers/emails/pipelines.rb b/app/mailers/emails/pipelines.rb index f9f45ab987b..31e183640ad 100644 --- a/app/mailers/emails/pipelines.rb +++ b/app/mailers/emails/pipelines.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Emails module Pipelines def pipeline_success_email(pipeline, recipients) @@ -39,10 +41,10 @@ module Emails end def pipeline_subject(status) - commit = @pipeline.short_sha - commit << " in #{@merge_request.to_reference}" if @merge_request + commit = [@pipeline.short_sha] + commit << "in #{@merge_request.to_reference}" if @merge_request - subject("Pipeline ##{@pipeline.id} has #{status} for #{@pipeline.ref}", commit) + subject("Pipeline ##{@pipeline.id} has #{status} for #{@pipeline.ref}", commit.join(' ')) end end end diff --git a/app/mailers/emails/profile.rb b/app/mailers/emails/profile.rb index 4f5edeb9bda..40d7b9ccd7a 100644 --- a/app/mailers/emails/profile.rb +++ b/app/mailers/emails/profile.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Emails module Profile def new_user_email(user_id, token = nil) diff --git a/app/mailers/emails/projects.rb b/app/mailers/emails/projects.rb index 761d873c01c..d7e6c2ba7b2 100644 --- a/app/mailers/emails/projects.rb +++ b/app/mailers/emails/projects.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Emails module Projects def project_was_moved_email(project_id, user_id, old_path_with_namespace) diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb index 0e1e39501f5..f4eeb85270e 100644 --- a/app/mailers/notify.rb +++ b/app/mailers/notify.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class Notify < BaseMailer include ActionDispatch::Routing::PolymorphicRoutes include GitlabRoutingHelper @@ -92,12 +94,14 @@ class Notify < BaseMailer # >> subject('Lorem ipsum', 'Dolor sit amet') # => "Lorem ipsum | Dolor sit amet" def subject(*extra) - subject = "" - subject << "#{@project.name} | " if @project - subject << "#{@group.name} | " if @group - subject << extra.join(' | ') if extra.present? - subject << " | #{Gitlab.config.gitlab.email_subject_suffix}" if Gitlab.config.gitlab.email_subject_suffix.present? - subject + subject = [] + + subject << @project.name if @project + subject << @group.name if @group + subject.concat(extra) if extra.present? + subject << Gitlab.config.gitlab.email_subject_suffix if Gitlab.config.gitlab.email_subject_suffix.present? + + subject.join(' | ') end # Return a string suitable for inclusion in the 'Message-Id' mail header. diff --git a/app/mailers/previews/devise_mailer_preview.rb b/app/mailers/previews/devise_mailer_preview.rb index d6588efc486..3b9ef0d3ac0 100644 --- a/app/mailers/previews/devise_mailer_preview.rb +++ b/app/mailers/previews/devise_mailer_preview.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class DeviseMailerPreview < ActionMailer::Preview def confirmation_instructions_for_signup DeviseMailer.confirmation_instructions(unsaved_user, 'faketoken', {}) diff --git a/app/mailers/previews/email_rejection_mailer_preview.rb b/app/mailers/previews/email_rejection_mailer_preview.rb index 639e8471232..402066151ef 100644 --- a/app/mailers/previews/email_rejection_mailer_preview.rb +++ b/app/mailers/previews/email_rejection_mailer_preview.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class EmailRejectionMailerPreview < ActionMailer::Preview def rejection EmailRejectionMailer.rejection("some rejection reason", "From: someone@example.com\nraw email here").message diff --git a/app/mailers/previews/notify_preview.rb b/app/mailers/previews/notify_preview.rb index 3615cde8026..df470930e9e 100644 --- a/app/mailers/previews/notify_preview.rb +++ b/app/mailers/previews/notify_preview.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class NotifyPreview < ActionMailer::Preview def note_merge_request_email_for_individual_note note_email(:note_merge_request_email) do diff --git a/app/mailers/previews/repository_check_mailer_preview.rb b/app/mailers/previews/repository_check_mailer_preview.rb index 19d4eab1805..834d7594719 100644 --- a/app/mailers/previews/repository_check_mailer_preview.rb +++ b/app/mailers/previews/repository_check_mailer_preview.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class RepositoryCheckMailerPreview < ActionMailer::Preview def notify RepositoryCheckMailer.notify(3).message diff --git a/app/mailers/repository_check_mailer.rb b/app/mailers/repository_check_mailer.rb index 22a9f5da646..4bcf371cfc0 100644 --- a/app/mailers/repository_check_mailer.rb +++ b/app/mailers/repository_check_mailer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class RepositoryCheckMailer < BaseMailer def notify(failed_count) @message = diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index bbe7811841a..c77faa4b71d 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -298,7 +298,8 @@ class ApplicationSetting < ActiveRecord::Base unique_ips_limit_time_window: 3600, usage_ping_enabled: Settings.gitlab['usage_ping_enabled'], instance_statistics_visibility_private: false, - user_default_external: false + user_default_external: false, + user_show_add_ssh_key_message: true } end diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 3c69677baf0..faa160ad6ba 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -67,6 +67,10 @@ module Ci '', Ci::JobArtifact.select(1).where('ci_builds.id = ci_job_artifacts.job_id').archive) end + scope :with_archived_trace, ->() do + where('EXISTS (?)', Ci::JobArtifact.select(1).where('ci_builds.id = ci_job_artifacts.job_id').trace) + end + scope :without_archived_trace, ->() do where('NOT EXISTS (?)', Ci::JobArtifact.select(1).where('ci_builds.id = ci_job_artifacts.job_id').trace) end @@ -77,6 +81,7 @@ module Ci end scope :with_artifacts_stored_locally, -> { with_artifacts_archive.where(artifacts_file_store: [nil, LegacyArtifactUploader::Store::LOCAL]) } + scope :with_archived_trace_stored_locally, -> { with_archived_trace.where(artifacts_file_store: [nil, LegacyArtifactUploader::Store::LOCAL]) } scope :with_artifacts_not_expired, ->() { with_artifacts_archive.where('artifacts_expire_at IS NULL OR artifacts_expire_at > ?', Time.now) } scope :with_expired_artifacts, ->() { with_artifacts_archive.where('artifacts_expire_at < ?', Time.now) } scope :last_month, ->() { where('created_at > ?', Date.today - 1.month) } diff --git a/app/models/license_template.rb b/app/models/license_template.rb new file mode 100644 index 00000000000..0ad75b27827 --- /dev/null +++ b/app/models/license_template.rb @@ -0,0 +1,53 @@ +class LicenseTemplate + PROJECT_TEMPLATE_REGEX = + %r{[\<\{\[] + (project|description| + one\sline\s.+\swhat\sit\sdoes\.) # matching the start and end is enough here + [\>\}\]]}xi.freeze + YEAR_TEMPLATE_REGEX = /[<{\[](year|yyyy)[>}\]]/i.freeze + FULLNAME_TEMPLATE_REGEX = + %r{[\<\{\[] + (fullname|name\sof\s(author|copyright\sowner)) + [\>\}\]]}xi.freeze + + attr_reader :id, :name, :category, :nickname, :url, :meta + + alias_method :key, :id + + def initialize(id:, name:, category:, content:, nickname: nil, url: nil, meta: {}) + @id = id + @name = name + @category = category + @content = content + @nickname = nickname + @url = url + @meta = meta + end + + def popular? + category == :Popular + end + alias_method :featured?, :popular? + + # Returns the text of the license + def content + if @content.respond_to?(:call) + @content = @content.call + else + @content + end + end + + # Populate placeholders in the LicenseTemplate content + def resolve!(project_name: nil, fullname: nil, year: Time.now.year.to_s) + # Ensure the string isn't shared with any other instance of LicenseTemplate + new_content = content.dup + new_content.gsub!(YEAR_TEMPLATE_REGEX, year) if year.present? + new_content.gsub!(PROJECT_TEMPLATE_REGEX, project_name) if project_name.present? + new_content.gsub!(FULLNAME_TEMPLATE_REGEX, fullname) if fullname.present? + + @content = new_content + + self + end +end diff --git a/app/models/namespace.rb b/app/models/namespace.rb index b974309aeb6..0deb44d7916 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -10,6 +10,7 @@ class Namespace < ActiveRecord::Base include Storage::LegacyNamespace include Gitlab::SQL::Pattern include IgnorableColumn + include FeatureGate ignore_column :deleted_at @@ -124,7 +125,6 @@ class Namespace < ActiveRecord::Base def to_param full_path end - alias_method :flipper_id, :to_param def human_name owner_name diff --git a/app/models/project.rb b/app/models/project.rb index 7735f23cb9e..94c1d60f071 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -27,6 +27,7 @@ class Project < ActiveRecord::Base include FastDestroyAll::Helpers include WithUploads include BatchDestroyDependentAssociations + include FeatureGate extend Gitlab::Cache::RequestCache extend Gitlab::ConfigHelper @@ -519,18 +520,19 @@ class Project < ActiveRecord::Base def auto_devops_enabled? if auto_devops&.enabled.nil? - Gitlab::CurrentSettings.auto_devops_enabled? + has_auto_devops_implicitly_enabled? else auto_devops.enabled? end end def has_auto_devops_implicitly_enabled? - auto_devops&.enabled.nil? && Gitlab::CurrentSettings.auto_devops_enabled? + auto_devops&.enabled.nil? && + (Gitlab::CurrentSettings.auto_devops_enabled? || Feature.enabled?(:force_autodevops_on_by_default, self)) end def has_auto_devops_implicitly_disabled? - auto_devops&.enabled.nil? && !Gitlab::CurrentSettings.auto_devops_enabled? + auto_devops&.enabled.nil? && !(Gitlab::CurrentSettings.auto_devops_enabled? || Feature.enabled?(:force_autodevops_on_by_default, self)) end def empty_repo? diff --git a/app/models/project_auto_devops.rb b/app/models/project_auto_devops.rb index 155400d1a43..dc6736dd9cd 100644 --- a/app/models/project_auto_devops.rb +++ b/app/models/project_auto_devops.rb @@ -47,12 +47,8 @@ class ProjectAutoDevops < ActiveRecord::Base end def needs_to_create_deploy_token? - auto_devops_enabled? && + project.auto_devops_enabled? && !project.public? && !project.deploy_tokens.find_by(name: DeployToken::GITLAB_DEPLOY_TOKEN_NAME).present? end - - def auto_devops_enabled? - Gitlab::CurrentSettings.auto_devops_enabled? || enabled? - end end diff --git a/app/services/groups/destroy_service.rb b/app/services/groups/destroy_service.rb index c4554ce45fb..12aeba4af71 100644 --- a/app/services/groups/destroy_service.rb +++ b/app/services/groups/destroy_service.rb @@ -2,6 +2,8 @@ module Groups class DestroyService < Groups::BaseService + DestroyError = Class.new(StandardError) + def async_execute job_id = GroupDestroyWorker.perform_async(group.id, current_user.id) Rails.logger.info("User #{current_user.id} scheduled a deletion of group ID #{group.id} with job ID #{job_id}") @@ -12,9 +14,8 @@ module Groups group.projects.each do |project| # Execute the destruction of the models immediately to ensure atomic cleanup. - # Skip repository removal because we remove directory with namespace - # that contain all these repositories - ::Projects::DestroyService.new(project, current_user, skip_repo: project.legacy_storage?).execute + success = ::Projects::DestroyService.new(project, current_user).execute + raise DestroyError, "Project #{project.id} can't be deleted" unless success end group.children.each do |group| diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb index 97f181ccea8..e390d7a04c3 100644 --- a/app/services/projects/update_service.rb +++ b/app/services/projects/update_service.rb @@ -30,7 +30,7 @@ module Projects def run_auto_devops_pipeline? return false if project.repository.gitlab_ci_yml || !project.auto_devops&.previous_changes&.include?('enabled') - project.auto_devops.enabled? || (project.auto_devops.enabled.nil? && Gitlab::CurrentSettings.auto_devops_enabled?) + project.auto_devops_enabled? end private diff --git a/app/views/admin/application_settings/_account_and_limit.html.haml b/app/views/admin/application_settings/_account_and_limit.html.haml index 7c8243a7a90..622cb11010e 100644 --- a/app/views/admin/application_settings/_account_and_limit.html.haml +++ b/app/views/admin/application_settings/_account_and_limit.html.haml @@ -29,5 +29,11 @@ = f.check_box :user_default_external, class: 'form-check-input' = f.label :user_default_external, class: 'form-check-label' do Newly registered users will by default be external + .form-group + = f.label :user_show_add_ssh_key_message, 'Prompt users to upload SSH keys', class: 'label-bold' + .form-check + = f.check_box :user_show_add_ssh_key_message, class: 'form-check-input' + = f.label :user_show_add_ssh_key_message, class: 'form-check-label' do + Inform users without uploaded SSH keys that they can't push over SSH until one is added = f.submit 'Save changes', class: 'btn btn-success' diff --git a/app/views/admin/application_settings/show.html.haml b/app/views/admin/application_settings/show.html.haml index 258d50ad676..6133a7646f4 100644 --- a/app/views/admin/application_settings/show.html.haml +++ b/app/views/admin/application_settings/show.html.haml @@ -325,6 +325,8 @@ .settings-content = render partial: 'repository_mirrors_form' += render_if_exists 'admin/application_settings/templates', expanded: expanded + %section.settings.as-third-party-offers.no-animate#js-third-party-offers-settings{ class: ('expanded' if expanded) } .settings-header %h4 diff --git a/app/views/admin/users/_access_levels.html.haml b/app/views/admin/users/_access_levels.html.haml index 04acc5f8423..5f68163163e 100644 --- a/app/views/admin/users/_access_levels.html.haml +++ b/app/views/admin/users/_access_levels.html.haml @@ -1,15 +1,18 @@ %fieldset %legend Access .form-group.row - = f.label :projects_limit, class: 'col-form-label col-sm-2' + .col-sm-2.text-right + = f.label :projects_limit, class: 'col-form-label' .col-sm-10= f.number_field :projects_limit, min: 0, max: Gitlab::Database::MAX_INT_VALUE, class: 'form-control' .form-group.row - = f.label :can_create_group, class: 'col-form-label col-sm-2' + .col-sm-2.text-right + = f.label :can_create_group, class: 'col-form-label' .col-sm-10= f.check_box :can_create_group .form-group.row - = f.label :access_level, class: 'col-form-label col-sm-2' + .col-sm-2.text-right + = f.label :access_level, class: 'col-form-label' .col-sm-10 - editing_current_user = (current_user == @user) @@ -29,7 +32,8 @@ You cannot remove your own admin rights. .form-group.row - = f.label :external, class: 'col-form-label col-sm-2' + .col-sm-2.text-right + = f.label :external, class: 'col-form-label' .col-sm-10 = f.check_box :external do External diff --git a/app/views/admin/users/_form.html.haml b/app/views/admin/users/_form.html.haml index 58be07fc83e..7f21bdb91c8 100644 --- a/app/views/admin/users/_form.html.haml +++ b/app/views/admin/users/_form.html.haml @@ -5,17 +5,20 @@ %fieldset %legend Account .form-group.row - = f.label :name, class: 'col-form-label col-sm-2' + .col-sm-2.text-right + = f.label :name, class: 'col-form-label' .col-sm-10 = f.text_field :name, required: true, autocomplete: 'off', class: 'form-control' %span.help-inline * required .form-group.row - = f.label :username, class: 'col-form-label col-sm-2' + .col-sm-2.text-right + = f.label :username, class: 'col-form-label' .col-sm-10 = f.text_field :username, required: true, autocomplete: 'off', autocorrect: 'off', autocapitalize: 'off', spellcheck: false, class: 'form-control' %span.help-inline * required .form-group.row - = f.label :email, class: 'col-form-label col-sm-2' + .col-sm-2.text-right + = f.label :email, class: 'col-form-label' .col-sm-10 = f.text_field :email, required: true, autocomplete: 'off', class: 'form-control' %span.help-inline * required @@ -24,7 +27,8 @@ %fieldset %legend Password .form-group.row - = f.label :password, class: 'col-form-label col-sm-2' + .col-sm-2.text-right + = f.label :password, class: 'col-form-label' .col-sm-10 %strong Reset link will be generated and sent to the user. @@ -34,10 +38,12 @@ %fieldset %legend Password .form-group.row - = f.label :password, class: 'col-form-label col-sm-2' + .col-sm-2.text-right + = f.label :password, class: 'col-form-label' .col-sm-10= f.password_field :password, disabled: f.object.force_random_password, class: 'form-control' .form-group.row - = f.label :password_confirmation, class: 'col-form-label col-sm-2' + .col-sm-2.text-right + = f.label :password_confirmation, class: 'col-form-label' .col-sm-10= f.password_field :password_confirmation, disabled: f.object.force_random_password, class: 'form-control' = render partial: 'access_levels', locals: { f: f } @@ -45,21 +51,26 @@ %fieldset %legend Profile .form-group.row - = f.label :avatar, class: 'col-form-label col-sm-2' + .col-sm-2.text-right + = f.label :avatar, class: 'col-form-label' .col-sm-10 = f.file_field :avatar .form-group.row - = f.label :skype, class: 'col-form-label col-sm-2' + .col-sm-2.text-right + = f.label :skype, class: 'col-form-label' .col-sm-10= f.text_field :skype, class: 'form-control' .form-group.row - = f.label :linkedin, class: 'col-form-label col-sm-2' + .col-sm-2.text-right + = f.label :linkedin, class: 'col-form-label' .col-sm-10= f.text_field :linkedin, class: 'form-control' .form-group.row - = f.label :twitter, class: 'col-form-label col-sm-2' + .col-sm-2.text-right + = f.label :twitter, class: 'col-form-label' .col-sm-10= f.text_field :twitter, class: 'form-control' .form-group.row - = f.label :website_url, 'Website', class: 'col-form-label col-sm-2' + .col-sm-2.text-right + = f.label :website_url, 'Website', class: 'col-form-label' .col-sm-10= f.text_field :website_url, class: 'form-control' .form-actions diff --git a/app/views/instance_statistics/cohorts/_cohorts_table.html.haml b/app/views/instance_statistics/cohorts/_cohorts_table.html.haml index 701a4e62b39..6a7c999bff3 100644 --- a/app/views/instance_statistics/cohorts/_cohorts_table.html.haml +++ b/app/views/instance_statistics/cohorts/_cohorts_table.html.haml @@ -3,7 +3,7 @@ User cohorts are shown for the last #{@cohorts[:months_included]} months. Only users with activity are counted in the cohort total; inactive users are counted separately. - = link_to icon('question-circle'), help_page_path('user/admin_area/user_cohorts', anchor: 'cohorts'), title: 'About this feature', target: '_blank' + = link_to icon('question-circle'), help_page_path('user/instance_statistics/user_cohorts', anchor: 'cohorts'), title: 'About this feature', target: '_blank' .table-holder %table.table diff --git a/app/views/instance_statistics/conversational_development_index/_no_data.html.haml b/app/views/instance_statistics/conversational_development_index/_no_data.html.haml index d69c46194b4..dd795aee135 100644 --- a/app/views/instance_statistics/conversational_development_index/_no_data.html.haml +++ b/app/views/instance_statistics/conversational_development_index/_no_data.html.haml @@ -4,4 +4,4 @@ %h4 Data is still calculating... %p In order to gather accurate feature usage data, it can take 1 to 2 weeks to see your index. - = link_to 'Learn more', help_page_path('user/admin_area/monitoring/convdev'), target: '_blank' + = link_to 'Learn more', help_page_path('user/instance_statistics/convdev'), target: '_blank' diff --git a/app/views/instance_statistics/conversational_development_index/index.html.haml b/app/views/instance_statistics/conversational_development_index/index.html.haml index e3d1aa31dc2..dd63b98376f 100644 --- a/app/views/instance_statistics/conversational_development_index/index.html.haml +++ b/app/views/instance_statistics/conversational_development_index/index.html.haml @@ -19,7 +19,7 @@ index %br score - = link_to icon('question-circle', 'aria-hidden' => 'true'), help_page_path('user/admin_area/monitoring/convdev') + = link_to icon('question-circle', 'aria-hidden' => 'true'), help_page_path('user/instance_statistics/convdev') .convdev-cards.board-card-container - @metric.cards.each do |card| diff --git a/app/views/projects/mirrors/_instructions.html.haml b/app/views/projects/mirrors/_instructions.html.haml index 3d811be3fe3..e051f9e6331 100644 --- a/app/views/projects/mirrors/_instructions.html.haml +++ b/app/views/projects/mirrors/_instructions.html.haml @@ -4,7 +4,7 @@ = _('The repository must be accessible over <code>http://</code>, <code>https://</code>, <code>ssh://</code> and <code>git://</code>.').html_safe %li= _('Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>.').html_safe - %li= _('The update action will time out after 15 minutes. For big repositories, use a clone/push combination.') + %li= _("The update action will time out after #{import_will_timeout_message(Gitlab.config.gitlab_shell.git_timeout)} minutes. For big repositories, use a clone/push combination.") %li= _('The Git LFS objects will <strong>not</strong> be synced.').html_safe %li = _('This user will be the author of all events in the activity feed that are the result of an update, diff --git a/app/views/projects/settings/ci_cd/_form.html.haml b/app/views/projects/settings/ci_cd/_form.html.haml index 434aed2f603..9134257b631 100644 --- a/app/views/projects/settings/ci_cd/_form.html.haml +++ b/app/views/projects/settings/ci_cd/_form.html.haml @@ -17,7 +17,7 @@ %h5.prepend-top-0 = _("Git strategy for pipelines") %p - = _("Choose between <code>clone</code> or <code>fetch</code> to get the recent application code") + = _("Choose between <code>clone</code> or <code>fetch</code> to get the recent application code").html_safe = link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'git-strategy'), target: '_blank' .form-check = f.radio_button :build_allow_git_fetch, 'false', { class: 'form-check-input' } @@ -47,7 +47,7 @@ = f.label :ci_config_path, _('Custom CI config path'), class: 'label-bold' = f.text_field :ci_config_path, class: 'form-control', placeholder: '.gitlab-ci.yml' %p.form-text.text-muted - = _("The path to CI config file. Defaults to <code>.gitlab-ci.yml</code>") + = _("The path to CI config file. Defaults to <code>.gitlab-ci.yml</code>").html_safe = link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'custom-ci-config-path'), target: '_blank' %hr diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index 8a5abb64515..df8a5742450 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -21,8 +21,7 @@ %nav.project-stats{ class: [container_class, ("limit-container-width" unless fluid_layout)] } = render 'stat_anchor_list', anchors: @project.statistics_anchors(show_auto_devops_callout: show_auto_devops_callout) = render 'stat_anchor_list', anchors: @project.statistics_buttons(show_auto_devops_callout: show_auto_devops_callout) - - if Feature.enabled?(:repository_languages, @project.namespace.becomes(Namespace)) - = repository_languages_bar(@project.repository_languages) + = repository_languages_bar(@project.repository_languages) %div{ class: [container_class, ("limit-container-width" unless fluid_layout)] } - if @project.archived? diff --git a/app/views/search/results/_blob.html.haml b/app/views/search/results/_blob.html.haml index fdcd126e7a3..a8d4d4af93a 100644 --- a/app/views/search/results/_blob.html.haml +++ b/app/views/search/results/_blob.html.haml @@ -1,4 +1,6 @@ - project = find_project_for_result_blob(blob) +- return unless project + - file_name, blob = parse_search_result(blob) - blob_link = project_blob_path(project, tree_join(blob.ref, file_name)) diff --git a/app/views/shared/runners/_form.html.haml b/app/views/shared/runners/_form.html.haml index 0337680d79b..fa93307be31 100644 --- a/app/views/shared/runners/_form.html.haml +++ b/app/views/shared/runners/_form.html.haml @@ -1,56 +1,56 @@ = form_for runner, url: runner_form_url do |f| = form_errors(runner) .form-group.row - = label :active, "Active", class: 'col-form-label col-sm-2' + = label :active, _("Active"), class: 'col-form-label col-sm-2' .col-sm-10 .form-check = f.check_box :active, { class: 'form-check-input' } - %span.light Paused Runners don't accept new jobs + %label.light{ for: :runner_active }= _("Paused Runners don't accept new jobs") .form-group.row - = label :protected, "Protected", class: 'col-form-label col-sm-2' + = label :protected, _("Protected"), class: 'col-form-label col-sm-2' .col-sm-10 .form-check = f.check_box :access_level, { class: 'form-check-input' }, 'ref_protected', 'not_protected' - %span.light This runner will only run on pipelines triggered on protected branches + %label.light{ for: :runner_access_level }= _('This runner will only run on pipelines triggered on protected branches') .form-group.row - = label :run_untagged, 'Run untagged jobs', class: 'col-form-label col-sm-2' + = label :run_untagged, _('Run untagged jobs'), class: 'col-form-label col-sm-2' .col-sm-10 .form-check = f.check_box :run_untagged, { class: 'form-check-input' } - %span.light Indicates whether this runner can pick jobs without tags + %label.light{ for: :runner_run_untagged }= _('Indicates whether this runner can pick jobs without tags') - unless runner.group_type? .form-group.row = label :locked, _('Lock to current projects'), class: 'col-form-label col-sm-2' .col-sm-10 .form-check = f.check_box :locked, { class: 'form-check-input' } - %span.light= _('When a runner is locked, it cannot be assigned to other projects') + %label.light{ for: :runner_locked }= _('When a runner is locked, it cannot be assigned to other projects') .form-group.row = label_tag :token, class: 'col-form-label col-sm-2' do - Token + = _('Token') .col-sm-10 = f.text_field :token, class: 'form-control', readonly: true .form-group.row = label_tag :ip_address, class: 'col-form-label col-sm-2' do - IP Address + = _('IP Address') .col-sm-10 = f.text_field :ip_address, class: 'form-control', readonly: true .form-group.row = label_tag :description, class: 'col-form-label col-sm-2' do - Description + = _('Description') .col-sm-10 = f.text_field :description, class: 'form-control' .form-group.row = label_tag :maximum_timeout_human_readable, class: 'col-form-label col-sm-2' do - Maximum job timeout + = _('Maximum job timeout') .col-sm-10 = f.text_field :maximum_timeout_human_readable, class: 'form-control' - .form-text.text-muted This timeout will take precedence when lower than Project-defined timeout + .form-text.text-muted= _('This timeout will take precedence when lower than Project-defined timeout') .form-group.row = label_tag :tag_list, class: 'col-form-label col-sm-2' do - Tags + = _('Tags') .col-sm-10 = f.text_field :tag_list, value: runner.tag_list.sort.join(', '), class: 'form-control' - .form-text.text-muted You can setup jobs to only use Runners with specific tags. Separate tags with commas. + .form-text.text-muted= _('You can setup jobs to only use Runners with specific tags. Separate tags with commas.') .form-actions - = f.submit 'Save changes', class: 'btn btn-save' + = f.submit _('Save changes'), class: 'btn btn-success' diff --git a/app/workers/detect_repository_languages_worker.rb b/app/workers/detect_repository_languages_worker.rb index cfbbbffc8a6..854b74b884a 100644 --- a/app/workers/detect_repository_languages_worker.rb +++ b/app/workers/detect_repository_languages_worker.rb @@ -16,8 +16,6 @@ class DetectRepositoryLanguagesWorker user = User.find_by(id: user_id) return unless project && user - return if Feature.disabled?(:repository_languages, project.namespace) - try_obtain_lease do ::Projects::DetectRepositoryLanguagesService.new(project, user).execute end |