Commit c0dffdff authored by Mike Greiling's avatar Mike Greiling

Merge branch 'master' into 43511-upgrade-to-babel-7

* master: (30 commits)
  Refactor quick actions docs into multiple tables
  CE Resolve "Refactor code quality similar to JUnit tests"
  Fix rename_login_root_namespaces post migration
  Merge branch 'master-i18n' into 'master'
  Hides Close MR button on merged MR
  Show the commit-sha for pre-release versions
  Resolve "Add "Link" shortcut/icon in markdown editor to make it easier to add references"
  Use tiller locally for Auto Devops
  Improve logging when username update fails due to registry tags
  [QA] Improve admin hashed-storage settings
  circumvent browser cache on browser back navigation
  Highlight current user in comments and system notes
  Resolve "Selecting an autofill suggestion for project name will not update the project slug"
  Update Workhorse to 7.0.0 for Gitaly's new auth scheme
  Update to Rouge 3.3.0 including frozen string literals for improved memory usage
  Don't check for the groups list before filtering
  Add missing changelog type [ci skip]
  Filter issues without an Assignee via the API
  Make single diff patch limit configurable
  Add yarn integrity hashes
  ...
parents a9df7d86 9ab59100
...@@ -444,10 +444,10 @@ setup-test-env: ...@@ -444,10 +444,10 @@ setup-test-env:
- vendor/gitaly-ruby - vendor/gitaly-ruby
danger-review: danger-review:
<<: *pull-cache
image: registry.gitlab.com/gitlab-org/gitlab-build-images:danger image: registry.gitlab.com/gitlab-org/gitlab-build-images:danger
stage: test stage: test
allow_failure: true allow_failure: true
cache: {}
dependencies: [] dependencies: []
before_script: [] before_script: []
only: only:
...@@ -461,6 +461,8 @@ danger-review: ...@@ -461,6 +461,8 @@ danger-review:
- $CI_COMMIT_REF_NAME =~ /.*-stable(-ee)?-prepare-.*/ - $CI_COMMIT_REF_NAME =~ /.*-stable(-ee)?-prepare-.*/
script: script:
- git version - git version
- node --version
- yarn install --frozen-lockfile --cache-folder .yarn-cache
- danger --fail-on-errors=true - danger --fail-on-errors=true
rspec-pg 0 30: *rspec-metadata-pg rspec-pg 0 30: *rspec-metadata-pg
......
...@@ -7,3 +7,5 @@ danger.import_dangerfile(path: 'danger/database') ...@@ -7,3 +7,5 @@ danger.import_dangerfile(path: 'danger/database')
danger.import_dangerfile(path: 'danger/documentation') danger.import_dangerfile(path: 'danger/documentation')
danger.import_dangerfile(path: 'danger/frozen_string') danger.import_dangerfile(path: 'danger/frozen_string')
danger.import_dangerfile(path: 'danger/commit_messages') danger.import_dangerfile(path: 'danger/commit_messages')
danger.import_dangerfile(path: 'danger/prettier')
danger.import_dangerfile(path: 'danger/eslint')
6.1.1 7.0.0
\ No newline at end of file
...@@ -729,7 +729,7 @@ GEM ...@@ -729,7 +729,7 @@ GEM
retriable (3.1.2) retriable (3.1.2)
rinku (2.0.0) rinku (2.0.0)
rotp (2.1.2) rotp (2.1.2)
rouge (3.2.1) rouge (3.3.0)
rqrcode (0.7.0) rqrcode (0.7.0)
chunky_png chunky_png
rqrcode-rails3 (0.1.7) rqrcode-rails3 (0.1.7)
......
...@@ -738,7 +738,7 @@ GEM ...@@ -738,7 +738,7 @@ GEM
retriable (3.1.2) retriable (3.1.2)
rinku (2.0.0) rinku (2.0.0)
rotp (2.1.2) rotp (2.1.2)
rouge (3.2.1) rouge (3.3.0)
rqrcode (0.7.0) rqrcode (0.7.0)
chunky_png chunky_png
rqrcode-rails3 (0.1.7) rqrcode-rails3 (0.1.7)
......
/**
* Highlights the current user in existing elements with a user ID data attribute.
*
* @param elements DOM elements that represent user mentions
*/
export default function highlightCurrentUser(elements) {
const currentUserId = gon && gon.current_user_id;
if (!currentUserId) {
return;
}
elements.forEach(element => {
if (parseInt(element.dataset.user, 10) === currentUserId) {
element.classList.add('current-user');
}
});
}
...@@ -2,6 +2,7 @@ import $ from 'jquery'; ...@@ -2,6 +2,7 @@ import $ from 'jquery';
import syntaxHighlight from '~/syntax_highlight'; import syntaxHighlight from '~/syntax_highlight';
import renderMath from './render_math'; import renderMath from './render_math';
import renderMermaid from './render_mermaid'; import renderMermaid from './render_mermaid';
import highlightCurrentUser from './highlight_current_user';
// Render GitLab flavoured Markdown // Render GitLab flavoured Markdown
// //
...@@ -11,6 +12,7 @@ $.fn.renderGFM = function renderGFM() { ...@@ -11,6 +12,7 @@ $.fn.renderGFM = function renderGFM() {
syntaxHighlight(this.find('.js-syntax-highlight')); syntaxHighlight(this.find('.js-syntax-highlight'));
renderMath(this.find('.js-render-math')); renderMath(this.find('.js-render-math'));
renderMermaid(this.find('.js-render-mermaid')); renderMermaid(this.find('.js-render-mermaid'));
highlightCurrentUser(this.find('.gfm-project_member').get());
return this; return this;
}; };
......
...@@ -25,6 +25,7 @@ import './components/board_sidebar'; ...@@ -25,6 +25,7 @@ import './components/board_sidebar';
import './components/new_list_dropdown'; import './components/new_list_dropdown';
import BoardAddIssuesModal from './components/modal/index.vue'; import BoardAddIssuesModal from './components/modal/index.vue';
import '~/vue_shared/vue_resource_interceptor'; import '~/vue_shared/vue_resource_interceptor';
import { NavigationType } from '~/lib/utils/common_utils';
export default () => { export default () => {
const $boardApp = document.getElementById('board-app'); const $boardApp = document.getElementById('board-app');
...@@ -32,6 +33,16 @@ export default () => { ...@@ -32,6 +33,16 @@ export default () => {
window.gl = window.gl || {}; window.gl = window.gl || {};
// check for browser back and trigger a hard reload to circumvent browser caching.
window.addEventListener('pageshow', (event) => {
const isNavTypeBackForward = window.performance &&
window.performance.navigation.type === NavigationType.TYPE_BACK_FORWARD;
if (event.persisted || isNavTypeBackForward) {
window.location.reload();
}
});
if (gl.IssueBoardsApp) { if (gl.IssueBoardsApp) {
gl.IssueBoardsApp.$destroy(true); gl.IssueBoardsApp.$destroy(true);
} }
......
...@@ -2,54 +2,114 @@ import _ from 'underscore'; ...@@ -2,54 +2,114 @@ import _ from 'underscore';
export const placeholderImage = export const placeholderImage =
'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='; 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==';
const SCROLL_THRESHOLD = 300; const SCROLL_THRESHOLD = 500;
export default class LazyLoader { export default class LazyLoader {
constructor(options = {}) { constructor(options = {}) {
this.intersectionObserver = null;
this.lazyImages = []; this.lazyImages = [];
this.observerNode = options.observerNode || '#content-body'; this.observerNode = options.observerNode || '#content-body';
const throttledScrollCheck = _.throttle(() => this.scrollCheck(), 300);
const debouncedElementsInView = _.debounce(() => this.checkElementsInView(), 300);
window.addEventListener('scroll', throttledScrollCheck);
window.addEventListener('resize', debouncedElementsInView);
const scrollContainer = options.scrollContainer || window; const scrollContainer = options.scrollContainer || window;
scrollContainer.addEventListener('load', () => this.loadCheck()); scrollContainer.addEventListener('load', () => this.register());
} }
static supportsIntersectionObserver() {
return 'IntersectionObserver' in window;
}
searchLazyImages() { searchLazyImages() {
const that = this;
requestIdleCallback( requestIdleCallback(
() => { () => {
that.lazyImages = [].slice.call(document.querySelectorAll('.lazy')); const lazyImages = [].slice.call(document.querySelectorAll('.lazy'));
if (that.lazyImages.length) { if (LazyLoader.supportsIntersectionObserver()) {
that.checkElementsInView(); if (this.intersectionObserver) {
lazyImages.forEach(img => this.intersectionObserver.observe(img));
}
} else if (lazyImages.length) {
this.lazyImages = lazyImages;
this.checkElementsInView();
} }
}, },
{ timeout: 500 }, { timeout: 500 },
); );
} }
startContentObserver() { startContentObserver() {
const contentNode = document.querySelector(this.observerNode) || document.querySelector('body'); const contentNode = document.querySelector(this.observerNode) || document.querySelector('body');
if (contentNode) { if (contentNode) {
const observer = new MutationObserver(() => this.searchLazyImages()); this.mutationObserver = new MutationObserver(() => this.searchLazyImages());
observer.observe(contentNode, { this.mutationObserver.observe(contentNode, {
childList: true, childList: true,
subtree: true, subtree: true,
}); });
} }
} }
loadCheck() {
this.searchLazyImages(); stopContentObserver() {
if (this.mutationObserver) {
this.mutationObserver.takeRecords();
this.mutationObserver.disconnect();
this.mutationObserver = null;
}
}
unregister() {
this.stopContentObserver();
if (this.intersectionObserver) {
this.intersectionObserver.takeRecords();
this.intersectionObserver.disconnect();
this.intersectionObserver = null;
}
if (this.throttledScrollCheck) {
window.removeEventListener('scroll', this.throttledScrollCheck);
}
if (this.debouncedElementsInView) {
window.removeEventListener('resize', this.debouncedElementsInView);
}
}
register() {
if (LazyLoader.supportsIntersectionObserver()) {
this.startIntersectionObserver();
} else {
this.startLegacyObserver();
}
this.startContentObserver(); this.startContentObserver();
this.searchLazyImages();
} }
startIntersectionObserver = () => {
this.throttledElementsInView = _.throttle(() => this.checkElementsInView(), 300);
this.intersectionObserver = new IntersectionObserver(this.onIntersection, {
rootMargin: `${SCROLL_THRESHOLD}px 0px`,
thresholds: 0.1,
});
};
onIntersection = entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
this.intersectionObserver.unobserve(entry.target);
this.lazyImages.push(entry.target);
}
});
this.throttledElementsInView();
};
startLegacyObserver() {
this.throttledScrollCheck = _.throttle(() => this.scrollCheck(), 300);
this.debouncedElementsInView = _.debounce(() => this.checkElementsInView(), 300);
window.addEventListener('scroll', this.throttledScrollCheck);
window.addEventListener('resize', this.debouncedElementsInView);
}
scrollCheck() { scrollCheck() {
requestAnimationFrame(() => this.checkElementsInView()); requestAnimationFrame(() => this.checkElementsInView());
} }
checkElementsInView() { checkElementsInView() {
const scrollTop = window.pageYOffset; const scrollTop = window.pageYOffset;
const visHeight = scrollTop + window.innerHeight + SCROLL_THRESHOLD; const visHeight = scrollTop + window.innerHeight + SCROLL_THRESHOLD;
...@@ -61,18 +121,29 @@ export default class LazyLoader { ...@@ -61,18 +121,29 @@ export default class LazyLoader {
const imgTop = scrollTop + imgBoundRect.top; const imgTop = scrollTop + imgBoundRect.top;
const imgBound = imgTop + imgBoundRect.height; const imgBound = imgTop + imgBoundRect.height;
if (scrollTop < imgBound && visHeight > imgTop) { if (scrollTop <= imgBound && visHeight >= imgTop) {
requestAnimationFrame(() => { requestAnimationFrame(() => {
LazyLoader.loadImage(selectedImage); LazyLoader.loadImage(selectedImage);
}); });
return false; return false;
} }
/*
If we are scrolling fast, the img we watched intersecting could have left the view port.
So we are going watch for new intersections.
*/
if (LazyLoader.supportsIntersectionObserver()) {
if (this.intersectionObserver) {
this.intersectionObserver.observe(selectedImage);
}
return false;
}
return true; return true;
} }
return false; return false;
}); });
} }
static loadImage(img) { static loadImage(img) {
if (img.getAttribute('data-src')) { if (img.getAttribute('data-src')) {
let imgUrl = img.getAttribute('data-src'); let imgUrl = img.getAttribute('data-src');
......
...@@ -616,6 +616,17 @@ export const roundOffFloat = (number, precision = 0) => { ...@@ -616,6 +616,17 @@ export const roundOffFloat = (number, precision = 0) => {
return Math.round(number * multiplier) / multiplier; return Math.round(number * multiplier) / multiplier;
}; };
/**
* Represents navigation type constants of the Performance Navigation API.
* Detailed explanation see https://developer.mozilla.org/en-US/docs/Web/API/PerformanceNavigation.
*/
export const NavigationType = {
TYPE_NAVIGATE: 0,
TYPE_RELOAD: 1,
TYPE_BACK_FORWARD: 2,
TYPE_RESERVED: 255,
};
window.gl = window.gl || {}; window.gl = window.gl || {};
window.gl.utils = { window.gl.utils = {
...(window.gl.utils || {}), ...(window.gl.utils || {}),
......
...@@ -31,11 +31,17 @@ function blockTagText(text, textArea, blockTag, selected) { ...@@ -31,11 +31,17 @@ function blockTagText(text, textArea, blockTag, selected) {
} }
} }
function moveCursor(textArea, tag, wrapped, removedLastNewLine) { function moveCursor({ textArea, tag, wrapped, removedLastNewLine, select }) {
var pos; var pos;
if (!textArea.setSelectionRange) { if (!textArea.setSelectionRange) {
return; return;
} }
if (select && select.length > 0) {
// calculate the part of the text to be selected
const startPosition = textArea.selectionStart - (tag.length - tag.indexOf(select));
const endPosition = startPosition + select.length;
return textArea.setSelectionRange(startPosition, endPosition);
}
if (textArea.selectionStart === textArea.selectionEnd) { if (textArea.selectionStart === textArea.selectionEnd) {
if (wrapped) { if (wrapped) {
pos = textArea.selectionStart - tag.length; pos = textArea.selectionStart - tag.length;
...@@ -51,7 +57,7 @@ function moveCursor(textArea, tag, wrapped, removedLastNewLine) { ...@@ -51,7 +57,7 @@ function moveCursor(textArea, tag, wrapped, removedLastNewLine) {
} }
} }
export function insertMarkdownText(textArea, text, tag, blockTag, selected, wrap) { export function insertMarkdownText({ textArea, text, tag, blockTag, selected, wrap, select }) {
var textToInsert, inserted, selectedSplit, startChar, removedLastNewLine, removedFirstNewLine, currentLineEmpty, lastNewLine; var textToInsert, inserted, selectedSplit, startChar, removedLastNewLine, removedFirstNewLine, currentLineEmpty, lastNewLine;
removedLastNewLine = false; removedLastNewLine = false;
removedFirstNewLine = false; removedFirstNewLine = false;
...@@ -82,11 +88,16 @@ export function insertMarkdownText(textArea, text, tag, blockTag, selected, wrap ...@@ -82,11 +88,16 @@ export function insertMarkdownText(textArea, text, tag, blockTag, selected, wrap
startChar = !wrap && !currentLineEmpty && textArea.selectionStart > 0 ? '\n' : ''; startChar = !wrap && !currentLineEmpty && textArea.selectionStart > 0 ? '\n' : '';
const textPlaceholder = '{text}';
if (selectedSplit.length > 1 && (!wrap || (blockTag != null && blockTag !== ''))) { if (selectedSplit.length > 1 && (!wrap || (blockTag != null && blockTag !== ''))) {
if (blockTag != null && blockTag !== '') { if (blockTag != null && blockTag !== '') {
textToInsert = blockTagText(text, textArea, blockTag, selected); textToInsert = blockTagText(text, textArea, blockTag, selected);
} else { } else {
textToInsert = selectedSplit.map(function(val) { textToInsert = selectedSplit.map(function(val) {
if (tag.indexOf(textPlaceholder) > -1) {
return tag.replace(textPlaceholder, val);
}
if (val.indexOf(tag) === 0) { if (val.indexOf(tag) === 0) {
return "" + (val.replace(tag, '')); return "" + (val.replace(tag, ''));
} else { } else {
...@@ -94,6 +105,8 @@ export function insertMarkdownText(textArea, text, tag, blockTag, selected, wrap ...@@ -94,6 +105,8 @@ export function insertMarkdownText(textArea, text, tag, blockTag, selected, wrap
} }
}).join('\n'); }).join('\n');
} }
} else if (tag.indexOf(textPlaceholder) > -1) {
textToInsert = tag.replace(textPlaceholder, selected);
} else { } else {
textToInsert = "" + startChar + tag + selected + (wrap ? tag : ' '); textToInsert = "" + startChar + tag + selected + (wrap ? tag : ' ');
} }
...@@ -107,17 +120,17 @@ export function insertMarkdownText(textArea, text, tag, blockTag, selected, wrap ...@@ -107,17 +120,17 @@ export function insertMarkdownText(textArea, text, tag, blockTag, selected, wrap
} }
insertText(textArea, textToInsert); insertText(textArea, textToInsert);
return moveCursor(textArea, tag, wrap, removedLastNewLine); return moveCursor({ textArea, tag: tag.replace(textPlaceholder, selected), wrap, removedLastNewLine, select });
} }
function updateText(textArea, tag, blockTag, wrap) { function updateText({ textArea, tag, blockTag, wrap, select }) {
var $textArea, selected, text; var $textArea, selected, text;
$textArea = $(textArea); $textArea = $(textArea);
textArea = $textArea.get(0); textArea = $textArea.get(0);
text = $textArea.val(); text = $textArea.val();
selected = selectedText(text, textArea); selected = selectedText(text, textArea);
$textArea.focus(); $textArea.focus();
return insertMarkdownText(textArea, text, tag, blockTag, selected, wrap); return insertMarkdownText({ textArea, text, tag, blockTag, selected, wrap, select });
} }
function replaceRange(s, start, end, substitute) { function replaceRange(s, start, end, substitute) {
...@@ -127,7 +140,12 @@ function replaceRange(s, start, end, substitute) { ...@@ -127,7 +140,12 @@ function replaceRange(s, start, end, substitute) {
export function addMarkdownListeners(form) { export function addMarkdownListeners(form) {
return $('.js-md', form).off('click').on('click', function() { return $('.js-md', form).off('click').on('click', function() {
const $this = $(this); const $this = $(this);
return updateText($this.closest('.md-area').find('textarea'), $this.data('mdTag'), $this.data('mdBlock'), !$this.data('mdPrepend')); return updateText({
textArea: $this.closest('.md-area').find('textarea'),
tag: $this.data('mdTag'),
blockTag: $this.data('mdBlock'),
wrap: !$this.data('mdPrepend'),
select: $this.data('mdSelect') });
}); });
} }
......
...@@ -11,6 +11,7 @@ import commentForm from './comment_form.vue'; ...@@ -11,6 +11,7 @@ import commentForm from './comment_form.vue';
import placeholderNote from '../../vue_shared/components/notes/placeholder_note.vue'; import placeholderNote from '../../vue_shared/components/notes/placeholder_note.vue';
import placeholderSystemNote from '../../vue_shared/components/notes/placeholder_system_note.vue'; import placeholderSystemNote from '../../vue_shared/components/notes/placeholder_system_note.vue';
import skeletonLoadingContainer from '../../vue_shared/components/notes/skeleton_note.vue'; import skeletonLoadingContainer from '../../vue_shared/components/notes/skeleton_note.vue';
import highlightCurrentUser from '~/behaviors/markdown/highlight_current_user';
export default { export default {
name: 'NotesApp', name: 'NotesApp',
...@@ -96,6 +97,9 @@ export default { ...@@ -96,6 +97,9 @@ export default {
}); });
} }
}, },
updated() {
this.$nextTick(() => highlightCurrentUser(this.$el.querySelectorAll('.gfm-project_member')));
},
methods: { methods: {
...mapActions({ ...mapActions({
fetchDiscussions: 'fetchDiscussions', fetchDiscussions: 'fetchDiscussions',
......
...@@ -147,7 +147,7 @@ const bindEvents = () => { ...@@ -147,7 +147,7 @@ const bindEvents = () => {
$projectImportUrl.keyup(() => deriveProjectPathFromUrl($projectImportUrl)); $projectImportUrl.keyup(() => deriveProjectPathFromUrl($projectImportUrl));
$projectName.keyup(() => { $projectName.on('keyup change', () => {
onProjectNameChange($projectName, $projectPath); onProjectNameChange($projectName, $projectPath);
hasUserDefinedProjectPath = $projectPath.val().trim().length > 0; hasUserDefinedProjectPath = $projectPath.val().trim().length > 0;
}); });
......
...@@ -139,7 +139,7 @@ export default { ...@@ -139,7 +139,7 @@ export default {
<section class="media-section"> <section class="media-section">
<div class="media"> <div class="media">
<status-icon :status="statusIconName" /> <status-icon :status="statusIconName" />
<div class="media-body space-children d-flex flex-align-self-center"> <div class="media-body d-flex flex-align-self-center">
<span class="js-code-text code-text"> <span class="js-code-text code-text">
{{ headerText }} {{ headerText }}
......
...@@ -105,6 +105,12 @@ ...@@ -105,6 +105,12 @@
button-title="Insert code" button-title="Insert code"
icon="code" icon="code"
/> />
<toolbar-button
tag="[{text}](url)"
tag-select="url"
button-title="Add a link"
icon="link"
/>
<toolbar-button <toolbar-button
:prepend="true" :prepend="true"
tag="* " tag="* "
......
...@@ -27,6 +27,11 @@ ...@@ -27,6 +27,11 @@
required: false, required: false,
default: '', default: '',
}, },
tagSelect: {
type: String,
required: false,
default: '',
},
prepend: { prepend: {
type: Boolean, type: Boolean,
required: false, required: false,
...@@ -40,6 +45,7 @@ ...@@ -40,6 +45,7 @@
<button <button
v-tooltip v-tooltip
:data-md-tag="tag" :data-md-tag="tag"
:data-md-select="tagSelect"
:data-md-block="tagBlock" :data-md-block="tagBlock"
:data-md-prepend="prepend" :data-md-prepend="prepend"
:title="buttonTitle" :title="buttonTitle"
......
...@@ -11,6 +11,10 @@ ...@@ -11,6 +11,10 @@
padding: 0 2px; padding: 0 2px;
background-color: $blue-100; background-color: $blue-100;
border-radius: $border-radius-default; border-radius: $border-radius-default;
&.current-user {
background-color: $orange-100;
}
} }
.gfm-color_chip { .gfm-color_chip {
......
...@@ -14,6 +14,8 @@ class Projects::ArtifactsController < Projects::ApplicationController ...@@ -14,6 +14,8 @@ class Projects::ArtifactsController < Projects::ApplicationController
before_action :entry, only: [:file] before_action :entry, only: [:file]
def download def download
return render_404 unless artifacts_file
send_upload(artifacts_file, attachment: artifacts_file.filename) send_upload(artifacts_file, attachment: artifacts_file.filename)
end end
...@@ -100,7 +102,7 @@ class Projects::ArtifactsController < Projects::ApplicationController ...@@ -100,7 +102,7 @@ class Projects::ArtifactsController < Projects::ApplicationController
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
def artifacts_file def artifacts_file
@artifacts_file ||= build.artifacts_file @artifacts_file ||= build.artifacts_file_for_type(params[:file_type] || :archive)
end end
def entry def entry
......
...@@ -236,16 +236,16 @@ class IssuableFinder ...@@ -236,16 +236,16 @@ class IssuableFinder
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
def assignee_id? def assignee_id?
params[:assignee_id].present? && params[:assignee_id] != NONE params[:assignee_id].present? && params[:assignee_id].to_s != NONE
end end
def assignee_username? def assignee_username?
params[:assignee_username].present? && params[:assignee_username] != NONE params[:assignee_username].present? && params[:assignee_username].to_s != NONE
end end
def no_assignee? def no_assignee?
# Assignee_id takes precedence over assignee_username # Assignee_id takes precedence over assignee_username
params[:assignee_id] == NONE || params[:assignee_username] == NONE params[:assignee_id].to_s == NONE || params[:assignee_username].to_s == NONE
end end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
......
...@@ -254,7 +254,8 @@ module ApplicationSettingsHelper ...@@ -254,7 +254,8 @@ module ApplicationSettingsHelper
:user_default_internal_regex, :user_default_internal_regex,
:user_oauth_applications, :user_oauth_applications,
:version_check_enabled, :version_check_enabled,
:web_ide_clientside_preview_enabled :web_ide_clientside_preview_enabled,
:diff_max_patch_bytes
] ]
end end
......
...@@ -327,11 +327,15 @@ module IssuablesHelper ...@@ -327,11 +327,15 @@ module IssuablesHelper
end end
def issuable_button_visibility(issuable, closed) def issuable_button_visibility(issuable, closed)
return 'hidden' if issuable_button_hidden?(issuable, closed)
end
def issuable_button_hidden?(issuable, closed)
case issuable case issuable
when Issue when Issue
issue_button_visibility(issuable, closed) issue_button_hidden?(issuable, closed)
when MergeRequest when MergeRequest
merge_request_button_visibility(issuable, closed) merge_request_button_hidden?(issuable, closed)
end end
end end
......
...@@ -64,7 +64,11 @@ module IssuesHelper ...@@ -64,7 +64,11 @@ module IssuesHelper
end end
def issue_button_visibility(issue, closed) def issue_button_visibility(issue, closed)
return 'hidden' if issue.closed? == closed return 'hidden' if issue_button_hidden?(issue, closed)
end
def issue_button_hidden?(issue, closed)
issue.closed? == closed || (!closed && issue.discussion_locked)
end end
def confidential_icon(issue) def confidential_icon(issue)
......
...@@ -80,7 +80,11 @@ module MergeRequestsHelper ...@@ -80,7 +80,11 @@ module MergeRequestsHelper
end end
def merge_request_button_visibility(merge_request, closed) def merge_request_button_visibility(merge_request, closed)
return 'hidden' if merge_request.closed? == closed || (merge_request.merged? == closed && !merge_request.closed?) || merge_request.closed_without_fork? return 'hidden' if merge_request_button_hidden?(merge_request, closed)
end
def merge_request_button_hidden?(merge_request, closed)
merge_request.closed? == closed || (merge_request.merged? == closed && !merge_request.closed?) || merge_request.closed_without_fork?
end end
def merge_request_version_path(project, merge_request, merge_request_diff, start_sha = nil) def merge_request_version_path(project, merge_request, merge_request_diff, start_sha = nil)
......
...@@ -9,4 +9,17 @@ module VersionCheckHelper ...@@ -9,4 +9,17 @@ module VersionCheckHelper
image_url = VersionCheck.new.url image_url = VersionCheck.new.url
image_tag image_url, class: 'js-version-status-badge' image_tag image_url, class: 'js-version-status-badge'
end end
def link_to_version
if Gitlab.pre_release?
commit_link = link_to(Gitlab.revision, Gitlab::COM_URL + namespace_project_commits_path('gitlab-org', source_code_project, Gitlab.revision))
[Gitlab::VERSION, content_tag(:small, commit_link)].join(' ').html_safe
else
link_to Gitlab::VERSION, Gitlab::COM_URL + namespace_project_tag_path('gitlab-org', source_code_project, "v#{Gitlab::VERSION}")
end
end
def source_code_project
'gitlab-ce'
end
end end
...@@ -182,6 +182,12 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -182,6 +182,12 @@ class ApplicationSetting < ActiveRecord::Base
numericality: { less_than_or_equal_to: :gitaly_timeout_default }, numericality: { less_than_or_equal_to: :gitaly_timeout_default },
if: :gitaly_timeout_default if: :gitaly_timeout_default
validates :diff_max_patch_bytes,
presence: true,
numericality: { only_integer: true,
greater_than_or_equal_to: Gitlab::Git::Diff::DEFAULT_MAX_PATCH_BYTES,
less_than_or_equal_to: Gitlab::Git::Diff::MAX_PATCH_BYTES_UPPER_BOUND }
validates :user_default_internal_regex, js_regex: true, allow_nil: true validates :user_default_internal_regex, js_regex: true, allow_nil: true
SUPPORTED_KEY_TYPES.each do |type| SUPPORTED_KEY_TYPES.each do |type|
...@@ -293,7 +299,8 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -293,7 +299,8 @@ class ApplicationSetting < ActiveRecord::Base
user_default_external: false, user_default_external: false,
user_default_internal_regex: nil, user_default_internal_regex: nil,
user_show_add_ssh_key_message: true, user_show_add_ssh_key_message: true,
usage_stats_set_by_user_id: nil usage_stats_set_by_user_id: nil,
diff_max_patch_bytes: Gitlab::Git::Diff::DEFAULT_MAX_PATCH_BYTES
} }
end end
......
...@@ -522,6 +522,13 @@ module Ci ...@@ -522,6 +522,13 @@ module Ci
self.job_artifacts.update_all(expire_at: nil) self.job_artifacts.update_all(expire_at: nil)
end end
def artifacts_file_for_type(type)
file = job_artifacts.find_by(file_type: Ci::JobArtifact.file_types[type])&.file
# TODO: to be removed once legacy artifacts is removed
file ||= legacy_artifacts_file if type == :archive
file
end
def coverage_regex def coverage_regex
super || project.try(:build_coverage_regex) super || project.try(:build_coverage_regex)
end end
......
...@@ -15,6 +15,7 @@ module Ci ...@@ -15,6 +15,7 @@ module Ci
metadata: nil, metadata: nil,
trace: nil, trace: nil,
junit: 'junit.xml', junit: 'junit.xml',
codequality: 'codequality.json',
sast: 'gl-sast-report.json', sast: 'gl-sast-report.json',
dependency_scanning: 'gl-dependency-scanning-report.json', dependency_scanning: 'gl-dependency-scanning-report.json',
container_scanning: 'gl-container-scanning-report.json', container_scanning: 'gl-container-scanning-report.json',
...@@ -26,6 +27,7 @@ module Ci ...@@ -26,6 +27,7 @@ module Ci
metadata: :gzip, metadata: :gzip,
trace: :raw, trace: :raw,
junit: :gzip, junit: :gzip,
codequality: :gzip,
sast: :gzip, sast: :gzip,
dependency_scanning: :gzip, dependency_scanning: :gzip,
container_scanning: :gzip, container_scanning: :gzip,
...@@ -73,7 +75,8 @@ module Ci ...@@ -73,7 +75,8 @@ module Ci
sast: 5, ## EE-specific sast: 5, ## EE-specific
dependency_scanning: 6, ## EE-specific dependency_scanning: 6, ## EE-specific
container_scanning: 7, ## EE-specific container_scanning: 7, ## EE-specific
dast: 8 ## EE-specific dast: 8, ## EE-specific
codequality: 9 ## EE-specific
} }
enum file_format: { enum file_format: {
......
...@@ -15,6 +15,9 @@ module Clusters ...@@ -15,6 +15,9 @@ module Clusters
state :scheduled, value: 1 state :scheduled, value: 1
state :installing, value: 2 state :installing, value: 2
state :installed, value: 3 state :installed, value: 3
state :updating, value: 4
state :updated, value: 5
state :update_errored, value: 6
event :make_scheduled do event :make_scheduled do
transition [:installable, :errored] => :scheduled transition [:installable, :errored] => :scheduled
...@@ -32,6 +35,18 @@ module Clusters ...@@ -32,6 +35,18 @@ module Clusters
transition any => :errored transition any => :errored
end end
event :make_updating do
transition [:installed, :updated, :update_errored] => :updating
end
event :make_updated do
transition [:updating] => :updated
end
event :make_update_errored do
transition any => :update_errored
end
before_transition any => [:scheduled] do |app_status, _| before_transition any => [:scheduled] do |app_status, _|
app_status.status_reason = nil app_status.status_reason = nil
end end
...@@ -40,6 +55,15 @@ module Clusters ...@@ -40,6 +55,15 @@ module Clusters
status_reason = transition.args.first status_reason = transition.args.first
app_status.status_reason = status_reason if status_reason app_status.status_reason = status_reason if status_reason
end end
before_transition any => [:updating] do |app_status, _|
app_status.status_reason = nil
end
before_transition any => [:update_errored] do |app_status, transition|
status_reason = transition.args.first
app_status.status_reason = status_reason if status_reason
end
end end
end end
end end
......
...@@ -5,8 +5,10 @@ module Storage ...@@ -5,8 +5,10 @@ module Storage
extend ActiveSupport::Concern extend ActiveSupport::Concern
def move_dir def move_dir
if any_project_has_container_registry_tags? proj_with_tags = first_project_with_container_registry_tags
raise Gitlab::UpdatePathError.new('Namespace cannot be moved, because at least one project has tags in container registry')
if proj_with_tags
raise Gitlab::UpdatePathError.new("Namespace #{name} (#{id}) cannot be moved because at least one project (e.g. #{proj_with_tags.name} (#{proj_with_tags.id})) has tags in container registry")
end end
parent_was = if parent_changed? && parent_id_was.present? parent_was = if parent_changed? && parent_id_was.present?
......
...@@ -135,6 +135,10 @@ class Namespace < ActiveRecord::Base ...@@ -135,6 +135,10 @@ class Namespace < ActiveRecord::Base
all_projects.any?(&:has_container_registry_tags?) all_projects.any?(&:has_container_registry_tags?)
end end
def first_project_with_container_registry_tags
all_projects.find(&:has_container_registry_tags?)
end
def send_update_instructions def send_update_instructions
projects.each do |project| projects.each do |project|
project.send_move_instructions("#{full_path_was}/#{project.path}") project.send_move_instructions("#{full_path_was}/#{project.path}")
......
...@@ -1365,6 +1365,18 @@ class Project < ActiveRecord::Base ...@@ -1365,6 +1365,18 @@ class Project < ActiveRecord::Base
end end
end end
# Filters `users` to return only authorized users of the project
def members_among(users)
if users.is_a?(ActiveRecord::Relation) && !users.loaded?
authorized_users.merge(users)
else
return [] if users.empty?
user_ids = authorized_users.where(users: { id: users.map(&:id) }).pluck(:id)
users.select { |user| user_ids.include?(user.id) }
end
end
def default_branch def default_branch
@default_branch ||= repository.root_ref if repository.exists? @default_branch ||= repository.root_ref if repository.exists?
end end
......
= form_for @application_setting, url: admin_application_settings_path(anchor: 'js-merge-request-settings'), html: { class: 'fieldset-form' } do |f|
= form_errors(@application_setting)
%fieldset
.form-group
= f.label :diff_max_patch_bytes, 'Maximum diff patch size (Bytes)', class: 'label-light'
= f.number_field :diff_max_patch_bytes, class: 'form-control'
%span.form-text.text-muted
Diff files surpassing this limit will be presented as 'too large'
and won't be expandable.
= link_to icon('question-circle'),
help_page_path('user/admin_area/diff_limits',
anchor: 'maximum-diff-patch-size')
= f.submit _('Save changes'), class: 'btn btn-success'
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
.sub-section .sub-section
.form-group .form-group
.form-check .form-check
= f.check_box :hashed_storage_enabled, class: 'form-check-input' = f.check_box :hashed_storage_enabled, class: 'form-check-input qa-hashed-storage-checkbox'
= f.label :hashed_storage_enabled, class: 'form-check-label' do = f.label :hashed_storage_enabled, class: 'form-check-label' do
Use hashed storage paths for newly created and renamed projects Use hashed storage paths for newly created and renamed projects
.form-text.text-muted .form-text.text-muted
...@@ -48,4 +48,4 @@ ...@@ -48,4 +48,4 @@
.form-text.text-muted .form-text.text-muted
= circuitbreaker_failure_reset_time_help_text = circuitbreaker_failure_reset_time_help_text
= f.submit 'Save changes', class: "btn btn-success" = f.submit 'Save changes', class: "btn btn-success qa-save-changes-button"
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
.settings-content .settings-content
= render partial: 'repository_mirrors_form' = render partial: 'repository_mirrors_form'
%section.settings.as-repository-storage.no-animate#js-repository-storage-settings{ class: ('expanded' if expanded_by_default?) } %section.settings.qa-repository-storage-settings.as-repository-storage.no-animate#js-repository-storage-settings{ class: ('expanded' if expanded_by_default?) }
.settings-header .settings-header
%h4 %h4
= _('Repository storage') = _('Repository storage')
......
...@@ -24,6 +24,17 @@ ...@@ -24,6 +24,17 @@
.settings-content .settings-content
= render 'account_and_limit' = render 'account_and_limit'
%section.settings.as-diff-limits.no-animate#js-merge-request-settings{ class: ('expanded' if expanded_by_default?) }
.settings-header
%h4
= _('Diff limits')
%button.btn.js-settings-toggle{ type: 'button' }
= expanded_by_default? ? _('Collapse') : _('Expand')
%p
= _('Diff content limits')
.settings-content
= render 'diff_limits'
%section.settings.as-signup.no-animate#js-signup-settings{ class: ('expanded' if expanded_by_default?) } %section.settings.as-signup.no-animate#js-signup-settings{ class: ('expanded' if expanded_by_default?) }
.settings-header .settings-header
%h4 %h4
...@@ -46,7 +57,7 @@ ...@@ -46,7 +57,7 @@
.settings-content .settings-content
= render 'signin' = render 'signin'
%section.qa-terms-settings.settings.as-terms.no-animate#js-terms-settings{ class: ('expanded' if expanded_by_default?) } %section.settings.as-terms.no-animate#js-terms-settings{ class: ('expanded' if expanded_by_default?) }
.settings-header .settings-header
%h4 %h4
= _('Terms of Service and Privacy Policy') = _('Terms of Service and Privacy Policy')
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
GitLab GitLab
Community Edition Community Edition
- if user_signed_in? - if user_signed_in?
%span= link_to Gitlab::VERSION, Gitlab::COM_URL + namespace_project_tag_path('gitlab-org', 'gitlab-ce', "v#{Gitlab::VERSION}") %span= link_to_version
= version_status_badge = version_status_badge
%hr %hr
......
.nav-sidebar{ class: ("sidebar-collapsed-desktop" if collapsed_sidebar?) } .nav-sidebar.qa-admin-sidebar{ class: ("sidebar-collapsed-desktop" if collapsed_sidebar?) }
.nav-sidebar-inner-scroll .nav-sidebar-inner-scroll
.context-header .context-header
= link_to admin_root_path, title: _('Admin Overview') do = link_to admin_root_path, title: _('Admin Overview') do
...@@ -197,10 +197,10 @@ ...@@ -197,10 +197,10 @@
= link_to admin_application_settings_path do = link_to admin_application_settings_path do
.nav-icon-container .nav-icon-container
= sprite_icon('settings') = sprite_icon('settings')
%span.nav-item-name %span.nav-item-name.qa-admin-settings-item
= _('Settings') = _('Settings')
%ul.sidebar-sub-level-items %ul.sidebar-sub-level-items.qa-admin-sidebar-submenu
= nav_link(controller: :application_settings, html_options: { class: "fly-out-top-item" } ) do = nav_link(controller: :application_settings, html_options: { class: "fly-out-top-item" } ) do
= link_to admin_application_settings_path do = link_to admin_application_settings_path do
%strong.fly-out-top-item-name %strong.fly-out-top-item-name
...@@ -215,7 +215,7 @@ ...@@ -215,7 +215,7 @@
%span %span
= _('Integrations') = _('Integrations')
= nav_link(path: 'application_settings#repository') do = nav_link(path: 'application_settings#repository') do
= link_to repository_admin_application_settings_path, title: _('Repository') do = link_to repository_admin_application_settings_path, title: _('Repository'), class: 'qa-admin-settings-repository-item' do
%span %span
= _('Repository') = _('Repository')
- if template_exists?('admin/application_settings/templates') - if template_exists?('admin/application_settings/templates')
......
...@@ -18,14 +18,15 @@ ...@@ -18,14 +18,15 @@
Preview Preview
%li.md-header-toolbar.active %li.md-header-toolbar.active
= markdown_toolbar_button({ icon: "bold", data: { "md-tag" => "**" }, title: "Add bold text" }) = markdown_toolbar_button({ icon: "bold", data: { "md-tag" => "**" }, title: s_("MarkdownToolbar|Add bold text") })
= markdown_toolbar_button({ icon: "italic", data: { "md-tag" => "*" }, title: "Add italic text" }) = markdown_toolbar_button({ icon: "italic", data: { "md-tag" => "*" }, title: s_("MarkdownToolbar|Add italic text") })
= markdown_toolbar_button({ icon: "quote", data: { "md-tag" => "> ", "md-prepend" => true }, title: "Insert a quote" }) = markdown_toolbar_button({ icon: "quote", data: { "md-tag" => "> ", "md-prepend" => true }, title: s_("MarkdownToolbar|Insert a quote") })
= markdown_toolbar_button({ icon: "code", data: { "md-tag" => "`", "md-block" => "```" }, title: "Insert code" }) = markdown_toolbar_button({ icon: "code", data: { "md-tag" => "`", "md-block" => "```" }, title: s_("MarkdownToolbar|Insert code") })
= markdown_toolbar_button({ icon: "list-bulleted", data: { "md-tag" => "* ", "md-prepend" => true }, title: "Add a bullet list" }) = markdown_toolbar_button({ icon: "link", data: { "md-tag" => "[{text}](url)", "md-select" => "url" }, title: s_("MarkdownToolbar|Add a link") })
= markdown_toolbar_button({ icon: "list-numbered", data: { "md-tag" => "1. ", "md-prepend" => true }, title: "Add a numbered list" }) = markdown_toolbar_button({ icon: "list-bulleted", data: { "md-tag" => "* ", "md-prepend" => true }, title: s_("MarkdownToolbar|Add a bullet list") })
= markdown_toolbar_button({ icon: "task-done", data: { "md-tag" => "* [ ] ", "md-prepend" => true }, title: "Add a task list" }) = markdown_toolbar_button({ icon: "list-numbered", data: { "md-tag" => "1. ", "md-prepend" => true }, title: s_("MarkdownToolbar|Add a numbered list") })
%button.toolbar-btn.toolbar-fullscreen-btn.js-zen-enter.has-tooltip{ type: "button", tabindex: -1, "aria-label": "Go full screen", title: "Go full screen", data: { container: "body" } } = markdown_toolbar_button({ icon: "task-done", data: { "md-tag" => "* [ ] ", "md-prepend" => true }, title: s_("MarkdownToolbar|Add a task list") })
%button.toolbar-btn.toolbar-fullscreen-btn.js-zen-enter.has-tooltip{ type: "button", tabindex: -1, "aria-label": "Go full screen", title: s_("MarkdownToolbar|Go full screen"), data: { container: "body" } }
= sprite_icon("screen-full") = sprite_icon("screen-full")
.md-write-holder .md-write-holder
......
.group-empty-state.row.align-items-center.justify-content-center.qa-groups-empty-state .group-empty-state.row.align-items-center.justify-content-center
.icon.text-center.order-md-2 .icon.text-center.order-md-2
= custom_icon("icon_empty_groups") = custom_icon("icon_empty_groups")
......
= form_tag request.path, method: :get, class: "group-filter-form js-group-filter-form", id: 'group-filter-form' do |f| = form_tag request.path, method: :get, class: "group-filter-form js-group-filter-form", id: 'group-filter-form' do |f|
= search_field_tag :filter, params[:filter], placeholder: s_('GroupsTree|Search by name'), class: 'group-filter-form-field form-control js-groups-list-filter', spellcheck: false, id: 'group-filter-form-field', tabindex: "2" = search_field_tag :filter, params[:filter], placeholder: s_('GroupsTree|Search by name'), class: 'group-filter-form-field form-control js-groups-list-filter qa-groups-filter', spellcheck: false, id: 'group-filter-form-field', tabindex: "2"
- is_current_user = issuable_author_is_current_user(issuable) - is_current_user = issuable_author_is_current_user(issuable)
- display_issuable_type = issuable_display_type(issuable) - display_issuable_type = issuable_display_type(issuable)
- button_method = issuable_close_reopen_button_method(issuable) - button_method = issuable_close_reopen_button_method(issuable)
- are_close_and_open_buttons_hidden = issuable_button_hidden?(issuable, true) && issuable_button_hidden?(issuable, false)
- if can_update - if is_current_user
- if is_current_user - if can_update
= link_to "Close #{display_issuable_type}", close_issuable_path(issuable), method: button_method, = link_to "Close #{display_issuable_type}", close_issuable_path(issuable), method: button_method,
class: "d-none d-sm-none d-md-block btn btn-grouped btn-close js-btn-issue-action #{issuable_button_visibility(issuable, true)}", title: "Close #{display_issuable_type}" class: "d-none d-sm-none d-md-block btn btn-grouped btn-close js-btn-issue-action #{issuable_button_visibility(issuable, true)}", title: "Close #{display_issuable_type}"
- else - if can_reopen
= render 'shared/issuable/close_reopen_report_toggle', issuable: issuable
- if can_reopen && is_current_user
= link_to "Reopen #{display_issuable_type}", reopen_issuable_path(issuable), method: button_method, = link_to "Reopen #{display_issuable_type}", reopen_issuable_path(issuable), method: button_method,
class: "d-none d-sm-none d-md-block btn btn-grouped btn-reopen js-btn-issue-action #{issuable_button_visibility(issuable, false)}", title: "Reopen #{display_issuable_type}" class: "d-none d-sm-none d-md-block btn btn-grouped btn-reopen js-btn-issue-action #{issuable_button_visibility(issuable, false)}", title: "Reopen #{display_issuable_type}"
- else - else
- if can_update && !are_close_and_open_buttons_hidden
= render 'shared/issuable/close_reopen_report_toggle', issuable: issuable
- else
= link_to 'Report abuse', new_abuse_report_path(user_id: issuable.author.id, ref_url: issuable_url(issuable)), = link_to 'Report abuse', new_abuse_report_path(user_id: issuable.author.id, ref_url: issuable_url(issuable)),
class: 'd-none d-sm-none d-md-block btn btn-grouped btn-close-color', title: 'Report abuse' class: 'd-none d-sm-none d-md-block btn btn-grouped btn-close-color', title: 'Report abuse'
---
title: Fix stale issue boards after browser back
merge_request: 22006
author: Johann Hubert Sonntagbauer
type: fixed
---
title: Improve lazy image loading performance by using IntersectionObserver where
available
merge_request: 21565
author:
type: performance
---
title: Filter issues without an Assignee via the API
merge_request: 22009
author: Eva Kadlecová
type: fixed
---
title: Add link button to markdown editor toolbar
merge_request: 18579
author: Jan Beckmann
type: added
---
title: Hides Close Merge request btn on merged Merge request
merge_request: 21840
author: Jacopo Beschi @jacopo-beschi
type: fixed
---
title: Fix migration to avoid an exception during upgrade
merge_request: 22055
author:
type: fixed
---
title: Use local tiller for Auto DevOps
merge_request: 22036
author:
type: changed
---
title: Update project path on project name autofill
merge_request: 22016
author:
type: other
---
title: Show SHA for pre-release versions on the help page
merge_request: 22026
author:
type: changed
---
title: Make single diff patch limit configurable
merge_request: 21886
author:
type: added
---
title: Update to Rouge 3.3.0 including frozen string literals for improved memory
usage
merge_request:
author:
type: changed
---
title: Improve logging when username update fails due to registry tags
merge_request: 22038
author:
type: other
---
title: Highlight current user in comments
merge_request: 21406
author:
type: changed
# frozen_string_literal: true
def get_eslint_files(files)
files.select do |file|
file.end_with?('.js', '.vue') &&
File.read(file).include?('/* eslint-disable')
end
end
eslint_candidates = get_eslint_files(git.added_files + git.modified_files)
return if eslint_candidates.empty?
warn 'This merge request changed files with disabled eslint rules. Please consider fixing them.'
markdown(<<~MARKDOWN)
## Disabled eslint rules
The following files have disabled `eslint` rules. Please consider fixing them:
* #{eslint_candidates.map { |path| "`#{path}`" }.join("\n* ")}
Run the following command for more details
```
node_modules/.bin/eslint --report-unused-disable-directives --no-inline-config \\
#{eslint_candidates.map { |path| " '#{path}'" }.join(" \\\n")}
```
MARKDOWN
# frozen_string_literal: true
def get_prettier_files(files)
files.select do |file|
file.end_with?('.js', '.scss', '.vue')
end
end
prettier_candidates = get_prettier_files(git.added_files + git.modified_files)
return if prettier_candidates.empty?
unpretty = `node_modules/prettier/bin-prettier.js --list-different #{prettier_candidates.join(" ")}`
.split(/$/)
.map(&:strip)
.reject(&:empty?)
return if unpretty.empty?
warn 'This merge request changed frontend files without pretty printing them.'
markdown(<<~MARKDOWN)
## Pretty print Frontend files
The following files should have been pretty printed with `prettier`:
* #{unpretty.map { |path| "`#{path}`" }.join("\n* ")}
Please run
```
node_modules/.bin/prettier --write \\
#{unpretty.map { |path| " '#{path}'" }.join(" \\\n")}
```
Also consider auto-formatting [on-save].
[on-save]: https://docs.gitlab.com/ee/development/new_fe_guide/style/prettier.html
MARKDOWN
# frozen_string_literal: true
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class AddDiffMaxPatchBytesToApplicationSettings < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
disable_ddl_transaction!
def up
add_column_with_default(:application_settings,
:diff_max_patch_bytes,
:integer,
default: 100.kilobytes,
allow_null: false)
end
def down
remove_column(:application_settings, :diff_max_patch_bytes)
end
end
...@@ -5,6 +5,8 @@ class RenameLoginRootNamespaces < ActiveRecord::Migration ...@@ -5,6 +5,8 @@ class RenameLoginRootNamespaces < ActiveRecord::Migration
DOWNTIME = false DOWNTIME = false
disable_ddl_transaction!
# We're taking over the /login namespace as part of a fix for the Jira integration # We're taking over the /login namespace as part of a fix for the Jira integration
def up def up
disable_statement_timeout do disable_statement_timeout do
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20180917172041) do ActiveRecord::Schema.define(version: 20180924141949) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -171,6 +171,7 @@ ActiveRecord::Schema.define(version: 20180917172041) do ...@@ -171,6 +171,7 @@ ActiveRecord::Schema.define(version: 20180917172041) do
t.boolean "user_show_add_ssh_key_message", default: true, null: false t.boolean "user_show_add_ssh_key_message", default: true, null: false
t.integer "usage_stats_set_by_user_id" t.integer "usage_stats_set_by_user_id"
t.integer "receive_max_input_size" t.integer "receive_max_input_size"
t.integer "diff_max_patch_bytes", default: 102400, null: false
end end
create_table "audit_events", force: :cascade do |t| create_table "audit_events", force: :cascade do |t|
......
...@@ -47,6 +47,7 @@ Learn how to install, configure, update, and maintain your GitLab instance. ...@@ -47,6 +47,7 @@ Learn how to install, configure, update, and maintain your GitLab instance.
- [Enforcing Terms of Service](../user/admin_area/settings/terms.md) - [Enforcing Terms of Service](../user/admin_area/settings/terms.md)
- [Third party offers](../user/admin_area/settings/third_party_offers.md) - [Third party offers](../user/admin_area/settings/third_party_offers.md)
- [Compliance](compliance.md): A collection of features from across the application that you may configure to help ensure that your GitLab instance and DevOps workflow meet compliance standards. - [Compliance](compliance.md): A collection of features from across the application that you may configure to help ensure that your GitLab instance and DevOps workflow meet compliance standards.
- [Diff limits](../user/admin_area/diff_limits.md): Configure the diff rendering size limits of branch comparison pages.
#### Customizing GitLab's appearance #### Customizing GitLab's appearance
......
...@@ -110,7 +110,8 @@ At this point the script would ask you to select the category of the change (map ...@@ -110,7 +110,8 @@ At this point the script would ask you to select the category of the change (map
4. New deprecation 4. New deprecation
5. Feature removal 5. Feature removal
6. Security fix 6. Security fix
7. Other 7. Performance improvement
8. Other
``` ```
The entry filename is based on the name of the current Git branch. If you run The entry filename is based on the name of the current Git branch. If you run
......
...@@ -2,13 +2,10 @@ ...@@ -2,13 +2,10 @@
Currently we rely on different sources to present diffs, these include: Currently we rely on different sources to present diffs, these include:
- Rugged gem
- Gitaly service - Gitaly service
- Database (through `merge_request_diff_files`) - Database (through `merge_request_diff_files`)
- Redis (cached highlighted diffs) - Redis (cached highlighted diffs)
We're constantly moving Rugged calls to Gitaly and the progress can be followed through [Gitaly repo](https://gitlab.com/gitlab-org/gitaly).
## Architecture overview ## Architecture overview
### Merge request diffs ### Merge request diffs
...@@ -19,8 +16,9 @@ we fetch the comparison information using `Gitlab::Git::Compare`, which fetches ...@@ -19,8 +16,9 @@ we fetch the comparison information using `Gitlab::Git::Compare`, which fetches
The diffs fetching process _limits_ single file diff sizes and the overall size of the whole diff through a series of constant values. Raw diff files are The diffs fetching process _limits_ single file diff sizes and the overall size of the whole diff through a series of constant values. Raw diff files are
then persisted on `merge_request_diff_files` table. then persisted on `merge_request_diff_files` table.
Even though diffs higher than 10kb are collapsed (`Gitlab::Git::Diff::COLLAPSE_LIMIT`), we still keep them on Postgres. However, diff files over _safety limits_ Even though diffs larger than 10% of the value of `ApplicationSettings#diff_max_patch_bytes` are collapsed,
(see the [Diff limits section](#diff-limits)) are _not_ persisted. we still keep them on Postgres. However, diff files larger than defined _safety limits_
(see the [Diff limits section](#diff-limits)) are _not_ persisted in the database.
In order to present diffs information on the Merge Request diffs page, we: In order to present diffs information on the Merge Request diffs page, we:
...@@ -102,23 +100,20 @@ Gitaly will only return the safe amount of data to be persisted on `merge_reques ...@@ -102,23 +100,20 @@ Gitaly will only return the safe amount of data to be persisted on `merge_reques
Limits that act onto each diff file of a collection. Files number, lines number and files size are considered. Limits that act onto each diff file of a collection. Files number, lines number and files size are considered.
```ruby #### Expandable patches (collapsed)
Gitlab::Git::Diff::COLLAPSE_LIMIT = 10.kilobytes
```
File diff will be collapsed (but be expandable) if it is larger than 10 kilobytes. Diff patches are collapsed when surpassing 10% of the value set in `ApplicationSettings#diff_max_patch_bytes`.
That is, it's equivalent to 10kb if the maximum allowed value is 100kb.
The diff will still be persisted and expandable if the patch size doesn't
surpass `ApplicationSettings#diff_max_patch_bytes`.
*Note:* Although this nomenclature (Collapsing) is also used on Gitaly, this limit is only used on GitLab (hardcoded - not sent to Gitaly). *Note:* Although this nomenclature (Collapsing) is also used on Gitaly, this limit is only used on GitLab (hardcoded - not sent to Gitaly).
Gitaly will only return `Diff.Collapsed` (RPC) when surpassing collection limits. Gitaly will only return `Diff.Collapsed` (RPC) when surpassing collection limits.
```ruby #### Not expandable patches (too large)
Gitlab::Git::Diff::SIZE_LIMIT = 100.kilobytes
```
File diff will not be rendered if it's larger than 100 kilobytes.
*Note:* This limit is currently hardcoded and applied on Gitaly and the RPC returns `Diff.TooLarge` when this limit is surpassed. The patch not be rendered if it's larger than `ApplicationSettings#diff_max_patch_bytes`.
Although we're still also applying it on GitLab, we should remove the redundancy from GitLab once we're confident with the Gitaly integration. Users will see a `This source diff could not be displayed because it is too large` message.
```ruby ```ruby
Commit::DIFF_SAFE_LINES = Gitlab::Git::DiffCollection::DEFAULT_LIMITS[:max_lines] = 5000 Commit::DIFF_SAFE_LINES = Gitlab::Git::DiffCollection::DEFAULT_LIMITS[:max_lines] = 5000
......
# Diff limits administration
NOTE: **Note:**
Merge requests and branch comparison views will be affected.
CAUTION: **Caution:**
These settings are currently under experimental state. They'll
increase the resource consumption of your instance and should
be edited mindfully.
1. Access **Admin area > Settings > General**
1. Expand **Diff limits**
### Maximum diff patch size
This is the content size each diff file (patch) is allowed to reach before
it's collapsed, without the possibility of being expanded. A link redirecting
to the blob view will be presented for the patches that surpass this limit.
Patches surpassing 10% of this content size will be automatically collapsed,
but expandable (a link to expand the diff will be presented).
# GitLab quick actions # GitLab quick actions
Quick actions are textual shortcuts for common actions on issues, merge requests Quick actions are textual shortcuts for common actions on issues, epics, merge requests,
or commits that are usually done by clicking buttons or dropdowns in GitLab's UI. and commits that are usually done by clicking buttons or dropdowns in GitLab's UI.
You can enter these commands while creating a new issue or merge request, and You can enter these commands while creating a new issue or merge request, or
in comments. Each command should be on a separate line in order to be properly in comments of issues, epics, merge requests, and commits. Each command should be
detected and executed. The commands are removed from the issue, merge request or on a separate line in order to be properly detected and executed. Once executed,
comment body before it is saved and will not be visible to anyone else. the commands are removed from the text body and not visible to anyone else.
Below is a list of all of the available commands and descriptions about what they ## Quick actions for issues and merge requests
do.
The following quick actions are applicable to both issues and merge requests threads,
discussions, and descriptions:
| Command | Action | Issue | Merge request |
|:---------------------------|:------------------------------ |:------|:--------------|
| `/tableflip <Comment>` | Append the comment with `(╯°□°)╯︵ ┻━┻` | ✓ | ✓ |
| `/shrug <Comment>` | Append the comment with `¯\_(ツ)_/¯` | ✓ | ✓ |
| `/todo` | Add a todo | ✓ | ✓ |
| `/done` | Mark todo as done | ✓ | ✓ |
| `/subscribe` | Subscribe | ✓ | ✓ |
| `/unsubscribe` | Unsubscribe | ✓ | ✓ |
| `/close` | Close | ✓ | ✓ |
| `/reopen` | Reopen | ✓ | ✓ |
| `/title <New title>` | Change title | ✓ | ✓ |
| `/award :emoji:` | Toggle emoji award | ✓ | ✓ |
| `/assign @user` | Assign one user | ✓ | ✓ |
| `/assign @user1 @user2` | Assign multiple users **[STARTER]** | ✓ | |
| `/unassign` | Remove assignee(s) | ✓ | ✓ |
| `/reassign @user1 @user2` | Change assignee | ✓ | ✓ |
| `/milestone %milestone` | Set milestone | ✓ | ✓ |
| `/remove_milestone` | Remove milestone | ✓ | ✓ |
| `/label ~label1 ~label2` | Add label(s) | ✓ | ✓ |
| `/unlabel ~label1 ~label2` | Remove all or specific label(s)| ✓ | ✓ |
| `/relabel ~label1 ~label2` | Replace label | ✓ | ✓ |
| <code>/copy_metadata #issue &#124; !merge_request</code> | Copy labels and milestone from other issue or merge request | ✓ | ✓ |
| <code>/estimate &lt;1w 3d 2h 14m&gt;</code> | Set time estimate | ✓ | ✓ |
| `/remove_estimate` | Remove time estimate | ✓ | ✓ |
| <code>/spend &lt;time(1h 30m &#124; -1h 5m)&gt; &lt;date(YYYY-MM-DD)&gt;</code> | Add or subtract spent time; optionally, specify the date that time was spent on | ✓ | ✓ |
| `/remove_time_spent` | Remove time spent | ✓ | ✓ |
| <code>/due &lt;in 2 days &#124; this Friday &#124; December 31st&gt;</code>| Set due date | ✓ |
| `/remove_due_date` | Remove due date | ✓ | |
| `/weight 0,1,2, ...` | Set weight **[STARTER]** | ✓ | |
| `/clear_weight` | Clears weight **[STARTER]** | ✓ | |
| `/epic <group&epic &#124; Epic URL>` | Add to epic **[ULTIMATE]** | ✓ | |
| `/remove_epic` | Removes from epic **[ULTIMATE]** | ✓ | |
| `/confidential` | Make confidential | ✓ | |
| `/duplicate #issue` | Mark this issue as a duplicate of another issue | ✓ |
| `/move path/to/project` | Move this issue to another project | ✓ | |
| `/target_branch <Local branch Name>` | Set target branch | | ✓ |
| `/wip` | Toggle the Work In Progress status | | ✓ |
| `/merge` | Merge (when pipeline succeeds) | | ✓ |
## Quick actions for commit messages
The following quick actions are applicable for commit messages:
| Command | Action | | Command | Action |
|:---------------------------|:-------------| |:------------------------|:------------------------------------------|
| `/close` | Close the issue or merge request | | `/tag v1.2.3 <message>` | Tags this commit with an optional message |
| `/reopen` | Reopen the issue or merge request |
| `/merge` | Merge (when pipeline succeeds) | ## Quick actions for Epics **[ULTIMATE]**
| `/title <New title>` | Change title |
| `/assign @username` | Assign | The following quick actions are applicable for epics threads and description:
| `/unassign` | Remove assignee |
| `/milestone %milestone` | Set milestone | | Command | Action |
| `/remove_milestone` | Remove milestone | |:---------------------------|:----------------------------------------|
| `/label ~foo ~"bar baz"` | Add label(s) | | `/tableflip <Comment>` | Append the comment with `(╯°□°)╯︵ ┻━┻` |
| `/unlabel ~foo ~"bar baz"` | Remove all or specific label(s) | | `/shrug <Comment>` | Append the comment with `¯\_(ツ)_/¯` |
| `/relabel ~foo ~"bar baz"` | Replace all label(s) |
| `/todo` | Add a todo | | `/todo` | Add a todo |
| `/done` | Mark todo as done | | `/done` | Mark todo as done |
| `/subscribe` | Subscribe | | `/subscribe` | Subscribe |
| `/unsubscribe` | Unsubscribe | | `/unsubscribe` | Unsubscribe |
| <code>/due &lt;in 2 days &#124; this Friday &#124; December 31st&gt;</code> | Set due date | | `/close` | Close |
| `/remove_due_date` | Remove due date | | `/reopen` | Reopen |
| `/wip` | Toggle the Work In Progress status | | `/title <New title>` | Change title |
| <code>/estimate &lt;1w 3d 2h 14m&gt;</code> | Set time estimate | | `/award :emoji:` | Toggle emoji award |
| `/remove_estimate` | Remove estimated time | | `/label ~label1 ~label2` | Add label(s) |
| <code>/spend &lt;time(1h 30m &#124; -1h 5m)&gt; &lt;date(YYYY-MM-DD)&gt;</code> | Add or subtract spent time; optionally, specify the date that time was spent on | | `/unlabel ~label1 ~label2` | Remove all or specific label(s) |
| `/remove_time_spent` | Remove time spent | | `/relabel ~label1 ~label2` | Replace label |
| `/target_branch <Branch Name>` | Set target branch for current merge request | \ No newline at end of file
| `/award :emoji:` | Toggle award for :emoji: |
| `/board_move ~column` | Move issue to column on the board |
| `/duplicate #issue` | Closes this issue and marks it as a duplicate of another issue |
| `/move path/to/project` | Moves issue to another project |
| `/tag v1.2.3 <message>` | Tags a commit with a given tag name and optional message |
| `/tableflip` | Append the comment with `(╯°□°)╯︵ ┻━┻` |
| `/shrug` | Append the comment with `¯\_(ツ)_/¯` |
| <code>/copy_metadata #issue &#124; !merge_request</code> | Copy labels and milestone from other issue or merge request |
| `/confidential` | Makes the issue confidential |
| `/lock` | Lock the discussion |
| `/unlock` | Unlock the discussion |
...@@ -47,4 +47,8 @@ module Gitlab ...@@ -47,4 +47,8 @@ module Gitlab
def self.dev_env_or_com? def self.dev_env_or_com?
Rails.env.development? || org? || com? Rails.env.development? || org? || com?
end end
def self.pre_release?
VERSION.include?('pre')
end
end end
...@@ -11,7 +11,7 @@ module Gitlab ...@@ -11,7 +11,7 @@ module Gitlab
include Validatable include Validatable
include Attributable include Attributable
ALLOWED_KEYS = %i[junit sast dependency_scanning container_scanning dast].freeze ALLOWED_KEYS = %i[junit codequality sast dependency_scanning container_scanning dast].freeze
attributes ALLOWED_KEYS attributes ALLOWED_KEYS
...@@ -21,6 +21,7 @@ module Gitlab ...@@ -21,6 +21,7 @@ module Gitlab
with_options allow_nil: true do with_options allow_nil: true do
validates :junit, array_of_strings_or_string: true validates :junit, array_of_strings_or_string: true
validates :codequality, array_of_strings_or_string: true
validates :sast, array_of_strings_or_string: true validates :sast, array_of_strings_or_string: true
validates :dependency_scanning, array_of_strings_or_string: true validates :dependency_scanning, array_of_strings_or_string: true
validates :container_scanning, array_of_strings_or_string: true validates :container_scanning, array_of_strings_or_string: true
......
...@@ -49,7 +49,7 @@ variables: ...@@ -49,7 +49,7 @@ variables:
POSTGRES_DB: $CI_ENVIRONMENT_SLUG POSTGRES_DB: $CI_ENVIRONMENT_SLUG
KUBERNETES_VERSION: 1.8.6 KUBERNETES_VERSION: 1.8.6
HELM_VERSION: 2.10.0 HELM_VERSION: 2.11.0
DOCKER_DRIVER: overlay2 DOCKER_DRIVER: overlay2
...@@ -239,7 +239,7 @@ review: ...@@ -239,7 +239,7 @@ review:
- install_dependencies - install_dependencies
- download_chart - download_chart
- ensure_namespace - ensure_namespace
- install_tiller - initialize_tiller
- create_secret - create_secret
- deploy - deploy
- persist_environment_url - persist_environment_url
...@@ -265,6 +265,7 @@ stop_review: ...@@ -265,6 +265,7 @@ stop_review:
GIT_STRATEGY: none GIT_STRATEGY: none
script: script:
- install_dependencies - install_dependencies
- initialize_tiller
- delete - delete
environment: environment:
name: review/$CI_COMMIT_REF_NAME name: review/$CI_COMMIT_REF_NAME
...@@ -299,7 +300,7 @@ staging: ...@@ -299,7 +300,7 @@ staging:
- install_dependencies - install_dependencies
- download_chart - download_chart
- ensure_namespace - ensure_namespace
- install_tiller - initialize_tiller
- create_secret - create_secret
- deploy - deploy
environment: environment:
...@@ -323,7 +324,7 @@ canary: ...@@ -323,7 +324,7 @@ canary:
- install_dependencies - install_dependencies
- download_chart - download_chart
- ensure_namespace - ensure_namespace
- install_tiller - initialize_tiller
- create_secret - create_secret
- deploy canary - deploy canary
environment: environment:
...@@ -344,7 +345,7 @@ canary: ...@@ -344,7 +345,7 @@ canary:
- install_dependencies - install_dependencies
- download_chart - download_chart
- ensure_namespace - ensure_namespace
- install_tiller - initialize_tiller
- create_secret - create_secret
- deploy - deploy
- delete canary - delete canary
...@@ -392,7 +393,7 @@ production_manual: ...@@ -392,7 +393,7 @@ production_manual:
- install_dependencies - install_dependencies
- download_chart - download_chart
- ensure_namespace - ensure_namespace
- install_tiller - initialize_tiller
- create_secret - create_secret
- deploy rollout $ROLLOUT_PERCENTAGE - deploy rollout $ROLLOUT_PERCENTAGE
- scale stable $((100-ROLLOUT_PERCENTAGE)) - scale stable $((100-ROLLOUT_PERCENTAGE))
...@@ -651,7 +652,12 @@ rollout 100%: ...@@ -651,7 +652,12 @@ rollout 100%:
curl "https://kubernetes-helm.storage.googleapis.com/helm-v${HELM_VERSION}-linux-amd64.tar.gz" | tar zx curl "https://kubernetes-helm.storage.googleapis.com/helm-v${HELM_VERSION}-linux-amd64.tar.gz" | tar zx
mv linux-amd64/helm /usr/bin/ mv linux-amd64/helm /usr/bin/
mv linux-amd64/tiller /usr/bin/
helm version --client helm version --client
tiller -version
helm init --client-only
helm plugin install https://github.com/adamreese/helm-local
curl -L -o /usr/bin/kubectl "https://storage.googleapis.com/kubernetes-release/release/v${KUBERNETES_VERSION}/bin/linux/amd64/kubectl" curl -L -o /usr/bin/kubectl "https://storage.googleapis.com/kubernetes-release/release/v${KUBERNETES_VERSION}/bin/linux/amd64/kubectl"
chmod +x /usr/bin/kubectl chmod +x /usr/bin/kubectl
...@@ -758,10 +764,13 @@ rollout 100%: ...@@ -758,10 +764,13 @@ rollout 100%:
echo "" echo ""
} }
function install_tiller() { function initialize_tiller() {
echo "Checking Tiller..." echo "Checking Tiller..."
helm init --upgrade
kubectl rollout status -n "$TILLER_NAMESPACE" -w "deployment/tiller-deploy" helm local start
helm local status
export HELM_HOST=":44134"
if ! helm version --debug; then if ! helm version --debug; then
echo "Failed to init Tiller." echo "Failed to init Tiller."
return 1 return 1
......
...@@ -19,13 +19,17 @@ module Gitlab ...@@ -19,13 +19,17 @@ module Gitlab
alias_method :expanded?, :expanded alias_method :expanded?, :expanded
SERIALIZE_KEYS = %i(diff new_path old_path a_mode b_mode new_file renamed_file deleted_file too_large).freeze # The default maximum content size to display a diff patch.
#
# If this value ever changes, make sure to create a migration to update
# current records, and default of `ApplicationSettings#diff_max_patch_bytes`.
DEFAULT_MAX_PATCH_BYTES = 100.kilobytes
# The maximum size of a diff to display. # This is a limitation applied on the source (Gitaly), therefore we don't allow
SIZE_LIMIT = 100.kilobytes # persisting limits over that.
MAX_PATCH_BYTES_UPPER_BOUND = 500.kilobytes
# The maximum size before a diff is collapsed. SERIALIZE_KEYS = %i(diff new_path old_path a_mode b_mode new_file renamed_file deleted_file too_large).freeze
COLLAPSE_LIMIT = 10.kilobytes
class << self class << self
def between(repo, head, base, options = {}, *paths) def between(repo, head, base, options = {}, *paths)
...@@ -105,6 +109,26 @@ module Gitlab ...@@ -105,6 +109,26 @@ module Gitlab
def binary_message(old_path, new_path) def binary_message(old_path, new_path)
"Binary files #{old_path} and #{new_path} differ\n" "Binary files #{old_path} and #{new_path} differ\n"
end end
# Returns the limit of bytes a single diff file can reach before it
# appears as 'collapsed' for end-users.
# By convention, it's 10% of the persisted `diff_max_patch_bytes`.
#
# Example: If we have 100k for the `diff_max_patch_bytes`, it will be 10k by
# default.
#
# Patches surpassing this limit should still be persisted in the database.
def patch_safe_limit_bytes
patch_hard_limit_bytes / 10
end
# Returns the limit for a single diff file (patch).
#
# Patches surpassing this limit shouldn't be persisted in the database
# and will be presented as 'too large' for end-users.
def patch_hard_limit_bytes
Gitlab::CurrentSettings.diff_max_patch_bytes
end
end end
def initialize(raw_diff, expanded: true) def initialize(raw_diff, expanded: true)
...@@ -150,7 +174,7 @@ module Gitlab ...@@ -150,7 +174,7 @@ module Gitlab
def too_large? def too_large?
if @too_large.nil? if @too_large.nil?
@too_large = @diff.bytesize >= SIZE_LIMIT @too_large = @diff.bytesize >= self.class.patch_hard_limit_bytes
else else
@too_large @too_large
end end
...@@ -168,7 +192,7 @@ module Gitlab ...@@ -168,7 +192,7 @@ module Gitlab
def collapsed? def collapsed?
return @collapsed if defined?(@collapsed) return @collapsed if defined?(@collapsed)
@collapsed = !expanded && @diff.bytesize >= COLLAPSE_LIMIT @collapsed = !expanded && @diff.bytesize >= self.class.patch_safe_limit_bytes
end end
def collapse! def collapse!
...@@ -219,30 +243,6 @@ module Gitlab ...@@ -219,30 +243,6 @@ module Gitlab
collapse! collapse!
end end
end end
# If the patch surpasses any of the diff limits it calls the appropiate
# prune method and returns true. Otherwise returns false.
def prune_large_patch(patch)
size = 0
patch.each_hunk do |hunk|
hunk.each_line do |line|
size += line.content.bytesize
if size >= SIZE_LIMIT
too_large!
return true # rubocop:disable Cop/AvoidReturnFromBlocks
end
end
end
if !expanded && size >= COLLAPSE_LIMIT
collapse!
return true
end
false
end
end end
end end
end end
...@@ -19,7 +19,7 @@ module Gitlab ...@@ -19,7 +19,7 @@ module Gitlab
limits[:safe_max_files] = [limits[:max_files], DEFAULT_LIMITS[:max_files]].min limits[:safe_max_files] = [limits[:max_files], DEFAULT_LIMITS[:max_files]].min
limits[:safe_max_lines] = [limits[:max_lines], DEFAULT_LIMITS[:max_lines]].min limits[:safe_max_lines] = [limits[:max_lines], DEFAULT_LIMITS[:max_lines]].min
limits[:safe_max_bytes] = limits[:safe_max_files] * 5.kilobytes # Average 5 KB per file limits[:safe_max_bytes] = limits[:safe_max_files] * 5.kilobytes # Average 5 KB per file
limits[:max_patch_bytes] = Gitlab::Git::Diff::SIZE_LIMIT limits[:max_patch_bytes] = Gitlab::Git::Diff.patch_hard_limit_bytes
OpenStruct.new(limits) OpenStruct.new(limits)
end end
......
...@@ -17,6 +17,12 @@ module Gitlab ...@@ -17,6 +17,12 @@ module Gitlab
kubeclient.create_pod(command.pod_resource) kubeclient.create_pod(command.pod_resource)
end end
def update(command)
namespace.ensure_exists!
update_config_map(command)
kubeclient.create_pod(command.pod_resource)
end
## ##
# Returns Pod phase # Returns Pod phase
# #
...@@ -36,6 +42,12 @@ module Gitlab ...@@ -36,6 +42,12 @@ module Gitlab
kubeclient.delete_pod(pod_name, namespace.name) kubeclient.delete_pod(pod_name, namespace.name)
end end
def get_config_map(config_map_name)
namespace.ensure_exists!
kubeclient.get_config_map(config_map_name, namespace.name)
end
private private
attr_reader :kubeclient, :namespace attr_reader :kubeclient, :namespace
...@@ -46,6 +58,12 @@ module Gitlab ...@@ -46,6 +58,12 @@ module Gitlab
end end
end end
def update_config_map(command)
command.config_map_resource.tap do |config_map_resource|
kubeclient.update_config_map(config_map_resource)
end
end
def create_service_account(command) def create_service_account(command)
command.service_account_resource.tap do |service_account_resource| command.service_account_resource.tap do |service_account_resource|
break unless service_account_resource break unless service_account_resource
......
# frozen_string_literal: true
module Gitlab
module Kubernetes
module Helm
class UpgradeCommand
include BaseCommand
attr_reader :name, :chart, :version, :repository, :files
def initialize(name, chart:, files:, rbac:, version: nil, repository: nil)
@name = name
@chart = chart
@rbac = rbac
@version = version
@files = files
@repository = repository
end
def generate_script
super + [
init_command,
repository_command,
script_command
].compact.join("\n")
end
def rbac?
@rbac
end
def pod_name
"upgrade-#{name}"
end
private
def init_command
'helm init --client-only >/dev/null'
end
def repository_command
"helm repo add #{name} #{repository}" if repository
end
def script_command
upgrade_flags = "#{optional_version_flag}#{optional_tls_flags}" \
" --reset-values" \
" --install" \
" --namespace #{::Gitlab::Kubernetes::Helm::NAMESPACE}" \
" -f /data/helm/#{name}/config/values.yaml"
"helm upgrade #{name} #{chart}#{upgrade_flags} >/dev/null\n"
end
def optional_version_flag
" --version #{version}" if version
end
def optional_tls_flags
return unless files.key?(:'ca.pem')
" --tls" \
" --tls-ca-cert #{files_dir}/ca.pem" \
" --tls-cert #{files_dir}/cert.pem" \
" --tls-key #{files_dir}/key.pem"
end
end
end
end
end
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
...@@ -2292,6 +2292,12 @@ msgstr "" ...@@ -2292,6 +2292,12 @@ msgstr ""
msgid "Details" msgid "Details"
msgstr "" msgstr ""
msgid "Diff content limits"
msgstr ""
msgid "Diff limits"
msgstr ""
msgid "Diffs|No file name available" msgid "Diffs|No file name available"
msgstr "" msgstr ""
...@@ -3645,6 +3651,33 @@ msgstr "" ...@@ -3645,6 +3651,33 @@ msgstr ""
msgid "Markdown enabled" msgid "Markdown enabled"
msgstr "" msgstr ""
msgid "MarkdownToolbar|Add a bullet list"
msgstr ""
msgid "MarkdownToolbar|Add a link"
msgstr ""
msgid "MarkdownToolbar|Add a numbered list"
msgstr ""
msgid "MarkdownToolbar|Add a task list"
msgstr ""
msgid "MarkdownToolbar|Add bold text"
msgstr ""
msgid "MarkdownToolbar|Add italic text"
msgstr ""
msgid "MarkdownToolbar|Go full screen"
msgstr ""
msgid "MarkdownToolbar|Insert a quote"
msgstr ""
msgid "MarkdownToolbar|Insert code"
msgstr ""
msgid "Max access level" msgid "Max access level"
msgstr "" msgstr ""
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment