Commit d02ba2fe authored by Nick Thomas's avatar Nick Thomas

Merge remote-tracking branch 'ce/master' into ce-to-ee-2017-07-06

parents 8b0ec861 9274c3c1
......@@ -61,3 +61,4 @@ eslint-report.html
/.gitlab_workhorse_secret
/webpack-report/
/locale/**/LC_MESSAGES
/.rspec
--color
--format Fuubar
......@@ -265,7 +265,7 @@ gem 'base32', '~> 0.3.0'
gem "gitlab-license", "~> 1.0"
# Sentry integration
gem 'sentry-raven', '~> 2.4.0'
gem 'sentry-raven', '~> 2.5.3'
gem 'premailer-rails', '~> 1.9.7'
......@@ -295,6 +295,7 @@ group :metrics do
# Prometheus
gem 'prometheus-client-mmap', '~>0.7.0.beta5'
gem 'raindrops', '~> 0.18'
end
group :development do
......
......@@ -627,8 +627,8 @@ GEM
premailer-rails (1.9.7)
actionmailer (>= 3, < 6)
premailer (~> 1.7, >= 1.7.9)
prometheus-client-mmap (0.7.0.beta5)
mmap2 (~> 2.2.6)
prometheus-client-mmap (0.7.0.beta8)
mmap2 (~> 2.2, >= 2.2.7)
pry (0.10.4)
coderay (~> 1.1.0)
method_source (~> 0.8.1)
......@@ -686,7 +686,7 @@ GEM
thor (>= 0.18.1, < 2.0)
rainbow (2.2.2)
rake
raindrops (0.17.0)
raindrops (0.18.0)
rake (10.5.0)
rblineprof (0.3.6)
debugger-ruby_core_source (~> 1.3)
......@@ -803,7 +803,7 @@ GEM
activesupport (>= 3.1)
select2-rails (3.5.9.3)
thor (~> 0.14)
sentry-raven (2.4.0)
sentry-raven (2.5.3)
faraday (>= 0.7.6, < 1.0)
settingslogic (2.0.9)
sexp_processor (4.9.0)
......@@ -1098,6 +1098,7 @@ DEPENDENCIES
rails-deprecated_sanitizer (~> 1.0.3)
rails-i18n (~> 4.0.9)
rainbow (~> 2.2)
raindrops (~> 0.18)
rblineprof (~> 0.3.6)
rdoc (~> 4.2)
recaptcha (~> 3.0)
......@@ -1125,7 +1126,7 @@ DEPENDENCIES
scss_lint (~> 0.47.0)
seed-fu (~> 2.3.5)
select2-rails (~> 3.5.9)
sentry-raven (~> 2.4.0)
sentry-raven (~> 2.5.3)
settingslogic (~> 2.0.9)
sham_rack (~> 1.3.6)
shoulda-matchers (~> 2.8.0)
......
......@@ -13,25 +13,21 @@ window.Build = (function () {
this.options = options || $('.js-build-options').data();
this.pageUrl = this.options.pageUrl;
this.buildUrl = this.options.buildUrl;
this.buildStatus = this.options.buildStatus;
this.state = this.options.logState;
this.buildStage = this.options.buildStage;
this.$document = $(document);
this.logBytes = 0;
this.scrollOffsetPadding = 30;
this.hasBeenScrolled = false;
this.updateDropdown = this.updateDropdown.bind(this);
this.getBuildTrace = this.getBuildTrace.bind(this);
this.scrollToBottom = this.scrollToBottom.bind(this);
this.$body = $('body');
this.$buildTrace = $('#build-trace');
this.$buildRefreshAnimation = $('.js-build-refresh');
this.$truncatedInfo = $('.js-truncated-info');
this.$buildTraceOutput = $('.js-build-output');
this.$scrollContainer = $('.js-scroll-container');
this.$topBar = $('.js-top-bar');
// Scroll controllers
this.$scrollTopBtn = $('.js-scroll-up');
......@@ -63,13 +59,22 @@ window.Build = (function () {
.off('click')
.on('click', this.scrollToBottom.bind(this));
const scrollThrottled = _.throttle(this.toggleScroll.bind(this), 100);
this.scrollThrottled = _.throttle(this.toggleScroll.bind(this), 100);
this.$scrollContainer
$(window)
.off('scroll')
.on('scroll', () => {
const contentHeight = this.$buildTraceOutput.prop('scrollHeight');
if (contentHeight > this.windowSize) {
// means the user did not scroll, the content was updated.
this.windowSize = contentHeight;
} else {
// User scrolled
this.hasBeenScrolled = true;
scrollThrottled();
this.toggleScrollAnimation(false);
}
this.scrollThrottled();
});
$(window)
......@@ -77,59 +82,73 @@ window.Build = (function () {
.on('resize.build', _.throttle(this.sidebarOnResize.bind(this), 100));
this.updateArtifactRemoveDate();
this.initAffixTopArea();
// eslint-disable-next-line
this.getBuildTrace()
.then(() => this.toggleScroll())
.then(() => {
if (!this.hasBeenScrolled) {
this.scrollToBottom();
}
})
.then(() => this.verifyTopPosition());
this.getBuildTrace();
}
Build.prototype.initAffixTopArea = function () {
/**
If the browser does not support position sticky, it returns the position as static.
If the browser does support sticky, then we allow the browser to handle it, if not
then we default back to Bootstraps affix
**/
if (this.$topBar.css('position') !== 'static') return;
const offsetTop = this.$buildTrace.offset().top;
this.$topBar.affix({
offset: {
top: offsetTop,
},
});
};
Build.prototype.canScroll = function () {
return (this.$scrollContainer.prop('scrollHeight') - this.scrollOffsetPadding) > this.$scrollContainer.height();
return document.body.scrollHeight > window.innerHeight;
};
/**
* | | Up | Down |
* |--------------------------|----------|----------|
* | on scroll bottom | active | disabled |
* | on scroll top | disabled | active |
* | no scroll | disabled | disabled |
* | on.('scroll') is on top | disabled | active |
* | on('scroll) is on bottom | active | disabled |
*
*/
Build.prototype.toggleScroll = function () {
const currentPosition = this.$scrollContainer.scrollTop();
const bottomScroll = currentPosition + this.$scrollContainer.innerHeight();
const currentPosition = document.body.scrollTop;
const windowHeight = window.innerHeight;
if (this.canScroll()) {
if (currentPosition === 0) {
if (currentPosition > 0 &&
(document.body.scrollHeight - currentPosition !== windowHeight)) {
// User is in the middle of the log
this.toggleDisableButton(this.$scrollTopBtn, false);
this.toggleDisableButton(this.$scrollBottomBtn, false);
} else if (currentPosition === 0) {
// User is at Top of Build Log
this.toggleDisableButton(this.$scrollTopBtn, true);
this.toggleDisableButton(this.$scrollBottomBtn, false);
} else if (bottomScroll === this.$scrollContainer.prop('scrollHeight')) {
} else if (document.body.scrollHeight - currentPosition === windowHeight) {
// User is at the bottom of the build log.
this.toggleDisableButton(this.$scrollTopBtn, false);
this.toggleDisableButton(this.$scrollBottomBtn, true);
} else {
this.toggleDisableButton(this.$scrollTopBtn, false);
this.toggleDisableButton(this.$scrollBottomBtn, false);
}
} else {
this.toggleDisableButton(this.$scrollTopBtn, true);
this.toggleDisableButton(this.$scrollBottomBtn, true);
}
};
Build.prototype.scrollToTop = function () {
Build.prototype.scrollDown = function () {
document.body.scrollTop = document.body.scrollHeight;
};
Build.prototype.scrollToBottom = function () {
this.scrollDown();
this.hasBeenScrolled = true;
this.$scrollContainer.scrollTop(0);
this.toggleScroll();
};
Build.prototype.scrollToBottom = function () {
Build.prototype.scrollToTop = function () {
document.body.scrollTop = 0;
this.hasBeenScrolled = true;
this.$scrollContainer.scrollTop(this.$scrollContainer.prop('scrollHeight'));
this.toggleScroll();
};
......@@ -142,47 +161,6 @@ window.Build = (function () {
this.$scrollBottomBtn.toggleClass('animate', toggle);
};
/**
* Build trace top position depends on the space ocupied by the elments rendered before
*/
Build.prototype.verifyTopPosition = function () {
const $buildPage = $('.build-page');
const $flashError = $('.alert-wrapper');
const $header = $('.build-header', $buildPage);
const $runnersStuck = $('.js-build-stuck', $buildPage);
const $startsEnvironment = $('.js-environment-container', $buildPage);
const $erased = $('.js-build-erased', $buildPage);
const prependTopDefault = 20;
// header + navigation + margin
let topPostion = 168;
if ($header.length) {
topPostion += $header.outerHeight();
}
if ($runnersStuck.length) {
topPostion += $runnersStuck.outerHeight();
}
if ($startsEnvironment.length) {
topPostion += $startsEnvironment.outerHeight() + prependTopDefault;
}
if ($erased.length) {
topPostion += $erased.outerHeight() + prependTopDefault;
}
if ($flashError.length) {
topPostion += $flashError.outerHeight() + prependTopDefault;
}
this.$buildTrace.css({
top: topPostion,
});
};
Build.prototype.initSidebar = function () {
this.$sidebar = $('.js-build-sidebar');
this.$sidebar.niceScroll();
......@@ -200,6 +178,8 @@ window.Build = (function () {
this.state = log.state;
}
this.windowSize = this.$buildTraceOutput.prop('scrollHeight');
if (log.append) {
this.$buildTraceOutput.append(log.html);
this.logBytes += log.size;
......@@ -227,14 +207,7 @@ window.Build = (function () {
}
Build.timeout = setTimeout(() => {
//eslint-disable-next-line
this.getBuildTrace()
.then(() => {
if (!this.hasBeenScrolled) {
this.scrollToBottom();
}
})
.then(() => this.verifyTopPosition());
this.getBuildTrace();
}, 4000);
} else {
this.$buildRefreshAnimation.remove();
......@@ -247,7 +220,13 @@ window.Build = (function () {
})
.fail(() => {
this.$buildRefreshAnimation.remove();
});
})
.then(() => {
if (!this.hasBeenScrolled) {
this.scrollDown();
}
})
.then(() => this.toggleScroll());
};
Build.prototype.shouldHideSidebarForViewport = function () {
......@@ -259,14 +238,11 @@ window.Build = (function () {
const shouldShow = typeof shouldHide === 'boolean' ? !shouldHide : undefined;
const $toggleButton = $('.js-sidebar-build-toggle-header');
this.$buildTrace
.toggleClass('sidebar-expanded', shouldShow)
.toggleClass('sidebar-collapsed', shouldHide);
this.$sidebar
.toggleClass('right-sidebar-expanded', shouldShow)
.toggleClass('right-sidebar-collapsed', shouldHide);
$('.js-build-page')
this.$topBar
.toggleClass('sidebar-expanded', shouldShow)
.toggleClass('sidebar-collapsed', shouldHide);
......@@ -279,17 +255,10 @@ window.Build = (function () {
Build.prototype.sidebarOnResize = function () {
this.toggleSidebar(this.shouldHideSidebarForViewport());
this.verifyTopPosition();
if (this.canScroll()) {
this.toggleScroll();
}
};
Build.prototype.sidebarOnClick = function () {
if (this.shouldHideSidebarForViewport()) this.toggleSidebar();
this.verifyTopPosition();
};
Build.prototype.updateArtifactRemoveDate = function () {
......
......@@ -60,10 +60,13 @@ import ShortcutsBlob from './shortcuts_blob';
import initSettingsPanels from './settings_panels';
import initExperimentalFlags from './experimental_flags';
import OAuthRememberMe from './oauth_remember_me';
<<<<<<< HEAD
// EE-only
import ApproversSelect from './approvers_select';
import AuditLogs from './audit_logs';
=======
>>>>>>> ce/master
(function() {
var Dispatcher;
......
......@@ -26,14 +26,6 @@ document.addEventListener('DOMContentLoaded', () => {
mounted() {
this.mediator.initBuildClass();
},
updated() {
// Wait for flash message to be appended
Vue.nextTick(() => {
if (this.mediator.build) {
this.mediator.build.verifyTopPosition();
}
});
},
render(createElement) {
return createElement('job-header', {
props: {
......
......@@ -35,7 +35,7 @@
data() {
return {
graphHeight: 500,
graphHeight: 450,
graphWidth: 600,
graphHeightOffset: 120,
xScale: {},
......@@ -88,7 +88,9 @@
},
paddingBottomRootSvg() {
return (Math.ceil(this.graphHeight * 100) / this.graphWidth) || 0;
return {
paddingBottom: `${(Math.ceil(this.graphHeight * 100) / this.graphWidth) || 0}%`,
};
},
},
......@@ -104,7 +106,7 @@
}
this.data = query.result[0].values;
this.unitOfDisplay = query.unit || 'N/A';
this.yAxisLabel = this.columnData.y_axis || 'Values';
this.yAxisLabel = this.columnData.y_label || 'Values';
this.legendTitle = query.legend || 'Average';
this.graphWidth = this.$refs.baseSvg.clientWidth -
this.margin.left - this.margin.right;
......@@ -157,12 +159,12 @@
const xAxis = d3.svg.axis()
.scale(axisXScale)
.ticks(measurements.ticks)
.ticks(measurements.xTicks)
.orient('bottom');
const yAxis = d3.svg.axis()
.scale(this.yScale)
.ticks(measurements.ticks)
.ticks(measurements.yTicks)
.orient('left');
d3.select(this.$refs.baseSvg).select('.x-axis').call(xAxis);
......@@ -170,8 +172,12 @@
const width = this.graphWidth;
d3.select(this.$refs.baseSvg).select('.y-axis').call(yAxis)
.selectAll('.tick')
.each(function createTickLines() {
d3.select(this).select('line').attr('x2', width);
.each(function createTickLines(d, i) {
if (i > 0) {
d3.select(this).select('line')
.attr('x2', width)
.attr('class', 'axis-tick');
} // Avoid adding the class to the first tick, to prevent coloring
}); // This will select all of the ticks once they're rendered
this.xScale = d3.time.scale()
......@@ -198,7 +204,7 @@
watch: {
updateAspectRatio() {
if (this.updateAspectRatio) {
this.graphHeight = 500;
this.graphHeight = 450;
this.graphWidth = 600;
this.measurements = measurements.large;
this.draw();
......@@ -216,14 +222,14 @@
<div
:class="classType">
<h5
class="text-center">
class="text-center graph-title">
{{columnData.title}}
</h5>
<div
class="prometheus-svg-container">
class="prometheus-svg-container"
:style="paddingBottomRootSvg">
<svg
:viewBox="outterViewBox"
:style="{ 'padding-bottom': paddingBottomRootSvg }"
ref="baseSvg">
<g
class="x-axis"
......
......@@ -87,14 +87,14 @@
</rect>
<text
class="text-metric text-metric-bold"
x="8"
x="16"
y="35"
transform="translate(-5, 20)">
{{formatTime}}
</text>
<text
class="text-metric-date"
x="8"
class="text-metric"
x="16"
y="15"
transform="translate(-5, 20)">
{{formatDate}}
......
......@@ -109,13 +109,13 @@
</text>
<rect
class="rect-axis-text"
:x="xPosition + 50"
:x="xPosition + 60"
:y="graphHeight - 80"
width="50"
width="35"
height="50">
</rect>
<text
class="label-axis-text"
class="label-axis-text x-label-text"
:x="xPosition + 60"
:y="yPosition"
dy=".35em">
......@@ -131,13 +131,13 @@
<text
class="text-metric-title"
x="50"
:y="graphHeight - 40">
:y="graphHeight - 25">
{{legendTitle}}
</text>
<text
class="text-metric-usage"
x="50"
:y="graphHeight - 25">
:y="graphHeight - 10">
{{metricUsage}}
</text>
</g>
......
......@@ -8,14 +8,14 @@ export default {
},
legends: {
width: 15,
height: 30,
height: 25,
},
backgroundLegend: {
width: 30,
height: 50,
},
axisLabelLineOffset: -20,
legendOffset: 52,
legendOffset: 35,
},
large: { // This covers both md and lg screen sizes
margin: {
......@@ -26,14 +26,15 @@ export default {
},
legends: {
width: 20,
height: 35,
height: 30,
},
backgroundLegend: {
width: 30,
height: 150,
},
axisLabelLineOffset: 20,
legendOffset: 55,
legendOffset: 38,
},
ticks: 3,
xTicks: 8,
yTicks: 3,
};
......@@ -2,12 +2,11 @@
/* eslint no-new: "off" */
import AccessorUtilities from './lib/utils/accessor';
((global) => {
/**
/**
* Memorize the last selected tab after reloading a page.
* Does that setting the current selected tab in the localStorage
*/
class ActiveTabMemoizer {
class ActiveTabMemoizer {
constructor({ currentTabKey = 'current_signin_tab', tabSelector = 'ul.nav-tabs' } = {}) {
this.currentTabKey = currentTabKey;
this.tabSelector = tabSelector;
......@@ -51,7 +50,6 @@ import AccessorUtilities from './lib/utils/accessor';
return window.localStorage.getItem(this.currentTabKey);
}
}
}
global.ActiveTabMemoizer = ActiveTabMemoizer;
})(window);
window.ActiveTabMemoizer = ActiveTabMemoizer;
......@@ -2,8 +2,7 @@
import FilesCommentButton from './files_comment_button';
(function() {
window.SingleFileDiff = (function() {
window.SingleFileDiff = (function() {
var COLLAPSED_HTML, ERROR_HTML, LOADING_HTML, WRAPPER;
WRAPPER = '<div class="diff-content"></div>';
......@@ -88,13 +87,12 @@ import FilesCommentButton from './files_comment_button';
};
return SingleFileDiff;
})();
})();
$.fn.singleFileDiff = function() {
$.fn.singleFileDiff = function() {
return this.each(function() {
if (!$.data(this, 'singleFileDiff')) {
return $.data(this, 'singleFileDiff', new window.SingleFileDiff(this));
}
});
};
}).call(window);
};
/*
* Instances of SmartInterval extend the functionality of `setInterval`, make it configurable
* and controllable by a public API.
*
* */
(() => {
class SmartInterval {
/**
* Instances of SmartInterval extend the functionality of `setInterval`, make it configurable
* and controllable by a public API.
*/
class SmartInterval {
/**
* @param { function } opts.callback Function to be called on each iteration (required)
* @param { milliseconds } opts.startingInterval `currentInterval` is set to this initially
......@@ -37,6 +35,7 @@
this.initInterval();
}
/* public */
start() {
......@@ -153,6 +152,6 @@
state.intervalId = window.clearInterval(state.intervalId);
}
}
gl.SmartInterval = SmartInterval;
})(window.gl || (window.gl = {}));
}
window.gl.SmartInterval = SmartInterval;
/* eslint-disable arrow-parens, no-param-reassign, space-before-function-paren, func-names, no-var, max-len */
(global => {
global.gl = global.gl || {};
gl.SnippetsList = function() {
window.gl.SnippetsList = function() {
var $holder = $('.snippets-list-holder');
$holder.find('.pagination').on('ajax:success', (e, data) => {
$holder.replaceWith(data.html);
});
};
})(window);
};
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-unused-vars, one-var, no-var, one-var-declaration-per-line, prefer-arrow-callback, no-new, max-len */
/* global Flash */
(function() {
this.Star = (function() {
window.Star = (function() {
function Star() {
$('.project-home-panel .toggle-star').on('ajax:success', function(e, data, status, xhr) {
var $starIcon, $starSpan, $this, toggleStar;
......@@ -26,5 +25,4 @@
}
return Star;
})();
}).call(window);
})();
(() => {
class Subscription {
class Subscription {
constructor(containerElm) {
this.containerElm = containerElm;
......@@ -40,8 +39,7 @@
static bindAll(selector) {
[].forEach.call(document.querySelectorAll(selector), elm => new Subscription(elm));
}
}
}
window.gl = window.gl || {};
window.gl.Subscription = Subscription;
})();
window.gl = window.gl || {};
window.gl.Subscription = Subscription;
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, quotes, object-shorthand, no-unused-vars, no-shadow, one-var, one-var-declaration-per-line, comma-dangle, max-len */
(function() {
this.SubscriptionSelect = (function() {
window.SubscriptionSelect = (function() {
function SubscriptionSelect() {
$('.js-subscription-event').each(function(i, el) {
var fieldName;
......@@ -30,5 +30,4 @@
}
return SubscriptionSelect;
})();
}).call(window);
})();
......@@ -9,8 +9,8 @@
//
// <div class="js-syntax-highlight"></div>
//
(function() {
$.fn.syntaxHighlight = function() {
$.fn.syntaxHighlight = function() {
var $children;
if ($(this).hasClass('js-syntax-highlight')) {
......@@ -23,5 +23,4 @@
return $children.syntaxHighlight();
}
}
};
}).call(window);
};
/* eslint-disable func-names, space-before-function-paren, wrap-iife, max-len, quotes, consistent-return, no-var, one-var, one-var-declaration-per-line, no-else-return, prefer-arrow-callback, max-len */
(function() {
this.TreeView = (function() {
window.TreeView = (function() {
function TreeView() {
this.initKeyNav();
// Code browser tree slider
......@@ -64,5 +63,4 @@
};
return TreeView;
})();
}).call(window);
})();
......@@ -2,8 +2,7 @@
import Cookies from 'js-cookie';
((global) => {
global.User = class {
class User {
constructor({ action }) {
this.action = action;
this.placeProfileAvatarsToTop();
......@@ -18,7 +17,7 @@ import Cookies from 'js-cookie';
}
initTabs() {
return new global.UserTabs({
return new window.gl.UserTabs({
parentEl: '.user-profile',
action: this.action
});
......@@ -31,5 +30,7 @@ import Cookies from 'js-cookie';
$(this).parents('.project-limit-message').remove();
});
}
};
})(window.gl || (window.gl = {}));
}
window.gl = window.gl || {};
window.gl.User = User;
......@@ -59,8 +59,8 @@ content on the Users#show page.
</div>
</div>
*/
((global) => {
class UserTabs {
class UserTabs {
constructor ({ defaultAction, action, parentEl }) {
this.loaded = {};
this.defaultAction = defaultAction || 'activity';
......@@ -170,6 +170,7 @@ content on the Users#show page.
getCurrentAction() {
return this.$parentEl.find('.nav-links .active a').data('action');
}
}
global.UserTabs = UserTabs;
})(window.gl || (window.gl = {}));
}
window.gl = window.gl || {};
window.gl.UserTabs = UserTabs;
/* eslint-disable comma-dangle, consistent-return, class-methods-use-this, arrow-parens, no-param-reassign, max-len */
((global) => {
const debounceTimeoutDuration = 1000;
const invalidInputClass = 'gl-field-error-outline';
const successInputClass = 'gl-field-success-outline';
const unavailableMessageSelector = '.username .validation-error';
const successMessageSelector = '.username .validation-success';
const pendingMessageSelector = '.username .validation-pending';
const invalidMessageSelector = '.username .gl-field-error';
class UsernameValidator {
const debounceTimeoutDuration = 1000;
const invalidInputClass = 'gl-field-error-outline';
const successInputClass = 'gl-field-success-outline';
const unavailableMessageSelector = '.username .validation-error';
const successMessageSelector = '.username .validation-success';
const pendingMessageSelector = '.username .validation-pending';
const invalidMessageSelector = '.username .gl-field-error';
class UsernameValidator {
constructor() {
this.inputElement = $('#new_user_username');
this.inputDomElement = this.inputElement.get(0);
......@@ -129,7 +128,6 @@
this.inputElement.addClass(invalidInputClass).removeClass(successInputClass);
$inputErrorMessage.show();
}
}
}
global.UsernameValidator = UsernameValidator;
})(window);
window.UsernameValidator = UsernameValidator;
......@@ -206,8 +206,6 @@ function UsersSelect(currentUser, els) {
return $dropdown.glDropdown({
showMenuAbove: showMenuAbove,
data: function(term, callback) {
var isAuthorFilter;
isAuthorFilter = $('.js-author-search');
return _this.users(term, options, function(users) {
// GitLabDropdownFilter returns this.instance
// GitLabDropdownRemote returns this.options.instance
......
(() => {
const gl = window.gl || (window.gl = {});
class VisibilitySelect {
class VisibilitySelect {
constructor(container) {
if (!container) throw new Error('VisibilitySelect requires a container element as argument 1');
this.container = container;
......@@ -21,7 +18,7 @@
updateHelpText() {
this.helpBlock.textContent = this.select.querySelector('option:checked').dataset.description;
}
}
}
gl.VisibilitySelect = VisibilitySelect;
})();
window.gl = window.gl || {};
window.gl.VisibilitySelect = VisibilitySelect;
......@@ -19,9 +19,6 @@ export default {
return hasCI && !ciStatus;
},
hasPipeline() {
return Object.keys(this.mr.pipeline || {}).length > 0;
},
svg() {
return statusIconEntityMap.icon_status_failed;
},
......@@ -48,11 +45,7 @@ export default {
template: `
<div class="mr-widget-heading">
<div class="ci-widget">
<template v-if="!hasPipeline">
<i class="fa fa-spinner fa-spin append-right-10" aria-hidden="true"></i>
Waiting for pipeline...
</template>
<template v-else-if="hasCIError">
<template v-if="hasCIError">
<div class="ci-status-icon ci-status-icon-failed ci-error js-ci-error">
<span class="js-icon-link icon-link">
<span
......
......@@ -4,8 +4,7 @@
import 'vendor/jquery.nicescroll';
import './breakpoints';
((global) => {
class Wikis {
class Wikis {
constructor() {
this.bp = Breakpoints.get();
this.sidebarEl = document.querySelector('.js-wiki-sidebar');
......@@ -63,7 +62,7 @@ import './breakpoints';
classList.remove('right-sidebar-expanded');
}
}
}
}
global.Wikis = Wikis;
})(window.gl || (window.gl = {}));
window.gl = window.gl || {};
window.gl.Wikis = Wikis;
......@@ -34,8 +34,8 @@ window.Dropzone = Dropzone;
// **Cancelable** No
// **Target** a.js-zen-leave
//
(function() {
this.ZenMode = (function() {
window.ZenMode = (function() {
function ZenMode() {
this.active_backdrop = null;
this.active_textarea = null;
......@@ -94,5 +94,4 @@ window.Dropzone = Dropzone;
};
return ZenMode;
})();
}).call(window);
})();
......@@ -92,7 +92,7 @@
@mixin maintain-sidebar-dimensions {
display: block;
width: $gutter-width;
padding: 10px 20px;
padding: 10px 0;
}
.issues-bulk-update.right-sidebar {
......
......@@ -37,43 +37,56 @@
}
.build-page {
.sticky {
position: absolute;
left: 0;
right: 0;
.build-trace-container {
position: relative;
}
.build-trace-container {
position: absolute;
top: 225px;
left: 15px;
bottom: 10px;
.build-trace {
background: $black;
color: $gray-darkest;
font-family: $monospace_font;
white-space: pre;
overflow-x: auto;
font-size: 12px;
border-radius: 0;
border: none;
&.sidebar-expanded {
right: 305px;
}
&.sidebar-collapsed {
right: 16px;
.bash {
display: block;
}
code {
background: $black;
color: $gray-darkest;
}
.top-bar {
top: 0;
height: 35px;
display: flex;
justify-content: flex-end;
background: $gray-light;
border: 1px solid $border-color;
color: $gl-text-color;
position: sticky;
position: -webkit-sticky;
top: 50px;
&.affix {
top: 50px;
}
// with sidebar
&.affix.sidebar-expanded {
right: 306px;
left: 16px;
}
// without sidebar
&.affix.sidebar-collapsed {
right: 16px;
left: 16px;
}
&.affix-top {
position: absolute;
right: 0;
left: 0;
}
.truncated-info {
margin: 0 auto;
......@@ -89,13 +102,12 @@
text-decoration: underline;
}
}
}
.controllers {
display: flex;
align-self: center;
font-size: 15px;
margin-bottom: 4px;
justify-content: center;
align-items: center;
svg {
height: 15px;
......@@ -103,17 +115,9 @@
fill: $gl-text-color;
}
.controllers-buttons,
.btn-scroll {
color: $gl-text-color;
height: 15px;
vertical-align: middle;
padding: 0;
width: 12px;
}
.controllers-buttons {
margin: 1px 10px;
color: $gl-text-color;
margin: 0 10px;
}
.btn-scroll.animate {
......@@ -143,15 +147,6 @@
}
}
.bash {
top: 35px;
left: 10px;
bottom: 0;
padding: 10px 20px 20px 5px;
white-space: pre-wrap;
overflow: auto;
}
.environment-information {
border: 1px solid $border-color;
padding: 8px $gl-padding 12px;
......
......@@ -337,8 +337,7 @@
}
.text-metric {
font-weight: 600;
font-size: 14px;
font-size: 12px;
}
.selected-metric-line {
......@@ -382,10 +381,6 @@
width: 100%;
padding: 0;
padding-bottom: 100%;
.text-metric-bold {
font-weight: 600;
}
}
.prometheus-svg-container > svg {
......@@ -400,11 +395,15 @@
stroke-width: 0;
}
.text-metric-bold {
font-weight: 600;
}
.label-axis-text,
.text-metric-usage {
fill: $black;
font-weight: 500;
font-size: 14px;
font-size: 12px;
}
.legend-axis-text {
......@@ -412,7 +411,20 @@
}
.tick > text {
font-size: 14px;
font-size: 12px;
}
.text-metric-title {
font-size: 12px;
}
.y-label-text,
.x-label-text {
fill: $gray-darkest;
}
.axis-tick {
stroke: $gray-darker;
}
@media (max-width: $screen-sm-max) {
......@@ -427,3 +439,9 @@
}
}
}
.prometheus-row {
h5 {
font-size: 16px;
}
}
......@@ -200,7 +200,6 @@
right: 0;
transition: width .3s;
background: $gray-light;
padding: 0 20px;
z-index: 200;
overflow: hidden;
......@@ -224,6 +223,10 @@
}
}
.issuable-sidebar {
padding: 0 20px;
}
.issuable-sidebar-header {
padding-top: 10px;
}
......
......@@ -72,6 +72,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
params[:application_setting][:disabled_oauth_sign_in_sources] =
AuthHelper.button_based_providers.map(&:to_s) -
Array(enabled_oauth_sign_in_sources)
params[:application_setting][:restricted_visibility_levels]&.delete("")
params.delete(:domain_blacklist_raw) if params[:domain_blacklist_file]
params.require(:application_setting).permit(
......
......@@ -110,6 +110,8 @@ class ApplicationController < ActionController::Base
end
def log_exception(exception)
Raven.capture_exception(exception) if sentry_enabled?
application_trace = ActionDispatch::ExceptionWrapper.new(env, exception).application_trace
application_trace.map!{ |t| " #{t}\n" }
logger.error "\n#{exception.class.name} (#{exception.message}):\n#{application_trace.join}"
......
......@@ -16,6 +16,8 @@ class Projects::EnvironmentsController < Projects::ApplicationController
respond_to do |format|
format.html
format.json do
Gitlab::PollingInterval.set_header(response, interval: 3_000)
render json: {
environments: EnvironmentSerializer
.new(project: @project, current_user: @current_user)
......
......@@ -230,7 +230,7 @@ class Projects::IssuesController < Projects::ApplicationController
def issue
return @issue if defined?(@issue)
# The Sortable default scope causes performance issues when used with find_by
@noteable = @issue ||= @project.issues.where(iid: params[:id]).reorder(nil).take!
@noteable = @issue ||= @project.issues.find_by!(iid: params[:id])
return render_404 unless can?(current_user, :read_issue, @issue)
......
......@@ -23,7 +23,7 @@ class Projects::PipelinesSettingsController < Projects::ApplicationController
def update_params
params.require(:project).permit(
:runners_token, :builds_enabled, :build_allow_git_fetch, :build_timeout_in_minutes, :build_coverage_regex,
:public_builds, :auto_cancel_pending_pipelines
:public_builds, :auto_cancel_pending_pipelines, :ci_config_path
)
end
end
......@@ -91,8 +91,12 @@ class ProjectsController < Projects::ApplicationController
end
def show
<<<<<<< HEAD
# If we're importing while we do have a repository, we're simply updating the mirror.
if @project.import_in_progress? && !@project.updating_mirror?
=======
if @project.import_in_progress?
>>>>>>> ce/master
redirect_to project_import_path(@project)
return
end
......
......@@ -83,7 +83,12 @@ class LabelsFinder < UnionFinder
def projects
return @projects if defined?(@projects)
@projects = skip_authorization ? Project.all : ProjectsFinder.new(current_user: current_user).execute
@projects = if skip_authorization
Project.all
else
ProjectsFinder.new(params: { non_archived: true }, current_user: current_user).execute
end
@projects = @projects.in_namespace(params[:group_id]) if group?
@projects = @projects.where(id: params[:project_ids]) if projects?
@projects = @projects.reorder(nil)
......
......@@ -28,7 +28,14 @@ class ProjectsFinder < UnionFinder
end
def execute
collection = init_collection
user = params.delete(:user)
collection =
if user
PersonalProjectsFinder.new(user).execute(current_user)
else
init_collection
end
collection = by_ids(collection)
collection = by_personal(collection)
collection = by_starred(collection)
......
......@@ -302,10 +302,6 @@ module ApplicationHelper
end
end
def can_toggle_new_nav?
Rails.env.development?
end
def show_new_nav?
cookies["new_nav"] == "true"
end
......
......@@ -34,17 +34,17 @@ module ApplicationSettingsHelper
# Return a group of checkboxes that use Bootstrap's button plugin for a
# toggle button effect.
def restricted_level_checkboxes(help_block_id)
def restricted_level_checkboxes(help_block_id, checkbox_name)
Gitlab::VisibilityLevel.options.map do |name, level|
checked = restricted_visibility_levels(true).include?(level)
css_class = checked ? 'active' : ''
checkbox_name = "application_setting[restricted_visibility_levels][]"
tag_name = "application_setting_visibility_level_#{level}"
label_tag(name, class: css_class) do
label_tag(tag_name, class: css_class) do
check_box_tag(checkbox_name, level, checked,
autocomplete: 'off',
'aria-describedby' => help_block_id,
id: name) + visibility_level_icon(level) + name
id: tag_name) + visibility_level_icon(level) + name
end
end
end
......
......@@ -16,8 +16,8 @@ module FormHelper
end
end
def issue_dropdown_options(issuable, has_multiple_assignees = true)
options = {
def issue_assignees_dropdown_options
{
toggle_class: 'js-user-search js-assignee-search js-multiselect js-save-user-data',
title: 'Select assignee',
filter: true,
......@@ -27,8 +27,8 @@ module FormHelper
first_user: current_user&.username,
null_user: true,
current_user: true,
project_id: issuable.project.try(:id),
field_name: "#{issuable.class.model_name.param_key}[assignee_ids][]",
project_id: @project.id,
field_name: 'issue[assignee_ids][]',
default_label: 'Unassigned',
'max-select': 1,
'dropdown-header': 'Assignee',
......@@ -38,13 +38,5 @@ module FormHelper
current_user_info: current_user.to_json(only: [:id, :name])
}
}
if has_multiple_assignees
options[:title] = 'Select assignee(s)'
options[:data][:'dropdown-header'] = 'Assignee(s)'
options[:data].delete(:'max-select')
end
options
end
end
......@@ -64,6 +64,11 @@ module GroupsHelper
IssuesFinder.new(current_user, group_id: group.id).execute
end
def remove_group_message(group)
_("You are going to remove %{group_name}.\nRemoved groups CANNOT be restored!\nAre you ABSOLUTELY sure?") %
{ group_name: group.name }
end
private
def group_title_link(group, hidable: false)
......
......@@ -134,6 +134,18 @@ module SearchHelper
search_path(options)
end
def search_filter_input_options(type)
{
id: "filtered-search-#{type}",
placeholder: 'Search or filter results...',
data: {
'project-id' => @project.id,
'username-params' => @users.to_json(only: [:id, :username]),
'base-endpoint' => project_path(@project)
}
}
end
# Sanitize a HTML field for search display. Most tags are stripped out and the
# maximum length is set to 200 characters.
def search_md_sanitize(object, field)
......
......@@ -180,9 +180,12 @@ module Ci
# * Lowercased
# * Anything not matching [a-z0-9-] is replaced with a -
# * Maximum length is 63 bytes
# * First/Last Character is not a hyphen
def ref_slug
slugified = ref.to_s.downcase
slugified.gsub(/[^a-z0-9]/, '-')[0..62]
ref.to_s
.downcase
.gsub(/[^a-z0-9]/, '-')[0..62]
.gsub(/(\A-+|-+\z)/, '')
end
# Variables whose value does not depend on environment
......
......@@ -337,10 +337,24 @@ module Ci
end
end
def ci_yaml_file_path
if project.ci_config_path.blank?
'.gitlab-ci.yml'
else
project.ci_config_path
end
end
def ci_yaml_file
return @ci_yaml_file if defined?(@ci_yaml_file)
@ci_yaml_file = project.repository.gitlab_ci_yml_for(sha) rescue nil
@ci_yaml_file = begin
project.repository.gitlab_ci_yml_for(sha, ci_yaml_file_path)
rescue Rugged::ReferenceError, GRPC::NotFound, GRPC::Internal
self.yaml_errors =
"Failed to load CI/CD config file at #{ci_yaml_file_path}"
nil
end
end
def has_yaml_errors?
......@@ -389,7 +403,11 @@ module Ci
def predefined_variables
[
{ key: 'CI_PIPELINE_ID', value: id.to_s, public: true },
<<<<<<< HEAD
{ key: 'CI_PIPELINE_SOURCE', value: source.to_s, public: true }
=======
{ key: 'CI_CONFIG_PATH', value: ci_yaml_file_path, public: true }
>>>>>>> ce/master
]
end
......
......@@ -78,7 +78,7 @@ module CacheMarkdownField
def cached_html_up_to_date?(markdown_field)
html_field = cached_markdown_fields.html_field(markdown_field)
cached = !cached_html_for(markdown_field).nil? && !__send__(markdown_field).nil?
cached = cached_html_for(markdown_field).present? && __send__(markdown_field).present?
return false unless cached
markdown_changed = attribute_changed?(markdown_field) || false
......
......@@ -105,6 +105,14 @@ module Issuable
def locking_enabled?
title_changed? || description_changed?
end
def allows_multiple_assignees?
false
end
def has_multiple_assignees?
assignees.count > 1
end
end
module ClassMethods
......
......@@ -5,6 +5,25 @@
module Sortable
extend ActiveSupport::Concern
module DropDefaultScopeOnFinders
# Override these methods to drop the `ORDER BY id DESC` default scope.
# See http://dba.stackexchange.com/a/110919 for why we do this.
%i[find find_by find_by!].each do |meth|
define_method meth do |*args, &block|
return super(*args, &block) if block
unordered_relation = unscope(:order)
# We cannot simply call `meth` on `unscope(:order)`, since that is also
# an instance of the same relation class this module is included into,
# which means we'd get infinite recursion.
# We explicitly use the original implementation to prevent this.
original_impl = method(__method__).super_method.unbind
original_impl.bind(unordered_relation).call(*args)
end
end
end
included do
# By default all models should be ordered
# by created_at field starting from newest
......@@ -18,6 +37,10 @@ module Sortable
scope :order_updated_asc, -> { reorder(updated_at: :asc) }
scope :order_name_asc, -> { reorder(name: :asc) }
scope :order_name_desc, -> { reorder(name: :desc) }
# All queries (relations) on this model are instances of this `relation_klass`.
relation_klass = relation_delegate_class(ActiveRecord::Relation)
relation_klass.prepend DropDefaultScopeOnFinders
end
module ClassMethods
......
......@@ -32,11 +32,19 @@ class Issue < ActiveRecord::Base
belongs_to :moved_to, class_name: 'Issue'
has_many :events, as: :target, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
<<<<<<< HEAD
has_many :merge_requests_closing_issues,
class_name: 'MergeRequestsClosingIssues',
dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
=======
has_many :merge_requests_closing_issues,
class_name: 'MergeRequestsClosingIssues',
dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
>>>>>>> ce/master
has_many :issue_assignees
has_many :assignees, class_name: "User", through: :issue_assignees
......
......@@ -15,9 +15,12 @@ class MergeRequest < ActiveRecord::Base
belongs_to :source_project, class_name: "Project"
belongs_to :merge_user, class_name: "User"
<<<<<<< HEAD
has_many :approvals, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
has_many :approvers, as: :target, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
has_many :approver_groups, as: :target, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
=======
>>>>>>> ce/master
has_many :merge_request_diffs
has_one :merge_request_diff,
-> { order('merge_request_diffs.id DESC') }
......@@ -209,11 +212,19 @@ class MergeRequest < ActiveRecord::Base
}
end
# This method is needed for compatibility with issues to not mess view and other code
# These method are needed for compatibility with issues to not mess view and other code
def assignees
Array(assignee)
end
def assignee_ids
Array(assignee_id)
end
def assignee_ids=(ids)
write_attribute(:assignee_id, ids.last)
end
def assignee_or_author?(user)
author_id == user.id || assignee_id == user.id
end
......
......@@ -186,8 +186,11 @@ class Project < ActiveRecord::Base
has_many :environments
has_many :deployments
has_many :pipeline_schedules, class_name: 'Ci::PipelineSchedule'
<<<<<<< HEAD
has_many :sourced_pipelines, class_name: Ci::Sources::Pipeline, foreign_key: :source_project_id
=======
>>>>>>> ce/master
has_many :source_pipelines, class_name: Ci::Sources::Pipeline, foreign_key: :project_id
has_many :active_runners, -> { active }, through: :runner_projects, source: :runner, class_name: 'Ci::Runner'
......@@ -205,6 +208,11 @@ class Project < ActiveRecord::Base
# Validations
validates :creator, presence: true, on: :create
validates :description, length: { maximum: 2000 }, allow_blank: true
validates :ci_config_path,
format: { without: /\.{2}/,
message: 'cannot include directory traversal.' },
length: { maximum: 255 },
allow_blank: true
validates :name,
presence: true,
length: { maximum: 255 },
......@@ -535,6 +543,11 @@ class Project < ActiveRecord::Base
import_data&.destroy
end
def ci_config_path=(value)
# Strip all leading slashes so that //foo -> foo
super(value&.sub(%r{\A/+}, '')&.delete("\0"))
end
def import_url=(value)
return super(value) unless Gitlab::UrlSanitizer.valid?(value)
......@@ -829,7 +842,7 @@ class Project < ActiveRecord::Base
end
def ci_service
@ci_service ||= ci_services.reorder(nil).find_by(active: true)
@ci_service ||= ci_services.find_by(active: true)
end
def deployment_services
......@@ -837,7 +850,7 @@ class Project < ActiveRecord::Base
end
def deployment_service
@deployment_service ||= deployment_services.reorder(nil).find_by(active: true)
@deployment_service ||= deployment_services.find_by(active: true)
end
def monitoring_services
......@@ -845,7 +858,7 @@ class Project < ActiveRecord::Base
end
def monitoring_service
@monitoring_service ||= monitoring_services.reorder(nil).find_by(active: true)
@monitoring_service ||= monitoring_services.find_by(active: true)
end
def jira_tracker?
......@@ -1029,7 +1042,8 @@ class Project < ActiveRecord::Base
namespace: namespace.name,
visibility_level: visibility_level,
path_with_namespace: path_with_namespace,
default_branch: default_branch
default_branch: default_branch,
ci_config_path: ci_config_path
}
# Backward compatibility
......
......@@ -98,10 +98,13 @@ class KubernetesService < DeploymentService
end
def predefined_variables
config = YAML.dump(kubeconfig)
variables = [
{ key: 'KUBE_URL', value: api_url, public: true },
{ key: 'KUBE_TOKEN', value: token, public: false },
{ key: 'KUBE_NAMESPACE', value: actual_namespace, public: true }
{ key: 'KUBE_NAMESPACE', value: actual_namespace, public: true },
{ key: 'KUBECONFIG', value: config, public: false, file: true }
]
if ca_pem.present?
......@@ -137,6 +140,14 @@ class KubernetesService < DeploymentService
private
def kubeconfig
to_kubeconfig(
url: api_url,
namespace: actual_namespace,
token: token,
ca_pem: ca_pem)
end
def namespace_placeholder
default_namespace || TEMPLATE_PLACEHOLDER
end
......
......@@ -1158,8 +1158,8 @@ class Repository
blob_data_at(sha, '.gitlab/route-map.yml')
end
def gitlab_ci_yml_for(sha)
blob_data_at(sha, '.gitlab-ci.yml')
def gitlab_ci_yml_for(sha, path = '.gitlab-ci.yml')
blob_data_at(sha, path)
end
private
......
......@@ -51,6 +51,14 @@ class Service < ActiveRecord::Base
active
end
def show_active_box?
true
end
def editable?
true
end
def template?
template
end
......
......@@ -38,9 +38,7 @@ class Snippet < ActiveRecord::Base
validates :author, presence: true
validates :title, presence: true, length: { maximum: 255 }
validates :file_name,
length: { maximum: 255 },
format: { with: Gitlab::Regex.file_name_regex,
message: Gitlab::Regex.file_name_regex_message }
length: { maximum: 255 }
validates :content, presence: true
validates :visibility_level, inclusion: { in: Gitlab::VisibilityLevel.values }
......
......@@ -119,6 +119,7 @@ class User < ActiveRecord::Base
has_many :todos, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :notification_settings, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :award_emoji, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
<<<<<<< HEAD
has_many :path_locks, dependent: :destroy # rubocop: disable Cop/ActiveRecordDependent
has_many :approvals, dependent: :destroy # rubocop: disable Cop/ActiveRecordDependent
......@@ -127,6 +128,8 @@ class User < ActiveRecord::Base
# Protected Branch Access
has_many :protected_branch_merge_access_levels, dependent: :destroy, class_name: ProtectedBranch::MergeAccessLevel # rubocop:disable Cop/ActiveRecordDependent
has_many :protected_branch_push_access_levels, dependent: :destroy, class_name: ProtectedBranch::PushAccessLevel # rubocop:disable Cop/ActiveRecordDependent
=======
>>>>>>> ce/master
has_many :triggers, dependent: :destroy, class_name: 'Ci::Trigger', foreign_key: :owner_id # rubocop:disable Cop/ActiveRecordDependent
has_many :issue_assignees
......
......@@ -17,6 +17,7 @@ class BasePolicy < DeclarativePolicy::Base
condition(:restricted_public_level, scope: :global) do
current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC)
end
<<<<<<< HEAD
# EE Extensions
with_scope :user
......@@ -27,4 +28,6 @@ class BasePolicy < DeclarativePolicy::Base
with_scope :global
condition(:license_block) { License.block_changes? }
=======
>>>>>>> ce/master
end
......@@ -73,12 +73,15 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
def conflict_resolution_path
if conflicts.can_be_resolved_in_ui? && conflicts.can_be_resolved_by?(current_user)
conflicts_project_merge_request_path(project, merge_request)
<<<<<<< HEAD
end
end
def rebase_path
if !rebase_in_progress? && should_be_rebased? && user_can_push_to_source_branch?
rebase_project_merge_request_path(project, merge_request)
=======
>>>>>>> ce/master
end
end
......@@ -91,12 +94,15 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
def source_branch_path
if source_branch_exists?
project_branch_path(source_project, source_branch)
<<<<<<< HEAD
end
end
def approvals_path
if requires_approve?
approvals_project_merge_request_path(project, merge_request)
=======
>>>>>>> ce/master
end
end
......
......@@ -24,11 +24,14 @@ class EnvironmentEntity < Grape::Entity
expose :terminal_path, if: ->(environment, _) { environment.deployment_service_ready? } do |environment|
can?(request.current_user, :admin_environment, environment.project) &&
terminal_project_environment_path(environment.project, environment)
<<<<<<< HEAD
end
expose :rollout_status_path, if: ->(environment, _) { environment.deployment_service_ready? } do |environment|
can?(request.current_user, :read_deploy_board, environment.project) &&
status_project_environment_path(environment.project, environment, format: :json)
=======
>>>>>>> ce/master
end
expose :created_at, :updated_at
......
......@@ -180,6 +180,7 @@ class MergeRequestEntity < IssuableEntity
expose :commit_change_content_path do |merge_request|
commit_change_content_project_merge_request_path(merge_request.project, merge_request)
<<<<<<< HEAD
end
# EE-specific
......@@ -195,6 +196,8 @@ class MergeRequestEntity < IssuableEntity
merge_request.base_codeclimate_artifact,
path: 'codeclimate.json')
end
=======
>>>>>>> ce/master
end
private
......
......@@ -37,7 +37,7 @@ module Ci
unless pipeline.config_processor
unless pipeline.ci_yaml_file
return error('Missing .gitlab-ci.yml file')
return error("Missing #{pipeline.ci_yaml_file_path} file")
end
return error(pipeline.yaml_errors, save: save_on_errors)
end
......
......@@ -10,6 +10,8 @@ class DeleteMergedBranchesService < BaseService
branches = branches.select { |branch| project.repository.merged_to_root_ref?(branch) }
# Prevent deletion of branches relevant to open merge requests
branches -= merge_request_branch_names
# Prevent deletion of protected branches
branches -= project.protected_branches.pluck(:name)
branches.each do |branch|
DeleteBranchService.new(project, current_user).execute(branch)
......
......@@ -90,10 +90,14 @@ module MergeRequests
MergeRequests::PostMergeService.new(project, current_user).execute(merge_request)
if params[:should_remove_source_branch].present? || @merge_request.force_remove_source_branch?
# Verify again that the source branch can be removed, since branch may be protected,
# or the source branch may have been updated.
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
def branch_deletion_user
@merge_request.force_remove_source_branch? ? @merge_request.author : current_user
......
......@@ -92,6 +92,7 @@ module QuickActions
desc 'Assign'
explanation do |users|
<<<<<<< HEAD
## EE-specific
users = issuable.is_a?(Issue) ? users : users.take(1)
"Assigns #{users.map(&:to_reference).to_sentence}."
......@@ -99,6 +100,13 @@ module QuickActions
params do
## EE-specific
issuable.is_a?(Issue) ? '@user1 @user2' : '@user'
=======
users = issuable.allows_multiple_assignees? ? users : users.take(1)
"Assigns #{users.map(&:to_reference).to_sentence}."
end
params do
issuable.allows_multiple_assignees? ? '@user1 @user2' : '@user'
>>>>>>> ce/master
end
condition do
current_user.can?(:"admin_#{issuable.to_ability_name}", project)
......@@ -109,14 +117,29 @@ module QuickActions
command :assign do |users|
next if users.empty?
<<<<<<< HEAD
if issuable.is_a?(Issue)
# EE specific. In CE we should replace one assignee with another
@updates[:assignee_ids] = issuable.assignees.pluck(:id) + users.map(&:id)
=======
@updates[:assignee_ids] =
if issuable.allows_multiple_assignees?
issuable.assignees.pluck(:id) + users.map(&:id)
else
@updates[:assignee_id] = users.last.id
[users.last.id]
end
end
desc do
if issuable.allows_multiple_assignees?
'Remove all or specific assignee(s)'
>>>>>>> ce/master
else
'Remove assignee'
end
end
<<<<<<< HEAD
desc do
if issuable.is_a?(Issue)
'Remove all or specific assignee(s)'
......@@ -129,12 +152,20 @@ module QuickActions
end
params do
issuable.is_a?(Issue) ? '@user1 @user2' : ''
=======
explanation do
"Removes #{'assignee'.pluralize(issuable.assignees.size)} #{issuable.assignees.map(&:to_reference).to_sentence}."
end
params do
issuable.allows_multiple_assignees? ? '@user1 @user2' : ''
>>>>>>> ce/master
end
condition do
issuable.persisted? &&
issuable.assignees.any? &&
current_user.can?(:"admin_#{issuable.to_ability_name}", project)
end
<<<<<<< HEAD
command :unassign do |unassign_param = nil|
users = extract_users(unassign_param)
......@@ -148,6 +179,45 @@ module QuickActions
else
@updates[:assignee_id] = nil
end
=======
parse_params do |unassign_param|
# When multiple users are assigned, all will be unassigned if multiple assignees are no longer allowed
extract_users(unassign_param) if issuable.allows_multiple_assignees?
end
command :unassign do |users = nil|
@updates[:assignee_ids] =
if users&.any?
issuable.assignees.pluck(:id) - users.map(&:id)
else
[]
end
end
desc do
"Change assignee#{'(s)' if issuable.allows_multiple_assignees?}"
end
explanation do |users|
users = issuable.allows_multiple_assignees? ? users : users.take(1)
"Change #{'assignee'.pluralize(users.size)} to #{users.map(&:to_reference).to_sentence}."
end
params do
issuable.allows_multiple_assignees? ? '@user1 @user2' : '@user'
end
condition do
issuable.persisted? &&
current_user.can?(:"admin_#{issuable.to_ability_name}", project)
end
parse_params do |assignee_param|
extract_users(assignee_param)
end
command :reassign do |users|
@updates[:assignee_ids] =
if issuable.allows_multiple_assignees?
users.map(&:id)
else
[users.last.id]
end
>>>>>>> ce/master
end
desc 'Change assignee(s)'
......
......@@ -22,7 +22,9 @@
.form-group
= f.label :restricted_visibility_levels, class: 'control-label col-sm-2'
.col-sm-10
- restricted_level_checkboxes('restricted-visibility-help').each do |level|
- checkbox_name = 'application_setting[restricted_visibility_levels][]'
= hidden_field_tag(checkbox_name)
- restricted_level_checkboxes('restricted-visibility-help', checkbox_name).each do |level|
.checkbox
= level
%span.help-block#restricted-visibility-help
......
......@@ -72,6 +72,7 @@
= reply_email
%span.light.pull-right
= boolean_to_icon Gitlab::IncomingEmail.enabled?
<<<<<<< HEAD
- elastic = "Elasticsearch"
%p{ "aria-label" => "#{elastic}: status " + (current_application_settings.elasticsearch_search? ? "on" : "off") }
= elastic
......@@ -82,6 +83,8 @@
= geo
%span.light.pull-right
= boolean_to_icon Gitlab::Geo.enabled?
=======
>>>>>>> ce/master
- container_reg = "Container Registry"
%p{ "aria-label" => "#{container_reg}: status " + (Gitlab.config.registry.enabled ? "on" : "off") }
= container_reg
......@@ -123,6 +126,7 @@
GitLab API
%span.pull-right
= API::API::version
<<<<<<< HEAD
- if Gitlab::Geo.enabled?
%p
Geo
......@@ -131,6 +135,8 @@
= Gitlab::Geo.current_node.primary ? 'Primary node' : 'Secondary node'
- else
Undefined
=======
>>>>>>> ce/master
%p
Git
%span.pull-right
......
......@@ -56,10 +56,13 @@
.panel.panel-danger
.panel-heading Remove group
.panel-body
= form_tag(@group, method: :delete) do
%p
Removing group will cause all child projects and resources to be removed.
%br
%strong Removed group can not be restored!
.form-actions
= link_to 'Remove group', @group, data: {confirm: 'Removed group can not be restored! Are you sure?'}, method: :delete, class: "btn btn-remove"
= button_to 'Remove group', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_group_message(@group) }
= render 'shared/confirm_modal', phrase: @group.path
......@@ -86,7 +86,6 @@
= link_to "Profile", current_user, class: 'profile-link', data: { user: current_user.username }
%li
= link_to "Settings", profile_path
- if can_toggle_new_nav?
%li
= link_to "Turn on new nav", profile_preferences_path(anchor: "new-navigation")
%li.divider
......
......@@ -11,7 +11,11 @@
Project
- if project_nav_tab? :files
<<<<<<< HEAD
= nav_link(controller: %w(tree blob blame edit_tree new_tree find_file commit commits compare projects/repositories tags branches releases graphs network path_locks)) do
=======
= nav_link(controller: %w(tree blob blame edit_tree new_tree find_file commit commits compare projects/repositories tags branches releases graphs network)) do
>>>>>>> ce/master
= link_to project_tree_path(@project), title: 'Repository', class: 'shortcuts-tree' do
%span
Repository
......
......@@ -12,7 +12,10 @@
- if current_user
:javascript
window.uploads_path = "#{project_uploads_path(project)}";
<<<<<<< HEAD
window.preview_markdown_path = "#{preview_markdown_path(project)}";
=======
>>>>>>> ce/master
- content_for :header_content do
.js-dropdown-menu-projects
......
......@@ -16,17 +16,14 @@
.preview= image_tag "#{scheme.css_class}-scheme-preview.png"
= f.radio_button :color_scheme_id, scheme.id
= scheme.name
- if can_toggle_new_nav?
.col-sm-12
%hr
.col-lg-3.profile-settings-sidebar#new-navigation
.col-lg-4.profile-settings-sidebar#new-navigation
%h4.prepend-top-0
New Navigation
%p
This setting allows you to turn on or off the new upcoming navigation concept.
= succeed '.' do
= link_to 'Learn more', '', target: '_blank'
.col-lg-9.syntax-theme
.col-lg-8.syntax-theme
= label_tag do
.preview= image_tag "old_nav.png"
%input.js-experiment-feature-toggle{ type: "radio", value: "false", name: "new_nav", checked: !show_new_nav? }
......
<<<<<<< HEAD
- path_to_file = file_project_job_artifacts_path(@project, @build, path: file.path) if @build.downloadable_single_artifacts_file?
=======
- path_to_file = file_project_job_artifacts_path(@project, @build, path: file.path)
>>>>>>> ce/master
%tr.tree-item{ 'data-link' => path_to_file }
- blob = file.blob
......
......@@ -19,10 +19,18 @@
":data-name" => "assignee.name",
":data-username" => "assignee.username" }
.dropdown
<<<<<<< HEAD
%button.dropdown-menu-toggle.js-user-search.js-author-search.js-multiselect.js-save-user-data.js-issue-board-sidebar{ type: "button", ref: "assigneeDropdown", data: { toggle: "dropdown", field_name: "issue[assignee_ids][]", first_user: (current_user.username if current_user), current_user: "true", project_id: @project.id, null_user: "true", multi_select: "true", dropdown: { header: 'Assignee(s)'} },
":data-issuable-id" => "issue.id",
":data-issue-update" => "'#{project_issues_path(@project)}/' + issue.id + '.json'" }
Select assignee(s)
=======
- dropdown_options = issue_assignees_dropdown_options
%button.dropdown-menu-toggle.js-user-search.js-author-search.js-multiselect.js-save-user-data.js-issue-board-sidebar{ type: 'button', ref: 'assigneeDropdown', data: { toggle: 'dropdown', field_name: 'issue[assignee_ids][]', first_user: current_user&.username, current_user: 'true', project_id: @project.id, null_user: 'true', multi_select: 'true', 'dropdown-header': dropdown_options[:data][:'dropdown-header'], 'max-select': dropdown_options[:data][:'max-select'] },
":data-issuable-id" => "issue.id",
":data-issue-update" => "'#{project_issues_path(@project)}/' + issue.id + '.json'" }
= dropdown_options[:title]
>>>>>>> ce/master
= icon("chevron-down")
.dropdown-menu.dropdown-select.dropdown-menu-user.dropdown-menu-selectable.dropdown-menu-author
= dropdown_title("Assign to")
......
......@@ -12,4 +12,4 @@
- if hidden > 0
%li.alert.alert-warning
= n_('%d additional commit has been omitted to prevent performance issues.', '%d additional commits have been omitted to prevent performance issues.', hidden) % number_with_delimiter(hidden)
= n_('%s additional commit has been omitted to prevent performance issues.', '%s additional commits have been omitted to prevent performance issues.', hidden) % number_with_delimiter(hidden)
<<<<<<< HEAD
- if environment.deployment_service_ready? && can?(current_user, :admin_environment, @project)
=======
- if environment.has_terminals? && can?(current_user, :admin_environment, @project)
>>>>>>> ce/master
= link_to terminal_project_environment_path(@project, environment), class: 'btn terminal-button' do
= icon('terminal')
......@@ -10,7 +10,7 @@
.top-area
.row
.col-sm-6
%h3.page-title
%h3
Environment:
= link_to @environment.name, environment_path(@environment)
......
......@@ -10,8 +10,12 @@
List
= nav_link(controller: :boards) do
<<<<<<< HEAD
-# EE should use plural "Boards"
= link_to project_boards_path(@project), title: 'Boards' do
=======
= link_to project_boards_path(@project), title: 'Board' do
>>>>>>> ce/master
%span
Boards
......
......@@ -68,6 +68,7 @@
= edited_time_ago_with_tooltip(@issue, placement: 'bottom', html_class: 'issue-edited-ago js-issue-edited-ago')
<<<<<<< HEAD
- if can?(current_user, :read_issue_link, @project)
.js-related-issues-root{ data: { endpoint: project_issue_links_path(@project, @issue),
can_add_related_issues: "#{can?(current_user, :update_issue, @issue)}",
......@@ -78,6 +79,8 @@
%h3.panel-title
Related issues
=======
>>>>>>> ce/master
#merge-requests{ data: { url: referenced_merge_requests_project_issue_url(@project, @issue) } }
// This element is filled in using JavaScript.
......
......@@ -32,7 +32,11 @@
= link_to download_project_job_artifacts_path(@project, @build), rel: 'nofollow', download: '', class: 'btn btn-sm btn-default' do
Download
<<<<<<< HEAD
- if @build.browsable_artifacts?
=======
- if @build.artifacts_metadata?
>>>>>>> ce/master
= link_to browse_project_job_artifacts_path(@project, @build), class: 'btn btn-sm btn-default' do
Browse
......
......@@ -56,13 +56,17 @@
- else
Job has been erased #{time_ago_with_tooltip(@build.erased_at)}
.build-trace-container#build-trace
.top-bar.sticky
.build-trace-container.prepend-top-default
.top-bar.js-top-bar
.js-truncated-info.truncated-info.hidden<
Showing last
%span.js-truncated-info-size.truncated-info-size><
KiB of log -
%a.js-raw-link.raw-link{ href: raw_project_job_path(@project, @build) }>< Complete Raw
<<<<<<< HEAD
=======
>>>>>>> ce/master
.controllers
- if @build.has_trace?
= link_to raw_project_job_path(@project, @build),
......@@ -84,10 +88,12 @@
.has-tooltip.controllers-buttons{ title: 'Scroll to bottom', data: { placement: 'top', container: 'body'} }
%button.js-scroll-down.btn-scroll.btn-transparent.btn-blank{ type: 'button', disabled: true }
= custom_icon('scroll_down')
.bash.sticky.js-scroll-container
%code.js-build-output
%pre.build-trace#build-trace
%code.bash.js-build-output
.build-loader-animation.js-build-refresh
= render "sidebar"
.js-build-options{ data: javascript_build_options }
......
- @no_container = true
- page_title "Charts", "Pipelines"
- page_title _("Charts"), _("Pipelines")
- content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('common_d3')
= page_specific_javascript_bundle_tag('graphs')
......@@ -8,7 +8,7 @@
%div{ class: container_class }
.sub-header-block
.oneline
A collection of graphs for Continuous Integration
= _("A collection of graphs regarding Continuous Integration")
#charts.ci-charts
.row
......
%h4 Overall stats
%h4= s_("PipelineCharts|Overall statistics")
%ul
%li
Total:
%strong= pluralize @counts[:total], 'pipeline'
= s_("PipelineCharts|Total:")
%strong= n_("1 pipeline", "%d pipelines", @counts[:total]) % @counts[:total]
%li
Successful:
%strong= pluralize @counts[:success], 'pipeline'
= s_("PipelineCharts|Successful:")
%strong= n_("1 pipeline", "%d pipelines", @counts[:success]) % @counts[:success]
%li
Failed:
%strong= pluralize @counts[:failed], 'pipeline'
= s_("PipelineCharts|Failed:")
%strong= n_("1 pipeline", "%d pipelines", @counts[:failed]) % @counts[:failed]
%li
Success ratio:
= s_("PipelineCharts|Success ratio:")
%strong
#{success_ratio(@counts)}%
%div
%p.light
Commit duration in minutes for last 30 commits
= _("Commit duration in minutes for last 30 commits")
%canvas#build_timesChart{ height: 200 }
......
%h4 Pipelines charts
%h4= _("Pipelines charts")
%p
&nbsp;
%span.cgreen
= icon("circle")
success
= s_("Pipeline|success")
&nbsp;
%span.cgray
= icon("circle")
all
= s_("Pipeline|all")
.prepend-top-default
%p.light
Jobs for last week
= _("Jobs for last week")
(#{date_from_to(Date.today - 7.days, Date.today)})
%canvas#weekChart{ height: 200 }
.prepend-top-default
%p.light
Jobs for last month
= _("Jobs for last month")
(#{date_from_to(Date.today - 30.days, Date.today)})
%canvas#monthChart{ height: 200 }
.prepend-top-default
%p.light
Jobs for last year
= _("Jobs for last year")
%canvas#yearChart.padded{ height: 250 }
- [:week, :month, :year].each do |scope|
......
......@@ -45,6 +45,14 @@
Per job in minutes. If a job passes this threshold, it will be marked as failed
= link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'timeout'), target: '_blank'
%hr
.form-group
= f.label :ci_config_path, 'Custom CI config path', class: 'label-light'
= f.text_field :ci_config_path, class: 'form-control', placeholder: '.gitlab-ci.yml'
%p.help-block
The path to CI config file. Defaults to <code>.gitlab-ci.yml</code>
= link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'custom-ci-config-path'), target: '_blank'
%hr
.form-group
.checkbox
......
......@@ -18,5 +18,9 @@
= text_field_tag :expires_at, nil, class: 'form-control js-access-expiration-date', placeholder: 'Expiration date'
%i.clear-icon.js-clear-input
= f.submit "Add to project", class: "btn btn-create"
<<<<<<< HEAD
- if can?(current_user, :admin_project_member, @project) && !membership_locked?
= link_to "Import", import_project_project_members_path(@project), class: "btn btn-default", title: "Import members from another project"
=======
= link_to "Import", import_project_project_members_path(@project), class: "btn btn-default", title: "Import members from another project"
>>>>>>> ce/master
......@@ -11,6 +11,7 @@
.col-lg-9
= form_for(@service, as: :service, url: project_service_path(@project, @service.to_param), method: :put, html: { class: 'gl-show-field-errors form-horizontal js-integration-settings-form', data: { 'can-test' => @service.can_test?, 'test-url' => test_project_service_path(@project, @service) } }) do |form|
= render 'shared/service_settings', form: form, subject: @service
- if @service.editable?
.footer-block.row-content-block
%button.btn.btn-save{ type: 'submit' }
= icon('spinner spin', class: 'hidden js-btn-spinner')
......
......@@ -75,7 +75,12 @@
.tree-controls
= render 'projects/find_file_link'
<<<<<<< HEAD
= lock_file_link(html_options: { class: 'btn btn-grouped path-lock' })
= link_to s_('Commits|History'), project_commits_path(@project, @id), class: 'btn btn-grouped'
=======
= link_to s_('Commits|History'), project_commits_path(@project, @id), class: 'btn'
>>>>>>> ce/master
= render 'projects/buttons/download', project: @project, ref: @ref
......@@ -5,7 +5,6 @@
= auto_discovery_link_tag(:atom, project_commits_url(@project, @ref, rss_url_options), title: "#{@project.name}:#{@ref} commits")
= render "projects/commits/head"
= render 'projects/last_push'
%div{ class: container_class }
%div{ class: [container_class, ("limit-container-width" unless fluid_layout)] }
= render 'projects/last_push'
= render 'projects/files', commit: @last_commit, project: @project, ref: @ref
......@@ -10,6 +10,11 @@
.blob-result
.file-holder
.js-file-title.file-title
<<<<<<< HEAD
=======
- ref = @search_results.repository_ref
- blob_link = project_blob_path(@project, tree_join(ref, file_name))
>>>>>>> ce/master
= link_to blob_link do
= icon('fa-file')
%strong
......
......@@ -4,7 +4,11 @@
.blob-result
.file-holder
.js-file-title.file-title
<<<<<<< HEAD
= link_to project_wiki_path(project, wiki_blob.basename) do
=======
= link_to project_wiki_path(@project, wiki_blob.basename) do
>>>>>>> ce/master
%i.fa.fa-file
%strong
- if @project
......
......@@ -7,6 +7,7 @@
= markdown @service.help
.service-settings
- if @service.show_active_box?
.form-group
= form.label :active, "Active", class: "control-label"
.col-sm-10
......
......@@ -27,7 +27,11 @@
.scroll-container
%ul.tokens-container.list-unstyled
%li.input-token
<<<<<<< HEAD
%input.form-control.filtered-search{ id: "filtered-search-#{type.to_s}", placeholder: 'Search or filter results...', data: { 'project-id' => @project.id, 'username-params' => @users.to_json(only: [:id, :username]), 'base-endpoint' => project_path(@project) } }
=======
%input.form-control.filtered-search{ search_filter_input_options(type) }
>>>>>>> ce/master
= icon('filter')
#js-dropdown-hint.filtered-search-input-dropdown-menu.dropdown-menu.hint-dropdown
%ul{ data: { dropdown: true } }
......
......@@ -37,18 +37,24 @@
- issuable.assignees.each do |assignee|
= hidden_field_tag "#{issuable.to_ability_name}[assignee_ids][]", assignee.id, id: nil, data: { avatar_url: assignee.avatar_url, name: assignee.name, username: assignee.username }
- options = { toggle_class: 'js-user-search js-author-search', title: 'Assign to', filter: true, dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author', placeholder: 'Search users', data: { first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), author_id: issuable.author_id, field_name: "#{issuable.to_ability_name}[assignee_ids][]", issue_update: issuable_json_path(issuable), ability_name: issuable.to_ability_name, null_user: true } }
- options = { toggle_class: 'js-user-search js-author-search', title: 'Assign to', filter: true, dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author', placeholder: 'Search users', data: { first_user: current_user&.username, current_user: true, project_id: @project&.id, author_id: issuable.author_id, field_name: "#{issuable.to_ability_name}[assignee_ids][]", issue_update: issuable_json_path(issuable), ability_name: issuable.to_ability_name, null_user: true } }
- title = 'Select assignee'
- if issuable.is_a?(Issue)
- unless issuable.assignees.any?
= hidden_field_tag "#{issuable.to_ability_name}[assignee_ids][]", 0, id: nil
- dropdown_options = issue_assignees_dropdown_options
- title = dropdown_options[:title]
- options[:toggle_class] += ' js-multiselect js-save-user-data'
- data = { field_name: "#{issuable.to_ability_name}[assignee_ids][]" }
- data[:multi_select] = true
- data['dropdown-title'] = title
<<<<<<< HEAD
- data['dropdown-header'] = 'Assignee'
=======
- data['dropdown-header'] = dropdown_options[:data][:'dropdown-header']
- data['max-select'] = dropdown_options[:data][:'max-select']
>>>>>>> ce/master
- options[:data].merge!(data)
= dropdown_tag(title, options: options)
......@@ -7,5 +7,9 @@
- if issuable.assignees.length === 0
= hidden_field_tag "#{issuable.to_ability_name}[assignee_ids][]", 0, id: nil, data: { meta: '' }
<<<<<<< HEAD
= dropdown_tag(users_dropdown_label(issuable.assignees), options: issue_dropdown_options(issuable,true))
=======
= dropdown_tag(users_dropdown_label(issuable.assignees), options: issue_assignees_dropdown_options)
>>>>>>> ce/master
= link_to 'Assign to me', '#', class: "assign-to-me-link #{'hide' if issuable.assignees.include?(current_user)}"
......@@ -32,8 +32,13 @@ class ExpirePipelineCacheWorker
Gitlab::Routing.url_helpers.project_pipelines_path(project, format: :json)
end
<<<<<<< HEAD
def project_pipeline_path(pipeline)
Gitlab::Routing.url_helpers.project_pipeline_path(pipeline.project, pipeline, format: :json)
=======
def project_pipeline_path(project, pipeline)
Gitlab::Routing.url_helpers.project_pipeline_path(project, pipeline, format: :json)
>>>>>>> ce/master
end
def commit_pipelines_path(project, commit)
......
---
title: Replace 'snippets/snippets.feature' spinach with rspec
merge_request: 12385
author: Alexander Randa @randaalex
---
title: Allow creation of files and directories with spaces through Web UI
merge_request: 12608
author:
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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