Commit f0706f80 authored by Stan Hu's avatar Stan Hu

Merge branch 'master' into sh-security-10-0-stable-ee

parents ccd35492 0bf399f5
...@@ -414,7 +414,7 @@ group :ed25519 do ...@@ -414,7 +414,7 @@ group :ed25519 do
end end
# Gitaly GRPC client # Gitaly GRPC client
gem 'gitaly-proto', '~> 0.41.0', require: 'gitaly' gem 'gitaly-proto', '~> 0.42.0', require: 'gitaly'
gem 'toml-rb', '~> 0.3.15', require: false gem 'toml-rb', '~> 0.3.15', require: false
......
...@@ -297,7 +297,7 @@ GEM ...@@ -297,7 +297,7 @@ GEM
po_to_json (>= 1.0.0) po_to_json (>= 1.0.0)
rails (>= 3.2.0) rails (>= 3.2.0)
gherkin-ruby (0.3.2) gherkin-ruby (0.3.2)
gitaly-proto (0.41.0) gitaly-proto (0.42.0)
google-protobuf (~> 3.1) google-protobuf (~> 3.1)
grpc (~> 1.0) grpc (~> 1.0)
github-linguist (4.7.6) github-linguist (4.7.6)
...@@ -349,7 +349,9 @@ GEM ...@@ -349,7 +349,9 @@ GEM
mime-types (~> 3.0) mime-types (~> 3.0)
representable (~> 3.0) representable (~> 3.0)
retriable (>= 2.0, < 4.0) retriable (>= 2.0, < 4.0)
google-protobuf (3.4.0.2) google-protobuf (3.4.1.1)
googleapis-common-protos-types (1.0.0)
google-protobuf (~> 3.0)
googleauth (0.5.3) googleauth (0.5.3)
faraday (~> 0.12) faraday (~> 0.12)
jwt (~> 1.4) jwt (~> 1.4)
...@@ -376,8 +378,9 @@ GEM ...@@ -376,8 +378,9 @@ GEM
rake rake
grape_logging (1.7.0) grape_logging (1.7.0)
grape grape
grpc (1.6.0) grpc (1.6.6)
google-protobuf (~> 3.1) google-protobuf (~> 3.1)
googleapis-common-protos-types (~> 1.0.0)
googleauth (~> 0.5.1) googleauth (~> 0.5.1)
gssapi (1.2.0) gssapi (1.2.0)
ffi (>= 1.0.1) ffi (>= 1.0.1)
...@@ -1062,7 +1065,7 @@ DEPENDENCIES ...@@ -1062,7 +1065,7 @@ DEPENDENCIES
gettext (~> 3.2.2) gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.2.0) gettext_i18n_rails_js (~> 1.2.0)
gitaly-proto (~> 0.41.0) gitaly-proto (~> 0.42.0)
github-linguist (~> 4.7.0) github-linguist (~> 4.7.0)
gitlab-flowdock-git-hook (~> 1.0.1) gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-license (~> 1.0) gitlab-license (~> 1.0)
......
...@@ -16,6 +16,7 @@ const Api = { ...@@ -16,6 +16,7 @@ const Api = {
issuableTemplatePath: '/:namespace_path/:project_path/templates/:type/:key', issuableTemplatePath: '/:namespace_path/:project_path/templates/:type/:key',
usersPath: '/api/:version/users.json', usersPath: '/api/:version/users.json',
commitPath: '/api/:version/projects/:id/repository/commits', commitPath: '/api/:version/projects/:id/repository/commits',
branchSinglePath: '/api/:version/projects/:id/repository/branches/:branch',
group(groupId, callback) { group(groupId, callback) {
const url = Api.buildUrl(Api.groupPath) const url = Api.buildUrl(Api.groupPath)
...@@ -124,6 +125,19 @@ const Api = { ...@@ -124,6 +125,19 @@ const Api = {
}); });
}, },
branchSingle(id, branch) {
const url = Api.buildUrl(Api.branchSinglePath)
.replace(':id', id)
.replace(':branch', branch);
return this.wrapAjaxCall({
url,
type: 'GET',
contentType: 'application/json; charset=utf-8',
dataType: 'json',
});
},
// Return text for a specific license // Return text for a specific license
licenseText(key, data, callback) { licenseText(key, data, callback) {
const url = Api.buildUrl(Api.licensePath) const url = Api.buildUrl(Api.licensePath)
......
...@@ -7,7 +7,7 @@ class BoardService { ...@@ -7,7 +7,7 @@ class BoardService {
this.boards = Vue.resource(`${boardsEndpoint}{/id}.json`, {}, { this.boards = Vue.resource(`${boardsEndpoint}{/id}.json`, {}, {
issues: { issues: {
method: 'GET', method: 'GET',
url: `${gon.relative_url_root}/boards/${boardId}/issues.json`, url: `${gon.relative_url_root}/-/boards/${boardId}/issues.json`,
} }
}); });
this.lists = Vue.resource(`${listsEndpoint}{/id}`, {}, { this.lists = Vue.resource(`${listsEndpoint}{/id}`, {}, {
...@@ -16,7 +16,7 @@ class BoardService { ...@@ -16,7 +16,7 @@ class BoardService {
url: `${listsEndpoint}/generate.json` url: `${listsEndpoint}/generate.json`
} }
}); });
this.issue = Vue.resource(`${gon.relative_url_root}/boards/${boardId}/issues{/id}`, {}); this.issue = Vue.resource(`${gon.relative_url_root}/-/boards/${boardId}/issues{/id}`, {});
this.issues = Vue.resource(`${listsEndpoint}{/id}/issues`, {}, { this.issues = Vue.resource(`${listsEndpoint}{/id}/issues`, {}, {
bulkUpdate: { bulkUpdate: {
method: 'POST', method: 'POST',
......
/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, no-var, quotes, no-else-return, object-shorthand, comma-dangle, max-len */ export default function initBroadcastMessagesForm() {
$('input#broadcast_message_color').on('input', function onMessageColorInput() {
$(function() { const previewColor = $(this).val();
var previewPath; $('div.broadcast-message-preview').css('background-color', previewColor);
$('input#broadcast_message_color').on('input', function() {
var previewColor;
previewColor = $(this).val();
return $('div.broadcast-message-preview').css('background-color', previewColor);
}); });
$('input#broadcast_message_font').on('input', function() {
var previewColor; $('input#broadcast_message_font').on('input', function onMessageFontInput() {
previewColor = $(this).val(); const previewColor = $(this).val();
return $('div.broadcast-message-preview').css('color', previewColor); $('div.broadcast-message-preview').css('color', previewColor);
}); });
previewPath = $('textarea#broadcast_message_message').data('preview-path');
return $('textarea#broadcast_message_message').on('input', function() { const previewPath = $('textarea#broadcast_message_message').data('preview-path');
var message;
message = $(this).val(); $('textarea#broadcast_message_message').on('input', _.debounce(function onMessageInput() {
const message = $(this).val();
if (message === '') { if (message === '') {
return $('.js-broadcast-message-preview').text("Your message here"); $('.js-broadcast-message-preview').text('Your message here');
} else { } else {
return $.ajax({ $.ajax({
url: previewPath, url: previewPath,
type: "POST", type: 'POST',
data: { data: {
broadcast_message: { broadcast_message: { message },
message: message },
}
}
}); });
} }
}); }, 250));
}); }
...@@ -68,6 +68,7 @@ import initSettingsPanels from './settings_panels'; ...@@ -68,6 +68,7 @@ import initSettingsPanels from './settings_panels';
import initExperimentalFlags from './experimental_flags'; import initExperimentalFlags from './experimental_flags';
import OAuthRememberMe from './oauth_remember_me'; import OAuthRememberMe from './oauth_remember_me';
import PerformanceBar from './performance_bar'; import PerformanceBar from './performance_bar';
import initBroadcastMessagesForm from './broadcast_message';
import initNotes from './init_notes'; import initNotes from './init_notes';
import initLegacyFilters from './init_legacy_filters'; import initLegacyFilters from './init_legacy_filters';
import initIssuableSidebar from './init_issuable_sidebar'; import initIssuableSidebar from './init_issuable_sidebar';
...@@ -78,11 +79,15 @@ import initChangesDropdown from './init_changes_dropdown'; ...@@ -78,11 +79,15 @@ import initChangesDropdown from './init_changes_dropdown';
import AbuseReports from './abuse_reports'; import AbuseReports from './abuse_reports';
import { ajaxGet, convertPermissionToBoolean } from './lib/utils/common_utils'; import { ajaxGet, convertPermissionToBoolean } from './lib/utils/common_utils';
import AjaxLoadingSpinner from './ajax_loading_spinner'; import AjaxLoadingSpinner from './ajax_loading_spinner';
import GlFieldErrors from './gl_field_errors';
import GLForm from './gl_form';
import Shortcuts from './shortcuts'; import Shortcuts from './shortcuts';
import ShortcutsNavigation from './shortcuts_navigation'; import ShortcutsNavigation from './shortcuts_navigation';
import ShortcutsFindFile from './shortcuts_find_file'; import ShortcutsFindFile from './shortcuts_find_file';
import ShortcutsIssuable from './shortcuts_issuable'; import ShortcutsIssuable from './shortcuts_issuable';
import U2FAuthenticate from './u2f/authenticate'; import U2FAuthenticate from './u2f/authenticate';
import Members from './members';
import memberExpirationDate from './member_expiration_date';
// EE-only // EE-only
import ApproversSelect from './approvers_select'; import ApproversSelect from './approvers_select';
...@@ -187,9 +192,6 @@ import initGroupAnalytics from './init_group_analytics'; ...@@ -187,9 +192,6 @@ import initGroupAnalytics from './init_group_analytics';
const filteredSearchManager = new gl.FilteredSearchManager(page === 'projects:issues:index' ? 'issues' : 'merge_requests'); const filteredSearchManager = new gl.FilteredSearchManager(page === 'projects:issues:index' ? 'issues' : 'merge_requests');
filteredSearchManager.setup(); filteredSearchManager.setup();
} }
if (page === 'projects:merge_requests:index') {
new UserCallout({ setCalloutPerProject: true });
}
const pagePrefix = page === 'projects:merge_requests:index' ? 'merge_request_' : 'issue_'; const pagePrefix = page === 'projects:merge_requests:index' ? 'merge_request_' : 'issue_';
IssuableIndex.init(pagePrefix); IssuableIndex.init(pagePrefix);
...@@ -255,7 +257,7 @@ import initGroupAnalytics from './init_group_analytics'; ...@@ -255,7 +257,7 @@ import initGroupAnalytics from './init_group_analytics';
case 'groups:milestones:update': case 'groups:milestones:update':
new ZenMode(); new ZenMode();
new gl.DueDateSelectors(); new gl.DueDateSelectors();
new gl.GLForm($('.milestone-form'), true); new GLForm($('.milestone-form'), true);
break; break;
case 'projects:compare:show': case 'projects:compare:show':
new gl.Diff(); new gl.Diff();
...@@ -272,7 +274,7 @@ import initGroupAnalytics from './init_group_analytics'; ...@@ -272,7 +274,7 @@ import initGroupAnalytics from './init_group_analytics';
case 'projects:issues:new': case 'projects:issues:new':
case 'projects:issues:edit': case 'projects:issues:edit':
shortcut_handler = new ShortcutsNavigation(); shortcut_handler = new ShortcutsNavigation();
new gl.GLForm($('.issue-form'), true); new GLForm($('.issue-form'), true);
new IssuableForm($('.issue-form')); new IssuableForm($('.issue-form'));
new LabelsSelect(); new LabelsSelect();
new MilestoneSelect(); new MilestoneSelect();
...@@ -298,7 +300,7 @@ import initGroupAnalytics from './init_group_analytics'; ...@@ -298,7 +300,7 @@ import initGroupAnalytics from './init_group_analytics';
case 'projects:merge_requests:edit': case 'projects:merge_requests:edit':
new gl.Diff(); new gl.Diff();
shortcut_handler = new ShortcutsNavigation(); shortcut_handler = new ShortcutsNavigation();
new gl.GLForm($('.merge-request-form'), true); new GLForm($('.merge-request-form'), true);
new IssuableForm($('.merge-request-form')); new IssuableForm($('.merge-request-form'));
new LabelsSelect(); new LabelsSelect();
new MilestoneSelect(); new MilestoneSelect();
...@@ -307,7 +309,7 @@ import initGroupAnalytics from './init_group_analytics'; ...@@ -307,7 +309,7 @@ import initGroupAnalytics from './init_group_analytics';
break; break;
case 'projects:tags:new': case 'projects:tags:new':
new ZenMode(); new ZenMode();
new gl.GLForm($('.tag-form'), true); new GLForm($('.tag-form'), true);
new RefSelectDropdown($('.js-branch-select')); new RefSelectDropdown($('.js-branch-select'));
break; break;
case 'projects:snippets:show': case 'projects:snippets:show':
...@@ -317,17 +319,17 @@ import initGroupAnalytics from './init_group_analytics'; ...@@ -317,17 +319,17 @@ import initGroupAnalytics from './init_group_analytics';
case 'projects:snippets:edit': case 'projects:snippets:edit':
case 'projects:snippets:create': case 'projects:snippets:create':
case 'projects:snippets:update': case 'projects:snippets:update':
new gl.GLForm($('.snippet-form'), true); new GLForm($('.snippet-form'), true);
break; break;
case 'snippets:new': case 'snippets:new':
case 'snippets:edit': case 'snippets:edit':
case 'snippets:create': case 'snippets:create':
case 'snippets:update': case 'snippets:update':
new gl.GLForm($('.snippet-form'), false); new GLForm($('.snippet-form'), false);
break; break;
case 'projects:releases:edit': case 'projects:releases:edit':
new ZenMode(); new ZenMode();
new gl.GLForm($('.release-form'), true); new GLForm($('.release-form'), true);
break; break;
case 'projects:merge_requests:show': case 'projects:merge_requests:show':
new gl.Diff(); new gl.Diff();
...@@ -377,7 +379,10 @@ import initGroupAnalytics from './init_group_analytics'; ...@@ -377,7 +379,10 @@ import initGroupAnalytics from './init_group_analytics';
case 'projects:show': case 'projects:show':
shortcut_handler = new ShortcutsNavigation(); shortcut_handler = new ShortcutsNavigation();
new NotificationsForm(); new NotificationsForm();
new UserCallout({ setCalloutPerProject: true }); new UserCallout({
setCalloutPerProject: true,
className: 'js-autodevops-banner',
});
if ($('#tree-slider').length) new TreeView(); if ($('#tree-slider').length) new TreeView();
if ($('.blob-viewer').length) new BlobViewer(); if ($('.blob-viewer').length) new BlobViewer();
...@@ -404,9 +409,6 @@ import initGroupAnalytics from './init_group_analytics'; ...@@ -404,9 +409,6 @@ import initGroupAnalytics from './init_group_analytics';
case 'projects:pipelines:new': case 'projects:pipelines:new':
new NewBranchForm($('.js-new-pipeline-form')); new NewBranchForm($('.js-new-pipeline-form'));
break; break;
case 'projects:pipelines:index':
new UserCallout({ setCalloutPerProject: true });
break;
case 'projects:pipelines:builds': case 'projects:pipelines:builds':
case 'projects:pipelines:failures': case 'projects:pipelines:failures':
case 'projects:pipelines:show': case 'projects:pipelines:show':
...@@ -433,15 +435,15 @@ import initGroupAnalytics from './init_group_analytics'; ...@@ -433,15 +435,15 @@ import initGroupAnalytics from './init_group_analytics';
new ProjectsList(); new ProjectsList();
break; break;
case 'groups:group_members:index': case 'groups:group_members:index':
new gl.MemberExpirationDate(); memberExpirationDate();
new gl.Members(); new Members();
new UsersSelect(); new UsersSelect();
break; break;
case 'projects:project_members:index': case 'projects:project_members:index':
new gl.MemberExpirationDate('.js-access-expiration-date-groups'); memberExpirationDate('.js-access-expiration-date-groups');
new GroupsSelect(); new GroupsSelect();
new gl.MemberExpirationDate(); memberExpirationDate();
new gl.Members(); new Members();
new UsersSelect(); new UsersSelect();
break; break;
case 'groups:new': case 'groups:new':
...@@ -472,7 +474,6 @@ import initGroupAnalytics from './init_group_analytics'; ...@@ -472,7 +474,6 @@ import initGroupAnalytics from './init_group_analytics';
); );
} }
new UserCallout({ setCalloutPerProject: true });
$('#tree-slider').waitForImages(function() { $('#tree-slider').waitForImages(function() {
ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath); ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath);
}); });
...@@ -625,6 +626,9 @@ import initGroupAnalytics from './init_group_analytics'; ...@@ -625,6 +626,9 @@ import initGroupAnalytics from './init_group_analytics';
case 'admin': case 'admin':
new Admin(); new Admin();
switch (path[1]) { switch (path[1]) {
case 'broadcast_messages':
initBroadcastMessagesForm();
break;
case 'cohorts': case 'cohorts':
new UsagePing(); new UsagePing();
break; break;
...@@ -688,7 +692,7 @@ import initGroupAnalytics from './init_group_analytics'; ...@@ -688,7 +692,7 @@ import initGroupAnalytics from './init_group_analytics';
new Wikis(); new Wikis();
shortcut_handler = new ShortcutsWiki(); shortcut_handler = new ShortcutsWiki();
new ZenMode(); new ZenMode();
new gl.GLForm($('.wiki-form'), true); new GLForm($('.wiki-form'), true);
break; break;
case 'snippets': case 'snippets':
shortcut_handler = new ShortcutsNavigation(); shortcut_handler = new ShortcutsNavigation();
...@@ -713,12 +717,6 @@ import initGroupAnalytics from './init_group_analytics'; ...@@ -713,12 +717,6 @@ import initGroupAnalytics from './init_group_analytics';
shortcut_handler = new ShortcutsNavigation(); shortcut_handler = new ShortcutsNavigation();
} }
break; break;
case 'users':
const action = path[1];
import(/* webpackChunkName: 'user_profile' */ './users')
.then(user => user.default(action))
.catch(() => {});
break;
} }
// If we haven't installed a custom shortcut handler, install the default one // If we haven't installed a custom shortcut handler, install the default one
if (!shortcut_handler) { if (!shortcut_handler) {
...@@ -739,7 +737,7 @@ import initGroupAnalytics from './init_group_analytics'; ...@@ -739,7 +737,7 @@ import initGroupAnalytics from './init_group_analytics';
Dispatcher.prototype.initFieldErrors = function() { Dispatcher.prototype.initFieldErrors = function() {
$('.gl-show-field-errors').each((i, form) => { $('.gl-show-field-errors').each((i, form) => {
new gl.GlFieldErrors(form); new GlFieldErrors(form);
}); });
}; };
......
...@@ -40,6 +40,10 @@ const createFlashEl = (message, type, isInContentWrapper = false) => ` ...@@ -40,6 +40,10 @@ const createFlashEl = (message, type, isInContentWrapper = false) => `
</div> </div>
`; `;
const removeFlashClickListener = (flashEl, fadeTransition) => {
flashEl.parentNode.addEventListener('click', () => hideFlash(flashEl, fadeTransition));
};
/* /*
* Flash banner supports different types of Flash configurations * Flash banner supports different types of Flash configurations
* along with ability to provide actionConfig which can be used to show * along with ability to provide actionConfig which can be used to show
...@@ -70,7 +74,7 @@ const createFlash = function createFlash( ...@@ -70,7 +74,7 @@ const createFlash = function createFlash(
flashContainer.innerHTML = createFlashEl(message, type, isInContentWrapper); flashContainer.innerHTML = createFlashEl(message, type, isInContentWrapper);
const flashEl = flashContainer.querySelector(`.flash-${type}`); const flashEl = flashContainer.querySelector(`.flash-${type}`);
flashEl.addEventListener('click', () => hideFlash(flashEl, fadeTransition)); removeFlashClickListener(flashEl, fadeTransition);
if (actionConfig) { if (actionConfig) {
flashEl.innerHTML += createAction(actionConfig); flashEl.innerHTML += createAction(actionConfig);
...@@ -90,5 +94,6 @@ export { ...@@ -90,5 +94,6 @@ export {
createFlashEl, createFlashEl,
createAction, createAction,
hideFlash, hideFlash,
removeFlashClickListener,
}; };
window.Flash = createFlash; window.Flash = createFlash;
...@@ -54,7 +54,7 @@ const inputErrorClass = 'gl-field-error-outline'; ...@@ -54,7 +54,7 @@ const inputErrorClass = 'gl-field-error-outline';
const errorAnchorSelector = '.gl-field-error-anchor'; const errorAnchorSelector = '.gl-field-error-anchor';
const ignoreInputSelector = '.gl-field-error-ignore'; const ignoreInputSelector = '.gl-field-error-ignore';
class GlFieldError { export default class GlFieldError {
constructor({ input, formErrors }) { constructor({ input, formErrors }) {
this.inputElement = $(input); this.inputElement = $(input);
this.inputDomElement = this.inputElement.get(0); this.inputDomElement = this.inputElement.get(0);
...@@ -159,6 +159,3 @@ class GlFieldError { ...@@ -159,6 +159,3 @@ class GlFieldError {
this.fieldErrorElement.hide(); this.fieldErrorElement.hide();
} }
} }
window.gl = window.gl || {};
window.gl.GlFieldError = GlFieldError;
/* eslint-disable comma-dangle, class-methods-use-this, max-len, space-before-function-paren, arrow-parens, no-param-reassign */ import GlFieldError from './gl_field_error';
import './gl_field_error';
const customValidationFlag = 'gl-field-error-ignore'; const customValidationFlag = 'gl-field-error-ignore';
class GlFieldErrors { export default class GlFieldErrors {
constructor(form) { constructor(form) {
this.form = $(form); this.form = $(form);
this.state = { this.state = {
inputs: [], inputs: [],
valid: false valid: false,
}; };
this.initValidators(); this.initValidators();
} }
initValidators () { initValidators() {
// register selectors here as needed // register selectors here as needed
const validateSelectors = [':text', ':password', '[type=email]'] const validateSelectors = [':text', ':password', '[type=email]']
.map((selector) => `input${selector}`).join(','); .map(selector => `input${selector}`).join(',');
this.state.inputs = this.form.find(validateSelectors).toArray() this.state.inputs = this.form.find(validateSelectors).toArray()
.filter((input) => !input.classList.contains(customValidationFlag)) .filter(input => !input.classList.contains(customValidationFlag))
.map((input) => new window.gl.GlFieldError({ input, formErrors: this })); .map(input => new GlFieldError({ input, formErrors: this }));
this.form.on('submit', this.catchInvalidFormSubmit); this.form.on('submit', GlFieldErrors.catchInvalidFormSubmit);
} }
/* Neccessary to prevent intercept and override invalid form submit /* Neccessary to prevent intercept and override invalid form submit
* because Safari & iOS quietly allow form submission when form is invalid * because Safari & iOS quietly allow form submission when form is invalid
* and prevents disabling of invalid submit button by application.js */ * and prevents disabling of invalid submit button by application.js */
catchInvalidFormSubmit (event) { static catchInvalidFormSubmit(e) {
const $form = $(event.currentTarget); const $form = $(e.currentTarget);
if (!$form.attr('novalidate')) { if (!$form.attr('novalidate')) {
if (!event.currentTarget.checkValidity()) { if (!e.currentTarget.checkValidity()) {
event.preventDefault(); e.preventDefault();
event.stopPropagation(); e.stopPropagation();
} }
} }
} }
...@@ -50,11 +48,9 @@ class GlFieldErrors { ...@@ -50,11 +48,9 @@ class GlFieldErrors {
}); });
} }
focusOnFirstInvalid () { focusOnFirstInvalid() {
const firstInvalid = this.state.inputs.filter((input) => !input.inputDomElement.validity.valid)[0]; const firstInvalid = this.state.inputs
.filter(input => !input.inputDomElement.validity.valid)[0];
firstInvalid.inputElement.focus(); firstInvalid.inputElement.focus();
} }
} }
window.gl = window.gl || {};
window.gl.GlFieldErrors = GlFieldErrors;
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-new, max-len */
/* global GitLab */
/* global DropzoneInput */ /* global DropzoneInput */
/* global autosize */ /* global autosize */
import GfmAutoComplete from './gfm_auto_complete'; import GfmAutoComplete from './gfm_auto_complete';
window.gl = window.gl || {}; export default class GLForm {
constructor(form, enableGFM = false) {
function GLForm(form, enableGFM = false) { this.form = form;
this.form = form; this.textarea = this.form.find('textarea.js-gfm-input');
this.textarea = this.form.find('textarea.js-gfm-input'); this.enableGFM = enableGFM;
this.enableGFM = enableGFM; // Before we start, we should clean up any previous data for this form
// Before we start, we should clean up any previous data for this form this.destroy();
this.destroy(); // Setup the form
// Setup the form this.setupForm();
this.setupForm(); this.form.data('gl-form', this);
this.form.data('gl-form', this);
}
GLForm.prototype.destroy = function() {
// Clean form listeners
this.clearEventListeners();
if (this.autoComplete) {
this.autoComplete.destroy();
} }
return this.form.data('gl-form', null);
};
GLForm.prototype.setupForm = function() { destroy() {
var isNewForm; // Clean form listeners
isNewForm = this.form.is(':not(.gfm-form)'); this.clearEventListeners();
this.form.removeClass('js-new-note-form'); if (this.autoComplete) {
if (isNewForm) { this.autoComplete.destroy();
this.form.find('.div-dropzone').remove(); }
this.form.addClass('gfm-form'); this.form.data('gl-form', null);
// remove notify commit author checkbox for non-commit notes
gl.utils.disableButtonIfEmptyField(this.form.find('.js-note-text'), this.form.find('.js-comment-button, .js-note-new-discussion'));
this.autoComplete = new GfmAutoComplete(gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources);
this.autoComplete.setup(this.form.find('.js-gfm-input'), {
emojis: true,
members: this.enableGFM,
issues: this.enableGFM,
milestones: this.enableGFM,
mergeRequests: this.enableGFM,
labels: this.enableGFM,
});
new DropzoneInput(this.form);
autosize(this.textarea);
} }
// form and textarea event listeners
this.addEventListeners();
gl.text.init(this.form);
// hide discard button
this.form.find('.js-note-discard').hide();
this.form.show();
if (this.isAutosizeable) this.setupAutosize();
};
GLForm.prototype.setupAutosize = function () { setupForm() {
this.textarea.off('autosize:resized') const isNewForm = this.form.is(':not(.gfm-form)');
.on('autosize:resized', this.setHeightData.bind(this)); this.form.removeClass('js-new-note-form');
if (isNewForm) {
this.form.find('.div-dropzone').remove();
this.form.addClass('gfm-form');
// remove notify commit author checkbox for non-commit notes
gl.utils.disableButtonIfEmptyField(this.form.find('.js-note-text'), this.form.find('.js-comment-button, .js-note-new-discussion'));
this.autoComplete = new GfmAutoComplete(gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources);
this.autoComplete.setup(this.form.find('.js-gfm-input'), {
emojis: true,
members: this.enableGFM,
issues: this.enableGFM,
milestones: this.enableGFM,
mergeRequests: this.enableGFM,
labels: this.enableGFM,
});
new DropzoneInput(this.form); // eslint-disable-line no-new
autosize(this.textarea);
}
// form and textarea event listeners
this.addEventListeners();
gl.text.init(this.form);
// hide discard button
this.form.find('.js-note-discard').hide();
this.form.show();
if (this.isAutosizeable) this.setupAutosize();
}
this.textarea.off('mouseup.autosize') setupAutosize() {
.on('mouseup.autosize', this.destroyAutosize.bind(this)); this.textarea.off('autosize:resized')
.on('autosize:resized', this.setHeightData.bind(this));
setTimeout(() => { this.textarea.off('mouseup.autosize')
autosize(this.textarea); .on('mouseup.autosize', this.destroyAutosize.bind(this));
this.textarea.css('resize', 'vertical');
}, 0);
};
GLForm.prototype.setHeightData = function () { setTimeout(() => {
this.textarea.data('height', this.textarea.outerHeight()); autosize(this.textarea);
}; this.textarea.css('resize', 'vertical');
}, 0);
}
GLForm.prototype.destroyAutosize = function () { setHeightData() {
const outerHeight = this.textarea.outerHeight(); this.textarea.data('height', this.textarea.outerHeight());
}
if (this.textarea.data('height') === outerHeight) return; destroyAutosize() {
const outerHeight = this.textarea.outerHeight();
autosize.destroy(this.textarea); if (this.textarea.data('height') === outerHeight) return;
this.textarea.data('height', outerHeight); autosize.destroy(this.textarea);
this.textarea.outerHeight(outerHeight);
this.textarea.css('max-height', window.outerHeight);
};
GLForm.prototype.clearEventListeners = function() { this.textarea.data('height', outerHeight);
this.textarea.off('focus'); this.textarea.outerHeight(outerHeight);
this.textarea.off('blur'); this.textarea.css('max-height', window.outerHeight);
return gl.text.removeListeners(this.form); }
};
GLForm.prototype.addEventListeners = function() { clearEventListeners() {
this.textarea.on('focus', function() { this.textarea.off('focus');
return $(this).closest('.md-area').addClass('is-focused'); this.textarea.off('blur');
}); gl.text.removeListeners(this.form);
return this.textarea.on('blur', function() { }
return $(this).closest('.md-area').removeClass('is-focused');
});
};
window.gl.GLForm = GLForm; addEventListeners() {
this.textarea.on('focus', function focusTextArea() {
$(this).closest('.md-area').addClass('is-focused');
});
this.textarea.on('blur', function blurTextArea() {
$(this).closest('.md-area').removeClass('is-focused');
});
}
}
...@@ -43,16 +43,6 @@ ...@@ -43,16 +43,6 @@
type: 'link', type: 'link',
}); });
} }
if (this.job.retry_path) {
actions.push({
label: 'Retry',
path: this.job.retry_path,
cssClass: 'js-retry-button btn btn-inverted-secondary visible-md-block visible-lg-block',
type: 'ujs-link',
});
}
return actions; return actions;
}, },
}, },
......
...@@ -403,7 +403,11 @@ export const setCiStatusFavicon = (pageUrl) => { ...@@ -403,7 +403,11 @@ export const setCiStatusFavicon = (pageUrl) => {
}); });
}; };
export const spriteIcon = icon => `<svg><use xlink:href="${gon.sprite_icons}#${icon}" /></svg>`; export const spriteIcon = (icon, className = '') => {
const classAttribute = className.length > 0 ? `class="${className}"` : '';
return `<svg ${classAttribute}><use xlink:href="${gon.sprite_icons}#${icon}" /></svg>`;
};
export const imagePath = imgUrl => `${gon.asset_host || ''}${gon.relative_url_root || ''}/assets/${imgUrl}`; export const imagePath = imgUrl => `${gon.asset_host || ''}${gon.relative_url_root || ''}/assets/${imgUrl}`;
......
...@@ -85,7 +85,7 @@ w.gl.utils.getLocationHash = function(url) { ...@@ -85,7 +85,7 @@ w.gl.utils.getLocationHash = function(url) {
return hashIndex === -1 ? null : url.substring(hashIndex + 1); return hashIndex === -1 ? null : url.substring(hashIndex + 1);
}; };
w.gl.utils.refreshCurrentPage = () => gl.utils.visitUrl(document.location.href); w.gl.utils.refreshCurrentPage = () => gl.utils.visitUrl(window.location.href);
// eslint-disable-next-line import/prefer-default-export // eslint-disable-next-line import/prefer-default-export
export function visitUrl(url, external = false) { export function visitUrl(url, external = false) {
...@@ -96,7 +96,7 @@ export function visitUrl(url, external = false) { ...@@ -96,7 +96,7 @@ export function visitUrl(url, external = false) {
otherWindow.opener = null; otherWindow.opener = null;
otherWindow.location = url; otherWindow.location = url;
} else { } else {
document.location.href = url; window.location.href = url;
} }
} }
......
...@@ -44,7 +44,6 @@ import './aside'; ...@@ -44,7 +44,6 @@ import './aside';
import './autosave'; import './autosave';
import loadAwardsHandler from './awards_handler'; import loadAwardsHandler from './awards_handler';
import bp from './breakpoints'; import bp from './breakpoints';
import './broadcast_message';
import './commits'; import './commits';
import './compare'; import './compare';
import './compare_autocomplete'; import './compare_autocomplete';
...@@ -55,7 +54,7 @@ import './diff'; ...@@ -55,7 +54,7 @@ import './diff';
import './dropzone_input'; import './dropzone_input';
import './due_date_select'; import './due_date_select';
import './files_comment_button'; import './files_comment_button';
import Flash from './flash'; import Flash, { removeFlashClickListener } from './flash';
import './gl_dropdown'; import './gl_dropdown';
import './gl_field_error'; import './gl_field_error';
import './gl_field_errors'; import './gl_field_errors';
...@@ -75,8 +74,6 @@ import './layout_nav'; ...@@ -75,8 +74,6 @@ import './layout_nav';
import LazyLoader from './lazy_loader'; import LazyLoader from './lazy_loader';
import './line_highlighter'; import './line_highlighter';
import './logo'; import './logo';
import './member_expiration_date';
import './members';
import './merge_request'; import './merge_request';
import './merge_request_tabs'; import './merge_request_tabs';
import './milestone'; import './milestone';
...@@ -347,4 +344,9 @@ $(function () { ...@@ -347,4 +344,9 @@ $(function () {
* EE specific scripts * EE specific scripts
*/ */
$('#modal-upload-trial-license').modal('show'); $('#modal-upload-trial-license').modal('show');
const flashContainer = document.querySelector('.flash-container');
if (flashContainer && flashContainer.children.length) {
removeFlashClickListener(flashContainer.children[0]);
}
}); });
...@@ -2,54 +2,51 @@ ...@@ -2,54 +2,51 @@
import Pikaday from 'pikaday'; import Pikaday from 'pikaday';
(() => { // Add datepickers to all `js-access-expiration-date` elements. If those elements are
// Add datepickers to all `js-access-expiration-date` elements. If those elements are // children of an element with the `clearable-input` class, and have a sibling
// children of an element with the `clearable-input` class, and have a sibling // `js-clear-input` element, then show that element when there is a value in the
// `js-clear-input` element, then show that element when there is a value in the // datepicker, and make clicking on that element clear the field.
// datepicker, and make clicking on that element clear the field. //
// export default function memberExpirationDate(selector = '.js-access-expiration-date') {
window.gl = window.gl || {}; function toggleClearInput() {
gl.MemberExpirationDate = (selector = '.js-access-expiration-date') => { $(this).closest('.clearable-input').toggleClass('has-value', $(this).val() !== '');
function toggleClearInput() { }
$(this).closest('.clearable-input').toggleClass('has-value', $(this).val() !== ''); const inputs = $(selector);
}
const inputs = $(selector); inputs.each((i, el) => {
const $input = $(el);
inputs.each((i, el) => {
const $input = $(el); const calendar = new Pikaday({
field: $input.get(0),
const calendar = new Pikaday({ theme: 'gitlab-theme animate-picker',
field: $input.get(0), format: 'yyyy-mm-dd',
theme: 'gitlab-theme animate-picker', minDate: new Date(),
format: 'yyyy-mm-dd', container: $input.parent().get(0),
minDate: new Date(), onSelect(dateText) {
container: $input.parent().get(0), $input.val(dateFormat(new Date(dateText), 'yyyy-mm-dd'));
onSelect(dateText) {
$input.val(dateFormat(new Date(dateText), 'yyyy-mm-dd')); $input.trigger('change');
$input.trigger('change'); toggleClearInput.call($input);
},
toggleClearInput.call($input);
},
});
calendar.setDate(new Date($input.val()));
$input.data('pikaday', calendar);
}); });
inputs.next('.js-clear-input').on('click', function clicked(event) { calendar.setDate(new Date($input.val()));
event.preventDefault(); $input.data('pikaday', calendar);
});
const input = $(this).closest('.clearable-input').find(selector); inputs.next('.js-clear-input').on('click', function clicked(event) {
const calendar = input.data('pikaday'); event.preventDefault();
calendar.setDate(null); const input = $(this).closest('.clearable-input').find(selector);
input.trigger('change'); const calendar = input.data('pikaday');
toggleClearInput.call(input);
}); calendar.setDate(null);
input.trigger('change');
toggleClearInput.call(input);
});
inputs.on('blur', toggleClearInput); inputs.on('blur', toggleClearInput);
inputs.each(toggleClearInput); inputs.each(toggleClearInput);
}; }
}).call(window);
/* eslint-disable class-methods-use-this, promise/catch-or-return */
/* eslint-disable no-new */
import Flash from './flash'; import Flash from './flash';
(() => { export default class Members {
window.gl = window.gl || {}; constructor() {
this.addListeners();
class Members { this.initGLDropdown();
constructor() { }
this.addListeners();
this.initGLDropdown();
}
addListeners() {
$('.js-ldap-permissions').off('click').on('click', this.showLDAPPermissionsWarning.bind(this));
$('.js-ldap-override').off('click').on('click', this.toggleMemberAccessToggle.bind(this));
$('.project_member, .group_member').off('ajax:success').on('ajax:success', this.removeRow);
$('.js-member-update-control').off('change').on('change', this.formSubmit.bind(this));
$('.js-edit-member-form').off('ajax:success').on('ajax:success', this.formSuccess.bind(this));
gl.utils.disableButtonIfEmptyField('#user_ids', 'input[name=commit]', 'change');
}
initGLDropdown() {
$('.js-member-permissions-dropdown').each((i, btn) => {
const $btn = $(btn);
$btn.glDropdown({
selectable: true,
isSelectable(selected, $el) {
if ($el.data('revert')) {
return false;
}
return !$el.hasClass('is-active');
},
fieldName: $btn.data('field-name'),
id(selected, $el) {
return $el.data('id');
},
toggleLabel(selected, $el) {
if ($el.data('revert')) {
return $btn.text();
}
return $el.text();
},
clicked: (options) => {
const $link = options.$el;
if (!$link.data('revert')) {
this.formSubmit(null, $link);
} else {
const { $memberListItem, $toggle, $dateInput } = this.getMemberListItems($link);
$toggle.disable();
$dateInput.disable();
this.overrideLdap($memberListItem, $link.data('endpoint'), false).fail(() => {
$toggle.enable();
$dateInput.enable();
});
}
},
});
});
}
removeRow(e) { addListeners() {
const $target = $(e.target); $('.js-ldap-permissions').off('click').on('click', this.showLDAPPermissionsWarning.bind(this));
$('.js-ldap-override').off('click').on('click', this.toggleMemberAccessToggle.bind(this));
$('.project_member, .group_member').off('ajax:success').on('ajax:success', this.removeRow);
$('.js-member-update-control').off('change').on('change', this.formSubmit.bind(this));
$('.js-edit-member-form').off('ajax:success').on('ajax:success', this.formSuccess.bind(this));
gl.utils.disableButtonIfEmptyField('#user_ids', 'input[name=commit]', 'change');
}
if ($target.hasClass('btn-remove')) { initGLDropdown() {
$target.closest('.member') $('.js-member-permissions-dropdown').each((i, btn) => {
.fadeOut(function fadeOutMemberRow() { const $btn = $(btn);
$(this).remove();
});
}
}
formSubmit(e, $el = null) { $btn.glDropdown({
const $this = e ? $(e.currentTarget) : $el; selectable: true,
const { $toggle, $dateInput } = this.getMemberListItems($this); isSelectable(selected, $el) {
if ($el.data('revert')) {
return false;
}
$this.closest('form').trigger('submit.rails'); return !$el.hasClass('is-active');
},
fieldName: $btn.data('field-name'),
id(selected, $el) {
return $el.data('id');
},
toggleLabel(selected, $el) {
if ($el.data('revert')) {
return $btn.text();
}
$toggle.disable(); return $el.text();
$dateInput.disable(); },
clicked: (options) => {
const $link = options.$el;
if (!$link.data('revert')) {
this.formSubmit(null, $link);
} else {
const { $memberListItem, $toggle, $dateInput } = this.getMemberListItems($link);
$toggle.disable();
$dateInput.disable();
this.overrideLdap($memberListItem, $link.data('endpoint'), false).fail(() => {
$toggle.enable();
$dateInput.enable();
});
}
},
});
});
}
// eslint-disable-next-line class-methods-use-this
removeRow(e) {
const $target = $(e.target);
if ($target.hasClass('btn-remove')) {
$target.closest('.member')
.fadeOut(function fadeOutMemberRow() {
$(this).remove();
});
} }
}
formSuccess(e) { formSubmit(e, $el = null) {
const { $toggle, $dateInput } = this.getMemberListItems($(e.currentTarget).closest('.member')); const $this = e ? $(e.currentTarget) : $el;
const { $toggle, $dateInput } = this.getMemberListItems($this);
$toggle.enable();
$dateInput.enable();
}
showLDAPPermissionsWarning(e) { $this.closest('form').trigger('submit.rails');
const $btn = $(e.currentTarget);
const { $memberListItem } = this.getMemberListItems($btn);
const $ldapPermissionsElement = $memberListItem.next();
$ldapPermissionsElement.toggle(); $toggle.disable();
} $dateInput.disable();
}
getMemberListItems($el) { formSuccess(e) {
const $memberListItem = $el.is('.member') ? $el : $(`#${$el.data('el-id')}`); const { $toggle, $dateInput } = this.getMemberListItems($(e.currentTarget).closest('.member'));
return { $toggle.enable();
$memberListItem, $dateInput.enable();
$toggle: $memberListItem.find('.dropdown-menu-toggle'), }
$dateInput: $memberListItem.find('.js-access-expiration-date'),
};
}
toggleMemberAccessToggle(e) { showLDAPPermissionsWarning(e) {
const $btn = $(e.currentTarget); const $btn = $(e.currentTarget);
const { $memberListItem, $toggle, $dateInput } = this.getMemberListItems($btn); const { $memberListItem } = this.getMemberListItems($btn);
const $ldapPermissionsElement = $memberListItem.next();
$btn.disable(); $ldapPermissionsElement.toggle();
}
// eslint-disable-next-line class-methods-use-this
getMemberListItems($el) {
const $memberListItem = $el.is('.member') ? $el : $(`#${$el.data('el-id')}`);
return {
$memberListItem,
$toggle: $memberListItem.find('.dropdown-menu-toggle'),
$dateInput: $memberListItem.find('.js-access-expiration-date'),
};
}
this.overrideLdap($memberListItem, $btn.data('endpoint'), true).then(() => { toggleMemberAccessToggle(e) {
this.showLDAPPermissionsWarning(e); const $btn = $(e.currentTarget);
const { $memberListItem, $toggle, $dateInput } = this.getMemberListItems($btn);
$toggle.enable(); $btn.disable();
$dateInput.enable(); // eslint-disable-next-line promise/catch-or-return
}).fail((xhr) => { this.overrideLdap($memberListItem, $btn.data('endpoint'), true).then(() => {
$btn.enable(); this.showLDAPPermissionsWarning(e);
if (xhr.status === 403) { $toggle.enable();
new Flash('You do not have the correct permissions to override the settings from the LDAP group sync.', 'alert'); $dateInput.enable();
} else { }).fail((xhr) => {
new Flash('An error occured whilst saving LDAP override status. Please try again.', 'alert'); $btn.enable();
}
});
}
overrideLdap($memberListitem, endpoint, override) { if (xhr.status === 403) {
return $.ajax({ Flash('You do not have the correct permissions to override the settings from the LDAP group sync.', 'alert');
url: endpoint, } else {
type: 'PATCH', Flash('An error occured whilst saving LDAP override status. Please try again.', 'alert');
data: { }
group_member: { });
override, }
}, // eslint-disable-next-line class-methods-use-this
overrideLdap($memberListitem, endpoint, override) {
return $.ajax({
url: endpoint,
type: 'PATCH',
data: {
group_member: {
override,
}, },
}).then(() => { },
$memberListitem.toggleClass('is-overriden', override); }).then(() => {
}); $memberListitem.toggleClass('is-overriden', override);
} });
} }
}
gl.Members = Members;
})();
...@@ -19,6 +19,7 @@ import 'vendor/jquery.atwho'; ...@@ -19,6 +19,7 @@ import 'vendor/jquery.atwho';
import AjaxCache from '~/lib/utils/ajax_cache'; import AjaxCache from '~/lib/utils/ajax_cache';
import Flash from './flash'; import Flash from './flash';
import CommentTypeToggle from './comment_type_toggle'; import CommentTypeToggle from './comment_type_toggle';
import GLForm from './gl_form';
import loadAwardsHandler from './awards_handler'; import loadAwardsHandler from './awards_handler';
import './autosave'; import './autosave';
import './dropzone_input'; import './dropzone_input';
...@@ -557,7 +558,7 @@ export default class Notes { ...@@ -557,7 +558,7 @@ export default class Notes {
*/ */
setupNoteForm(form) { setupNoteForm(form) {
var textarea, key; var textarea, key;
new gl.GLForm(form, this.enableGFM); this.glForm = new GLForm(form, this.enableGFM);
textarea = form.find('.js-note-text'); textarea = form.find('.js-note-text');
key = [ key = [
'Note', 'Note',
...@@ -1152,7 +1153,7 @@ export default class Notes { ...@@ -1152,7 +1153,7 @@ export default class Notes {
var targetId = $originalContentEl.data('target-id'); var targetId = $originalContentEl.data('target-id');
var targetType = $originalContentEl.data('target-type'); var targetType = $originalContentEl.data('target-type');
new gl.GLForm($editForm.find('form'), this.enableGFM); this.glForm = new GLForm($editForm.find('form'), this.enableGFM);
$editForm.find('form') $editForm.find('form')
.attr('action', postUrl) .attr('action', postUrl)
......
import Vue from 'vue'; import Vue from 'vue';
import Translate from '../vue_shared/translate'; import Translate from '../vue_shared/translate';
import GlFieldErrors from '../gl_field_errors';
import intervalPatternInput from './components/interval_pattern_input.vue'; import intervalPatternInput from './components/interval_pattern_input.vue';
import TimezoneDropdown from './components/timezone_dropdown'; import TimezoneDropdown from './components/timezone_dropdown';
import TargetBranchDropdown from './components/target_branch_dropdown'; import TargetBranchDropdown from './components/target_branch_dropdown';
...@@ -39,7 +40,7 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -39,7 +40,7 @@ document.addEventListener('DOMContentLoaded', () => {
gl.timezoneDropdown = new TimezoneDropdown(); gl.timezoneDropdown = new TimezoneDropdown();
gl.targetBranchDropdown = new TargetBranchDropdown(); gl.targetBranchDropdown = new TargetBranchDropdown();
gl.pipelineScheduleFieldErrors = new gl.GlFieldErrors(formElement); gl.pipelineScheduleFieldErrors = new GlFieldErrors(formElement);
setupPipelineVariableList($('.js-pipeline-variable-list')); setupPipelineVariableList($('.js-pipeline-variable-list'));
}); });
...@@ -81,7 +81,11 @@ export default class PrometheusMetrics { ...@@ -81,7 +81,11 @@ export default class PrometheusMetrics {
loadActiveMetrics() { loadActiveMetrics() {
this.showMonitoringMetricsPanelState(PANEL_STATE.LOADING); this.showMonitoringMetricsPanelState(PANEL_STATE.LOADING);
backOff((next, stop) => { backOff((next, stop) => {
$.getJSON(this.activeMetricsEndpoint) $.ajax({
url: this.activeMetricsEndpoint,
dataType: 'json',
global: false,
})
.done((res) => { .done((res) => {
if (res && res.success) { if (res && res.success) {
stop(res); stop(res);
......
...@@ -3,11 +3,17 @@ import Flash from '../../flash'; ...@@ -3,11 +3,17 @@ import Flash from '../../flash';
import Store from '../stores/repo_store'; import Store from '../stores/repo_store';
import RepoMixin from '../mixins/repo_mixin'; import RepoMixin from '../mixins/repo_mixin';
import Service from '../services/repo_service'; import Service from '../services/repo_service';
import PopupDialog from '../../vue_shared/components/popup_dialog.vue';
import { visitUrl } from '../../lib/utils/url_utility';
export default { export default {
mixins: [RepoMixin],
data: () => Store, data: () => Store,
mixins: [RepoMixin], components: {
PopupDialog,
},
computed: { computed: {
showCommitable() { showCommitable() {
...@@ -28,7 +34,16 @@ export default { ...@@ -28,7 +34,16 @@ export default {
}, },
methods: { methods: {
makeCommit() { commitToNewBranch(status) {
if (status) {
this.showNewBranchDialog = false;
this.tryCommit(null, true, true);
} else {
// reset the state
}
},
makeCommit(newBranch) {
// see https://docs.gitlab.com/ce/api/commits.html#create-a-commit-with-multiple-files-and-actions // see https://docs.gitlab.com/ce/api/commits.html#create-a-commit-with-multiple-files-and-actions
const commitMessage = this.commitMessage; const commitMessage = this.commitMessage;
const actions = this.changedFiles.map(f => ({ const actions = this.changedFiles.map(f => ({
...@@ -36,19 +51,63 @@ export default { ...@@ -36,19 +51,63 @@ export default {
file_path: f.path, file_path: f.path,
content: f.newContent, content: f.newContent,
})); }));
const branch = newBranch ? `${this.currentBranch}-${this.currentShortHash}` : this.currentBranch;
const payload = { const payload = {
branch: Store.currentBranch, branch,
commit_message: commitMessage, commit_message: commitMessage,
actions, actions,
}; };
Store.submitCommitsLoading = true; if (newBranch) {
payload.start_branch = this.currentBranch;
}
this.submitCommitsLoading = true;
Service.commitFiles(payload) Service.commitFiles(payload)
.then(this.resetCommitState) .then(() => {
.catch(() => Flash('An error occurred while committing your changes')); this.resetCommitState();
if (this.startNewMR) {
this.redirectToNewMr(branch);
} else {
this.redirectToBranch(branch);
}
})
.catch(() => {
Flash('An error occurred while committing your changes');
});
},
tryCommit(e, skipBranchCheck = false, newBranch = false) {
if (skipBranchCheck) {
this.makeCommit(newBranch);
} else {
Store.setBranchHash()
.then(() => {
if (Store.branchChanged) {
Store.showNewBranchDialog = true;
return;
}
this.makeCommit(newBranch);
})
.catch(() => {
Flash('An error occurred while committing your changes');
});
}
},
redirectToNewMr(branch) {
visitUrl(this.newMrTemplateUrl.replace('{{source_branch}}', branch));
},
redirectToBranch(branch) {
visitUrl(this.customBranchURL.replace('{{branch}}', branch));
}, },
resetCommitState() { resetCommitState() {
this.submitCommitsLoading = false; this.submitCommitsLoading = false;
this.openedFiles = this.openedFiles.map((file) => {
const f = file;
f.changed = false;
return f;
});
this.changedFiles = []; this.changedFiles = [];
this.commitMessage = ''; this.commitMessage = '';
this.editMode = false; this.editMode = false;
...@@ -62,9 +121,17 @@ export default { ...@@ -62,9 +121,17 @@ export default {
<div <div
v-if="showCommitable" v-if="showCommitable"
id="commit-area"> id="commit-area">
<popup-dialog
v-if="showNewBranchDialog"
:primary-button-label="__('Create new branch')"
kind="primary"
:title="__('Branch has changed')"
:text="__('This branch has changed since you started editing. Would you like to create a new branch?')"
@submit="commitToNewBranch"
/>
<form <form
class="form-horizontal" class="form-horizontal"
@submit.prevent="makeCommit"> @submit.prevent="tryCommit">
<fieldset> <fieldset>
<div class="form-group"> <div class="form-group">
<label class="col-md-4 control-label staged-files"> <label class="col-md-4 control-label staged-files">
...@@ -117,7 +184,7 @@ export default { ...@@ -117,7 +184,7 @@ export default {
class="btn btn-success"> class="btn btn-success">
<i <i
v-if="submitCommitsLoading" v-if="submitCommitsLoading"
class="fa fa-spinner fa-spin" class="js-commit-loading-icon fa fa-spinner fa-spin"
aria-hidden="true" aria-hidden="true"
aria-label="loading"> aria-label="loading">
</i> </i>
...@@ -126,6 +193,14 @@ export default { ...@@ -126,6 +193,14 @@ export default {
</span> </span>
</button> </button>
</div> </div>
<div class="col-md-offset-4 col-md-6">
<div class="checkbox">
<label>
<input type="checkbox" v-model="startNewMR">
<span>Start a <strong>new merge request</strong> with these changes</span>
</label>
</div>
</div>
</fieldset> </fieldset>
</form> </form>
</div> </div>
......
...@@ -22,7 +22,8 @@ const RepoEditor = { ...@@ -22,7 +22,8 @@ const RepoEditor = {
const monacoInstance = Helper.monaco.editor.create(this.$el, { const monacoInstance = Helper.monaco.editor.create(this.$el, {
model: null, model: null,
readOnly: false, readOnly: false,
contextmenu: false, contextmenu: true,
scrollBeyondLastLine: false,
}); });
Helper.monacoInstance = monacoInstance; Helper.monacoInstance = monacoInstance;
......
...@@ -31,8 +31,11 @@ function setInitialStore(data) { ...@@ -31,8 +31,11 @@ function setInitialStore(data) {
Store.projectUrl = data.projectUrl; Store.projectUrl = data.projectUrl;
Store.canCommit = data.canCommit; Store.canCommit = data.canCommit;
Store.onTopOfBranch = data.onTopOfBranch; Store.onTopOfBranch = data.onTopOfBranch;
Store.newMrTemplateUrl = decodeURIComponent(data.newMrTemplateUrl);
Store.customBranchURL = decodeURIComponent(data.blobUrl);
Store.currentBranch = $('button.dropdown-menu-toggle').attr('data-ref'); Store.currentBranch = $('button.dropdown-menu-toggle').attr('data-ref');
Store.checkIsCommitable(); Store.checkIsCommitable();
Store.setBranchHash();
} }
function initRepo(el) { function initRepo(el) {
......
...@@ -64,6 +64,10 @@ const RepoService = { ...@@ -64,6 +64,10 @@ const RepoService = {
return urlArray.join('/'); return urlArray.join('/');
}, },
getBranch() {
return Api.branchSingle(Store.projectId, Store.currentBranch);
},
commitFiles(payload) { commitFiles(payload) {
return Api.commitMultiple(Store.projectId, payload) return Api.commitMultiple(Store.projectId, payload)
.then(this.commitFlash); .then(this.commitFlash);
......
...@@ -23,6 +23,7 @@ const RepoStore = { ...@@ -23,6 +23,7 @@ const RepoStore = {
title: '', title: '',
status: false, status: false,
}, },
showNewBranchDialog: false,
activeFile: Helper.getDefaultActiveFile(), activeFile: Helper.getDefaultActiveFile(),
activeFileIndex: 0, activeFileIndex: 0,
activeLine: -1, activeLine: -1,
...@@ -31,6 +32,12 @@ const RepoStore = { ...@@ -31,6 +32,12 @@ const RepoStore = {
isCommitable: false, isCommitable: false,
binary: false, binary: false,
currentBranch: '', currentBranch: '',
startNewMR: false,
currentHash: '',
currentShortHash: '',
customBranchURL: '',
newMrTemplateUrl: '',
branchChanged: false,
commitMessage: '', commitMessage: '',
binaryTypes: { binaryTypes: {
png: false, png: false,
...@@ -49,6 +56,17 @@ const RepoStore = { ...@@ -49,6 +56,17 @@ const RepoStore = {
}); });
}, },
setBranchHash() {
return Service.getBranch()
.then((data) => {
if (RepoStore.currentHash !== '' && data.commit.id !== RepoStore.currentHash) {
RepoStore.branchChanged = true;
}
RepoStore.currentHash = data.commit.id;
RepoStore.currentShortHash = data.commit.short_id;
});
},
// mutations // mutations
checkIsCommitable() { checkIsCommitable() {
RepoStore.isCommitable = RepoStore.onTopOfBranch && RepoStore.canCommit; RepoStore.isCommitable = RepoStore.onTopOfBranch && RepoStore.canCommit;
......
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
import UserTabs from './user_tabs'; import UserTabs from './user_tabs';
export default function initUserProfile(action) { function initUserProfile(action) {
// place profile avatars to top // place profile avatars to top
$('.profile-groups-avatars').tooltip({ $('.profile-groups-avatars').tooltip({
placement: 'top', placement: 'top',
...@@ -17,3 +17,9 @@ export default function initUserProfile(action) { ...@@ -17,3 +17,9 @@ export default function initUserProfile(action) {
$(this).parents('.project-limit-message').remove(); $(this).parents('.project-limit-message').remove();
}); });
} }
document.addEventListener('DOMContentLoaded', () => {
const page = $('body').attr('data-page');
const action = page.split(':')[1];
initUserProfile(action);
});
<script> <script>
import Flash from '../../../flash'; import Flash from '../../../flash';
import GLForm from '../../../gl_form';
import markdownHeader from './header.vue'; import markdownHeader from './header.vue';
import markdownToolbar from './toolbar.vue'; import markdownToolbar from './toolbar.vue';
...@@ -85,7 +86,7 @@ ...@@ -85,7 +86,7 @@
/* /*
GLForm class handles all the toolbar buttons GLForm class handles all the toolbar buttons
*/ */
return new gl.GLForm($(this.$refs['gl-form']), true); return new GLForm($(this.$refs['gl-form']), true);
}, },
beforeDestroy() { beforeDestroy() {
const glForm = $(this.$refs['gl-form']).data('gl-form'); const glForm = $(this.$refs['gl-form']).data('gl-form');
......
...@@ -16,6 +16,11 @@ export default { ...@@ -16,6 +16,11 @@ export default {
required: false, required: false,
default: 'primary', default: 'primary',
}, },
closeKind: {
type: String,
required: false,
default: 'default',
},
closeButtonLabel: { closeButtonLabel: {
type: String, type: String,
required: false, required: false,
...@@ -33,6 +38,11 @@ export default { ...@@ -33,6 +38,11 @@ export default {
[`btn-${this.kind}`]: true, [`btn-${this.kind}`]: true,
}; };
}, },
btnCancelKindClass() {
return {
[`btn-${this.closeKind}`]: true,
};
},
}, },
methods: { methods: {
...@@ -70,7 +80,8 @@ export default { ...@@ -70,7 +80,8 @@ export default {
<div class="modal-footer"> <div class="modal-footer">
<button <button
type="button" type="button"
class="btn btn-default" class="btn"
:class="btnCancelKindClass"
@click="emitSubmit(false)"> @click="emitSubmit(false)">
{{closeButtonLabel}} {{closeButtonLabel}}
</button> </button>
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
@import "framework/animations"; @import "framework/animations";
@import "framework/avatar"; @import "framework/avatar";
@import "framework/asciidoctor"; @import "framework/asciidoctor";
@import "framework/banner";
@import "framework/blocks"; @import "framework/blocks";
@import "framework/buttons"; @import "framework/buttons";
@import "framework/badges"; @import "framework/badges";
......
.banner-callout {
display: flex;
position: relative;
flex-wrap: wrap;
.banner-close {
position: absolute;
top: 10px;
right: 10px;
opacity: 1;
.dismiss-icon {
color: $gl-text-color;
font-size: $gl-font-size;
}
}
.banner-graphic {
margin: 20px auto;
}
&.banner-non-empty-state {
border-bottom: 1px solid $border-color;
}
}
...@@ -838,6 +838,7 @@ ...@@ -838,6 +838,7 @@
a { a {
padding: 8px 40px; padding: 8px 40px;
&.is-indeterminate::before,
&.is-active::before { &.is-active::before {
left: 16px; left: 16px;
} }
......
...@@ -10,6 +10,10 @@ ...@@ -10,6 +10,10 @@
border: 0; border: 0;
} }
&.file-holder-bottom-radius {
border-radius: 0 0 $border-radius-small $border-radius-small;
}
&.readme-holder { &.readme-holder {
margin: $gl-padding 0; margin: $gl-padding 0;
......
...@@ -90,7 +90,7 @@ $new-sidebar-collapsed-width: 50px; ...@@ -90,7 +90,7 @@ $new-sidebar-collapsed-width: 50px;
top: $header-height; top: $header-height;
bottom: 0; bottom: 0;
left: 0; left: 0;
background-color: $gray-normal; background-color: $gray-light;
box-shadow: inset -2px 0 0 $border-color; box-shadow: inset -2px 0 0 $border-color;
transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0);
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
margin: 0; margin: 0;
list-style: none; list-style: none;
height: auto; height: auto;
border-bottom: 1px solid $border-color;
li { li {
display: flex; display: flex;
...@@ -24,6 +25,7 @@ ...@@ -24,6 +25,7 @@
&:focus { &:focus {
text-decoration: none; text-decoration: none;
color: $black; color: $black;
border-bottom: 2px solid $gray-darkest;
.badge { .badge {
color: $black; color: $black;
......
...@@ -234,6 +234,7 @@ $container-text-max-width: 540px; ...@@ -234,6 +234,7 @@ $container-text-max-width: 540px;
$gl-avatar-size: 40px; $gl-avatar-size: 40px;
$error-exclamation-point: $red-500; $error-exclamation-point: $red-500;
$border-radius-default: 4px; $border-radius-default: 4px;
$border-radius-small: 2px;
$settings-icon-size: 18px; $settings-icon-size: 18px;
$provider-btn-not-active-color: $blue-500; $provider-btn-not-active-color: $blue-500;
$link-underline-blue: $blue-500; $link-underline-blue: $blue-500;
......
...@@ -707,11 +707,11 @@ ...@@ -707,11 +707,11 @@
.frame.click-to-comment { .frame.click-to-comment {
position: relative; position: relative;
cursor: url(icon_image_comment.svg) cursor: image-url('icon_image_comment.svg')
$image-comment-cursor-left-offset $image-comment-cursor-top-offset, auto; $image-comment-cursor-left-offset $image-comment-cursor-top-offset, auto;
// Retina cursor // Retina cursor
cursor: -webkit-image-set(url(icon_image_comment.svg) 1x, url(icon_image_comment@2x.svg) 2x) cursor: -webkit-image-set(image-url('icon_image_comment.svg') 1x, image-url('icon_image_comment@2x.svg') 2x)
$image-comment-cursor-left-offset $image-comment-cursor-top-offset, auto; $image-comment-cursor-left-offset $image-comment-cursor-top-offset, auto;
.comment-indicator { .comment-indicator {
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
border-right: 1px solid $border-color; border-right: 1px solid $border-color;
border-left: 1px solid $border-color; border-left: 1px solid $border-color;
border-bottom: none; border-bottom: none;
border-radius: 2px; border-radius: $border-radius-small $border-radius-small 0 0;
background: $gray-normal; background: $gray-normal;
} }
......
...@@ -117,7 +117,7 @@ ...@@ -117,7 +117,7 @@
} }
.right-sidebar { .right-sidebar {
a, a:not(.btn-retry),
.btn-link { .btn-link {
color: inherit; color: inherit;
} }
...@@ -459,7 +459,7 @@ ...@@ -459,7 +459,7 @@
} }
} }
a { a:not(.btn-retry) {
&:hover { &:hover {
color: $md-link-color; color: $md-link-color;
text-decoration: none; text-decoration: none;
......
...@@ -653,10 +653,6 @@ a.deploy-project-label { ...@@ -653,10 +653,6 @@ a.deploy-project-label {
} }
.project-import { .project-import {
.form-group {
margin-bottom: 0;
}
.import-btn-container { .import-btn-container {
margin-bottom: 0; margin-bottom: 0;
} }
......
...@@ -53,7 +53,7 @@ class Projects::MergeRequests::ConflictsController < Projects::MergeRequests::Ap ...@@ -53,7 +53,7 @@ class Projects::MergeRequests::ConflictsController < Projects::MergeRequests::Ap
flash[:notice] = 'All merge conflicts were resolved. The merge request can now be merged.' flash[:notice] = 'All merge conflicts were resolved. The merge request can now be merged.'
render json: { redirect_to: project_merge_request_url(@project, @merge_request, resolved_conflicts: true) } render json: { redirect_to: project_merge_request_url(@project, @merge_request, resolved_conflicts: true) }
rescue Gitlab::Conflict::ResolutionError => e rescue Gitlab::Git::Conflict::Resolver::ResolutionError => e
render status: :bad_request, json: { message: e.message } render status: :bad_request, json: { message: e.message }
end end
end end
......
...@@ -315,20 +315,12 @@ module IssuablesHelper ...@@ -315,20 +315,12 @@ module IssuablesHelper
@issuable_templates ||= @issuable_templates ||=
case issuable case issuable
when Issue when Issue
issue_template_names ref_project.repository.issue_template_names
when MergeRequest when MergeRequest
merge_request_template_names ref_project.repository.merge_request_template_names
end end
end end
def merge_request_template_names
@merge_request_templates ||= Gitlab::Template::MergeRequestTemplate.dropdown_names(ref_project)
end
def issue_template_names
@issue_templates ||= Gitlab::Template::IssueTemplate.dropdown_names(ref_project)
end
def selected_template(issuable) def selected_template(issuable)
params[:issuable_template] if issuable_templates(issuable).any? { |template| template[:name] == params[:issuable_template] } params[:issuable_template] if issuable_templates(issuable).any? { |template| template[:name] == params[:issuable_template] }
end end
......
...@@ -156,7 +156,9 @@ class Blob < SimpleDelegator ...@@ -156,7 +156,9 @@ class Blob < SimpleDelegator
end end
def file_type def file_type
Gitlab::FileDetector.type_of(path) name = File.basename(path)
Gitlab::FileDetector.type_of(path) || Gitlab::FileDetector.type_of(name)
end end
def video? def video?
......
...@@ -7,6 +7,9 @@ module Gcp ...@@ -7,6 +7,9 @@ module Gcp
belongs_to :user belongs_to :user
belongs_to :service belongs_to :service
scope :enabled, -> { where(enabled: true) }
scope :disabled, -> { where(enabled: false) }
default_value_for :gcp_cluster_zone, 'us-central1-a' default_value_for :gcp_cluster_zone, 'us-central1-a'
default_value_for :gcp_cluster_size, 3 default_value_for :gcp_cluster_size, 3
default_value_for :gcp_machine_type, 'n1-standard-4' default_value_for :gcp_machine_type, 'n1-standard-4'
......
...@@ -20,19 +20,29 @@ class Geo::ProjectRegistry < Geo::BaseRegistry ...@@ -20,19 +20,29 @@ class Geo::ProjectRegistry < Geo::BaseRegistry
.where(resync_repository: false, resync_wiki: false) .where(resync_repository: false, resync_wiki: false)
end end
def resync_repository? def repository_sync_due?(scheduled_time)
resync_repository || last_repository_successful_sync_at.nil? never_synced_repository? || repository_sync_needed?(scheduled_time)
end end
def resync_wiki? def wiki_sync_due?(scheduled_time)
resync_wiki || last_wiki_successful_sync_at.nil? project.wiki_enabled? && (never_synced_wiki? || wiki_sync_needed?(scheduled_time))
end end
def repository_synced_since?(timestamp) private
last_repository_synced_at && last_repository_synced_at > timestamp
def never_synced_repository?
last_repository_successful_sync_at.nil?
end
def never_synced_wiki?
last_wiki_successful_sync_at.nil?
end
def repository_sync_needed?(timestamp)
resync_repository? && (last_repository_synced_at.nil? || timestamp > last_repository_synced_at)
end end
def wiki_synced_since?(timestamp) def wiki_sync_needed?(timestamp)
last_wiki_synced_at && last_wiki_synced_at > timestamp resync_wiki? && (last_wiki_synced_at.nil? || timestamp > last_wiki_synced_at)
end end
end end
...@@ -404,7 +404,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -404,7 +404,7 @@ class MergeRequest < ActiveRecord::Base
end end
def merge_ongoing? def merge_ongoing?
!!merge_jid && !merged? !!merge_jid && !merged? && Gitlab::SidekiqStatus.running?(merge_jid)
end end
def closed_without_fork? def closed_without_fork?
......
class OauthAccessToken < Doorkeeper::AccessToken class OauthAccessToken < Doorkeeper::AccessToken
belongs_to :resource_owner, class_name: 'User' belongs_to :resource_owner, class_name: 'User'
belongs_to :application, class_name: 'Doorkeeper::Application' belongs_to :application, class_name: 'Doorkeeper::Application'
alias_method :user, :resource_owner
end end
...@@ -41,7 +41,8 @@ class Repository ...@@ -41,7 +41,8 @@ class Repository
CACHED_METHODS = %i(size commit_count rendered_readme contribution_guide CACHED_METHODS = %i(size commit_count rendered_readme contribution_guide
changelog license_blob license_key gitignore koding_yml changelog license_blob license_key gitignore koding_yml
gitlab_ci_yml branch_names tag_names branch_count gitlab_ci_yml branch_names tag_names branch_count
tag_count avatar exists? empty? root_ref has_visible_content?).freeze tag_count avatar exists? empty? root_ref has_visible_content?
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 empty_repo?).freeze
...@@ -57,7 +58,9 @@ class Repository ...@@ -57,7 +58,9 @@ class Repository
gitignore: :gitignore, gitignore: :gitignore,
koding: :koding_yml, koding: :koding_yml,
gitlab_ci: :gitlab_ci_yml, gitlab_ci: :gitlab_ci_yml,
avatar: :avatar avatar: :avatar,
issue_template: :issue_template_names,
merge_request_template: :merge_request_template_names
}.freeze }.freeze
# Wraps around the given method and caches its output in Redis and an instance # Wraps around the given method and caches its output in Redis and an instance
...@@ -472,9 +475,7 @@ class Repository ...@@ -472,9 +475,7 @@ class Repository
end end
def blob_at(sha, path) def blob_at(sha, path)
unless Gitlab::Git.blank_ref?(sha) Blob.decorate(raw_repository.blob_at(sha, path), project)
Blob.decorate(Gitlab::Git::Blob.find(self, sha, path), project)
end
rescue Gitlab::Git::Repository::NoRepository rescue Gitlab::Git::Repository::NoRepository
nil nil
end end
...@@ -542,6 +543,16 @@ class Repository ...@@ -542,6 +543,16 @@ class Repository
end end
cache_method :avatar cache_method :avatar
def issue_template_names
Gitlab::Template::IssueTemplate.dropdown_names(project)
end
cache_method :issue_template_names, fallback: []
def merge_request_template_names
Gitlab::Template::MergeRequestTemplate.dropdown_names(project)
end
cache_method :merge_request_template_names, fallback: []
def readme def readme
if readme = tree(:head)&.readme if readme = tree(:head)&.readme
ReadmeBlob.new(readme, self) ReadmeBlob.new(readme, self)
...@@ -908,14 +919,6 @@ class Repository ...@@ -908,14 +919,6 @@ class Repository
end end
end end
def resolve_conflicts(user, branch_name, params)
with_branch(user, branch_name) do
committer = user_to_committer(user)
create_commit(params.merge(author: committer, committer: committer))
end
end
def merged_to_root_ref?(branch_name) def merged_to_root_ref?(branch_name)
branch_commit = commit(branch_name) branch_commit = commit(branch_name)
root_ref_commit = commit(root_ref) root_ref_commit = commit(root_ref)
...@@ -1175,7 +1178,7 @@ class Repository ...@@ -1175,7 +1178,7 @@ class Repository
def last_commit_id_for_path_by_shelling_out(sha, path) def last_commit_id_for_path_by_shelling_out(sha, path)
args = %W(rev-list --max-count=1 #{sha} -- #{path}) args = %W(rev-list --max-count=1 #{sha} -- #{path})
run_git(args).first.strip raw_repository.run_git_with_timeout(args, Gitlab::Git::Popen::FAST_GIT_PROCESS_TIMEOUT).first.strip
end end
def repository_storage_path def repository_storage_path
......
...@@ -56,11 +56,22 @@ module Auth ...@@ -56,11 +56,22 @@ module Auth
def process_scope(scope) def process_scope(scope)
type, name, actions = scope.split(':', 3) type, name, actions = scope.split(':', 3)
actions = actions.split(',') actions = actions.split(',')
path = ContainerRegistry::Path.new(name)
return unless type == 'repository' case type
when 'registry'
process_registry_access(type, name, actions)
when 'repository'
path = ContainerRegistry::Path.new(name)
process_repository_access(type, path, actions)
end
end
def process_registry_access(type, name, actions)
return unless current_user&.admin?
return unless name == 'catalog'
return unless actions == ['*']
process_repository_access(type, path, actions) { type: type, name: name, actions: ['*'] }
end end
def process_repository_access(type, path, actions) def process_repository_access(type, path, actions)
......
...@@ -28,6 +28,8 @@ module Ci ...@@ -28,6 +28,8 @@ module Ci
attributes.push([:user, current_user]) attributes.push([:user, current_user])
build.retried = true
Ci::Build.transaction do Ci::Build.transaction do
# mark all other builds of that name as retried # mark all other builds of that name as retried
build.pipeline.builds.latest build.pipeline.builds.latest
......
...@@ -10,7 +10,7 @@ module Geo ...@@ -10,7 +10,7 @@ module Geo
repository_storage_name: project.repository.storage, repository_storage_name: project.repository.storage,
repository_storage_path: project.repository_storage_path, repository_storage_path: project.repository_storage_path,
repo_path: project.disk_path, repo_path: project.disk_path,
wiki_path: "#{project.disk_path}.wiki", wiki_path: ("#{project.disk_path}.wiki" if project.wiki_enabled?),
project_name: project.name project_name: project.name
) )
end end
......
...@@ -3,7 +3,7 @@ module MergeRequests ...@@ -3,7 +3,7 @@ module MergeRequests
# Adds a todo to the parent merge_request when a CI build fails # Adds a todo to the parent merge_request when a CI build fails
# #
def execute(commit_status) def execute(commit_status)
return if commit_status.allow_failure? return if commit_status.allow_failure? || commit_status.retried?
commit_status_merge_requests(commit_status) do |merge_request| commit_status_merge_requests(commit_status) do |merge_request|
todo_service.merge_request_build_failed(merge_request) todo_service.merge_request_build_failed(merge_request)
......
...@@ -23,13 +23,13 @@ module MergeRequests ...@@ -23,13 +23,13 @@ module MergeRequests
# when there are no conflict files. # when there are no conflict files.
conflicts.files.each(&:lines) conflicts.files.each(&:lines)
@conflicts_can_be_resolved_in_ui = conflicts.files.length > 0 @conflicts_can_be_resolved_in_ui = conflicts.files.length > 0
rescue Rugged::OdbError, Gitlab::Conflict::Parser::UnresolvableError, Gitlab::Conflict::FileCollection::ConflictSideMissing rescue Rugged::OdbError, Gitlab::Git::Conflict::Parser::UnresolvableError, Gitlab::Git::Conflict::Resolver::ConflictSideMissing
@conflicts_can_be_resolved_in_ui = false @conflicts_can_be_resolved_in_ui = false
end end
end end
def conflicts def conflicts
@conflicts ||= Gitlab::Conflict::FileCollection.read_only(merge_request) @conflicts ||= Gitlab::Conflict::FileCollection.new(merge_request)
end end
end end
end end
......
module MergeRequests module MergeRequests
module Conflicts module Conflicts
class ResolveService < MergeRequests::Conflicts::BaseService class ResolveService < MergeRequests::Conflicts::BaseService
MissingFiles = Class.new(Gitlab::Conflict::ResolutionError)
def execute(current_user, params) def execute(current_user, params)
rugged = merge_request.source_project.repository.rugged conflicts = Gitlab::Conflict::FileCollection.new(merge_request)
Gitlab::Conflict::FileCollection.for_resolution(merge_request) do |conflicts_for_resolution|
merge_index = conflicts_for_resolution.merge_index
params[:files].each do |file_params|
conflict_file = conflicts_for_resolution.file_for_path(file_params[:old_path], file_params[:new_path])
write_resolved_file_to_index(merge_index, rugged, conflict_file, file_params)
end
unless merge_index.conflicts.empty?
missing_files = merge_index.conflicts.map { |file| file[:ours][:path] }
raise MissingFiles, "Missing resolutions for the following files: #{missing_files.join(', ')}"
end
commit_params = {
message: params[:commit_message] || conflicts_for_resolution.default_commit_message,
parents: [conflicts_for_resolution.our_commit, conflicts_for_resolution.their_commit].map(&:oid),
tree: merge_index.write_tree(rugged)
}
conflicts_for_resolution
.project
.repository
.resolve_conflicts(current_user, merge_request.source_branch, commit_params)
end
end
private
def write_resolved_file_to_index(merge_index, rugged, file, params)
if params[:sections]
new_file = file.resolve_lines(params[:sections]).map(&:text).join("\n")
new_file << "\n" if file.our_blob.data.ends_with?("\n")
elsif params[:content]
new_file = file.resolve_content(params[:content])
end
our_path = file.our_path
merge_index.add(path: our_path, oid: rugged.write(new_file, :blob), mode: file.our_mode) conflicts.resolve(current_user, params[:commit_message], params[:files])
merge_index.conflict_remove(our_path)
end end
end end
end end
......
...@@ -84,13 +84,9 @@ module MergeRequests ...@@ -84,13 +84,9 @@ module MergeRequests
def after_merge def after_merge
MergeRequests::PostMergeService.new(project, current_user).execute(merge_request) MergeRequests::PostMergeService.new(project, current_user).execute(merge_request)
if params[:should_remove_source_branch].present? || @merge_request.force_remove_source_branch? if delete_source_branch?
# Verify again that the source branch can be removed, since branch may be protected, DeleteBranchService.new(@merge_request.source_project, branch_deletion_user)
# or the source branch may have been updated. .execute(merge_request.source_branch)
if @merge_request.can_remove_source_branch?(branch_deletion_user)
DeleteBranchService.new(@merge_request.source_project, branch_deletion_user)
.execute(merge_request.source_branch)
end
end end
end end
...@@ -102,6 +98,14 @@ module MergeRequests ...@@ -102,6 +98,14 @@ module MergeRequests
@merge_request.force_remove_source_branch? ? @merge_request.author : current_user @merge_request.force_remove_source_branch? ? @merge_request.author : current_user
end end
# Verify again that the source branch can be removed, since branch may be protected,
# or the source branch may have been updated, or the user may not have permission
#
def delete_source_branch?
params.fetch('should_remove_source_branch', @merge_request.force_remove_source_branch?) &&
@merge_request.can_remove_source_branch?(branch_deletion_user)
end
# Logs merge error message and cleans `MergeRequest#merge_jid`. # Logs merge error message and cleans `MergeRequest#merge_jid`.
# #
def handle_merge_error(log_message:, save_message_on_model: false) def handle_merge_error(log_message:, save_message_on_model: false)
......
...@@ -413,7 +413,7 @@ class NotificationService ...@@ -413,7 +413,7 @@ class NotificationService
end end
def relabeled_resource_email(target, labels, current_user, method) def relabeled_resource_email(target, labels, current_user, method)
recipients = labels.flat_map { |l| l.subscribers(target.project) } recipients = labels.flat_map { |l| l.subscribers(target.project) }.uniq
recipients = notifiable_users( recipients = notifiable_users(
recipients, :subscription, recipients, :subscription,
target: target, target: target,
......
...@@ -29,3 +29,17 @@ ...@@ -29,3 +29,17 @@
= hidden_field_tag "#{form.object_name}[namespace_ids]", geo_node.namespace_ids.join(","), class: 'js-geo-node-namespaces', data: { selected: node_namespaces_options(geo_node.namespaces).to_json } = hidden_field_tag "#{form.object_name}[namespace_ids]", geo_node.namespace_ids.join(","), class: 'js-geo-node-namespaces', data: { selected: node_namespaces_options(geo_node.namespaces).to_json }
.help-block .help-block
#{ s_("Choose which groups you wish to replicate to this secondary node. Leave blank to replicate all.") } #{ s_("Choose which groups you wish to replicate to this secondary node. Leave blank to replicate all.") }
.form-group.js-hide-if-geo-primary{ class: ('hidden' unless geo_node.secondary?) }
= form.label :repos_max_capacity, s_('Geo|Repository sync capacity'), class: 'control-label'
.col-sm-10
= form.number_field :repos_max_capacity, class: 'form-control', min: 0
.help-block
#{ s_('Control the maximum concurrency of repository backfill for this secondary node') }
.form-group.js-hide-if-geo-primary{ class: ('hidden' unless geo_node.secondary?) }
= form.label :files_max_capacity, s_('Geo|File sync capacity'), class: 'control-label'
.col-sm-10
= form.number_field :files_max_capacity, class: 'form-control', min: 0
.help-block
#{ s_('Control the maximum concurrency of LFS/attachment backfill for this secondary node') }
...@@ -15,10 +15,6 @@ ...@@ -15,10 +15,6 @@
%tr %tr
%th %th
%th Global Shortcuts %th Global Shortcuts
%tr
%td.shortcut
.key n
%td Main Navigation
%tr %tr
%td.shortcut %td.shortcut
.key s .key s
......
...@@ -31,7 +31,7 @@ ...@@ -31,7 +31,7 @@
%tbody %tbody
%tr %tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" } %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" }
%img{ height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-branch-gray.gif'), style: "display:block;", width: "13", alt: "Branch icon" }/ %img{ height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-branch-gray.gif'), style: "display:block;", width: "13", alt: "" }/
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" } %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" }
%a.muted{ href: commits_url(@pipeline), style: "color:#333333;text-decoration:none;" } %a.muted{ href: commits_url(@pipeline), style: "color:#333333;text-decoration:none;" }
= @pipeline.ref = @pipeline.ref
...@@ -42,7 +42,7 @@ ...@@ -42,7 +42,7 @@
%tbody %tbody
%tr %tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" } %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" }
%img{ height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-commit-gray.gif'), style: "display:block;", width: "13", alt: "Commit icon" }/ %img{ height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-commit-gray.gif'), style: "display:block;", width: "13", alt: "" }/
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" } %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" }
%a{ href: commit_url(@pipeline), style: "color:#3777b0;text-decoration:none;" } %a{ href: commit_url(@pipeline), style: "color:#3777b0;text-decoration:none;" }
= @pipeline.short_sha = @pipeline.short_sha
...@@ -60,7 +60,7 @@ ...@@ -60,7 +60,7 @@
%tbody %tbody
%tr %tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" } %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" }
%img.avatar{ height: "24", src: avatar_icon(commit.author || commit.author_email, 24, only_path: false), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "Avatar" }/ %img.avatar{ height: "24", src: avatar_icon(commit.author || commit.author_email, 24, only_path: false), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "" }/
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" } %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" }
- if commit.author - if commit.author
%a.muted{ href: user_url(commit.author), style: "color:#333333;text-decoration:none;" } %a.muted{ href: user_url(commit.author), style: "color:#333333;text-decoration:none;" }
...@@ -76,7 +76,7 @@ ...@@ -76,7 +76,7 @@
%tbody %tbody
%tr %tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" } %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" }
%img.avatar{ height: "24", src: avatar_icon(commit.committer || commit.committer_email, 24, only_path: false), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "Avatar" }/ %img.avatar{ height: "24", src: avatar_icon(commit.committer || commit.committer_email, 24, only_path: false), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "" }/
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" } %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" }
- if commit.committer - if commit.committer
%a.muted{ href: user_url(commit.committer), style: "color:#333333;text-decoration:none;" } %a.muted{ href: user_url(commit.committer), style: "color:#333333;text-decoration:none;" }
...@@ -100,7 +100,7 @@ ...@@ -100,7 +100,7 @@
triggered by triggered by
- if @pipeline.user - if @pipeline.user
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;padding-left:5px", width: "24" } %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;padding-left:5px", width: "24" }
%img.avatar{ height: "24", src: avatar_icon(@pipeline.user, 24, only_path: false), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "Avatar" }/ %img.avatar{ height: "24", src: avatar_icon(@pipeline.user, 24, only_path: false), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "" }/
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;font-weight:500;line-height:1.4;vertical-align:baseline;" } %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;font-weight:500;line-height:1.4;vertical-align:baseline;" }
%a.muted{ href: user_url(@pipeline.user), style: "color:#333333;text-decoration:none;" } %a.muted{ href: user_url(@pipeline.user), style: "color:#333333;text-decoration:none;" }
= @pipeline.user.name = @pipeline.user.name
......
...@@ -31,7 +31,7 @@ ...@@ -31,7 +31,7 @@
%tbody %tbody
%tr %tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" } %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" }
%img{ height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-branch-gray.gif'), style: "display:block;", width: "13", alt: "Branch icon" }/ %img{ height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-branch-gray.gif'), style: "display:block;", width: "13", alt: "" }/
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" } %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" }
%a.muted{ href: commits_url(@pipeline), style: "color:#333333;text-decoration:none;" } %a.muted{ href: commits_url(@pipeline), style: "color:#333333;text-decoration:none;" }
= @pipeline.ref = @pipeline.ref
...@@ -42,7 +42,7 @@ ...@@ -42,7 +42,7 @@
%tbody %tbody
%tr %tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" } %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" }
%img{ height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-commit-gray.gif'), style: "display:block;", width: "13", alt: "Commit icon" }/ %img{ height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-commit-gray.gif'), style: "display:block;", width: "13", alt: "" }/
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" } %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" }
%a{ href: commit_url(@pipeline), style: "color:#3777b0;text-decoration:none;" } %a{ href: commit_url(@pipeline), style: "color:#3777b0;text-decoration:none;" }
= @pipeline.short_sha = @pipeline.short_sha
...@@ -60,7 +60,7 @@ ...@@ -60,7 +60,7 @@
%tbody %tbody
%tr %tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" } %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" }
%img.avatar{ height: "24", src: avatar_icon(commit.author || commit.author_email, 24, only_path: false), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "Avatar" }/ %img.avatar{ height: "24", src: avatar_icon(commit.author || commit.author_email, 24, only_path: false), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "" }/
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" } %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" }
- if commit.author - if commit.author
%a.muted{ href: user_url(commit.author), style: "color:#333333;text-decoration:none;" } %a.muted{ href: user_url(commit.author), style: "color:#333333;text-decoration:none;" }
...@@ -76,7 +76,7 @@ ...@@ -76,7 +76,7 @@
%tbody %tbody
%tr %tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" } %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" }
%img.avatar{ height: "24", src: avatar_icon(commit.committer || commit.committer_email, 24, only_path: false), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "Avatar" }/ %img.avatar{ height: "24", src: avatar_icon(commit.committer || commit.committer_email, 24, only_path: false), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "" }/
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" } %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" }
- if commit.committer - if commit.committer
%a.muted{ href: user_url(commit.committer), style: "color:#333333;text-decoration:none;" } %a.muted{ href: user_url(commit.committer), style: "color:#333333;text-decoration:none;" }
...@@ -100,7 +100,7 @@ ...@@ -100,7 +100,7 @@
triggered by triggered by
- if @pipeline.user - if @pipeline.user
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;padding-left:5px", width: "24" } %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;padding-left:5px", width: "24" }
%img.avatar{ height: "24", src: avatar_icon(@pipeline.user, 24, only_path: false), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "Avatar" }/ %img.avatar{ height: "24", src: avatar_icon(@pipeline.user, 24, only_path: false), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "" }/
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;font-weight:500;line-height:1.4;vertical-align:baseline;" } %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;font-weight:500;line-height:1.4;vertical-align:baseline;" }
%a.muted{ href: user_url(@pipeline.user), style: "color:#333333;text-decoration:none;" } %a.muted{ href: user_url(@pipeline.user), style: "color:#333333;text-decoration:none;" }
= @pipeline.user.name = @pipeline.user.name
......
- action = current_action?(:edit) || current_action?(:update) ? 'edit' : 'create' - action = current_action?(:edit) || current_action?(:update) ? 'edit' : 'create'
.file-holder.file.append-bottom-default .file-holder-bottom-radius.file-holder.file.append-bottom-default
.js-file-title.file-title.clearfix{ data: { current_action: action } } .js-file-title.file-title.clearfix{ data: { current_action: action } }
.editor-ref .editor-ref
= icon('code-fork') = icon('code-fork')
......
...@@ -24,10 +24,15 @@ ...@@ -24,10 +24,15 @@
%p %p
You will need to be owner or have the master permission level for the initial push, as the master branch is automatically protected. You will need to be owner or have the master permission level for the initial push, as the master branch is automatically protected.
- if show_auto_devops_callout?(@project)
%p
- link = link_to(s_('AutoDevOps|Auto DevOps (Beta)'), project_settings_ci_cd_path(@project, anchor: 'js-general-pipeline-settings'))
= s_('AutoDevOps|You can activate %{link_to_settings} for this project.').html_safe % { link_to_settings: link }
%p
= s_('AutoDevOps|It will automatically build, test, and deploy your application based on a predefined CI/CD configuration.')
- if can?(current_user, :push_code, @project) - if can?(current_user, :push_code, @project)
%div{ class: container_class } %div{ class: container_class }
- if show_auto_devops_callout?(@project)
= render 'shared/auto_devops_callout'
.prepend-top-20 .prepend-top-20
.empty_wrapper .empty_wrapper
%h3.page-title-empty %h3.page-title-empty
......
...@@ -4,8 +4,10 @@ ...@@ -4,8 +4,10 @@
.sidebar-container .sidebar-container
.blocks-container .blocks-container
.block .block
%strong %strong.prepend-top-10
= @build.name = @build.name
- if can?(current_user, :update_build, @build) && @build.retryable?
= link_to "Retry", retry_namespace_project_job_path(@project.namespace, @project, @build), class: 'js-retry-button pull-right btn btn-inverted-secondary btn-retry visible-md-block visible-lg-block', method: :post
%a.gutter-toggle.pull-right.visible-xs-block.visible-sm-block.js-sidebar-build-toggle{ href: "#", 'aria-label': 'Toggle Sidebar', role: 'button' } %a.gutter-toggle.pull-right.visible-xs-block.visible-sm-block.js-sidebar-build-toggle{ href: "#", 'aria-label': 'Toggle Sidebar', role: 'button' }
= icon('angle-double-right') = icon('angle-double-right')
......
...@@ -13,8 +13,6 @@ ...@@ -13,8 +13,6 @@
- if @project.merge_requests.exists? - if @project.merge_requests.exists?
%div{ class: container_class } %div{ class: container_class }
- if show_auto_devops_callout?(@project)
= render 'shared/auto_devops_callout'
.top-area .top-area
= render 'shared/issuable/nav', type: :merge_requests = render 'shared/issuable/nav', type: :merge_requests
.nav-controls .nav-controls
......
...@@ -54,6 +54,10 @@ ...@@ -54,6 +54,10 @@
= f.label :visibility_level, class: 'label-light' do #the label here seems wrong = f.label :visibility_level, class: 'label-light' do #the label here seems wrong
Import project from Import project from
.import-buttons .import-buttons
- if gitlab_project_import_enabled?
.import_gitlab_project.has-tooltip{ data: { container: 'body' } }
= link_to new_import_gitlab_project_path, class: 'btn btn_import_gitlab_project project-submit' do
= icon('gitlab', text: 'GitLab export')
%div %div
- if github_import_enabled? - if github_import_enabled?
= link_to new_import_github_path, class: 'btn import_github' do = link_to new_import_github_path, class: 'btn import_github' do
...@@ -87,10 +91,6 @@ ...@@ -87,10 +91,6 @@
- if git_import_enabled? - if git_import_enabled?
%button.btn.js-toggle-button.import_git{ type: "button" } %button.btn.js-toggle-button.import_git{ type: "button" }
= icon('git', text: 'Repo by URL') = icon('git', text: 'Repo by URL')
- if gitlab_project_import_enabled?
.import_gitlab_project.has-tooltip{ data: { container: 'body' } }
= link_to new_import_gitlab_project_path, class: 'btn btn_import_gitlab_project project-submit' do
= icon('gitlab', text: 'GitLab export')
.col-lg-12 .col-lg-12
.js-toggle-content.hide.toggle-import-form .js-toggle-content.hide.toggle-import-form
%hr %hr
......
...@@ -5,8 +5,6 @@ ...@@ -5,8 +5,6 @@
= render 'shared/shared_runners_minutes_limit', project: @project = render 'shared/shared_runners_minutes_limit', project: @project
%div{ 'class' => container_class } %div{ 'class' => container_class }
- if show_auto_devops_callout?(@project)
= render 'shared/auto_devops_callout'
#pipelines-list-vue{ data: { endpoint: project_pipelines_path(@project, format: :json), #pipelines-list-vue{ data: { endpoint: project_pipelines_path(@project, format: :json),
"help-page-path" => help_page_path('ci/quick_start/README'), "help-page-path" => help_page_path('ci/quick_start/README'),
"help-auto-devops-path" => help_page_path('topics/autodevops/index.md'), "help-auto-devops-path" => help_page_path('topics/autodevops/index.md'),
......
...@@ -12,7 +12,5 @@ ...@@ -12,7 +12,5 @@
= webpack_bundle_tag 'repo' = webpack_bundle_tag 'repo'
%div{ class: [container_class, ("limit-container-width" unless fluid_layout)] } %div{ class: [container_class, ("limit-container-width" unless fluid_layout)] }
- if show_auto_devops_callout?(@project) && !show_new_repo?
= render 'shared/auto_devops_callout'
= render 'projects/last_push' = render 'projects/last_push'
= render 'projects/files', commit: @last_commit, project: @project, ref: @ref, content_url: project_tree_path(@project, @id) = render 'projects/files', commit: @last_commit, project: @project, ref: @ref, content_url: project_tree_path(@project, @id)
.user-callout{ data: { uid: 'auto_devops_settings_dismissed', project_path: project_path(@project) } } .js-autodevops-banner.banner-callout.banner-non-empty-state.append-bottom-20{ data: { uid: 'auto_devops_settings_dismissed', project_path: project_path(@project) } }
.bordered-box.landing.content-block .banner-graphic
%button.btn.btn-default.close.js-close-callout{ type: 'button', = custom_icon('icon_autodevops')
'aria-label' => 'Dismiss Auto DevOps box' }
= icon('times', class: 'dismiss-icon', 'aria-hidden' => 'true')
.svg-container
= custom_icon('icon_autodevops')
.user-callout-copy
%h4= s_('AutoDevOps|Auto DevOps (Beta)')
%p= s_('AutoDevOps|Auto DevOps can be activated for this project. It will automatically build, test, and deploy your application based on a predefined CI/CD configuration.')
%p
- link = link_to(s_('AutoDevOps|Auto DevOps documentation'), help_page_path('topics/autodevops/index.md'), target: '_blank', rel: 'noopener noreferrer')
= s_('AutoDevOps|Learn more in the %{link_to_documentation}').html_safe % { link_to_documentation: link }
= link_to s_('AutoDevOps|Enable in settings'), project_settings_ci_cd_path(@project, anchor: 'js-general-pipeline-settings'), class: 'btn btn-primary js-close-callout' .prepend-top-10.prepend-left-10.append-bottom-10
%h5= s_('AutoDevOps|Auto DevOps (Beta)')
%p= s_('AutoDevOps|It will automatically build, test, and deploy your application based on a predefined CI/CD configuration.')
%p
- link = link_to(s_('AutoDevOps|Auto DevOps documentation'), help_page_path('topics/autodevops/index.md'), target: '_blank', rel: 'noopener noreferrer')
= s_('AutoDevOps|Learn more in the %{link_to_documentation}').html_safe % { link_to_documentation: link }
.prepend-top-10
= link_to s_('AutoDevOps|Enable in settings'), project_settings_ci_cd_path(@project, anchor: 'js-general-pipeline-settings'), class: 'btn js-close-callout'
%button.btn-transparent.banner-close.close.js-close-callout{ type: 'button',
'aria-label' => 'Dismiss Auto DevOps box' }
= icon('times', class: 'dismiss-icon', 'aria-hidden' => 'true')
<svg xmlns="http://www.w3.org/2000/svg" width="189" height="179" viewBox="0 0 189 179"> <svg xmlns="http://www.w3.org/2000/svg" width="189" height="110" viewBox="0 0 189 179">
<g fill="none" fill-rule="evenodd"> <g fill="none" fill-rule="evenodd">
<path fill="#FFFFFF" fill-rule="nonzero" d="M110.160166,47.6956996 L160.160166,47.6956996 C165.683013,47.6956996 170.160166,52.1728521 170.160166,57.6956996 L170.160166,117.6957 C170.160166,123.218547 165.683013,127.6957 160.160166,127.6957 L110.160166,127.6957 C104.637318,127.6957 100.160166,123.218547 100.160166,117.6957 L100.160166,57.6956996 C100.160166,52.1728521 104.637318,47.6956996 110.160166,47.6956996 Z" transform="rotate(10 135.16 87.696)"/> <path fill="#FFFFFF" fill-rule="nonzero" d="M110.160166,47.6956996 L160.160166,47.6956996 C165.683013,47.6956996 170.160166,52.1728521 170.160166,57.6956996 L170.160166,117.6957 C170.160166,123.218547 165.683013,127.6957 160.160166,127.6957 L110.160166,127.6957 C104.637318,127.6957 100.160166,123.218547 100.160166,117.6957 L100.160166,57.6956996 C100.160166,52.1728521 104.637318,47.6956996 110.160166,47.6956996 Z" transform="rotate(10 135.16 87.696)"/>
<path fill="#EEEEEE" fill-rule="nonzero" d="M110.160166,51.6956996 C106.846457,51.6956996 104.160166,54.3819911 104.160166,57.6956996 L104.160166,117.6957 C104.160166,121.009408 106.846457,123.6957 110.160166,123.6957 L160.160166,123.6957 C163.473874,123.6957 166.160166,121.009408 166.160166,117.6957 L166.160166,57.6956996 C166.160166,54.3819911 163.473874,51.6956996 160.160166,51.6956996 L110.160166,51.6956996 Z M110.160166,47.6956996 L160.160166,47.6956996 C165.683013,47.6956996 170.160166,52.1728521 170.160166,57.6956996 L170.160166,117.6957 C170.160166,123.218547 165.683013,127.6957 160.160166,127.6957 L110.160166,127.6957 C104.637318,127.6957 100.160166,123.218547 100.160166,117.6957 L100.160166,57.6956996 C100.160166,52.1728521 104.637318,47.6956996 110.160166,47.6956996 Z" transform="rotate(10 135.16 87.696)"/> <path fill="#EEEEEE" fill-rule="nonzero" d="M110.160166,51.6956996 C106.846457,51.6956996 104.160166,54.3819911 104.160166,57.6956996 L104.160166,117.6957 C104.160166,121.009408 106.846457,123.6957 110.160166,123.6957 L160.160166,123.6957 C163.473874,123.6957 166.160166,121.009408 166.160166,117.6957 L166.160166,57.6956996 C166.160166,54.3819911 163.473874,51.6956996 160.160166,51.6956996 L110.160166,51.6956996 Z M110.160166,47.6956996 L160.160166,47.6956996 C165.683013,47.6956996 170.160166,52.1728521 170.160166,57.6956996 L170.160166,117.6957 C170.160166,123.218547 165.683013,127.6957 160.160166,127.6957 L110.160166,127.6957 C104.637318,127.6957 100.160166,123.218547 100.160166,117.6957 L100.160166,57.6956996 C100.160166,52.1728521 104.637318,47.6956996 110.160166,47.6956996 Z" transform="rotate(10 135.16 87.696)"/>
......
...@@ -3,5 +3,7 @@ ...@@ -3,5 +3,7 @@
refs_url: refs_project_path(project, format: :json), refs_url: refs_project_path(project, format: :json),
project_url: project_path(project), project_url: project_path(project),
project_id: project.id, project_id: project.id,
blob_url: namespace_project_blob_path(project.namespace, project, '{{branch}}'),
new_mr_template_url: namespace_project_new_merge_request_path(project.namespace, project, merge_request: { source_branch: '{{source_branch}}' }),
can_commit: (!!can_push_branch?(project, @ref)).to_s, can_commit: (!!can_push_branch?(project, @ref)).to_s,
on_top_of_branch: (!!on_top_of_branch?(project, @ref)).to_s } } on_top_of_branch: (!!on_top_of_branch?(project, @ref)).to_s } }
...@@ -4,6 +4,9 @@ ...@@ -4,6 +4,9 @@
- page_description @user.bio - page_description @user.bio
- header_title @user.name, user_path(@user) - header_title @user.name, user_path(@user)
- @no_container = true - @no_container = true
- content_for :page_specific_javascripts do
= webpack_bundle_tag 'common_d3'
= webpack_bundle_tag 'users'
= content_for :meta_tags do = content_for :meta_tags do
= auto_discovery_link_tag(:atom, user_url(@user, format: :atom), title: "#{@user.name} activity") = auto_discovery_link_tag(:atom, user_url(@user, format: :atom), title: "#{@user.name} activity")
......
...@@ -5,7 +5,6 @@ module Geo ...@@ -5,7 +5,6 @@ module Geo
DB_RETRIEVE_BATCH_SIZE = 1000 DB_RETRIEVE_BATCH_SIZE = 1000
LEASE_TIMEOUT = 60.minutes LEASE_TIMEOUT = 60.minutes
MAX_CAPACITY = 10
RUN_TIME = 60.minutes.to_i RUN_TIME = 60.minutes.to_i
attr_reader :pending_resources, :scheduled_jobs, :start_time, :loops attr_reader :pending_resources, :scheduled_jobs, :start_time, :loops
...@@ -18,7 +17,7 @@ module Geo ...@@ -18,7 +17,7 @@ module Geo
# The scheduling works as the following: # The scheduling works as the following:
# #
# 1. Load a batch of IDs that we need to schedule (DB_RETRIEVE_BATCH_SIZE) into a pending list. # 1. Load a batch of IDs that we need to schedule (DB_RETRIEVE_BATCH_SIZE) into a pending list.
# 2. Schedule them so that at most MAX_CAPACITY are running at once. # 2. Schedule them so that at most `max_capacity` are running at once.
# 3. When a slot frees, schedule another job. # 3. When a slot frees, schedule another job.
# 4. When we have drained the pending list, load another batch into memory, and schedule the # 4. When we have drained the pending list, load another batch into memory, and schedule the
# remaining jobs, excluding ones in progress. # remaining jobs, excluding ones in progress.
...@@ -82,7 +81,7 @@ module Geo ...@@ -82,7 +81,7 @@ module Geo
end end
def max_capacity def max_capacity
MAX_CAPACITY raise NotImplementedError
end end
def run_time def run_time
......
...@@ -2,6 +2,10 @@ module Geo ...@@ -2,6 +2,10 @@ module Geo
class FileDownloadDispatchWorker < Geo::BaseSchedulerWorker class FileDownloadDispatchWorker < Geo::BaseSchedulerWorker
private private
def max_capacity
current_node.files_max_capacity
end
def schedule_job(object_db_id, object_type) def schedule_job(object_db_id, object_type)
job_id = GeoFileDownloadWorker.perform_async(object_type, object_db_id) job_id = GeoFileDownloadWorker.perform_async(object_type, object_db_id)
......
...@@ -11,30 +11,16 @@ module Geo ...@@ -11,30 +11,16 @@ module Geo
end end
def perform(project_id, scheduled_time) def perform(project_id, scheduled_time)
project = Project.find(project_id)
registry = Geo::ProjectRegistry.find_or_initialize_by(project_id: project_id) registry = Geo::ProjectRegistry.find_or_initialize_by(project_id: project_id)
project = registry.project
Geo::RepositorySyncService.new(project).execute if sync_repository?(registry, scheduled_time) if project.nil?
Geo::WikiSyncService.new(project).execute if sync_wiki?(registry, scheduled_time) Gitlab::Geo::Logger.error(class: self.class.name, message: "Couldn't find project, skipping syncing", project_id: project_id)
rescue ActiveRecord::RecordNotFound => e return
Gitlab::Geo::Logger.error( end
class: self.class.name,
message: "Couldn't find project, skipping syncing",
project_id: project_id,
error: e
)
end
private
def sync_repository?(registry, scheduled_time)
!registry.repository_synced_since?(scheduled_time) &&
registry.resync_repository?
end
def sync_wiki?(registry, scheduled_time) Geo::RepositorySyncService.new(project).execute if registry.repository_sync_due?(scheduled_time)
!registry.wiki_synced_since?(scheduled_time) && Geo::WikiSyncService.new(project).execute if registry.wiki_sync_due?(scheduled_time)
registry.resync_wiki?
end end
end end
end end
module Geo module Geo
class RepositorySyncWorker < Geo::BaseSchedulerWorker class RepositorySyncWorker < Geo::BaseSchedulerWorker
MAX_CAPACITY = 25
private private
def max_capacity def max_capacity
MAX_CAPACITY current_node.repos_max_capacity
end end
def schedule_job(project_id) def schedule_job(project_id)
......
...@@ -28,6 +28,7 @@ class ChangelogOptionParser ...@@ -28,6 +28,7 @@ class ChangelogOptionParser
Type.new('deprecated', 'New deprecation'), Type.new('deprecated', 'New deprecation'),
Type.new('removed', 'Feature removal'), Type.new('removed', 'Feature removal'),
Type.new('security', 'Security fix'), Type.new('security', 'Security fix'),
Type.new('performance', 'Performance improvement'),
Type.new('other', 'Other') Type.new('other', 'Other')
].freeze ].freeze
TYPES_OFFSET = 1 TYPES_OFFSET = 1
......
---
title: Make the maximum capacity of Geo backfill operations configurable
merge_request: 3107
author:
type: added
---
title: 'Geo: Don''t sync disabled project wikis'
merge_request: 3109
author:
type: fixed
---
title: Always allow the default branch as a branch name
merge_request: 3154
author:
type: fixed
---
title: Issue JWT token with registry:catalog:* scope when requested by GitLab admin
merge_request: 14751
author: Vratislav Kalenda
type: added
--- ---
title: Decreases z-index of select2 to a lower number of our navigation bar title: Move retry button in job page to sidebar
merge_request: merge_request:
author: author:
type: fixed type: fixed
---
title: Fixed 'Removed source branch' checkbox in merge widget being ignored.
merge_request: 14832
author:
type: fixed
---
title: Fix flash errors showing up on a non configured prometheus integration
merge_request: 35652
author:
type: fixed
---
title: Replace WikiPage::CreateService calls with wiki_page factory in specs
merge_request: 14850
author: Jacopo Beschi @jacopo-beschi
type: changed
---
title: Change background color of nav sidebar to match other gl sidebars
merge_request:
author:
type: changed
---
title: Fixed duplicate notifications when added multiple labels on an issue
merge_request: 14798
author:
type: fixed
---
title: Removed extra border radius from .file-editor and .file-holder when editing
a file
merge_request: 14803
author: Rachel Pipkin
type: fixed
---
title: Don't create build failed todos when the job is automatically retried
merge_request:
author:
type: fixed
---
title: Make usage ping scheduling more robust
merge_request:
author:
type: fixed
---
title: Make "merge ongoing" check more consistent
merge_request:
author:
type: fixed
---
title: Removed d3.js from the graph and users bundles and used the common_d3 bundle
instead
merge_request: 14826
author:
type: other
---
title: 14830 Move GitLab export option to top of import list when creating a new project
merge_request:
author:
type: changed
---
title: Use a timeout on certain git operations
merge_request: 14872
author:
type: security
---
title: Cache issue and MR template names in Redis
merge_request:
author:
type: other
---
title: Fix unnecessary ajax requests in admin broadcast message form
merge_request: 14853
author:
type: fixed
---
title: Fix diff parser so it tolerates to diff special markers in the content
merge_request:
author:
type: fixed
---
title: Allow boards as top level route
merge_request:
author:
type: fixed
---
title: Improve autodevops banner UX and render it only in project page
merge_request:
author:
type: fixed
---
title: Remove unnecessary alt-texts from pipeline emails
merge_request: 14602
author: gernberg
type: fixed
---
title: 'Repo Editor: Add option to start a new MR directly from comit section'
merge_request: 14665
author:
type: added
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.
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