Commit 7ad8704c authored by Filipa Lacerda's avatar Filipa Lacerda

Merge branch 'master' into 40468-empty-states

* master: (76 commits)
  Fix image view mode
  Remove the need for destroy and add a comment in the spec
  Use build instead of create in importer spec
  Simplify normalizing of paths
  Fix failing importer test case on MySQL due to missing trailing slash in root path
  Fix gitlab:import:repos Rake task moving repositories into the wrong location
  Allow git pull/push on project redirects
  Resolve "Include asset_sync gem"
  Use prefix for TableOfContents filter hrefs
  Resolve "No feedback when checking on checklist if potential spam was detected"
  Fix Rubocop
  Fix N+1 query when displaying events
  Add Chain::Command specs
  Fix a bug of before_sha being inproperly evaluated to `checkout_sha`
  Implement and use Gitlab::Ci::Pipeline::Chain::Command
  Fix invalid pipeline build chain tag evaluation
  Add docs explaining why you get signed out with "Remember me"
  Move the circuitbreaker check out in a separate process
  Migrate Git::Repository#fsck to Gitaly
  Fix new personal access token showing up in a flash message
  ...
parents 37450e51 fb47f2a7
...@@ -598,6 +598,7 @@ merge request: ...@@ -598,6 +598,7 @@ merge request:
present time and never use past tense (has been/was). For example instead present time and never use past tense (has been/was). For example instead
of _prohibited this user from being saved due to the following errors:_ the of _prohibited this user from being saved due to the following errors:_ the
text should be _sorry, we could not create your account because:_ text should be _sorry, we could not create your account because:_
1. Code should be written in [US English][us-english]
This is also the style used by linting tools such as This is also the style used by linting tools such as
[RuboCop](https://github.com/bbatsov/rubocop), [RuboCop](https://github.com/bbatsov/rubocop),
...@@ -663,6 +664,7 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor ...@@ -663,6 +664,7 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor
[GitLab Inc engineering workflow]: https://about.gitlab.com/handbook/engineering/workflow/#labelling-issues [GitLab Inc engineering workflow]: https://about.gitlab.com/handbook/engineering/workflow/#labelling-issues
[polling-etag]: https://docs.gitlab.com/ce/development/polling.html [polling-etag]: https://docs.gitlab.com/ce/development/polling.html
[testing]: doc/development/testing_guide/index.md [testing]: doc/development/testing_guide/index.md
[us-english]: https://en.wikipedia.org/wiki/American_English
[^1]: Please note that specs other than JavaScript specs are considered backend [^1]: Please note that specs other than JavaScript specs are considered backend
code. code.
...@@ -411,3 +411,6 @@ gem 'flipper-active_record', '~> 0.10.2' ...@@ -411,3 +411,6 @@ gem 'flipper-active_record', '~> 0.10.2'
# Structured logging # Structured logging
gem 'lograge', '~> 0.5' gem 'lograge', '~> 0.5'
gem 'grape_logging', '~> 1.7' gem 'grape_logging', '~> 1.7'
# Asset synchronization
gem 'asset_sync', '~> 2.2.0'
...@@ -58,6 +58,11 @@ GEM ...@@ -58,6 +58,11 @@ GEM
asciidoctor (1.5.3) asciidoctor (1.5.3)
asciidoctor-plantuml (0.0.7) asciidoctor-plantuml (0.0.7)
asciidoctor (~> 1.5) asciidoctor (~> 1.5)
asset_sync (2.2.0)
activemodel (>= 4.1.0)
fog-core
mime-types (>= 2.99)
unf
ast (2.3.0) ast (2.3.0)
atomic (1.1.99) atomic (1.1.99)
attr_encrypted (3.0.3) attr_encrypted (3.0.3)
...@@ -486,7 +491,6 @@ GEM ...@@ -486,7 +491,6 @@ GEM
mini_mime (0.1.4) mini_mime (0.1.4)
mini_portile2 (2.3.0) mini_portile2 (2.3.0)
minitest (5.7.0) minitest (5.7.0)
mmap2 (2.2.9)
mousetrap-rails (1.4.6) mousetrap-rails (1.4.6)
multi_json (1.12.2) multi_json (1.12.2)
multi_xml (0.6.0) multi_xml (0.6.0)
...@@ -623,8 +627,7 @@ GEM ...@@ -623,8 +627,7 @@ GEM
parser parser
unparser unparser
procto (0.0.3) procto (0.0.3)
prometheus-client-mmap (0.7.0.beta39) prometheus-client-mmap (0.7.0.beta43)
mmap2 (~> 2.2, >= 2.2.9)
pry (0.10.4) pry (0.10.4)
coderay (~> 1.1.0) coderay (~> 1.1.0)
method_source (~> 0.8.1) method_source (~> 0.8.1)
...@@ -977,6 +980,7 @@ DEPENDENCIES ...@@ -977,6 +980,7 @@ DEPENDENCIES
asana (~> 0.6.0) asana (~> 0.6.0)
asciidoctor (~> 1.5.2) asciidoctor (~> 1.5.2)
asciidoctor-plantuml (= 0.0.7) asciidoctor-plantuml (= 0.0.7)
asset_sync (~> 2.2.0)
attr_encrypted (~> 3.0.0) attr_encrypted (~> 3.0.0)
awesome_print (~> 1.2.0) awesome_print (~> 1.2.0)
babosa (~> 1.0.2) babosa (~> 1.0.2)
......
...@@ -130,7 +130,8 @@ freeze date (the 7th) should have a corresponding Enterprise Edition merge ...@@ -130,7 +130,8 @@ freeze date (the 7th) should have a corresponding Enterprise Edition merge
request, even if there are no conflicts. This is to reduce the size of the request, even if there are no conflicts. This is to reduce the size of the
subsequent EE merge, as we often merge a lot to CE on the release date. For more subsequent EE merge, as we often merge a lot to CE on the release date. For more
information, see information, see
[limit conflicts with EE when developing on CE][limit_ee_conflicts]. [Automatic CE->EE merge][automatic_ce_ee_merge] and
[Guidelines for implementing Enterprise Edition features][ee_features].
### After the 7th ### After the 7th
...@@ -281,4 +282,5 @@ still an issue I encourage you to open it on the [GitLab.com issue tracker](http ...@@ -281,4 +282,5 @@ still an issue I encourage you to open it on the [GitLab.com issue tracker](http
["Implement design & UI elements" guidelines]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#implement-design-ui-elements ["Implement design & UI elements" guidelines]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#implement-design-ui-elements
[Thoughtbot code review guide]: https://github.com/thoughtbot/guides/tree/master/code-review [Thoughtbot code review guide]: https://github.com/thoughtbot/guides/tree/master/code-review
[done]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#definition-of-done [done]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#definition-of-done
[limit_ee_conflicts]: https://docs.gitlab.com/ce/development/limit_ee_conflicts.html [automatic_ce_ee_merge]: https://docs.gitlab.com/ce/development/automatic_ce_ee_merge.html
[ee_features]: https://docs.gitlab.com/ce/development/ee_features.html
...@@ -121,7 +121,7 @@ export default class ImageFile { ...@@ -121,7 +121,7 @@ export default class ImageFile {
return $('.swipe.view', this.file).each((function(_this) { return $('.swipe.view', this.file).each((function(_this) {
return function(index, view) { return function(index, view) {
var $swipeWrap, $swipeBar, $swipeFrame, wrapPadding, ref; var $swipeWrap, $swipeBar, $swipeFrame, wrapPadding, ref;
ref = this.prepareFrames(view), maxWidth = ref[0], maxHeight = ref[1]; ref = _this.prepareFrames(view), maxWidth = ref[0], maxHeight = ref[1];
$swipeFrame = $('.swipe-frame', view); $swipeFrame = $('.swipe-frame', view);
$swipeWrap = $('.swipe-wrap', view); $swipeWrap = $('.swipe-wrap', view);
$swipeBar = $('.swipe-bar', view); $swipeBar = $('.swipe-bar', view);
...@@ -158,7 +158,7 @@ export default class ImageFile { ...@@ -158,7 +158,7 @@ export default class ImageFile {
return $('.onion-skin.view', this.file).each((function(_this) { return $('.onion-skin.view', this.file).each((function(_this) {
return function(index, view) { return function(index, view) {
var $frame, $track, $dragger, $frameAdded, framePadding, ref, dragging = false; var $frame, $track, $dragger, $frameAdded, framePadding, ref, dragging = false;
ref = this.prepareFrames(view), maxWidth = ref[0], maxHeight = ref[1]; ref = _this.prepareFrames(view), maxWidth = ref[0], maxHeight = ref[1];
$frame = $('.onion-skin-frame', view); $frame = $('.onion-skin-frame', view);
$frameAdded = $('.frame.added', view); $frameAdded = $('.frame.added', view);
$track = $('.drag-track', view); $track = $('.drag-track', view);
......
/* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, no-var, object-shorthand, consistent-return, no-unused-vars, comma-dangle, vars-on-top, prefer-template, max-len */ /* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, no-var, object-shorthand, consistent-return, no-unused-vars, comma-dangle, vars-on-top, prefer-template, max-len */
window.Compare = (function() { export default class Compare {
function Compare(opts) { constructor(opts) {
this.opts = opts; this.opts = opts;
this.source_loading = $(".js-source-loading"); this.source_loading = $(".js-source-loading");
this.target_loading = $(".js-target-loading"); this.target_loading = $(".js-target-loading");
...@@ -34,12 +34,12 @@ window.Compare = (function() { ...@@ -34,12 +34,12 @@ window.Compare = (function() {
this.initialState(); this.initialState();
} }
Compare.prototype.initialState = function() { initialState() {
this.getSourceHtml(); this.getSourceHtml();
return this.getTargetHtml(); this.getTargetHtml();
}; }
Compare.prototype.getTargetProject = function() { getTargetProject() {
return $.ajax({ return $.ajax({
url: this.opts.targetProjectUrl, url: this.opts.targetProjectUrl,
data: { data: {
...@@ -52,22 +52,22 @@ window.Compare = (function() { ...@@ -52,22 +52,22 @@ window.Compare = (function() {
return $('.js-target-branch-dropdown .dropdown-content').html(html); return $('.js-target-branch-dropdown .dropdown-content').html(html);
} }
}); });
}; }
Compare.prototype.getSourceHtml = function() { getSourceHtml() {
return this.sendAjax(this.opts.sourceBranchUrl, this.source_loading, '.mr_source_commit', { return this.constructor.sendAjax(this.opts.sourceBranchUrl, this.source_loading, '.mr_source_commit', {
ref: $("input[name='merge_request[source_branch]']").val() ref: $("input[name='merge_request[source_branch]']").val()
}); });
}; }
Compare.prototype.getTargetHtml = function() { getTargetHtml() {
return this.sendAjax(this.opts.targetBranchUrl, this.target_loading, '.mr_target_commit', { return this.constructor.sendAjax(this.opts.targetBranchUrl, this.target_loading, '.mr_target_commit', {
target_project_id: $("input[name='merge_request[target_project_id]']").val(), target_project_id: $("input[name='merge_request[target_project_id]']").val(),
ref: $("input[name='merge_request[target_branch]']").val() ref: $("input[name='merge_request[target_branch]']").val()
}); });
}; }
Compare.prototype.sendAjax = function(url, loading, target, data) { static sendAjax(url, loading, target, data) {
var $target; var $target;
$target = $(target); $target = $(target);
return $.ajax({ return $.ajax({
...@@ -84,7 +84,5 @@ window.Compare = (function() { ...@@ -84,7 +84,5 @@ window.Compare = (function() {
gl.utils.localTimeAgo($('.js-timeago', className)); gl.utils.localTimeAgo($('.js-timeago', className));
} }
}); });
}; }
}
return Compare;
})();
/* eslint-disable func-names, space-before-function-paren, one-var, no-var, one-var-declaration-per-line, object-shorthand, comma-dangle, prefer-arrow-callback, no-else-return, newline-per-chained-call, wrap-iife, max-len */ /* eslint-disable func-names, space-before-function-paren, one-var, no-var, one-var-declaration-per-line, object-shorthand, comma-dangle, prefer-arrow-callback, no-else-return, newline-per-chained-call, wrap-iife, max-len */
window.CompareAutocomplete = (function() { export default function initCompareAutocomplete() {
function CompareAutocomplete() { $('.js-compare-dropdown').each(function() {
this.initDropdown(); var $dropdown, selected;
} $dropdown = $(this);
selected = $dropdown.data('selected');
CompareAutocomplete.prototype.initDropdown = function() { const $dropdownContainer = $dropdown.closest('.dropdown');
return $('.js-compare-dropdown').each(function() { const $fieldInput = $(`input[name="${$dropdown.data('field-name')}"]`, $dropdownContainer);
var $dropdown, selected; const $filterInput = $('input[type="search"]', $dropdownContainer);
$dropdown = $(this); $dropdown.glDropdown({
selected = $dropdown.data('selected'); data: function(term, callback) {
const $dropdownContainer = $dropdown.closest('.dropdown'); return $.ajax({
const $fieldInput = $(`input[name="${$dropdown.data('field-name')}"]`, $dropdownContainer); url: $dropdown.data('refs-url'),
const $filterInput = $('input[type="search"]', $dropdownContainer); data: {
$dropdown.glDropdown({ ref: $dropdown.data('ref'),
data: function(term, callback) { search: term,
return $.ajax({
url: $dropdown.data('refs-url'),
data: {
ref: $dropdown.data('ref'),
search: term,
}
}).done(function(refs) {
return callback(refs);
});
},
selectable: true,
filterable: true,
filterRemote: true,
fieldName: $dropdown.data('field-name'),
filterInput: 'input[type="search"]',
renderRow: function(ref) {
var link;
if (ref.header != null) {
return $('<li />').addClass('dropdown-header').text(ref.header);
} else {
link = $('<a />').attr('href', '#').addClass(ref === selected ? 'is-active' : '').text(ref).attr('data-ref', escape(ref));
return $('<li />').append(link);
} }
}, }).done(function(refs) {
id: function(obj, $el) { return callback(refs);
return $el.attr('data-ref'); });
}, },
toggleLabel: function(obj, $el) { selectable: true,
return $el.text().trim(); filterable: true,
} filterRemote: true,
}); fieldName: $dropdown.data('field-name'),
$filterInput.on('keyup', (e) => { filterInput: 'input[type="search"]',
const keyCode = e.keyCode || e.which; renderRow: function(ref) {
if (keyCode !== 13) return; var link;
const text = $filterInput.val(); if (ref.header != null) {
$fieldInput.val(text); return $('<li />').addClass('dropdown-header').text(ref.header);
$('.dropdown-toggle-text', $dropdown).text(text); } else {
$dropdownContainer.removeClass('open'); link = $('<a />').attr('href', '#').addClass(ref === selected ? 'is-active' : '').text(ref).attr('data-ref', escape(ref));
}); return $('<li />').append(link);
$dropdownContainer.on('click', '.dropdown-content a', (e) => {
$dropdown.prop('title', e.target.text.replace(/_+?/g, '-'));
if ($dropdown.hasClass('has-tooltip')) {
$dropdown.tooltip('fixTitle');
} }
}); },
id: function(obj, $el) {
return $el.attr('data-ref');
},
toggleLabel: function(obj, $el) {
return $el.text().trim();
}
});
$filterInput.on('keyup', (e) => {
const keyCode = e.keyCode || e.which;
if (keyCode !== 13) return;
const text = $filterInput.val();
$fieldInput.val(text);
$('.dropdown-toggle-text', $dropdown).text(text);
$dropdownContainer.removeClass('open');
}); });
};
return CompareAutocomplete; $dropdownContainer.on('click', '.dropdown-content a', (e) => {
})(); $dropdown.prop('title', e.target.text.replace(/_+?/g, '-'));
if ($dropdown.hasClass('has-tooltip')) {
$dropdown.tooltip('fixTitle');
}
});
});
}
...@@ -32,7 +32,9 @@ ...@@ -32,7 +32,9 @@
doAction() { doAction() {
this.isLoading = true; this.isLoading = true;
eventHub.$emit(`${this.type}.key`, this.deployKey); eventHub.$emit(`${this.type}.key`, this.deployKey, () => {
this.isLoading = false;
});
}, },
}, },
computed: { computed: {
...@@ -50,6 +52,9 @@ ...@@ -50,6 +52,9 @@
:disabled="isLoading" :disabled="isLoading"
@click="doAction"> @click="doAction">
{{ text }} {{ text }}
<loading-icon v-if="isLoading" /> <loading-icon
v-if="isLoading"
:inline="true"
/>
</button> </button>
</template> </template>
...@@ -47,12 +47,15 @@ ...@@ -47,12 +47,15 @@
.then(() => this.fetchKeys()) .then(() => this.fetchKeys())
.catch(() => new Flash('Error enabling deploy key')); .catch(() => new Flash('Error enabling deploy key'));
}, },
disableKey(deployKey) { disableKey(deployKey, callback) {
// eslint-disable-next-line no-alert // eslint-disable-next-line no-alert
if (confirm('You are going to remove this deploy key. Are you sure?')) { if (confirm('You are going to remove this deploy key. Are you sure?')) {
this.service.disableKey(deployKey.id) this.service.disableKey(deployKey.id)
.then(() => this.fetchKeys()) .then(() => this.fetchKeys())
.then(callback)
.catch(() => new Flash('Error removing deploy key')); .catch(() => new Flash('Error removing deploy key'));
} else {
callback();
} }
}, },
}, },
......
...@@ -16,7 +16,8 @@ import './components/diff_note_avatars'; ...@@ -16,7 +16,8 @@ import './components/diff_note_avatars';
import './components/new_issue_for_discussion'; import './components/new_issue_for_discussion';
$(() => { $(() => {
const projectPath = document.querySelector('.merge-request').dataset.projectPath; const projectPathHolder = document.querySelector('.merge-request') || document.querySelector('.commit-box');
const projectPath = projectPathHolder.dataset.projectPath;
const COMPONENT_SELECTOR = 'resolve-btn, resolve-discussion-btn, jump-to-discussion, comment-and-resolve-btn, new-issue-for-discussion-btn'; const COMPONENT_SELECTOR = 'resolve-btn, resolve-discussion-btn, jump-to-discussion, comment-and-resolve-btn, new-issue-for-discussion-btn';
window.gl = window.gl || {}; window.gl = window.gl || {};
......
...@@ -43,7 +43,7 @@ class ResolveServiceClass { ...@@ -43,7 +43,7 @@ class ResolveServiceClass {
discussion.resolveAllNotes(resolvedBy); discussion.resolveAllNotes(resolvedBy);
} }
gl.mrWidget.checkStatus(); if (gl.mrWidget) gl.mrWidget.checkStatus();
discussion.updateHeadline(data); discussion.updateHeadline(data);
}) })
.catch(() => new Flash('An error occurred when trying to resolve a discussion. Please try again.')); .catch(() => new Flash('An error occurred when trying to resolve a discussion. Please try again.'));
......
...@@ -22,8 +22,8 @@ import NewCommitForm from './new_commit_form'; ...@@ -22,8 +22,8 @@ import NewCommitForm from './new_commit_form';
import Project from './project'; import Project from './project';
import projectAvatar from './project_avatar'; import projectAvatar from './project_avatar';
/* global MergeRequest */ /* global MergeRequest */
/* global Compare */ import Compare from './compare';
/* global CompareAutocomplete */ import initCompareAutocomplete from './compare_autocomplete';
/* global ProjectFindFile */ /* global ProjectFindFile */
import ProjectNew from './project_new'; import ProjectNew from './project_new';
import projectImport from './project_import'; import projectImport from './project_import';
...@@ -525,13 +525,6 @@ import ProjectVariables from './project_variables'; ...@@ -525,13 +525,6 @@ import ProjectVariables from './project_variables';
case 'projects:settings:ci_cd:show': case 'projects:settings:ci_cd:show':
// Initialize expandable settings panels // Initialize expandable settings panels
initSettingsPanels(); initSettingsPanels();
import(/* webpackChunkName: "ci-cd-settings" */ './projects/ci_cd_settings_bundle')
.then(ciCdSettings => ciCdSettings.default())
.catch((err) => {
Flash(s__('ProjectSettings|Problem setting up the CI/CD settings JavaScript'));
throw err;
});
case 'groups:settings:ci_cd:show': case 'groups:settings:ci_cd:show':
new ProjectVariables(); new ProjectVariables();
break; break;
...@@ -629,7 +622,7 @@ import ProjectVariables from './project_variables'; ...@@ -629,7 +622,7 @@ import ProjectVariables from './project_variables';
projectAvatar(); projectAvatar();
switch (path[1]) { switch (path[1]) {
case 'compare': case 'compare':
new CompareAutocomplete(); initCompareAutocomplete();
break; break;
case 'edit': case 'edit':
shortcut_handler = new ShortcutsNavigation(); shortcut_handler = new ShortcutsNavigation();
......
...@@ -8,18 +8,7 @@ import IssuablesHelper from './helpers/issuables_helper'; ...@@ -8,18 +8,7 @@ import IssuablesHelper from './helpers/issuables_helper';
export default class Issue { export default class Issue {
constructor() { constructor() {
if ($('a.btn-close').length) { if ($('a.btn-close').length) this.initIssueBtnEventListeners();
this.taskList = new TaskList({
dataType: 'issue',
fieldName: 'description',
selector: '.detail-page-description',
onSuccess: (result) => {
document.querySelector('#task_status').innerText = result.task_status;
document.querySelector('#task_status_short').innerText = result.task_status_short;
}
});
this.initIssueBtnEventListeners();
}
Issue.$btnNewBranch = $('#new-branch'); Issue.$btnNewBranch = $('#new-branch');
Issue.createMrDropdownWrap = document.querySelector('.create-mr-dropdown-wrap'); Issue.createMrDropdownWrap = document.querySelector('.create-mr-dropdown-wrap');
......
...@@ -9,6 +9,7 @@ import descriptionComponent from './description.vue'; ...@@ -9,6 +9,7 @@ import descriptionComponent from './description.vue';
import editedComponent from './edited.vue'; import editedComponent from './edited.vue';
import formComponent from './form.vue'; import formComponent from './form.vue';
import '../../lib/utils/url_utility'; import '../../lib/utils/url_utility';
import RecaptchaDialogImplementor from '../../vue_shared/mixins/recaptcha_dialog_implementor';
export default { export default {
props: { props: {
...@@ -149,6 +150,11 @@ export default { ...@@ -149,6 +150,11 @@ export default {
editedComponent, editedComponent,
formComponent, formComponent,
}, },
mixins: [
RecaptchaDialogImplementor,
],
methods: { methods: {
openForm() { openForm() {
if (!this.showForm) { if (!this.showForm) {
...@@ -164,9 +170,11 @@ export default { ...@@ -164,9 +170,11 @@ export default {
closeForm() { closeForm() {
this.showForm = false; this.showForm = false;
}, },
updateIssuable() { updateIssuable() {
this.service.updateIssuable(this.store.formState) this.service.updateIssuable(this.store.formState)
.then(res => res.json()) .then(res => res.json())
.then(data => this.checkForSpam(data))
.then((data) => { .then((data) => {
if (location.pathname !== data.web_url) { if (location.pathname !== data.web_url) {
gl.utils.visitUrl(data.web_url); gl.utils.visitUrl(data.web_url);
...@@ -179,11 +187,24 @@ export default { ...@@ -179,11 +187,24 @@ export default {
this.store.updateState(data); this.store.updateState(data);
eventHub.$emit('close.form'); eventHub.$emit('close.form');
}) })
.catch(() => { .catch((error) => {
eventHub.$emit('close.form'); if (error && error.name === 'SpamError') {
window.Flash(`Error updating ${this.issuableType}`); this.openRecaptcha();
} else {
eventHub.$emit('close.form');
window.Flash(`Error updating ${this.issuableType}`);
}
}); });
}, },
closeRecaptchaDialog() {
this.store.setFormState({
updateLoading: false,
});
this.closeRecaptcha();
},
deleteIssuable() { deleteIssuable() {
this.service.deleteIssuable() this.service.deleteIssuable()
.then(res => res.json()) .then(res => res.json())
...@@ -237,9 +258,9 @@ export default { ...@@ -237,9 +258,9 @@ export default {
</script> </script>
<template> <template>
<div> <div>
<div v-if="canUpdate && showForm">
<form-component <form-component
v-if="canUpdate && showForm"
:form-state="formState" :form-state="formState"
:can-destroy="canDestroy" :can-destroy="canDestroy"
:issuable-templates="issuableTemplates" :issuable-templates="issuableTemplates"
...@@ -251,30 +272,37 @@ export default { ...@@ -251,30 +272,37 @@ export default {
:can-attach-file="canAttachFile" :can-attach-file="canAttachFile"
:enable-autocomplete="enableAutocomplete" :enable-autocomplete="enableAutocomplete"
/> />
<div v-else>
<title-component <recaptcha-dialog
:issuable-ref="issuableRef" v-show="showRecaptcha"
:can-update="canUpdate" :html="recaptchaHTML"
:title-html="state.titleHtml" @close="closeRecaptchaDialog"
:title-text="state.titleText" />
:show-inline-edit-button="showInlineEditButton" </div>
/> <div v-else>
<description-component <title-component
v-if="state.descriptionHtml" :issuable-ref="issuableRef"
:can-update="canUpdate" :can-update="canUpdate"
:description-html="state.descriptionHtml" :title-html="state.titleHtml"
:description-text="state.descriptionText" :title-text="state.titleText"
:updated-at="state.updatedAt" :show-inline-edit-button="showInlineEditButton"
:task-status="state.taskStatus" />
:issuable-type="issuableType" <description-component
:update-url="updateEndpoint" v-if="state.descriptionHtml"
/> :can-update="canUpdate"
<edited-component :description-html="state.descriptionHtml"
v-if="hasUpdated" :description-text="state.descriptionText"
:updated-at="state.updatedAt" :updated-at="state.updatedAt"
:updated-by-name="state.updatedByName" :task-status="state.taskStatus"
:updated-by-path="state.updatedByPath" :issuable-type="issuableType"
/> :update-url="updateEndpoint"
</div> />
<edited-component
v-if="hasUpdated"
:updated-at="state.updatedAt"
:updated-by-name="state.updatedByName"
:updated-by-path="state.updatedByPath"
/>
</div> </div>
</div>
</template> </template>
<script> <script>
import animateMixin from '../mixins/animate'; import animateMixin from '../mixins/animate';
import TaskList from '../../task_list'; import TaskList from '../../task_list';
import RecaptchaDialogImplementor from '../../vue_shared/mixins/recaptcha_dialog_implementor';
export default { export default {
mixins: [animateMixin], mixins: [
animateMixin,
RecaptchaDialogImplementor,
],
props: { props: {
canUpdate: { canUpdate: {
type: Boolean, type: Boolean,
...@@ -51,6 +56,7 @@ ...@@ -51,6 +56,7 @@
this.updateTaskStatusText(); this.updateTaskStatusText();
}, },
}, },
methods: { methods: {
renderGFM() { renderGFM() {
$(this.$refs['gfm-content']).renderGFM(); $(this.$refs['gfm-content']).renderGFM();
...@@ -61,9 +67,19 @@ ...@@ -61,9 +67,19 @@
dataType: this.issuableType, dataType: this.issuableType,
fieldName: 'description', fieldName: 'description',
selector: '.detail-page-description', selector: '.detail-page-description',
onSuccess: this.taskListUpdateSuccess.bind(this),
}); });
} }
}, },
taskListUpdateSuccess(data) {
try {
this.checkForSpam(data);
} catch (error) {
if (error && error.name === 'SpamError') this.openRecaptcha();
}
},
updateTaskStatusText() { updateTaskStatusText() {
const taskRegexMatches = this.taskStatus.match(/(\d+) of ((?!0)\d+)/); const taskRegexMatches = this.taskStatus.match(/(\d+) of ((?!0)\d+)/);
const $issuableHeader = $('.issuable-meta'); const $issuableHeader = $('.issuable-meta');
...@@ -109,5 +125,11 @@ ...@@ -109,5 +125,11 @@
:data-update-url="updateUrl" :data-update-url="updateUrl"
> >
</textarea> </textarea>
<recaptcha-dialog
v-show="showRecaptcha"
:html="recaptchaHTML"
@close="closeRecaptcha"
/>
</div> </div>
</template> </template>
...@@ -5,7 +5,7 @@ import '../vue_shared/vue_resource_interceptor'; ...@@ -5,7 +5,7 @@ import '../vue_shared/vue_resource_interceptor';
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
const initialDataEl = document.getElementById('js-issuable-app-initial-data'); const initialDataEl = document.getElementById('js-issuable-app-initial-data');
const initialData = JSON.parse(initialDataEl.innerHTML.replace(/&quot;/g, '"')); const props = JSON.parse(initialDataEl.innerHTML.replace(/&quot;/g, '"'));
$('.issuable-edit').on('click', (e) => { $('.issuable-edit').on('click', (e) => {
e.preventDefault(); e.preventDefault();
...@@ -18,32 +18,9 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -18,32 +18,9 @@ document.addEventListener('DOMContentLoaded', () => {
components: { components: {
issuableApp, issuableApp,
}, },
data() {
return {
...initialData,
};
},
render(createElement) { render(createElement) {
return createElement('issuable-app', { return createElement('issuable-app', {
props: { props,
canUpdate: this.canUpdate,
canDestroy: this.canDestroy,
endpoint: this.endpoint,
issuableRef: this.issuableRef,
initialTitleHtml: this.initialTitleHtml,
initialTitleText: this.initialTitleText,
initialDescriptionHtml: this.initialDescriptionHtml,
initialDescriptionText: this.initialDescriptionText,
issuableTemplates: this.issuableTemplates,
markdownPreviewPath: this.markdownPreviewPath,
markdownDocsPath: this.markdownDocsPath,
projectPath: this.projectPath,
projectNamespace: this.projectNamespace,
updatedAt: this.updatedAt,
updatedByName: this.updatedByName,
updatedByPath: this.updatedByPath,
initialTaskStatus: this.initialTaskStatus,
},
}); });
}, },
}); });
......
...@@ -9,7 +9,7 @@ export default class Job { ...@@ -9,7 +9,7 @@ export default class Job {
this.state = null; this.state = null;
this.options = options || $('.js-build-options').data(); this.options = options || $('.js-build-options').data();
this.pageUrl = this.options.pageUrl; this.pagePath = this.options.pagePath;
this.buildStatus = this.options.buildStatus; this.buildStatus = this.options.buildStatus;
this.state = this.options.logState; this.state = this.options.logState;
this.buildStage = this.options.buildStage; this.buildStage = this.options.buildStage;
...@@ -167,11 +167,11 @@ export default class Job { ...@@ -167,11 +167,11 @@ export default class Job {
getBuildTrace() { getBuildTrace() {
return $.ajax({ return $.ajax({
url: `${this.pageUrl}/trace.json`, url: `${this.pagePath}/trace.json`,
data: { state: this.state }, data: { state: this.state },
}) })
.done((log) => { .done((log) => {
setCiStatusFavicon(`${this.pageUrl}/status.json`); setCiStatusFavicon(`${this.pagePath}/status.json`);
if (log.state) { if (log.state) {
this.state = log.state; this.state = log.state;
...@@ -209,7 +209,7 @@ export default class Job { ...@@ -209,7 +209,7 @@ export default class Job {
} }
if (log.status !== this.buildStatus) { if (log.status !== this.buildStatus) {
gl.utils.visitUrl(this.pageUrl); gl.utils.visitUrl(this.pagePath);
} }
}) })
.fail(() => { .fail(() => {
......
...@@ -40,9 +40,6 @@ import './admin'; ...@@ -40,9 +40,6 @@ import './admin';
import './aside'; import './aside';
import loadAwardsHandler from './awards_handler'; import loadAwardsHandler from './awards_handler';
import bp from './breakpoints'; import bp from './breakpoints';
import './commits';
import './compare';
import './compare_autocomplete';
import './confirm_danger_modal'; import './confirm_danger_modal';
import Flash, { removeFlashClickListener } from './flash'; import Flash, { removeFlashClickListener } from './flash';
import './gl_dropdown'; import './gl_dropdown';
......
...@@ -21,6 +21,8 @@ ...@@ -21,6 +21,8 @@
hasMetrics: convertPermissionToBoolean(metricsData.hasMetrics), hasMetrics: convertPermissionToBoolean(metricsData.hasMetrics),
documentationPath: metricsData.documentationPath, documentationPath: metricsData.documentationPath,
settingsPath: metricsData.settingsPath, settingsPath: metricsData.settingsPath,
tagsPath: metricsData.tagsPath,
projectPath: metricsData.projectPath,
metricsEndpoint: metricsData.additionalMetrics, metricsEndpoint: metricsData.additionalMetrics,
deploymentEndpoint: metricsData.deploymentEndpoint, deploymentEndpoint: metricsData.deploymentEndpoint,
emptyGettingStartedSvgPath: metricsData.emptyGettingStartedSvgPath, emptyGettingStartedSvgPath: metricsData.emptyGettingStartedSvgPath,
...@@ -112,6 +114,8 @@ ...@@ -112,6 +114,8 @@
:hover-data="hoverData" :hover-data="hoverData"
:update-aspect-ratio="updateAspectRatio" :update-aspect-ratio="updateAspectRatio"
:deployment-data="store.deploymentData" :deployment-data="store.deploymentData"
:project-path="projectPath"
:tags-path="tagsPath"
/> />
</graph-group> </graph-group>
</div> </div>
......
...@@ -30,6 +30,14 @@ ...@@ -30,6 +30,14 @@
required: false, required: false,
default: () => ({}), default: () => ({}),
}, },
projectPath: {
type: String,
required: true,
},
tagsPath: {
type: String,
required: true,
},
}, },
mixins: [MonitoringMixin], mixins: [MonitoringMixin],
...@@ -251,6 +259,14 @@ ...@@ -251,6 +259,14 @@
:line-color="path.lineColor" :line-color="path.lineColor"
:area-color="path.areaColor" :area-color="path.areaColor"
/> />
<rect
class="prometheus-graph-overlay"
:width="(graphWidth - 70)"
:height="(graphHeight - 100)"
transform="translate(-5, 20)"
ref="graphOverlay"
@mousemove="handleMouseOverGraph($event)">
</rect>
<graph-deployment <graph-deployment
:show-deploy-info="showDeployInfo" :show-deploy-info="showDeployInfo"
:deployment-data="reducedDeploymentData" :deployment-data="reducedDeploymentData"
...@@ -267,14 +283,6 @@ ...@@ -267,14 +283,6 @@
:graph-height-offset="graphHeightOffset" :graph-height-offset="graphHeightOffset"
:show-flag-content="showFlagContent" :show-flag-content="showFlagContent"
/> />
<rect
class="prometheus-graph-overlay"
:width="(graphWidth - 70)"
:height="(graphHeight - 100)"
transform="translate(-5, 20)"
ref="graphOverlay"
@mousemove="handleMouseOverGraph($event)">
</rect>
</svg> </svg>
</svg> </svg>
</div> </div>
......
<script> <script>
import { dateFormat, timeFormat } from '../../utils/date_time_formatters'; import { dateFormatWithName, timeFormat } from '../../utils/date_time_formatters';
import Icon from '../../../vue_shared/components/icon.vue';
export default { export default {
props: { props: {
...@@ -25,6 +26,10 @@ ...@@ -25,6 +26,10 @@
}, },
}, },
components: {
Icon,
},
computed: { computed: {
calculatedHeight() { calculatedHeight() {
return this.graphHeight - this.graphHeightOffset; return this.graphHeight - this.graphHeightOffset;
...@@ -33,7 +38,7 @@ ...@@ -33,7 +38,7 @@
methods: { methods: {
refText(d) { refText(d) {
return d.tag ? d.ref : d.sha.slice(0, 6); return d.tag ? d.ref : d.sha.slice(0, 8);
}, },
formatTime(deploymentTime) { formatTime(deploymentTime) {
...@@ -41,7 +46,7 @@ ...@@ -41,7 +46,7 @@
}, },
formatDate(deploymentTime) { formatDate(deploymentTime) {
return dateFormat(deploymentTime); return dateFormatWithName(deploymentTime);
}, },
nameDeploymentClass(deployment) { nameDeploymentClass(deployment) {
...@@ -54,11 +59,19 @@ ...@@ -54,11 +59,19 @@
positionFlag(deployment) { positionFlag(deployment) {
let xPosition = 3; let xPosition = 3;
if (deployment.xPos > (this.graphWidth - 200)) { if (deployment.xPos > (this.graphWidth - 225)) {
xPosition = -97; xPosition = -142;
} }
return xPosition; return xPosition;
}, },
svgContainerHeight(tag) {
let svgHeight = 80;
if (!tag) {
svgHeight -= 20;
}
return svgHeight;
},
}, },
}; };
</script> </script>
...@@ -91,35 +104,75 @@ ...@@ -91,35 +104,75 @@
class="js-deploy-info-box" class="js-deploy-info-box"
:x="positionFlag(deployment)" :x="positionFlag(deployment)"
y="0" y="0"
width="92" width="134"
height="60"> :height="svgContainerHeight(deployment.tag)">
<rect <rect
class="rect-text-metric deploy-info-rect rect-metric" class="rect-text-metric deploy-info-rect rect-metric"
x="1" x="1"
y="1" y="1"
rx="2" rx="2"
width="90" width="132"
height="58"> :height="svgContainerHeight(deployment.tag) - 2">
</rect> </rect>
<g
transform="translate(5, 2)">
<text
class="deploy-info-text text-metric-bold">
{{refText(deployment)}}
</text>
</g>
<text
class="deploy-info-text"
y="18"
transform="translate(5, 2)">
{{formatDate(deployment.time)}}
</text>
<text <text
class="deploy-info-text text-metric-bold" class="deploy-info-text text-metric-bold"
y="38"
transform="translate(5, 2)"> transform="translate(5, 2)">
{{formatTime(deployment.time)}} Deployed
</text> </text>
<!--The date info-->
<g transform="translate(5, 20)">
<text class="deploy-info-text">
{{formatDate(deployment.time)}}
</text>
<text
class="deploy-info-text text-metric-bold"
x="62">
{{formatTime(deployment.time)}}
</text>
</g>
<line
class="divider-line"
x1="0"
y1="38"
x2="132"
:y2="38"
stroke="#000">
</line>
<!--Commit information-->
<g transform="translate(5, 40)">
<icon
name="commit"
:width="12"
:height="12"
:y="3">
</icon>
<a :xlink:href="deployment.commitUrl">
<text
class="deploy-info-text deploy-info-text-link"
transform="translate(20, 2)">
{{refText(deployment)}}
</text>
</a>
</g>
<!--Tag information-->
<g
transform="translate(5, 55)"
v-if="deployment.tag">
<icon
name="label"
:width="12"
:height="12"
:y="5">
</icon>
<a :xlink:href="deployment.tagUrl">
<text
class="deploy-info-text deploy-info-text-link"
transform="translate(20, 2)"
y="2">
{{deployment.tag}}
</text>
</a>
</g>
</svg> </svg>
</g> </g>
<svg <svg
......
...@@ -33,7 +33,9 @@ const mixins = { ...@@ -33,7 +33,9 @@ const mixins = {
id: deployment.id, id: deployment.id,
time, time,
sha: deployment.sha, sha: deployment.sha,
commitUrl: `${this.projectPath}/commit/${deployment.sha}`,
tag: deployment.tag, tag: deployment.tag,
tagUrl: `${this.tagsPath}/${deployment.tag}`,
ref: deployment.ref.name, ref: deployment.ref.name,
xPos, xPos,
showDeploymentFlag: false, showDeploymentFlag: false,
......
import d3 from 'd3'; import d3 from 'd3';
export const dateFormat = d3.time.format('%b %-d, %Y'); export const dateFormat = d3.time.format('%b %-d, %Y');
export const dateFormatWithName = d3.time.format('%a, %b %-d');
export const timeFormat = d3.time.format('%-I:%M%p'); export const timeFormat = d3.time.format('%-I:%M%p');
export const bisectDate = d3.bisector(d => d.time).left; export const bisectDate = d3.bisector(d => d.time).left;
......
...@@ -8,10 +8,18 @@ document.addEventListener('DOMContentLoaded', () => new Vue({ ...@@ -8,10 +8,18 @@ document.addEventListener('DOMContentLoaded', () => new Vue({
}, },
data() { data() {
const notesDataset = document.getElementById('js-vue-notes').dataset; const notesDataset = document.getElementById('js-vue-notes').dataset;
const parsedUserData = JSON.parse(notesDataset.currentUserData);
const currentUserData = parsedUserData ? {
id: parsedUserData.id,
name: parsedUserData.name,
username: parsedUserData.username,
avatar_url: parsedUserData.avatar_path || parsedUserData.avatar_url,
path: parsedUserData.path,
} : {};
return { return {
noteableData: JSON.parse(notesDataset.noteableData), noteableData: JSON.parse(notesDataset.noteableData),
currentUserData: JSON.parse(notesDataset.currentUserData), currentUserData,
notesData: { notesData: {
lastFetchedAt: notesDataset.lastFetchedAt, lastFetchedAt: notesDataset.lastFetchedAt,
discussionsPath: notesDataset.discussionsPath, discussionsPath: notesDataset.discussionsPath,
......
function updateAutoDevopsRadios(radioWrappers) {
radioWrappers.forEach((radioWrapper) => {
const radio = radioWrapper.querySelector('.js-auto-devops-enable-radio');
const runPipelineCheckboxWrapper = radioWrapper.querySelector('.js-run-auto-devops-pipeline-checkbox-wrapper');
const runPipelineCheckbox = radioWrapper.querySelector('.js-run-auto-devops-pipeline-checkbox');
if (runPipelineCheckbox) {
runPipelineCheckbox.checked = radio.checked;
runPipelineCheckboxWrapper.classList.toggle('hide', !radio.checked);
}
});
}
export default function initCiCdSettings() {
const radioWrappers = document.querySelectorAll('.js-auto-devops-enable-radio-wrapper');
radioWrappers.forEach(radioWrapper =>
radioWrapper.addEventListener('change', () => updateAutoDevopsRadios(radioWrappers)),
);
}
import Project from '~/project';
import SmartInterval from '~/smart_interval'; import SmartInterval from '~/smart_interval';
import Flash from '../flash'; import Flash from '../flash';
import { import {
...@@ -140,6 +141,7 @@ export default { ...@@ -140,6 +141,7 @@ export default {
const el = document.createElement('div'); const el = document.createElement('div');
el.innerHTML = res.body; el.innerHTML = res.body;
document.body.appendChild(el); document.body.appendChild(el);
Project.initRefSwitcher();
} }
}) })
.catch(() => { .catch(() => {
......
...@@ -36,6 +36,30 @@ ...@@ -36,6 +36,30 @@
required: false, required: false,
default: '', default: '',
}, },
width: {
type: Number,
required: false,
default: null,
},
height: {
type: Number,
required: false,
default: null,
},
y: {
type: Number,
required: false,
default: null,
},
x: {
type: Number,
required: false,
default: null,
},
}, },
computed: { computed: {
...@@ -51,7 +75,11 @@ ...@@ -51,7 +75,11 @@
<template> <template>
<svg <svg
:class="[iconSizeClass, cssClasses]"> :class="[iconSizeClass, cssClasses]"
:width="width"
:height="height"
:x="x"
:y="y">
<use <use
v-bind="{'xlink:href':spriteHref}"/> v-bind="{'xlink:href':spriteHref}"/>
</svg> </svg>
......
...@@ -38,7 +38,8 @@ export default { ...@@ -38,7 +38,8 @@ export default {
}, },
primaryButtonLabel: { primaryButtonLabel: {
type: String, type: String,
required: true, required: false,
default: '',
}, },
submitDisabled: { submitDisabled: {
type: Boolean, type: Boolean,
...@@ -113,8 +114,9 @@ export default { ...@@ -113,8 +114,9 @@ export default {
{{ closeButtonLabel }} {{ closeButtonLabel }}
</button> </button>
<button <button
v-if="primaryButtonLabel"
type="button" type="button"
class="btn pull-right" class="btn pull-right js-primary-button"
:disabled="submitDisabled" :disabled="submitDisabled"
:class="btnKindClass" :class="btnKindClass"
@click="emitSubmit(true)"> @click="emitSubmit(true)">
......
<script>
import PopupDialog from './popup_dialog.vue';
export default {
name: 'recaptcha-dialog',
props: {
html: {
type: String,
required: false,
default: '',
},
},
data() {
return {
script: {},
scriptSrc: 'https://www.google.com/recaptcha/api.js',
};
},
components: {
PopupDialog,
},
methods: {
appendRecaptchaScript() {
this.removeRecaptchaScript();
const script = document.createElement('script');
script.src = this.scriptSrc;
script.classList.add('js-recaptcha-script');
script.async = true;
script.defer = true;
this.script = script;
document.body.appendChild(script);
},
removeRecaptchaScript() {
if (this.script instanceof Element) this.script.remove();
},
close() {
this.removeRecaptchaScript();
this.$emit('close');
},
submit() {
this.$el.querySelector('form').submit();
},
},
watch: {
html() {
this.appendRecaptchaScript();
},
},
mounted() {
window.recaptchaDialogCallback = this.submit.bind(this);
},
};
</script>
<template>
<popup-dialog
kind="warning"
class="recaptcha-dialog js-recaptcha-dialog"
:hide-footer="true"
:title="__('Please solve the reCAPTCHA')"
@toggle="close"
>
<div slot="body">
<p>
{{__('We want to be sure it is you, please confirm you are not a robot.')}}
</p>
<div
ref="recaptcha"
v-html="html"
></div>
</div>
</popup-dialog>
</template>
import RecaptchaDialog from '../components/recaptcha_dialog.vue';
export default {
data() {
return {
showRecaptcha: false,
recaptchaHTML: '',
};
},
components: {
RecaptchaDialog,
},
methods: {
openRecaptcha() {
this.showRecaptcha = true;
},
closeRecaptcha() {
this.showRecaptcha = false;
},
checkForSpam(data) {
if (!data.recaptcha_html) return data;
this.recaptchaHTML = data.recaptcha_html;
const spamError = new Error(data.error_message);
spamError.name = 'SpamError';
spamError.message = 'SpamError';
throw spamError;
},
},
};
...@@ -48,3 +48,10 @@ body.modal-open { ...@@ -48,3 +48,10 @@ body.modal-open {
display: block; display: block;
} }
.recaptcha-dialog .recaptcha-form {
display: inline-block;
.recaptcha {
margin: 0;
}
}
...@@ -201,8 +201,9 @@ ...@@ -201,8 +201,9 @@
stroke-width: 1; stroke-width: 1;
} }
.deploy-info-text { .divider-line {
dominant-baseline: text-before-edge; stroke-width: 1;
stroke: $gray-darkest;
} }
.prometheus-state { .prometheus-state {
...@@ -312,6 +313,20 @@ ...@@ -312,6 +313,20 @@
stroke: $gray-darker; stroke: $gray-darker;
} }
.deploy-info-text {
dominant-baseline: text-before-edge;
font-size: 12px;
}
.deploy-info-text-link {
font-family: $monospace_font;
fill: $gl-link-color;
&:hover {
fill: $gl-link-hover-color;
}
}
@media (max-width: $screen-sm-max) { @media (max-width: $screen-sm-max) {
.label-axis-text, .label-axis-text,
.text-metric-usage, .text-metric-usage,
......
...@@ -5,7 +5,7 @@ class Admin::HealthCheckController < Admin::ApplicationController ...@@ -5,7 +5,7 @@ class Admin::HealthCheckController < Admin::ApplicationController
end end
def reset_storage_health def reset_storage_health
Gitlab::Git::Storage::CircuitBreaker.reset_all! Gitlab::Git::Storage::FailureInfo.reset_all!
redirect_to admin_health_check_path, redirect_to admin_health_check_path,
notice: _('Git storage health information has been reset') notice: _('Git storage health information has been reset')
end end
......
...@@ -25,7 +25,7 @@ module IssuableActions ...@@ -25,7 +25,7 @@ module IssuableActions
end end
format.json do format.json do
render_entity_json recaptcha_check_with_fallback(false) { render_entity_json }
end end
end end
......
...@@ -23,8 +23,8 @@ module SpammableActions ...@@ -23,8 +23,8 @@ module SpammableActions
@spam_config_loaded = Gitlab::Recaptcha.load_configurations! @spam_config_loaded = Gitlab::Recaptcha.load_configurations!
end end
def recaptcha_check_with_fallback(&fallback) def recaptcha_check_with_fallback(should_redirect = true, &fallback)
if spammable.valid? if should_redirect && spammable.valid?
redirect_to spammable_path redirect_to spammable_path
elsif render_recaptcha? elsif render_recaptcha?
ensure_spam_config_loaded! ensure_spam_config_loaded!
...@@ -33,7 +33,18 @@ module SpammableActions ...@@ -33,7 +33,18 @@ module SpammableActions
flash[:alert] = 'There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.' flash[:alert] = 'There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.'
end end
render :verify respond_to do |format|
format.html do
render :verify
end
format.json do
locals = { spammable: spammable, script: false, has_submit: false }
recaptcha_html = render_to_string(partial: 'shared/recaptcha_form', formats: :html, locals: locals)
render json: { recaptcha_html: recaptcha_html }
end
end
else else
yield yield
end end
......
class HealthController < ActionController::Base class HealthController < ActionController::Base
protect_from_forgery with: :exception protect_from_forgery with: :exception, except: :storage_check
include RequiresWhitelistedMonitoringClient include RequiresWhitelistedMonitoringClient
CHECKS = [ CHECKS = [
...@@ -23,6 +23,15 @@ class HealthController < ActionController::Base ...@@ -23,6 +23,15 @@ class HealthController < ActionController::Base
render_check_results(results) render_check_results(results)
end end
def storage_check
results = Gitlab::Git::Storage::Checker.check_all
render json: {
check_interval: Gitlab::CurrentSettings.current_application_settings.circuitbreaker_check_interval,
results: results
}
end
private private
def render_check_results(results) def render_check_results(results)
......
...@@ -8,7 +8,7 @@ class Profiles::PersonalAccessTokensController < Profiles::ApplicationController ...@@ -8,7 +8,7 @@ class Profiles::PersonalAccessTokensController < Profiles::ApplicationController
@personal_access_token = finder.build(personal_access_token_params) @personal_access_token = finder.build(personal_access_token_params)
if @personal_access_token.save if @personal_access_token.save
flash[:personal_access_token] = @personal_access_token.token PersonalAccessToken.redis_store!(current_user.id, @personal_access_token.token)
redirect_to profile_personal_access_tokens_path, notice: "Your new personal access token has been created." redirect_to profile_personal_access_tokens_path, notice: "Your new personal access token has been created."
else else
set_index_vars set_index_vars
...@@ -43,5 +43,7 @@ class Profiles::PersonalAccessTokensController < Profiles::ApplicationController ...@@ -43,5 +43,7 @@ class Profiles::PersonalAccessTokensController < Profiles::ApplicationController
@inactive_personal_access_tokens = finder(state: 'inactive').execute @inactive_personal_access_tokens = finder(state: 'inactive').execute
@active_personal_access_tokens = finder(state: 'active').execute.order(:expires_at) @active_personal_access_tokens = finder(state: 'active').execute.order(:expires_at)
@new_personal_access_token = PersonalAccessToken.redis_getdel(current_user.id)
end end
end end
...@@ -134,6 +134,23 @@ class Projects::CommitController < Projects::ApplicationController ...@@ -134,6 +134,23 @@ class Projects::CommitController < Projects::ApplicationController
@grouped_diff_discussions = commit.grouped_diff_discussions @grouped_diff_discussions = commit.grouped_diff_discussions
@discussions = commit.discussions @discussions = commit.discussions
if merge_request_iid = params[:merge_request_iid]
@merge_request = MergeRequestsFinder.new(current_user, project_id: @project.id).find_by(iid: merge_request_iid)
if @merge_request
@new_diff_note_attrs.merge!(
noteable_type: 'MergeRequest',
noteable_id: @merge_request.id
)
merge_request_commit_notes = @merge_request.notes.where(commit_id: @commit.id).inc_relations_for_view
merge_request_commit_diff_discussions = merge_request_commit_notes.grouped_diff_discussions(@commit.diff_refs)
@grouped_diff_discussions.merge!(merge_request_commit_diff_discussions) do |line_code, left, right|
left + right
end
end
end
@notes = (@grouped_diff_discussions.values.flatten + @discussions).flat_map(&:notes) @notes = (@grouped_diff_discussions.values.flatten + @discussions).flat_map(&:notes)
@notes = prepare_notes_for_rendering(@notes, @commit) @notes = prepare_notes_for_rendering(@notes, @commit)
end end
......
...@@ -28,7 +28,6 @@ class Projects::MergeRequests::ApplicationController < Projects::ApplicationCont ...@@ -28,7 +28,6 @@ class Projects::MergeRequests::ApplicationController < Projects::ApplicationCont
:task_num, :task_num,
:title, :title,
:discussion_locked, :discussion_locked,
label_ids: [] label_ids: []
] ]
end end
......
...@@ -4,6 +4,7 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic ...@@ -4,6 +4,7 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic
include RendersNotes include RendersNotes
before_action :apply_diff_view_cookie! before_action :apply_diff_view_cookie!
before_action :commit
before_action :define_diff_vars before_action :define_diff_vars
before_action :define_diff_comment_vars before_action :define_diff_comment_vars
...@@ -20,18 +21,33 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic ...@@ -20,18 +21,33 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic
private private
def define_diff_vars def define_diff_vars
@merge_request_diffs = @merge_request.merge_request_diffs.viewable.order_id_desc
@compare = commit || find_merge_request_diff_compare
return render_404 unless @compare
@diffs = @compare.diffs(diff_options)
end
def commit
return nil unless commit_id = params[:commit_id].presence
return nil unless @merge_request.all_commits.exists?(sha: commit_id)
@commit ||= @project.commit(commit_id)
end
def find_merge_request_diff_compare
@merge_request_diff = @merge_request_diff =
if params[:diff_id] if diff_id = params[:diff_id].presence
@merge_request.merge_request_diffs.viewable.find(params[:diff_id]) @merge_request.merge_request_diffs.viewable.find_by(id: diff_id)
else else
@merge_request.merge_request_diff @merge_request.merge_request_diff
end end
@merge_request_diffs = @merge_request.merge_request_diffs.viewable.order_id_desc return unless @merge_request_diff
@comparable_diffs = @merge_request_diffs.select { |diff| diff.id < @merge_request_diff.id } @comparable_diffs = @merge_request_diffs.select { |diff| diff.id < @merge_request_diff.id }
if params[:start_sha].present? if @start_sha = params[:start_sha].presence
@start_sha = params[:start_sha]
@start_version = @comparable_diffs.find { |diff| diff.head_commit_sha == @start_sha } @start_version = @comparable_diffs.find { |diff| diff.head_commit_sha == @start_sha }
unless @start_version unless @start_version
...@@ -40,20 +56,18 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic ...@@ -40,20 +56,18 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic
end end
end end
@compare = if @start_sha
if @start_sha @merge_request_diff.compare_with(@start_sha)
@merge_request_diff.compare_with(@start_sha) else
else @merge_request_diff
@merge_request_diff end
end
@diffs = @compare.diffs(diff_options)
end end
def define_diff_comment_vars def define_diff_comment_vars
@new_diff_note_attrs = { @new_diff_note_attrs = {
noteable_type: 'MergeRequest', noteable_type: 'MergeRequest',
noteable_id: @merge_request.id noteable_id: @merge_request.id,
commit_id: @commit&.id
} }
@diff_notes_disabled = false @diff_notes_disabled = false
......
...@@ -7,11 +7,8 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo ...@@ -7,11 +7,8 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
include IssuableCollections include IssuableCollections
skip_before_action :merge_request, only: [:index, :bulk_update] skip_before_action :merge_request, only: [:index, :bulk_update]
before_action :authorize_update_issuable!, only: [:close, :edit, :update, :remove_wip, :sort] before_action :authorize_update_issuable!, only: [:close, :edit, :update, :remove_wip, :sort]
before_action :set_issuables_index, only: [:index] before_action :set_issuables_index, only: [:index]
before_action :authenticate_user!, only: [:assign_related_issues] before_action :authenticate_user!, only: [:assign_related_issues]
def index def index
......
...@@ -29,7 +29,6 @@ class Projects::PipelinesSettingsController < Projects::ApplicationController ...@@ -29,7 +29,6 @@ class Projects::PipelinesSettingsController < Projects::ApplicationController
:runners_token, :builds_enabled, :build_allow_git_fetch, :runners_token, :builds_enabled, :build_allow_git_fetch,
:build_timeout_in_minutes, :build_coverage_regex, :public_builds, :build_timeout_in_minutes, :build_coverage_regex, :public_builds,
:auto_cancel_pending_pipelines, :ci_config_path, :auto_cancel_pending_pipelines, :ci_config_path,
:run_auto_devops_pipeline_implicit, :run_auto_devops_pipeline_explicit,
auto_devops_attributes: [:id, :domain, :enabled] auto_devops_attributes: [:id, :domain, :enabled]
) )
end end
......
...@@ -124,17 +124,6 @@ module ApplicationSettingsHelper ...@@ -124,17 +124,6 @@ module ApplicationSettingsHelper
_('The number of attempts GitLab will make to access a storage.') _('The number of attempts GitLab will make to access a storage.')
end end
def circuitbreaker_backoff_threshold_help_text
_("The number of failures after which GitLab will start temporarily "\
"disabling access to a storage shard on a host")
end
def circuitbreaker_failure_wait_time_help_text
_("When access to a storage fails. GitLab will prevent access to the "\
"storage for the time specified here. This allows the filesystem to "\
"recover. Repositories on failing shards are temporarly unavailable")
end
def circuitbreaker_failure_reset_time_help_text def circuitbreaker_failure_reset_time_help_text
_("The time in seconds GitLab will keep failure information. When no "\ _("The time in seconds GitLab will keep failure information. When no "\
"failures occur during this time, information about the mount is reset.") "failures occur during this time, information about the mount is reset.")
...@@ -145,6 +134,11 @@ module ApplicationSettingsHelper ...@@ -145,6 +134,11 @@ module ApplicationSettingsHelper
"timeout error will be raised.") "timeout error will be raised.")
end end
def circuitbreaker_check_interval_help_text
_("The time in seconds between storage checks. When a previous check did "\
"complete yet, GitLab will skip a check.")
end
def visible_attributes def visible_attributes
[ [
:admin_notification_email, :admin_notification_email,
...@@ -154,10 +148,9 @@ module ApplicationSettingsHelper ...@@ -154,10 +148,9 @@ module ApplicationSettingsHelper
:akismet_enabled, :akismet_enabled,
:auto_devops_enabled, :auto_devops_enabled,
:circuitbreaker_access_retries, :circuitbreaker_access_retries,
:circuitbreaker_backoff_threshold, :circuitbreaker_check_interval,
:circuitbreaker_failure_count_threshold, :circuitbreaker_failure_count_threshold,
:circuitbreaker_failure_reset_time, :circuitbreaker_failure_reset_time,
:circuitbreaker_failure_wait_time,
:circuitbreaker_storage_timeout, :circuitbreaker_storage_timeout,
:clientside_sentry_dsn, :clientside_sentry_dsn,
:clientside_sentry_enabled, :clientside_sentry_enabled,
......
...@@ -8,22 +8,6 @@ module AutoDevopsHelper ...@@ -8,22 +8,6 @@ module AutoDevopsHelper
!project.ci_service !project.ci_service
end end
def show_run_auto_devops_pipeline_checkbox_for_instance_setting?(project)
return false if project.repository.gitlab_ci_yml
if project&.auto_devops&.enabled.present?
!project.auto_devops.enabled && current_application_settings.auto_devops_enabled?
else
current_application_settings.auto_devops_enabled?
end
end
def show_run_auto_devops_pipeline_checkbox_for_explicit_setting?(project)
return false if project.repository.gitlab_ci_yml
!project.auto_devops_enabled?
end
def auto_devops_warning_message(project) def auto_devops_warning_message(project)
missing_domain = !project.auto_devops&.has_domain? missing_domain = !project.auto_devops&.has_domain?
missing_service = !project.deployment_platform&.active? missing_service = !project.deployment_platform&.active?
......
...@@ -20,8 +20,7 @@ module BuildsHelper ...@@ -20,8 +20,7 @@ module BuildsHelper
def javascript_build_options def javascript_build_options
{ {
page_url: project_job_url(@project, @build), page_path: project_job_path(@project, @build),
build_url: project_job_url(@project, @build, :json),
build_status: @build.status, build_status: @build.status,
build_stage: @build.stage, build_stage: @build.stage,
log_state: '' log_state: ''
......
...@@ -228,4 +228,12 @@ module CommitsHelper ...@@ -228,4 +228,12 @@ module CommitsHelper
[commits, 0] [commits, 0]
end end
end end
def commit_path(project, commit, merge_request: nil)
if merge_request&.persisted?
diffs_project_merge_request_path(project, merge_request, commit_id: commit.id)
else
project_commit_path(project, commit)
end
end
end end
...@@ -101,6 +101,30 @@ module MergeRequestsHelper ...@@ -101,6 +101,30 @@ module MergeRequestsHelper
}.merge(merge_params_ee(merge_request)) }.merge(merge_params_ee(merge_request))
end end
def tab_link_for(merge_request, tab, options = {}, &block)
data_attrs = {
action: tab.to_s,
target: "##{tab}",
toggle: options.fetch(:force_link, false) ? '' : 'tab'
}
url = case tab
when :show
data_attrs[:target] = '#notes'
method(:project_merge_request_path)
when :commits
method(:commits_project_merge_request_path)
when :pipelines
method(:pipelines_project_merge_request_path)
when :diffs
method(:diffs_project_merge_request_path)
else
raise "Cannot create tab #{tab}."
end
link_to(url[merge_request.project, merge_request], data: data_attrs, &block)
end
def merge_params_ee(merge_request) def merge_params_ee(merge_request)
{} {}
end end
......
...@@ -18,16 +18,12 @@ module StorageHealthHelper ...@@ -18,16 +18,12 @@ module StorageHealthHelper
current_failures = circuit_breaker.failure_count current_failures = circuit_breaker.failure_count
translation_params = { number_of_failures: current_failures, translation_params = { number_of_failures: current_failures,
maximum_failures: maximum_failures, maximum_failures: maximum_failures }
number_of_seconds: circuit_breaker.failure_wait_time }
if circuit_breaker.circuit_broken? if circuit_breaker.circuit_broken?
s_("%{number_of_failures} of %{maximum_failures} failures. GitLab will not "\ s_("%{number_of_failures} of %{maximum_failures} failures. GitLab will not "\
"retry automatically. Reset storage information when the problem is "\ "retry automatically. Reset storage information when the problem is "\
"resolved.") % translation_params "resolved.") % translation_params
elsif circuit_breaker.backing_off?
_("%{number_of_failures} of %{maximum_failures} failures. GitLab will "\
"block access for %{number_of_seconds} seconds.") % translation_params
else else
_("%{number_of_failures} of %{maximum_failures} failures. GitLab will "\ _("%{number_of_failures} of %{maximum_failures} failures. GitLab will "\
"allow access on the next attempt.") % translation_params "allow access on the next attempt.") % translation_params
......
...@@ -153,11 +153,10 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -153,11 +153,10 @@ class ApplicationSetting < ActiveRecord::Base
presence: true, presence: true,
numericality: { greater_than_or_equal_to: 0 } numericality: { greater_than_or_equal_to: 0 }
validates :circuitbreaker_backoff_threshold, validates :circuitbreaker_failure_count_threshold,
:circuitbreaker_failure_count_threshold,
:circuitbreaker_failure_wait_time,
:circuitbreaker_failure_reset_time, :circuitbreaker_failure_reset_time,
:circuitbreaker_storage_timeout, :circuitbreaker_storage_timeout,
:circuitbreaker_check_interval,
presence: true, presence: true,
numericality: { only_integer: true, greater_than_or_equal_to: 0 } numericality: { only_integer: true, greater_than_or_equal_to: 0 }
...@@ -165,13 +164,6 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -165,13 +164,6 @@ class ApplicationSetting < ActiveRecord::Base
presence: true, presence: true,
numericality: { only_integer: true, greater_than_or_equal_to: 1 } numericality: { only_integer: true, greater_than_or_equal_to: 1 }
validates_each :circuitbreaker_backoff_threshold do |record, attr, value|
if value.to_i >= record.circuitbreaker_failure_count_threshold
record.errors.add(attr, _("The circuitbreaker backoff threshold should be "\
"lower than the failure count threshold"))
end
end
validates :gitaly_timeout_default, validates :gitaly_timeout_default,
presence: true, presence: true,
numericality: { only_integer: true, greater_than_or_equal_to: 0 } numericality: { only_integer: true, greater_than_or_equal_to: 0 }
......
...@@ -6,6 +6,8 @@ module Ci ...@@ -6,6 +6,8 @@ module Ci
include Presentable include Presentable
include Importable include Importable
MissingDependenciesError = Class.new(StandardError)
belongs_to :runner belongs_to :runner
belongs_to :trigger_request belongs_to :trigger_request
belongs_to :erased_by, class_name: 'User' belongs_to :erased_by, class_name: 'User'
...@@ -139,6 +141,10 @@ module Ci ...@@ -139,6 +141,10 @@ module Ci
Ci::Build.retry(build, build.user) Ci::Build.retry(build, build.user)
end end
end end
before_transition any => [:running] do |build|
build.validates_dependencies! unless Feature.enabled?('ci_disable_validates_dependencies')
end
end end
def detailed_status(current_user) def detailed_status(current_user)
...@@ -478,6 +484,20 @@ module Ci ...@@ -478,6 +484,20 @@ module Ci
options[:dependencies]&.empty? options[:dependencies]&.empty?
end end
def validates_dependencies!
dependencies.each do |dependency|
raise MissingDependenciesError unless dependency.valid_dependency?
end
end
def valid_dependency?
return false unless complete?
return false if artifacts_expired?
return false if erased?
true
end
def hide_secrets(trace) def hide_secrets(trace)
return unless trace return unless trace
......
# coding: utf-8
class Commit class Commit
extend ActiveModel::Naming extend ActiveModel::Naming
extend Gitlab::Cache::RequestCache extend Gitlab::Cache::RequestCache
...@@ -25,7 +26,7 @@ class Commit ...@@ -25,7 +26,7 @@ class Commit
DIFF_HARD_LIMIT_FILES = 1000 DIFF_HARD_LIMIT_FILES = 1000
DIFF_HARD_LIMIT_LINES = 50000 DIFF_HARD_LIMIT_LINES = 50000
MIN_SHA_LENGTH = 7 MIN_SHA_LENGTH = Gitlab::Git::Commit::MIN_SHA_LENGTH
COMMIT_SHA_PATTERN = /\h{#{MIN_SHA_LENGTH},40}/.freeze COMMIT_SHA_PATTERN = /\h{#{MIN_SHA_LENGTH},40}/.freeze
def banzai_render_context(field) def banzai_render_context(field)
......
...@@ -43,7 +43,8 @@ class CommitStatus < ActiveRecord::Base ...@@ -43,7 +43,8 @@ class CommitStatus < ActiveRecord::Base
script_failure: 1, script_failure: 1,
api_failure: 2, api_failure: 2,
stuck_or_timeout_failure: 3, stuck_or_timeout_failure: 3,
runner_system_failure: 4 runner_system_failure: 4,
missing_dependency_failure: 5
} }
## ##
......
...@@ -32,6 +32,10 @@ module DiscussionOnDiff ...@@ -32,6 +32,10 @@ module DiscussionOnDiff
first_note.position.new_path first_note.position.new_path
end end
def on_merge_request_commit?
for_merge_request? && commit_id.present?
end
# Returns an array of at most 16 highlighted lines above a diff note # Returns an array of at most 16 highlighted lines above a diff note
def truncated_diff_lines(highlight: true) def truncated_diff_lines(highlight: true)
lines = highlight ? highlighted_diff_lines : diff_lines lines = highlight ? highlighted_diff_lines : diff_lines
......
...@@ -24,7 +24,11 @@ class DiffDiscussion < Discussion ...@@ -24,7 +24,11 @@ class DiffDiscussion < Discussion
return unless for_merge_request? return unless for_merge_request?
return {} if active? return {} if active?
noteable.version_params_for(position.diff_refs) if on_merge_request_commit?
{ commit_id: commit_id }
else
noteable.version_params_for(position.diff_refs)
end
end end
def reply_attributes def reply_attributes
......
...@@ -17,6 +17,7 @@ class DiffNote < Note ...@@ -17,6 +17,7 @@ class DiffNote < Note
validates :noteable_type, inclusion: { in: NOTEABLE_TYPES } validates :noteable_type, inclusion: { in: NOTEABLE_TYPES }
validate :positions_complete validate :positions_complete
validate :verify_supported validate :verify_supported
validate :diff_refs_match_commit, if: :for_commit?
before_validation :set_original_position, on: :create before_validation :set_original_position, on: :create
before_validation :update_position, on: :create, if: :on_text? before_validation :update_position, on: :create, if: :on_text?
...@@ -135,6 +136,12 @@ class DiffNote < Note ...@@ -135,6 +136,12 @@ class DiffNote < Note
errors.add(:position, "is invalid") errors.add(:position, "is invalid")
end end
def diff_refs_match_commit
return if self.original_position.diff_refs == self.commit.diff_refs
errors.add(:commit_id, 'does not match the diff refs')
end
def keep_around_commits def keep_around_commits
project.repository.keep_around(self.original_position.base_sha) project.repository.keep_around(self.original_position.base_sha)
project.repository.keep_around(self.original_position.start_sha) project.repository.keep_around(self.original_position.start_sha)
......
...@@ -11,6 +11,7 @@ class Discussion ...@@ -11,6 +11,7 @@ class Discussion
:author, :author,
:noteable, :noteable,
:commit_id,
:for_commit?, :for_commit?,
:for_merge_request?, :for_merge_request?,
......
...@@ -72,7 +72,7 @@ class Event < ActiveRecord::Base ...@@ -72,7 +72,7 @@ class Event < ActiveRecord::Base
# We're using preload for "push_event_payload" as otherwise the association # We're using preload for "push_event_payload" as otherwise the association
# is not always available (depending on the query being built). # is not always available (depending on the query being built).
includes(:author, :project, project: :namespace) includes(:author, :project, project: :namespace)
.preload(:target, :push_event_payload) .preload(:push_event_payload, target: :author)
end end
scope :for_milestone_id, ->(milestone_id) { where(target_type: "Milestone", target_id: milestone_id) } scope :for_milestone_id, ->(milestone_id) { where(target_type: "Milestone", target_id: milestone_id) }
......
...@@ -649,6 +649,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -649,6 +649,7 @@ class MergeRequest < ActiveRecord::Base
.to_sql .to_sql
Note.from("(#{union}) #{Note.table_name}") Note.from("(#{union}) #{Note.table_name}")
.includes(:noteable)
end end
alias_method :discussion_notes, :related_notes alias_method :discussion_notes, :related_notes
...@@ -925,21 +926,27 @@ class MergeRequest < ActiveRecord::Base ...@@ -925,21 +926,27 @@ class MergeRequest < ActiveRecord::Base
.order(id: :desc) .order(id: :desc)
end end
# Note that this could also return SHA from now dangling commits def all_commits
#
def all_commit_shas
return commit_shas unless persisted?
diffs_relation = merge_request_diffs
# MySQL doesn't support LIMIT in a subquery. # MySQL doesn't support LIMIT in a subquery.
diffs_relation = diffs_relation.recent if Gitlab::Database.postgresql? diffs_relation = if Gitlab::Database.postgresql?
merge_request_diffs.recent
else
merge_request_diffs
end
MergeRequestDiffCommit MergeRequestDiffCommit
.where(merge_request_diff: diffs_relation) .where(merge_request_diff: diffs_relation)
.limit(10_000) .limit(10_000)
.pluck('sha') end
.uniq
# Note that this could also return SHA from now dangling commits
#
def all_commit_shas
@all_commit_shas ||= begin
return commit_shas unless persisted?
all_commits.pluck(:sha).uniq
end
end end
def merge_commit def merge_commit
......
...@@ -40,6 +40,7 @@ class Namespace < ActiveRecord::Base ...@@ -40,6 +40,7 @@ class Namespace < ActiveRecord::Base
namespace_path: true namespace_path: true
validate :nesting_level_allowed validate :nesting_level_allowed
validate :allowed_path_by_redirects
delegate :name, to: :owner, allow_nil: true, prefix: true delegate :name, to: :owner, allow_nil: true, prefix: true
...@@ -257,4 +258,14 @@ class Namespace < ActiveRecord::Base ...@@ -257,4 +258,14 @@ class Namespace < ActiveRecord::Base
Namespace.where(id: descendants.select(:id)) Namespace.where(id: descendants.select(:id))
.update_all(share_with_group_lock: true) .update_all(share_with_group_lock: true)
end end
def allowed_path_by_redirects
return if path.nil?
errors.add(:path, "#{path} has been taken before. Please use another one") if namespace_previously_created_with_same_path?
end
def namespace_previously_created_with_same_path?
RedirectRoute.permanent.exists?(path: path)
end
end end
...@@ -230,16 +230,18 @@ class Note < ActiveRecord::Base ...@@ -230,16 +230,18 @@ class Note < ActiveRecord::Base
for_personal_snippet? for_personal_snippet?
end end
def commit
@commit ||= project.commit(commit_id) if commit_id.present?
end
# override to return commits, which are not active record # override to return commits, which are not active record
def noteable def noteable
if for_commit? return commit if for_commit?
@commit ||= project.commit(commit_id)
else super
super
end
# Temp fix to prevent app crash
# if note commit id doesn't exist
rescue rescue
# Temp fix to prevent app crash
# if note commit id doesn't exist
nil nil
end end
...@@ -401,6 +403,10 @@ class Note < ActiveRecord::Base ...@@ -401,6 +403,10 @@ class Note < ActiveRecord::Base
noteable_object&.touch noteable_object&.touch
end end
def banzai_render_context(field)
super.merge(noteable: noteable)
end
private private
def keep_around_commit def keep_around_commit
......
...@@ -3,6 +3,8 @@ class PersonalAccessToken < ActiveRecord::Base ...@@ -3,6 +3,8 @@ class PersonalAccessToken < ActiveRecord::Base
include TokenAuthenticatable include TokenAuthenticatable
add_authentication_token_field :token add_authentication_token_field :token
REDIS_EXPIRY_TIME = 3.minutes
serialize :scopes, Array # rubocop:disable Cop/ActiveRecordSerialize serialize :scopes, Array # rubocop:disable Cop/ActiveRecordSerialize
belongs_to :user belongs_to :user
...@@ -27,6 +29,21 @@ class PersonalAccessToken < ActiveRecord::Base ...@@ -27,6 +29,21 @@ class PersonalAccessToken < ActiveRecord::Base
!revoked? && !expired? !revoked? && !expired?
end end
def self.redis_getdel(user_id)
Gitlab::Redis::SharedState.with do |redis|
token = redis.get(redis_shared_state_key(user_id))
redis.del(redis_shared_state_key(user_id))
token
end
end
def self.redis_store!(user_id, token)
Gitlab::Redis::SharedState.with do |redis|
redis.set(redis_shared_state_key(user_id), token, ex: REDIS_EXPIRY_TIME)
token
end
end
protected protected
def validate_scopes def validate_scopes
...@@ -38,4 +55,8 @@ class PersonalAccessToken < ActiveRecord::Base ...@@ -38,4 +55,8 @@ class PersonalAccessToken < ActiveRecord::Base
def set_default_scopes def set_default_scopes
self.scopes = Gitlab::Auth::DEFAULT_SCOPES if self.scopes.empty? self.scopes = Gitlab::Auth::DEFAULT_SCOPES if self.scopes.empty?
end end
def self.redis_shared_state_key(user_id)
"gitlab:personal_access_token:#{user_id}"
end
end end
...@@ -227,7 +227,6 @@ class Project < ActiveRecord::Base ...@@ -227,7 +227,6 @@ class Project < ActiveRecord::Base
delegate :members, to: :team, prefix: true delegate :members, to: :team, prefix: true
delegate :add_user, :add_users, to: :team delegate :add_user, :add_users, to: :team
delegate :add_guest, :add_reporter, :add_developer, :add_master, to: :team delegate :add_guest, :add_reporter, :add_developer, :add_master, to: :team
delegate :empty_repo?, to: :repository
# Validations # Validations
validates :creator, presence: true, on: :create validates :creator, presence: true, on: :create
...@@ -499,6 +498,10 @@ class Project < ActiveRecord::Base ...@@ -499,6 +498,10 @@ class Project < ActiveRecord::Base
auto_devops&.enabled.nil? && !current_application_settings.auto_devops_enabled? auto_devops&.enabled.nil? && !current_application_settings.auto_devops_enabled?
end end
def empty_repo?
repository.empty?
end
def repository_storage_path def repository_storage_path
Gitlab.config.repositories.storages[repository_storage].try(:[], 'path') Gitlab.config.repositories.storages[repository_storage].try(:[], 'path')
end end
......
...@@ -17,4 +17,32 @@ class RedirectRoute < ActiveRecord::Base ...@@ -17,4 +17,32 @@ class RedirectRoute < ActiveRecord::Base
where(wheres, path, "#{sanitize_sql_like(path)}/%") where(wheres, path, "#{sanitize_sql_like(path)}/%")
end end
scope :permanent, -> do
if column_permanent_exists?
where(permanent: true)
else
none
end
end
scope :temporary, -> do
if column_permanent_exists?
where(permanent: [false, nil])
else
all
end
end
default_value_for :permanent, false
def permanent=(value)
if self.class.column_permanent_exists?
super
end
end
def self.column_permanent_exists?
ActiveRecord::Base.connection.column_exists?(:redirect_routes, :permanent)
end
end end
...@@ -37,7 +37,7 @@ class Repository ...@@ -37,7 +37,7 @@ class Repository
issue_template_names merge_request_template_names).freeze issue_template_names merge_request_template_names).freeze
# Methods that use cache_method but only memoize the value # Methods that use cache_method but only memoize the value
MEMOIZED_CACHED_METHODS = %i(license empty_repo?).freeze MEMOIZED_CACHED_METHODS = %i(license).freeze
# Certain method caches should be refreshed when certain types of files are # Certain method caches should be refreshed when certain types of files are
# changed. This Hash maps file types (as returned by Gitlab::FileDetector) to # changed. This Hash maps file types (as returned by Gitlab::FileDetector) to
...@@ -497,7 +497,11 @@ class Repository ...@@ -497,7 +497,11 @@ class Repository
end end
cache_method :exists? cache_method :exists?
delegate :empty?, to: :raw_repository def empty?
return true unless exists?
!has_visible_content?
end
cache_method :empty? cache_method :empty?
# The size of this repository in megabytes. # The size of this repository in megabytes.
...@@ -944,13 +948,8 @@ class Repository ...@@ -944,13 +948,8 @@ class Repository
end end
end end
def empty_repo?
!exists? || !has_visible_content?
end
cache_method :empty_repo?, memoize_only: true
def search_files_by_content(query, ref) def search_files_by_content(query, ref)
return [] if empty_repo? || query.blank? return [] if empty? || query.blank?
offset = 2 offset = 2
args = %W(grep -i -I -n --before-context #{offset} --after-context #{offset} -E -e #{Regexp.escape(query)} #{ref || root_ref}) args = %W(grep -i -I -n --before-context #{offset} --after-context #{offset} -E -e #{Regexp.escape(query)} #{ref || root_ref})
...@@ -959,7 +958,7 @@ class Repository ...@@ -959,7 +958,7 @@ class Repository
end end
def search_files_by_name(query, ref) def search_files_by_name(query, ref)
return [] if empty_repo? || query.blank? return [] if empty? || query.blank?
args = %W(ls-tree --full-tree -r #{ref || root_ref} --name-status | #{Regexp.escape(query)}) args = %W(ls-tree --full-tree -r #{ref || root_ref} --name-status | #{Regexp.escape(query)})
......
...@@ -8,6 +8,8 @@ class Route < ActiveRecord::Base ...@@ -8,6 +8,8 @@ class Route < ActiveRecord::Base
presence: true, presence: true,
uniqueness: { case_sensitive: false } uniqueness: { case_sensitive: false }
validate :ensure_permanent_paths
after_create :delete_conflicting_redirects after_create :delete_conflicting_redirects
after_update :delete_conflicting_redirects, if: :path_changed? after_update :delete_conflicting_redirects, if: :path_changed?
after_update :create_redirect_for_old_path after_update :create_redirect_for_old_path
...@@ -40,7 +42,7 @@ class Route < ActiveRecord::Base ...@@ -40,7 +42,7 @@ class Route < ActiveRecord::Base
# We are not calling route.delete_conflicting_redirects here, in hopes # We are not calling route.delete_conflicting_redirects here, in hopes
# of avoiding deadlocks. The parent (self, in this method) already # of avoiding deadlocks. The parent (self, in this method) already
# called it, which deletes conflicts for all descendants. # called it, which deletes conflicts for all descendants.
route.create_redirect(old_path) if attributes[:path] route.create_redirect(old_path, permanent: permanent_redirect?) if attributes[:path]
end end
end end
end end
...@@ -50,16 +52,30 @@ class Route < ActiveRecord::Base ...@@ -50,16 +52,30 @@ class Route < ActiveRecord::Base
end end
def conflicting_redirects def conflicting_redirects
RedirectRoute.matching_path_and_descendants(path) RedirectRoute.temporary.matching_path_and_descendants(path)
end end
def create_redirect(path) def create_redirect(path, permanent: false)
RedirectRoute.create(source: source, path: path) RedirectRoute.create(source: source, path: path, permanent: permanent)
end end
private private
def create_redirect_for_old_path def create_redirect_for_old_path
create_redirect(path_was) if path_changed? create_redirect(path_was, permanent: permanent_redirect?) if path_changed?
end
def permanent_redirect?
source_type != "Project"
end
def ensure_permanent_paths
return if path.nil?
errors.add(:path, "#{path} has been taken before. Please use another one") if conflicting_redirect_exists?
end
def conflicting_redirect_exists?
RedirectRoute.permanent.matching_path_and_descendants(path).exists?
end end
end end
...@@ -12,18 +12,19 @@ module Ci ...@@ -12,18 +12,19 @@ module Ci
def execute(source, ignore_skip_ci: false, save_on_errors: true, trigger_request: nil, schedule: nil, &block) def execute(source, ignore_skip_ci: false, save_on_errors: true, trigger_request: nil, schedule: nil, &block)
@pipeline = Ci::Pipeline.new @pipeline = Ci::Pipeline.new
command = OpenStruct.new(source: source, command = Gitlab::Ci::Pipeline::Chain::Command.new(
origin_ref: params[:ref], source: source,
checkout_sha: params[:checkout_sha], origin_ref: params[:ref],
after_sha: params[:after], checkout_sha: params[:checkout_sha],
before_sha: params[:before], after_sha: params[:after],
trigger_request: trigger_request, before_sha: params[:before],
schedule: schedule, trigger_request: trigger_request,
ignore_skip_ci: ignore_skip_ci, schedule: schedule,
save_incompleted: save_on_errors, ignore_skip_ci: ignore_skip_ci,
seeds_block: block, save_incompleted: save_on_errors,
project: project, seeds_block: block,
current_user: current_user) project: project,
current_user: current_user)
sequence = Gitlab::Ci::Pipeline::Chain::Sequence sequence = Gitlab::Ci::Pipeline::Chain::Sequence
.new(pipeline, command, SEQUENCE) .new(pipeline, command, SEQUENCE)
......
...@@ -54,6 +54,9 @@ module Ci ...@@ -54,6 +54,9 @@ module Ci
# we still have to return 409 in the end, # we still have to return 409 in the end,
# to make sure that this is properly handled by runner. # to make sure that this is properly handled by runner.
valid = false valid = false
rescue Ci::Build::MissingDependenciesError
build.drop!(:missing_dependency_failure)
valid = false
end end
end end
......
...@@ -6,7 +6,7 @@ module MergeRequests ...@@ -6,7 +6,7 @@ module MergeRequests
@oldrev, @newrev = oldrev, newrev @oldrev, @newrev = oldrev, newrev
@branch_name = Gitlab::Git.ref_name(ref) @branch_name = Gitlab::Git.ref_name(ref)
find_new_commits Gitlab::GitalyClient.allow_n_plus_1_calls(&method(:find_new_commits))
# Be sure to close outstanding MRs before reloading them to avoid generating an # Be sure to close outstanding MRs before reloading them to avoid generating an
# empty diff during a manual merge # empty diff during a manual merge
close_merge_requests close_merge_requests
......
...@@ -20,7 +20,7 @@ class MetricsService ...@@ -20,7 +20,7 @@ class MetricsService
end end
def metrics_text def metrics_text
"#{health_metrics_text}#{prometheus_metrics_text}" prometheus_metrics_text.concat(health_metrics_text)
end end
private private
......
...@@ -15,7 +15,7 @@ module Projects ...@@ -15,7 +15,7 @@ module Projects
return error("Could not set the default branch") unless project.change_head(params[:default_branch]) return error("Could not set the default branch") unless project.change_head(params[:default_branch])
end end
if project.update_attributes(update_params) if project.update_attributes(params.except(:default_branch))
if project.previous_changes.include?('path') if project.previous_changes.include?('path')
project.rename_repo project.rename_repo
else else
...@@ -32,15 +32,13 @@ module Projects ...@@ -32,15 +32,13 @@ module Projects
end end
def run_auto_devops_pipeline? def run_auto_devops_pipeline?
params.dig(:run_auto_devops_pipeline_explicit) == 'true' || params.dig(:run_auto_devops_pipeline_implicit) == 'true' return false if project.repository.gitlab_ci_yml || !project.auto_devops.previous_changes.include?('enabled')
project.auto_devops.enabled? || (project.auto_devops.enabled.nil? && current_application_settings.auto_devops_enabled?)
end end
private private
def update_params
params.except(:default_branch, :run_auto_devops_pipeline_explicit, :run_auto_devops_pipeline_implicit)
end
def renaming_project_with_container_registry_tags? def renaming_project_with_container_registry_tags?
new_path = params[:path] new_path = params[:path]
......
...@@ -545,6 +545,12 @@ ...@@ -545,6 +545,12 @@
%fieldset %fieldset
%legend Git Storage Circuitbreaker settings %legend Git Storage Circuitbreaker settings
.form-group
= f.label :circuitbreaker_check_interval, _('Check interval'), class: 'control-label col-sm-2'
.col-sm-10
= f.number_field :circuitbreaker_check_interval, class: 'form-control'
.help-block
= circuitbreaker_check_interval_help_text
.form-group .form-group
= f.label :circuitbreaker_access_retries, _('Number of access attempts'), class: 'control-label col-sm-2' = f.label :circuitbreaker_access_retries, _('Number of access attempts'), class: 'control-label col-sm-2'
.col-sm-10 .col-sm-10
...@@ -557,18 +563,6 @@ ...@@ -557,18 +563,6 @@
= f.number_field :circuitbreaker_storage_timeout, class: 'form-control' = f.number_field :circuitbreaker_storage_timeout, class: 'form-control'
.help-block .help-block
= circuitbreaker_storage_timeout_help_text = circuitbreaker_storage_timeout_help_text
.form-group
= f.label :circuitbreaker_backoff_threshold, _('Number of failures before backing off'), class: 'control-label col-sm-2'
.col-sm-10
= f.number_field :circuitbreaker_backoff_threshold, class: 'form-control'
.help-block
= circuitbreaker_backoff_threshold_help_text
.form-group
= f.label :circuitbreaker_failure_wait_time, _('Seconds to wait after a storage failure'), class: 'control-label col-sm-2'
.col-sm-10
= f.number_field :circuitbreaker_failure_wait_time, class: 'form-control'
.help-block
= circuitbreaker_failure_wait_time_help_text
.form-group .form-group
= f.label :circuitbreaker_failure_count_threshold, _('Maximum git storage failures'), class: 'control-label col-sm-2' = f.label :circuitbreaker_failure_count_threshold, _('Maximum git storage failures'), class: 'control-label col-sm-2'
.col-sm-10 .col-sm-10
......
...@@ -32,9 +32,17 @@ ...@@ -32,9 +32,17 @@
- elsif discussion.diff_discussion? - elsif discussion.diff_discussion?
on on
= conditional_link_to url.present?, url do = conditional_link_to url.present?, url do
- unless discussion.active? - if discussion.on_merge_request_commit?
an old version of - unless discussion.active?
the diff an outdated change in
commit
%span.commit-sha= Commit.truncate_sha(discussion.commit_id)
- else
- unless discussion.active?
an old version of
the diff
= time_ago_with_tooltip(discussion.created_at, placement: "bottom", html_class: "note-created-ago") = time_ago_with_tooltip(discussion.created_at, placement: "bottom", html_class: "note-created-ago")
= render "discussions/headline", discussion: discussion = render "discussions/headline", discussion: discussion
......
- humanized_resource_name = spammable.class.model_name.human.downcase - humanized_resource_name = spammable.class.model_name.human.downcase
- resource_name = spammable.class.model_name.singular
%h3.page-title %h3.page-title
Anti-spam verification Anti-spam verification
...@@ -8,16 +7,4 @@ ...@@ -8,16 +7,4 @@
%p %p
#{"We detected potential spam in the #{humanized_resource_name}. Please solve the reCAPTCHA to proceed."} #{"We detected potential spam in the #{humanized_resource_name}. Please solve the reCAPTCHA to proceed."}
= form_for form do |f| = render 'shared/recaptcha_form', spammable: spammable
.recaptcha
- params[resource_name].each do |field, value|
= hidden_field(resource_name, field, value: value)
= hidden_field_tag(:spam_log_id, spammable.spam_log.id)
= hidden_field_tag(:recaptcha_verification, true)
= recaptcha_tags
-# Yields a block with given extra params.
= yield
.row-content-block.footer-block
= f.submit "Submit #{humanized_resource_name}", class: 'btn btn-create'
...@@ -13,7 +13,14 @@ ...@@ -13,7 +13,14 @@
.location-badge= label .location-badge= label
.search-input-wrap .search-input-wrap
.dropdown{ data: { url: search_autocomplete_path } } .dropdown{ data: { url: search_autocomplete_path } }
= search_field_tag 'search', nil, placeholder: 'Search', class: 'search-input dropdown-menu-toggle no-outline js-search-dashboard-options', spellcheck: false, tabindex: '1', autocomplete: 'off', data: { issues_path: issues_dashboard_url, mr_path: merge_requests_dashboard_url }, aria: { label: 'Search' } = search_field_tag 'search', nil, placeholder: 'Search',
class: 'search-input dropdown-menu-toggle no-outline js-search-dashboard-options',
spellcheck: false,
tabindex: '1',
autocomplete: 'off',
data: { issues_path: issues_dashboard_path,
mr_path: merge_requests_dashboard_path },
aria: { label: 'Search' }
%button.hidden.js-dropdown-search-toggle{ type: 'button', data: { toggle: 'dropdown' } } %button.hidden.js-dropdown-search-toggle{ type: 'button', data: { toggle: 'dropdown' } }
.dropdown-menu.dropdown-select .dropdown-menu.dropdown-select
= dropdown_content do = dropdown_content do
......
...@@ -15,14 +15,13 @@ ...@@ -15,14 +15,13 @@
They are the only accepted password when you have Two-Factor Authentication (2FA) enabled. They are the only accepted password when you have Two-Factor Authentication (2FA) enabled.
.col-lg-8 .col-lg-8
- if @new_personal_access_token
- if flash[:personal_access_token]
.created-personal-access-token-container .created-personal-access-token-container
%h5.prepend-top-0 %h5.prepend-top-0
Your New Personal Access Token Your New Personal Access Token
.form-group .form-group
= text_field_tag 'created-personal-access-token', flash[:personal_access_token], readonly: true, class: "form-control js-select-on-focus", 'aria-describedby' => "created-personal-access-token-help-block" = text_field_tag 'created-personal-access-token', @new_personal_access_token, readonly: true, class: "form-control js-select-on-focus", 'aria-describedby' => "created-personal-access-token-help-block"
= clipboard_button(text: flash[:personal_access_token], title: "Copy personal access token to clipboard", placement: "left") = clipboard_button(text: @new_personal_access_token, title: "Copy personal access token to clipboard", placement: "left")
%span#created-personal-access-token-help-block.help-block.text-danger Make sure you save it - you won't be able to access it again. %span#created-personal-access-token-help-block.help-block.text-danger Make sure you save it - you won't be able to access it again.
%hr %hr
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
- if defined?(@merge_request) && @merge_request.discussion_locked? - if defined?(@merge_request) && @merge_request.discussion_locked?
.issuable-note-warning .issuable-note-warning
= icon('lock', class: 'icon') = sprite_icon('lock', size: 16, css_class: 'icon')
%span %span
= _('This merge request is locked.') = _('This merge request is locked.')
= _('Only project members can comment.') = _('Only project members can comment.')
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
%td.branch-commit %td.branch-commit
- if can?(current_user, :read_build, job) - if can?(current_user, :read_build, job)
= link_to project_job_url(job.project, job) do = link_to project_job_path(job.project, job) do
%span.build-link ##{job.id} %span.build-link ##{job.id}
- else - else
%span.build-link ##{job.id} %span.build-link ##{job.id}
......
...@@ -47,7 +47,7 @@ ...@@ -47,7 +47,7 @@
%li= link_to s_("DownloadCommit|Email Patches"), project_commit_path(@project, @commit, format: :patch) %li= link_to s_("DownloadCommit|Email Patches"), project_commit_path(@project, @commit, format: :patch)
%li= link_to s_("DownloadCommit|Plain Diff"), project_commit_path(@project, @commit, format: :diff) %li= link_to s_("DownloadCommit|Plain Diff"), project_commit_path(@project, @commit, format: :diff)
.commit-box .commit-box{ data: { project_path: project_path(@project) } }
%h3.commit-title %h3.commit-title
= markdown(@commit.title, pipeline: :single_line, author: @commit.author) = markdown(@commit.title, pipeline: :single_line, author: @commit.author)
- if @commit.description.present? - if @commit.description.present?
...@@ -80,3 +80,13 @@ ...@@ -80,3 +80,13 @@
- if last_pipeline.duration - if last_pipeline.duration
in in
= time_interval_in_words last_pipeline.duration = time_interval_in_words last_pipeline.duration
- if @merge_request
.well-segment
= icon('info-circle fw')
This commit is part of merge request
= succeed '.' do
= link_to @merge_request.to_reference, diffs_project_merge_request_path(@project, @merge_request, commit_id: @commit.id)
Comments created here will be created in the context of that merge request.
...@@ -6,6 +6,9 @@ ...@@ -6,6 +6,9 @@
- @content_class = limited_container_width - @content_class = limited_container_width
- page_title "#{@commit.title} (#{@commit.short_id})", "Commits" - page_title "#{@commit.title} (#{@commit.short_id})", "Commits"
- page_description @commit.description - page_description @commit.description
- content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('common_vue')
= page_specific_javascript_bundle_tag('diff_notes')
.container-fluid{ class: [limited_container_width, container_class] } .container-fluid{ class: [limited_container_width, container_class] }
= render "commit_box" = render "commit_box"
......
- ref = local_assigns.fetch(:ref) - view_details = local_assigns.fetch(:view_details, false)
- merge_request = local_assigns.fetch(:merge_request, nil)
- cache_key = [project.full_path, commit.id, current_application_settings, @path.presence, current_controller?(:commits), I18n.locale] - project = local_assigns.fetch(:project) { merge_request&.project }
- cache_key.push(commit.status(ref)) if commit.status(ref) - ref = local_assigns.fetch(:ref) { merge_request&.source_branch }
- link = commit_path(project, commit, merge_request: merge_request)
- cache_key = [project.full_path,
commit.id,
current_application_settings,
@path.presence,
current_controller?(:commits),
merge_request&.iid,
view_details,
commit.status(ref),
I18n.locale].compact
= cache(cache_key, expires_in: 1.day) do = cache(cache_key, expires_in: 1.day) do
%li.commit.flex-row.js-toggle-container{ id: "commit-#{commit.short_id}" } %li.commit.flex-row.js-toggle-container{ id: "commit-#{commit.short_id}" }
...@@ -11,7 +22,7 @@ ...@@ -11,7 +22,7 @@
.commit-detail .commit-detail
.commit-content .commit-content
= link_to_markdown_field(commit, :title, project_commit_path(project, commit.id), class: "commit-row-message item-title") = link_to_markdown_field(commit, :title, link, class: "commit-row-message item-title")
%span.commit-row-message.visible-xs-inline %span.commit-row-message.visible-xs-inline
&middot; &middot;
= commit.short_id = commit.short_id
...@@ -31,8 +42,7 @@ ...@@ -31,8 +42,7 @@
- commit_text = _('%{commit_author_link} committed %{commit_timeago}') % { commit_author_link: commit_author_link, commit_timeago: commit_timeago } - commit_text = _('%{commit_author_link} committed %{commit_timeago}') % { commit_author_link: commit_author_link, commit_timeago: commit_timeago }
#{ commit_text.html_safe } #{ commit_text.html_safe }
.commit-actions.flex-row.hidden-xs
.commit-actions.hidden-xs
- if request.xhr? - if request.xhr?
= render partial: 'projects/commit/signature', object: commit.signature = render partial: 'projects/commit/signature', object: commit.signature
- else - else
...@@ -41,6 +51,9 @@ ...@@ -41,6 +51,9 @@
- if commit.status(ref) - if commit.status(ref)
= render_commit_status(commit, ref: ref) = render_commit_status(commit, ref: ref)
= link_to commit.short_id, project_commit_path(project, commit), class: "commit-sha btn btn-transparent btn-link" = link_to commit.short_id, link, class: "commit-sha btn btn-transparent btn-link"
= clipboard_button(text: commit.id, title: _("Copy commit SHA to clipboard")) = clipboard_button(text: commit.id, title: _("Copy commit SHA to clipboard"))
= link_to_browse_code(project, commit) = link_to_browse_code(project, commit)
- if view_details && merge_request
= link_to "View details", project_commit_path(project, commit.id, merge_request_iid: merge_request.iid), class: "btn btn-default"
- ref = local_assigns.fetch(:ref) - merge_request = local_assigns.fetch(:merge_request, nil)
- project = local_assigns.fetch(:project) { merge_request&.project }
- ref = local_assigns.fetch(:ref) { merge_request&.source_branch }
- commits, hidden = limited_commits(@commits) - commits, hidden = limited_commits(@commits)
- commits.chunk { |c| c.committed_date.in_time_zone.to_date }.each do |day, commits| - commits.chunk { |c| c.committed_date.in_time_zone.to_date }.each do |day, commits|
...@@ -8,7 +11,7 @@ ...@@ -8,7 +11,7 @@
%li.commits-row{ data: { day: day } } %li.commits-row{ data: { day: day } }
%ul.content-list.commit-list.flex-list %ul.content-list.commit-list.flex-list
= render partial: 'projects/commits/commit', collection: commits, locals: { project: project, ref: ref } = render partial: 'projects/commits/commit', collection: commits, locals: { project: project, ref: ref, merge_request: merge_request }
- if hidden > 0 - if hidden > 0
%li.alert.alert-warning %li.alert.alert-warning
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
- page_title _("Commits"), @ref - page_title _("Commits"), @ref
= content_for :meta_tags do = content_for :meta_tags do
= auto_discovery_link_tag(:atom, project_commits_url(@project, @ref, rss_url_options), title: "#{@project.name}:#{@ref} commits") = auto_discovery_link_tag(:atom, project_commits_path(@project, @ref, rss_url_options), title: "#{@project.name}:#{@ref} commits")
.js-project-commits-show{ 'data-commits-limit' => @limit } .js-project-commits-show{ 'data-commits-limit' => @limit }
%div{ class: container_class } %div{ class: container_class }
......
...@@ -19,4 +19,6 @@ ...@@ -19,4 +19,6 @@
"empty-loading-svg-path": image_path('illustrations/monitoring/loading'), "empty-loading-svg-path": image_path('illustrations/monitoring/loading'),
"empty-unable-to-connect-svg-path": image_path('illustrations/monitoring/unable_to_connect'), "empty-unable-to-connect-svg-path": image_path('illustrations/monitoring/unable_to_connect'),
"additional-metrics": additional_metrics_project_environment_path(@project, @environment, format: :json), "additional-metrics": additional_metrics_project_environment_path(@project, @environment, format: :json),
"project-path": project_path(@project),
"tags-path": project_tags_path(@project),
"has-metrics": "#{@environment.has_metrics?}", deployment_endpoint: project_environment_deployments_path(@project, @environment, format: :json) } } "has-metrics": "#{@environment.has_metrics?}", deployment_endpoint: project_environment_deployments_path(@project, @environment, format: :json) } }
...@@ -14,4 +14,4 @@ ...@@ -14,4 +14,4 @@
notes_path: notes_url, notes_path: notes_url,
last_fetched_at: Time.now.to_i, last_fetched_at: Time.now.to_i,
noteable_data: serialize_issuable(@issue), noteable_data: serialize_issuable(@issue),
current_user_data: UserSerializer.new.represent(current_user).to_json } } current_user_data: UserSerializer.new.represent(current_user, only_path: true).to_json } }
...@@ -74,10 +74,10 @@ ...@@ -74,10 +74,10 @@
= edited_time_ago_with_tooltip(@issue, placement: 'bottom', html_class: 'issue-edited-ago js-issue-edited-ago') = edited_time_ago_with_tooltip(@issue, placement: 'bottom', html_class: 'issue-edited-ago js-issue-edited-ago')
#merge-requests{ data: { url: referenced_merge_requests_project_issue_url(@project, @issue) } } #merge-requests{ data: { url: referenced_merge_requests_project_issue_path(@project, @issue) } }
// This element is filled in using JavaScript. // This element is filled in using JavaScript.
#related-branches{ data: { url: related_branches_project_issue_url(@project, @issue) } } #related-branches{ data: { url: related_branches_project_issue_path(@project, @issue) } }
// This element is filled in using JavaScript. // This element is filled in using JavaScript.
.content-block.emoji-block .content-block.emoji-block
......
...@@ -5,4 +5,4 @@ ...@@ -5,4 +5,4 @@
= custom_icon ('illustration_no_commits') = custom_icon ('illustration_no_commits')
- else - else
%ol#commits-list.list-unstyled %ol#commits-list.list-unstyled
= render "projects/commits/commits", project: @merge_request.source_project, ref: @merge_request.source_branch = render "projects/commits/commits", merge_request: @merge_request
- if @commit
.info-well.hidden-xs.prepend-top-default
.well-segment
%ul.blob-commit-info
= render 'projects/commits/commit', commit: @commit, merge_request: @merge_request, view_details: true
- if @merge_request_diff && different_base?(@start_version, @merge_request_diff)
.mr-version-controls
.content-block
= icon('info-circle')
Selected versions have different base commits.
Changes will include
= link_to project_compare_path(@project, from: @start_version.base_commit_sha, to: @merge_request_diff.base_commit_sha) do
new commits
from
= succeed '.' do
%code.ref-name= @merge_request.target_branch
- if @merge_request_diff.collected? || @merge_request_diff.overflow? = render 'projects/merge_requests/diffs/version_controls'
= render 'projects/merge_requests/diffs/versions' = render 'projects/merge_requests/diffs/different_base'
= render "projects/diffs/diffs", diffs: @diffs, environment: @environment, merge_request: true = render 'projects/merge_requests/diffs/not_all_comments_displayed'
- elsif @merge_request_diff.empty? = render 'projects/merge_requests/diffs/commit_widget'
- if @merge_request_diff&.empty?
.nothing-here-block .nothing-here-block
= image_tag 'illustrations/merge_request_changes_empty.svg' = image_tag 'illustrations/merge_request_changes_empty.svg'
%p = succeed '.' do
Nothing to merge from No changes between
%strong= @merge_request.source_branch %span.ref-name= @merge_request.source_branch
into and
%strong= @merge_request.target_branch %span.ref-name= @merge_request.target_branch
%p= link_to 'Create commit', project_new_blob_path(@project, @merge_request.source_branch), class: 'btn btn-save' %p= link_to 'Create commit', project_new_blob_path(@project, @merge_request.source_branch), class: 'btn btn-save'
- else
- diff_viewable = @merge_request_diff ? @merge_request_diff.collected? || @merge_request_diff.overflow? : true
- if diff_viewable
= render "projects/diffs/diffs", diffs: @diffs, environment: @environment, merge_request: true
- if @commit || @start_version || (@merge_request_diff && !@merge_request_diff.latest?)
.mr-version-controls
.content-block.comments-disabled-notif.clearfix
= icon('info-circle')
= succeed '.' do
- if @commit
Only comments from the following commit are shown below
- else
Not all comments are displayed because you're
- if @start_version
comparing two versions of the diff
- else
viewing an old version of the diff
.pull-right
= link_to diffs_project_merge_request_path(@merge_request.project, @merge_request), class: 'btn btn-sm' do
Show latest version
= "of the diff" if @commit
- if @merge_request_diffs.size > 1 - if @merge_request_diff && @merge_request_diffs.size > 1
.mr-version-controls .mr-version-controls
.mr-version-menus-container.content-block .mr-version-menus-container.content-block
Changes between Changes between
...@@ -71,27 +71,3 @@ ...@@ -71,27 +71,3 @@
(base) (base)
%div %div
%strong.commit-sha= short_sha(@merge_request_diff.base_commit_sha) %strong.commit-sha= short_sha(@merge_request_diff.base_commit_sha)
- if different_base?(@start_version, @merge_request_diff)
.content-block
= icon('info-circle')
Selected versions have different base commits.
Changes will include
= link_to project_compare_path(@project, from: @start_version.base_commit_sha, to: @merge_request_diff.base_commit_sha) do
new commits
from
= succeed '.' do
%code= @merge_request.target_branch
- if @start_version || !@merge_request_diff.latest?
.comments-disabled-notif.content-block
= icon('info-circle')
Not all comments are displayed because you're
- if @start_version
comparing two versions
- else
viewing an old version
of the diff.
.pull-right
= link_to 'Show latest version', diffs_project_merge_request_path(@project, @merge_request), class: 'btn btn-sm'
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
= webpack_bundle_tag('common_vue') = webpack_bundle_tag('common_vue')
= webpack_bundle_tag('diff_notes') = webpack_bundle_tag('diff_notes')
.merge-request{ 'data-mr-action': "#{j params[:tab].presence || 'show'}", 'data-url' => merge_request_path(@merge_request, format: :json), 'data-project-path' => project_path(@merge_request.project) } .merge-request{ data: { mr_action: j(params[:tab].presence || 'show'), url: merge_request_path(@merge_request, format: :json), project_path: project_path(@merge_request.project) } }
= render "projects/merge_requests/mr_title" = render "projects/merge_requests/mr_title"
.merge-request-details.issuable-details{ data: { id: @merge_request.project.id } } .merge-request-details.issuable-details{ data: { id: @merge_request.project.id } }
...@@ -38,21 +38,21 @@ ...@@ -38,21 +38,21 @@
.nav-links.scrolling-tabs .nav-links.scrolling-tabs
%ul.merge-request-tabs %ul.merge-request-tabs
%li.notes-tab %li.notes-tab
= link_to project_merge_request_path(@project, @merge_request), data: { target: 'div#notes', action: 'show', toggle: 'tab' } do = tab_link_for @merge_request, :show, force_link: @commit.present? do
Discussion Discussion
%span.badge= @merge_request.related_notes.user.count %span.badge= @merge_request.related_notes.user.count
- if @merge_request.source_project - if @merge_request.source_project
%li.commits-tab %li.commits-tab
= link_to commits_project_merge_request_path(@project, @merge_request), data: { target: 'div#commits', action: 'commits', toggle: 'tab' } do = tab_link_for @merge_request, :commits do
Commits Commits
%span.badge= @commits_count %span.badge= @commits_count
- if @pipelines.any? - if @pipelines.any?
%li.pipelines-tab %li.pipelines-tab
= link_to pipelines_project_merge_request_path(@project, @merge_request), data: { target: '#pipelines', action: 'pipelines', toggle: 'tab' } do = tab_link_for @merge_request, :pipelines do
Pipelines Pipelines
%span.badge.js-pipelines-mr-count= @pipelines.size %span.badge.js-pipelines-mr-count= @pipelines.size
%li.diffs-tab %li.diffs-tab
= link_to diffs_project_merge_request_path(@project, @merge_request), data: { target: 'div#diffs', action: 'diffs', toggle: 'tab' } do = tab_link_for @merge_request, :diffs do
Changes Changes
%span.badge= @merge_request.diff_size %span.badge= @merge_request.diff_size
#resolve-count-app.line-resolve-all-container.prepend-top-10{ "v-cloak" => true } #resolve-count-app.line-resolve-all-container.prepend-top-10{ "v-cloak" => true }
......
...@@ -18,7 +18,8 @@ ...@@ -18,7 +18,8 @@
A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), #{link_to 'among other things', help_page_path("user/project/index.md", anchor: "projects-features"), target: '_blank'}. A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), #{link_to 'among other things', help_page_path("user/project/index.md", anchor: "projects-features"), target: '_blank'}.
%p %p
All features are enabled when you create a project, but you can disable the ones you don’t need in the project settings. All features are enabled when you create a project, but you can disable the ones you don’t need in the project settings.
= brand_new_project_guidelines .md
= brand_new_project_guidelines
.col-lg-9.js-toggle-container .col-lg-9.js-toggle-container
%ul.nav-links.gitlab-tabs{ role: 'tablist' } %ul.nav-links.gitlab-tabs{ role: 'tablist' }
%li.active{ role: 'presentation' } %li.active{ role: 'presentation' }
...@@ -85,7 +86,7 @@ ...@@ -85,7 +86,7 @@
= icon('bug', text: 'Fogbugz') = icon('bug', text: 'Fogbugz')
%div %div
- if gitea_import_enabled? - if gitea_import_enabled?
= link_to new_import_gitea_url, class: 'btn import_gitea' do = link_to new_import_gitea_path, class: 'btn import_gitea' do
= custom_icon('go_logo') = custom_icon('go_logo')
Gitea Gitea
%div %div
......
...@@ -6,46 +6,35 @@ ...@@ -6,46 +6,35 @@
%h5 Auto DevOps (Beta) %h5 Auto DevOps (Beta)
%p %p
Auto DevOps will automatically build, test, and deploy your application based on a predefined Continuous Integration and Delivery configuration. Auto DevOps will automatically build, test, and deploy your application based on a predefined Continuous Integration and Delivery configuration.
This will happen starting with the next event (e.g.: push) that occurs to the project.
= link_to 'Learn more about Auto DevOps', help_page_path('topics/autodevops/index.md') = link_to 'Learn more about Auto DevOps', help_page_path('topics/autodevops/index.md')
- message = auto_devops_warning_message(@project) - message = auto_devops_warning_message(@project)
- if message - if message
%p.settings-message.text-center %p.settings-message.text-center
= message.html_safe = message.html_safe
= f.fields_for :auto_devops_attributes, @auto_devops do |form| = f.fields_for :auto_devops_attributes, @auto_devops do |form|
.radio.js-auto-devops-enable-radio-wrapper .radio
= form.label :enabled_true do = form.label :enabled_true do
= form.radio_button :enabled, 'true', class: 'js-auto-devops-enable-radio' = form.radio_button :enabled, 'true'
%strong Enable Auto DevOps %strong Enable Auto DevOps
%br %br
%span.descr %span.descr
The Auto DevOps pipeline configuration will be used when there is no <code>.gitlab-ci.yml</code> in the project. The Auto DevOps pipeline configuration will be used when there is no <code>.gitlab-ci.yml</code> in the project.
- if show_run_auto_devops_pipeline_checkbox_for_explicit_setting?(@project)
.checkbox.hide.js-run-auto-devops-pipeline-checkbox-wrapper
= label_tag 'project[run_auto_devops_pipeline_explicit]' do
= check_box_tag 'project[run_auto_devops_pipeline_explicit]', true, false, class: 'js-run-auto-devops-pipeline-checkbox'
= s_('ProjectSettings|Immediately run a pipeline on the default branch')
.radio.js-auto-devops-enable-radio-wrapper .radio
= form.label :enabled_false do = form.label :enabled_false do
= form.radio_button :enabled, 'false', class: 'js-auto-devops-enable-radio' = form.radio_button :enabled, 'false'
%strong Disable Auto DevOps %strong Disable Auto DevOps
%br %br
%span.descr %span.descr
An explicit <code>.gitlab-ci.yml</code> needs to be specified before you can begin using Continuous Integration and Delivery. An explicit <code>.gitlab-ci.yml</code> needs to be specified before you can begin using Continuous Integration and Delivery.
.radio.js-auto-devops-enable-radio-wrapper .radio
= form.label :enabled_ do = form.label :enabled_ do
= form.radio_button :enabled, '', class: 'js-auto-devops-enable-radio' = form.radio_button :enabled, ''
%strong Instance default (#{current_application_settings.auto_devops_enabled? ? 'enabled' : 'disabled'}) %strong Instance default (#{current_application_settings.auto_devops_enabled? ? 'enabled' : 'disabled'})
%br %br
%span.descr %span.descr
Follow the instance default to either have Auto DevOps enabled or disabled when there is no project specific <code>.gitlab-ci.yml</code>. Follow the instance default to either have Auto DevOps enabled or disabled when there is no project specific <code>.gitlab-ci.yml</code>.
- if show_run_auto_devops_pipeline_checkbox_for_instance_setting?(@project)
.checkbox.hide.js-run-auto-devops-pipeline-checkbox-wrapper
= label_tag 'project[run_auto_devops_pipeline_implicit]' do
= check_box_tag 'project[run_auto_devops_pipeline_implicit]', true, false, class: 'js-run-auto-devops-pipeline-checkbox'
= s_('ProjectSettings|Immediately run a pipeline on the default branch')
%p %p
You need to specify a domain if you want to use Auto Review Apps and Auto Deploy stages. You need to specify a domain if you want to use Auto Review Apps and Auto Deploy stages.
= form.text_field :domain, class: 'form-control', placeholder: 'domain.com' = form.text_field :domain, class: 'form-control', placeholder: 'domain.com'
......
...@@ -4,5 +4,5 @@ ...@@ -4,5 +4,5 @@
GitLab may not work properly because you are using an outdated web browser. GitLab may not work properly because you are using an outdated web browser.
%br %br
Please install a Please install a
= link_to 'supported web browser', help_page_url('install/requirements', anchor: 'supported-web-browsers') = link_to 'supported web browser', help_page_path('install/requirements', anchor: 'supported-web-browsers')
for a better experience. for a better experience.
- resource_name = spammable.class.model_name.singular
- humanized_resource_name = spammable.class.model_name.human.downcase
- script = local_assigns.fetch(:script, true)
- has_submit = local_assigns.fetch(:has_submit, true)
= form_for resource_name, method: :post, html: { class: 'recaptcha-form js-recaptcha-form' } do |f|
.recaptcha
- params[resource_name].each do |field, value|
= hidden_field(resource_name, field, value: value)
= hidden_field_tag(:spam_log_id, spammable.spam_log.id)
= hidden_field_tag(:recaptcha_verification, true)
= recaptcha_tags script: script, callback: 'recaptchaDialogCallback'
-# Yields a block with given extra params.
= yield
- if has_submit
.row-content-block.footer-block
= f.submit "Submit #{humanized_resource_name}", class: 'btn btn-create'
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
- elsif discussion_locked - elsif discussion_locked
.disabled-comment.text-center.prepend-top-default .disabled-comment.text-center.prepend-top-default
%span.issuable-note-warning %span.issuable-note-warning
%span.icon= sprite_icon('lock', size: 14) = sprite_icon('lock', size: 16, css_class: 'icon')
%span %span
This This
= issuable.class.to_s.titleize.downcase = issuable.class.to_s.titleize.downcase
......
#!/usr/bin/env ruby
require 'optparse'
require 'net/http'
require 'json'
require 'socket'
require 'logger'
require_relative '../lib/gitlab/storage_check'
Gitlab::StorageCheck::CLI.start!(ARGV)
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.
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.
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