Commit 30464bce authored by Shinya Maeda's avatar Shinya Maeda

Merge branch 'master' into live-trace-v2

parents 9ad20891 b2e1b709
......@@ -2,6 +2,20 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
## 10.7.3 (2018-05-02)
### Fixed (8 changes)
- Fixed wrong avatar URL when the avatar is on object storage. !18092
- Fix errors on pushing to an empty repository. !18462
- Update doorkeeper to 4.3.2 to fix GitLab OAuth authentication. !18543
- Ports omniauth-jwt gem onto GitLab OmniAuth Strategies suite. !18580
- Fix redirection error for applications using OpenID. !18599
- Fix commit trailer rendering when Gravatar is disabled.
- Fix file_store for artifacts and lfs when saving.
- Fix users not seeing labels from private groups when being a member of a child project.
## 10.7.2 (2018-04-25)
### Security (2 changes)
......
......@@ -184,6 +184,9 @@ gem 're2', '~> 1.1.1'
gem 'version_sorter', '~> 2.1.0'
# User agent parsing
gem 'device_detector'
# Cache
gem 'redis-rails', '~> 5.0.2'
......
......@@ -161,6 +161,7 @@ GEM
activerecord (>= 3.2.0, < 5.1)
descendants_tracker (0.0.4)
thread_safe (~> 0.3, >= 0.3.1)
device_detector (1.0.0)
devise (4.2.0)
bcrypt (~> 3.0)
orm_adapter (~> 0.1)
......@@ -1026,6 +1027,7 @@ DEPENDENCIES
database_cleaner (~> 1.5.0)
deckar01-task_list (= 2.0.0)
default_value_for (~> 3.0.0)
device_detector
devise (~> 4.2)
devise-two-factor (~> 3.0.0)
diffy (~> 3.1.0)
......
......@@ -304,12 +304,12 @@ GEM
flowdock (~> 0.7)
gitlab-grit (>= 2.4.1)
multi_json
gitlab-gollum-lib (4.2.7.1)
gitlab-gollum-lib (4.2.7.2)
gemojione (~> 3.2)
github-markup (~> 1.6)
gollum-grit_adapter (~> 1.0)
nokogiri (>= 1.6.1, < 2.0)
rouge (~> 2.1)
rouge (~> 3.1)
sanitize (~> 2.1)
stringex (~> 2.6)
gitlab-gollum-rugged_adapter (0.4.4)
......@@ -602,8 +602,6 @@ GEM
atomic (>= 1.0.0)
mysql2
peek
peek-performance_bar (1.3.1)
peek (>= 0.1.0)
peek-pg (1.3.0)
concurrent-ruby
concurrent-ruby-ext
......@@ -752,7 +750,7 @@ GEM
retriable (3.1.1)
rinku (2.0.4)
rotp (2.1.2)
rouge (2.2.1)
rouge (3.1.1)
rqrcode (0.10.1)
chunky_png (~> 1.0)
rqrcode-rails3 (0.1.7)
......@@ -1134,7 +1132,6 @@ DEPENDENCIES
peek (~> 1.0.1)
peek-gc (~> 0.0.2)
peek-mysql2 (~> 1.1.0)
peek-performance_bar (~> 1.3.0)
peek-pg (~> 1.3.0)
peek-rblineprof (~> 0.2.0)
peek-redis (~> 1.2.0)
......@@ -1166,7 +1163,7 @@ DEPENDENCIES
redis-rails (~> 5.0.2)
request_store (~> 1.3)
responders (~> 2.0)
rouge (~> 2.0)
rouge (~> 3.1)
rqrcode-rails3 (~> 0.1.7)
rspec-parameterized
rspec-rails (~> 3.6.0)
......
......@@ -7,27 +7,24 @@ export default function installGlEmojiElement() {
const GlEmojiElementProto = Object.create(HTMLElement.prototype);
GlEmojiElementProto.createdCallback = function createdCallback() {
const emojiUnicode = this.textContent.trim();
const {
name,
unicodeVersion,
fallbackSrc,
fallbackSpriteClass,
} = this.dataset;
const { name, unicodeVersion, fallbackSrc, fallbackSpriteClass } = this.dataset;
const isEmojiUnicode = this.childNodes && Array.prototype.every.call(
this.childNodes,
childNode => childNode.nodeType === 3,
);
const isEmojiUnicode =
this.childNodes &&
Array.prototype.every.call(this.childNodes, childNode => childNode.nodeType === 3);
const hasImageFallback = fallbackSrc && fallbackSrc.length > 0;
const hasCssSpriteFalback = fallbackSpriteClass && fallbackSpriteClass.length > 0;
if (
emojiUnicode &&
isEmojiUnicode &&
!isEmojiUnicodeSupported(emojiUnicode, unicodeVersion)
) {
if (emojiUnicode && isEmojiUnicode && !isEmojiUnicodeSupported(emojiUnicode, unicodeVersion)) {
// CSS sprite fallback takes precedence over image fallback
if (hasCssSpriteFalback) {
if (!gon.emoji_sprites_css_added && gon.emoji_sprites_css_path) {
const emojiSpriteLinkTag = document.createElement('link');
emojiSpriteLinkTag.setAttribute('rel', 'stylesheet');
emojiSpriteLinkTag.setAttribute('href', gon.emoji_sprites_css_path);
document.head.appendChild(emojiSpriteLinkTag);
gon.emoji_sprites_css_added = true;
}
// IE 11 doesn't like adding multiple at once :(
this.classList.add('emoji-icon');
this.classList.add(fallbackSpriteClass);
......
/* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, no-var, object-shorthand, consistent-return, no-unused-vars, comma-dangle, vars-on-top, prefer-template, max-len */
import $ from 'jquery';
import { localTimeAgo } from './lib/utils/datetime_utility';
import axios from './lib/utils/axios_utils';
export default class Compare {
constructor(opts) {
this.opts = opts;
this.source_loading = $(".js-source-loading");
this.target_loading = $(".js-target-loading");
$('.js-compare-dropdown').each((function(_this) {
return function(i, dropdown) {
var $dropdown;
$dropdown = $(dropdown);
return $dropdown.glDropdown({
selectable: true,
fieldName: $dropdown.data('fieldName'),
filterable: true,
id: function(obj, $el) {
return $el.data('id');
},
toggleLabel: function(obj, $el) {
return $el.text().trim();
},
clicked: function(e, el) {
if ($dropdown.is('.js-target-branch')) {
return _this.getTargetHtml();
} else if ($dropdown.is('.js-source-branch')) {
return _this.getSourceHtml();
} else if ($dropdown.is('.js-target-project')) {
return _this.getTargetProject();
}
}
});
};
})(this));
this.initialState();
}
initialState() {
this.getSourceHtml();
this.getTargetHtml();
}
getTargetProject() {
$('.mr_target_commit').empty();
return axios.get(this.opts.targetProjectUrl, {
params: {
target_project_id: $("input[name='merge_request[target_project_id]']").val(),
},
}).then(({ data }) => {
$('.js-target-branch-dropdown .dropdown-content').html(data);
});
}
getSourceHtml() {
return this.constructor.sendAjax(this.opts.sourceBranchUrl, this.source_loading, '.mr_source_commit', {
ref: $("input[name='merge_request[source_branch]']").val()
});
}
getTargetHtml() {
return this.constructor.sendAjax(this.opts.targetBranchUrl, this.target_loading, '.mr_target_commit', {
target_project_id: $("input[name='merge_request[target_project_id]']").val(),
ref: $("input[name='merge_request[target_branch]']").val()
});
}
static sendAjax(url, loading, target, params) {
const $target = $(target);
loading.show();
$target.empty();
return axios.get(url, {
params,
}).then(({ data }) => {
loading.hide();
$target.html(data);
const className = '.' + $target[0].className.replace(' ', '.');
localTimeAgo($('.js-timeago', className));
});
}
}
......@@ -4,8 +4,9 @@ import $ from 'jquery';
import { __ } from './locale';
import axios from './lib/utils/axios_utils';
import flash from './flash';
import { capitalizeFirstCharacter } from './lib/utils/text_utility';
export default function initCompareAutocomplete() {
export default function initCompareAutocomplete(limitTo = null, clickHandler = () => {}) {
$('.js-compare-dropdown').each(function() {
var $dropdown, selected;
$dropdown = $(this);
......@@ -15,14 +16,27 @@ export default function initCompareAutocomplete() {
const $filterInput = $('input[type="search"]', $dropdownContainer);
$dropdown.glDropdown({
data: function(term, callback) {
axios.get($dropdown.data('refsUrl'), {
params: {
ref: $dropdown.data('ref'),
search: term,
},
}).then(({ data }) => {
callback(data);
}).catch(() => flash(__('Error fetching refs')));
const params = {
ref: $dropdown.data('ref'),
search: term,
};
if (limitTo) {
params.find = limitTo;
}
axios
.get($dropdown.data('refsUrl'), {
params,
})
.then(({ data }) => {
if (limitTo) {
callback(data[capitalizeFirstCharacter(limitTo)] || []);
} else {
callback(data);
}
})
.catch(() => flash(__('Error fetching refs')));
},
selectable: true,
filterable: true,
......@@ -32,9 +46,15 @@ export default function initCompareAutocomplete() {
renderRow: function(ref) {
var link;
if (ref.header != null) {
return $('<li />').addClass('dropdown-header').text(ref.header);
return $('<li />')
.addClass('dropdown-header')
.text(ref.header);
} else {
link = $('<a />').attr('href', '#').addClass(ref === selected ? 'is-active' : '').text(ref).attr('data-ref', escape(ref));
link = $('<a />')
.attr('href', '#')
.addClass(ref === selected ? 'is-active' : '')
.text(ref)
.attr('data-ref', escape(ref));
return $('<li />').append(link);
}
},
......@@ -43,9 +63,10 @@ export default function initCompareAutocomplete() {
},
toggleLabel: function(obj, $el) {
return $el.text().trim();
}
},
clicked: () => clickHandler($dropdown),
});
$filterInput.on('keyup', (e) => {
$filterInput.on('keyup', e => {
const keyCode = e.keyCode || e.which;
if (keyCode !== 13) return;
const text = $filterInput.val();
......@@ -54,7 +75,7 @@ export default function initCompareAutocomplete() {
$dropdownContainer.removeClass('open');
});
$dropdownContainer.on('click', '.dropdown-content a', (e) => {
$dropdownContainer.on('click', '.dropdown-content a', e => {
$dropdown.prop('title', e.target.text.replace(/_+?/g, '-'));
if ($dropdown.hasClass('has-tooltip')) {
$dropdown.tooltip('fixTitle');
......
......@@ -34,7 +34,7 @@ export function getEmojiCategoryMap() {
symbols: [],
flags: [],
};
Object.keys(emojiMap).forEach((name) => {
Object.keys(emojiMap).forEach(name => {
const emoji = emojiMap[name];
if (emojiCategoryMap[emoji.category]) {
emojiCategoryMap[emoji.category].push(name);
......@@ -79,7 +79,9 @@ export function glEmojiTag(inputName, options) {
classList.push(fallbackSpriteClass);
}
const classAttribute = classList.length > 0 ? `class="${classList.join(' ')}"` : '';
const fallbackSpriteAttribute = opts.sprite ? `data-fallback-sprite-class="${fallbackSpriteClass}"` : '';
const fallbackSpriteAttribute = opts.sprite
? `data-fallback-sprite-class="${fallbackSpriteClass}"`
: '';
let contents = emojiInfo.moji;
if (opts.forceFallback && !opts.sprite) {
contents = emojiImageTag(name, fallbackImageSrc);
......
......@@ -54,7 +54,8 @@ const unicodeSupportTestMap = {
function checkPixelInImageDataArray(pixelOffset, imageDataArray) {
// `4 *` because RGBA
const indexOffset = 4 * pixelOffset;
const hasColor = imageDataArray[indexOffset + 0] ||
const hasColor =
imageDataArray[indexOffset + 0] ||
imageDataArray[indexOffset + 1] ||
imageDataArray[indexOffset + 2];
const isVisible = imageDataArray[indexOffset + 3];
......@@ -75,23 +76,23 @@ const chromeVersion = chromeMatches && chromeMatches[1] && parseInt(chromeMatche
const fontSize = 16;
function generateUnicodeSupportMap(testMap) {
const testMapKeys = Object.keys(testMap);
const numTestEntries = testMapKeys
.reduce((list, testKey) => list.concat(testMap[testKey]), []).length;
const numTestEntries = testMapKeys.reduce((list, testKey) => list.concat(testMap[testKey]), [])
.length;
const canvas = document.createElement('canvas');
(window.gl || window).testEmojiUnicodeSupportMapCanvas = canvas;
const ctx = canvas.getContext('2d');
canvas.width = (2 * fontSize);
canvas.height = (numTestEntries * fontSize);
canvas.width = 2 * fontSize;
canvas.height = numTestEntries * fontSize;
ctx.fillStyle = '#000000';
ctx.textBaseline = 'middle';
ctx.font = `${fontSize}px "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"`;
// Write each emoji to the canvas vertically
let writeIndex = 0;
testMapKeys.forEach((testKey) => {
testMapKeys.forEach(testKey => {
const testEntry = testMap[testKey];
[].concat(testEntry).forEach((emojiUnicode) => {
ctx.fillText(emojiUnicode, 0, (writeIndex * fontSize) + (fontSize / 2));
[].concat(testEntry).forEach(emojiUnicode => {
ctx.fillText(emojiUnicode, 0, writeIndex * fontSize + fontSize / 2);
writeIndex += 1;
});
});
......@@ -99,29 +100,25 @@ function generateUnicodeSupportMap(testMap) {
// Read from the canvas
const resultMap = {};
let readIndex = 0;
testMapKeys.forEach((testKey) => {
testMapKeys.forEach(testKey => {
const testEntry = testMap[testKey];
// This needs to be a `reduce` instead of `every` because we need to
// keep the `readIndex` in sync from the writes by running all entries
const isTestSatisfied = [].concat(testEntry).reduce((isSatisfied) => {
const isTestSatisfied = [].concat(testEntry).reduce(isSatisfied => {
// Sample along the vertical-middle for a couple of characters
const imageData = ctx.getImageData(
0,
(readIndex * fontSize) + (fontSize / 2),
2 * fontSize,
1,
).data;
const imageData = ctx.getImageData(0, readIndex * fontSize + fontSize / 2, 2 * fontSize, 1)
.data;
let isValidEmoji = false;
for (let currentPixel = 0; currentPixel < 64; currentPixel += 1) {
const isLookingAtFirstChar = currentPixel < fontSize;
const isLookingAtSecondChar = currentPixel >= (fontSize + (fontSize / 2));
const isLookingAtSecondChar = currentPixel >= fontSize + fontSize / 2;
// Check for the emoji somewhere along the row
if (isLookingAtFirstChar && checkPixelInImageDataArray(currentPixel, imageData)) {
isValidEmoji = true;
// Check to see that nothing is rendered next to the first character
// to ensure that the ZWJ sequence rendered as one piece
// Check to see that nothing is rendered next to the first character
// to ensure that the ZWJ sequence rendered as one piece
} else if (isLookingAtSecondChar && checkPixelInImageDataArray(currentPixel, imageData)) {
isValidEmoji = false;
break;
......@@ -170,7 +167,10 @@ export default function getUnicodeSupportMap() {
if (isLocalStorageAvailable) {
window.localStorage.setItem('gl-emoji-version', GL_EMOJI_VERSION);
window.localStorage.setItem('gl-emoji-user-agent', navigator.userAgent);
window.localStorage.setItem('gl-emoji-unicode-support-map', JSON.stringify(unicodeSupportMap));
window.localStorage.setItem(
'gl-emoji-unicode-support-map',
JSON.stringify(unicodeSupportMap),
);
}
}
......
import initCompareAutocomplete from '~/compare_autocomplete';
document.addEventListener('DOMContentLoaded', initCompareAutocomplete);
document.addEventListener('DOMContentLoaded', () => initCompareAutocomplete());
import $ from 'jquery';
import { localTimeAgo } from '~/lib/utils/datetime_utility';
import axios from '~/lib/utils/axios_utils';
import initCompareAutocomplete from '~/compare_autocomplete';
import initTargetProjectDropdown from './target_project_dropdown';
const updateCommitList = (url, $loadingIndicator, $commitList, params) => {
$loadingIndicator.show();
$commitList.empty();
return axios
.get(url, {
params,
})
.then(({ data }) => {
$loadingIndicator.hide();
$commitList.html(data);
localTimeAgo($('.js-timeago', $commitList));
});
};
export default mrNewCompareNode => {
const { sourceBranchUrl, targetBranchUrl } = mrNewCompareNode.dataset;
initTargetProjectDropdown();
const updateSourceBranchCommitList = () =>
updateCommitList(
sourceBranchUrl,
$(mrNewCompareNode).find('.js-source-loading'),
$(mrNewCompareNode).find('.mr_source_commit'),
{
ref: $(mrNewCompareNode)
.find("input[name='merge_request[source_branch]']")
.val(),
},
);
const updateTargetBranchCommitList = () =>
updateCommitList(
targetBranchUrl,
$(mrNewCompareNode).find('.js-target-loading'),
$(mrNewCompareNode).find('.mr_target_commit'),
{
target_project_id: $(mrNewCompareNode)
.find("input[name='merge_request[target_project_id]']")
.val(),
ref: $(mrNewCompareNode)
.find("input[name='merge_request[target_branch]']")
.val(),
},
);
initCompareAutocomplete('branches', $dropdown => {
if ($dropdown.is('.js-target-branch')) {
updateTargetBranchCommitList();
} else if ($dropdown.is('.js-source-branch')) {
updateSourceBranchCommitList();
}
});
updateSourceBranchCommitList();
updateTargetBranchCommitList();
};
import Compare from '~/compare';
import MergeRequest from '~/merge_request';
import initPipelines from '~/commit/pipelines/pipelines_bundle';
import initCompare from './compare';
document.addEventListener('DOMContentLoaded', () => {
const mrNewCompareNode = document.querySelector('.js-merge-request-new-compare');
if (mrNewCompareNode) {
new Compare({ // eslint-disable-line no-new
targetProjectUrl: mrNewCompareNode.dataset.targetProjectUrl,
sourceBranchUrl: mrNewCompareNode.dataset.sourceBranchUrl,
targetBranchUrl: mrNewCompareNode.dataset.targetBranchUrl,
});
initCompare(mrNewCompareNode);
} else {
const mrNewSubmitNode = document.querySelector('.js-merge-request-new-submit');
new MergeRequest({ // eslint-disable-line no-new
// eslint-disable-next-line no-new
new MergeRequest({
action: mrNewSubmitNode.dataset.mrSubmitAction,
});
initPipelines();
......
import $ from 'jquery';
export default () => {
const $targetProjectDropdown = $('.js-target-project');
$targetProjectDropdown.glDropdown({
selectable: true,
fieldName: $targetProjectDropdown.data('fieldName'),
filterable: true,
id(obj, $el) {
return $el.data('id');
},
toggleLabel(obj, $el) {
return $el.text().trim();
},
clicked({ $el }) {
$('.mr_target_commit').empty();
const $targetBranchDropdown = $('.js-target-branch');
$targetBranchDropdown.data('refsUrl', $el.data('refsUrl'));
$targetBranchDropdown.data('glDropdown').clearMenu();
},
});
};
export default {
name: 'time-tracking-no-tracking-pane',
template: `
<div class="time-tracking-no-tracking-pane">
<span class="no-value">
{{ __('No estimate or time spent') }}
</span>
</div>
`,
};
<script>
export default {
name: 'TimeTrackingNoTrackingPane',
};
</script>
<template>
<div class="time-tracking-no-tracking-pane">
<span class="no-value">
{{ __('No estimate or time spent') }}
</span>
</div>
</template>
<script>
import $ from 'jquery';
import _ from 'underscore';
......@@ -10,14 +11,17 @@ import Mediator from '../../sidebar_mediator';
import eventHub from '../../event_hub';
export default {
components: {
IssuableTimeTracker,
},
data() {
return {
mediator: new Mediator(),
store: new Store(),
};
},
components: {
IssuableTimeTracker,
mounted() {
this.listenForQuickActions();
},
methods: {
listenForQuickActions() {
......@@ -41,18 +45,17 @@ export default {
}
},
},
mounted() {
this.listenForQuickActions();
},
template: `
<div class="block">
<issuable-time-tracker
:time_estimate="store.timeEstimate"
:time_spent="store.totalTimeSpent"
:human_time_estimate="store.humanTimeEstimate"
:human_time_spent="store.humanTotalTimeSpent"
:rootPath="store.rootPath"
/>
</div>
`,
};
</script>
<template>
<div class="block">
<issuable-time-tracker
:time_estimate="store.timeEstimate"
:time_spent="store.totalTimeSpent"
:human_time_estimate="store.humanTimeEstimate"
:human_time_spent="store.humanTotalTimeSpent"
:root-path="store.rootPath"
/>
</div>
</template>
......@@ -2,7 +2,7 @@
import TimeTrackingHelpState from './help_state.vue';
import TimeTrackingCollapsedState from './collapsed_state.vue';
import timeTrackingSpentOnlyPane from './spent_only_pane';
import timeTrackingNoTrackingPane from './no_tracking_pane';
import TimeTrackingNoTrackingPane from './no_tracking_pane.vue';
import TimeTrackingEstimateOnlyPane from './estimate_only_pane.vue';
import TimeTrackingComparisonPane from './comparison_pane.vue';
......@@ -14,7 +14,7 @@ export default {
TimeTrackingCollapsedState,
TimeTrackingEstimateOnlyPane,
'time-tracking-spent-only-pane': timeTrackingSpentOnlyPane,
'time-tracking-no-tracking-pane': timeTrackingNoTrackingPane,
TimeTrackingNoTrackingPane,
TimeTrackingComparisonPane,
TimeTrackingHelpState,
},
......
import $ from 'jquery';
import Vue from 'vue';
import SidebarTimeTracking from './components/time_tracking/sidebar_time_tracking';
import SidebarTimeTracking from './components/time_tracking/sidebar_time_tracking.vue';
import SidebarAssignees from './components/assignees/sidebar_assignees.vue';
import ConfidentialIssueSidebar from './components/confidential/confidential_issue_sidebar.vue';
import SidebarMoveIssue from './lib/sidebar_move_issue';
......
<script>
import $ from 'jquery';
import statusIcon from '../mr_widget_status_icon.vue';
import tooltip from '../../../vue_shared/directives/tooltip';
import eventHub from '../../event_hub';
export default {
name: 'MRWidgetWIP',
props: {
mr: { type: Object, required: true },
service: { type: Object, required: true },
name: 'WorkInProgress',
components: {
statusIcon,
},
directives: {
tooltip,
},
props: {
mr: { type: Object, required: true },
service: { type: Object, required: true },
},
data() {
return {
isMakingRequest: false,
};
},
components: {
statusIcon,
},
methods: {
removeWIP() {
this.isMakingRequest = true;
......@@ -36,32 +37,40 @@ export default {
});
},
},
template: `
<div class="mr-widget-body media">
<status-icon status="warning" :show-disabled-button="Boolean(mr.removeWIPPath)" />
<div class="media-body space-children">
<span class="bold">
This is a Work in Progress
<i
v-tooltip
class="fa fa-question-circle"
title="When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
aria-label="When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged">
</i>
</span>
<button
v-if="mr.removeWIPPath"
@click="removeWIP"
:disabled="isMakingRequest"
type="button"
class="btn btn-default btn-xs js-remove-wip">
<i
v-if="isMakingRequest"
class="fa fa-spinner fa-spin"
aria-hidden="true" />
Resolve WIP status
</button>
</div>
</div>
`,
};
</script>
<template>
<div class="mr-widget-body media">
<status-icon
status="warning"
:show-disabled-button="Boolean(mr.removeWIPPath)"
/>
<div class="media-body space-children">
<span class="bold">
This is a Work in Progress
<i
v-tooltip
class="fa fa-question-circle"
title="When this merge request is ready,
remove the WIP: prefix from the title to allow it to be merged"
aria-label="When this merge request is ready,
remove the WIP: prefix from the title to allow it to be merged">
</i>
</span>
<button
v-if="mr.removeWIPPath"
@click="removeWIP"
:disabled="isMakingRequest"
type="button"
class="btn btn-default btn-xs js-remove-wip">
<i
v-if="isMakingRequest"
class="fa fa-spinner fa-spin"
aria-hidden="true">
</i>
Resolve WIP status
</button>
</div>
</div>
</template>
......@@ -21,7 +21,7 @@ export { default as MergedState } from './components/states/mr_widget_merged.vue
export { default as FailedToMerge } from './components/states/mr_widget_failed_to_merge.vue';
export { default as ClosedState } from './components/states/mr_widget_closed.vue';
export { default as MergingState } from './components/states/mr_widget_merging.vue';
export { default as WipState } from './components/states/mr_widget_wip';
export { default as WorkInProgressState } from './components/states/work_in_progress.vue';
export { default as ArchivedState } from './components/states/mr_widget_archived.vue';
export { default as ConflictsState } from './components/states/mr_widget_conflicts.vue';
export { default as NothingToMergeState } from './components/states/nothing_to_merge.vue';
......
......@@ -12,7 +12,7 @@ import {
ClosedState,
MergingState,
RebaseState,
WipState,
WorkInProgressState,
ArchivedState,
ConflictsState,
NothingToMergeState,
......@@ -220,7 +220,7 @@ export default {
'mr-widget-closed': ClosedState,
'mr-widget-merging': MergingState,
'mr-widget-failed-to-merge': FailedToMerge,
'mr-widget-wip': WipState,
'mr-widget-wip': WorkInProgressState,
'mr-widget-archived': ArchivedState,
'mr-widget-conflicts': ConflictsState,
'mr-widget-nothing-to-merge': NothingToMergeState,
......
This diff is collapsed.
@import "framework/variables";
@import "framework/mixins";
@import 'framework/variables';
@import 'framework/mixins';
@import 'framework/tw_bootstrap_variables';
@import 'framework/tw_bootstrap';
@import "framework/layout";
@import 'framework/layout';
@import "framework/animations";
@import "framework/vue_transitions";
@import "framework/avatar";
@import "framework/asciidoctor";
@import "framework/banner";
@import "framework/blocks";
@import "framework/buttons";
@import "framework/badges";
@import "framework/calendar";
@import "framework/callout";
@import "framework/common";
@import "framework/dropdowns";
@import "framework/files";
@import "framework/filters";
@import "framework/flash";
@import "framework/forms";
@import "framework/gfm";
@import "framework/gitlab_theme";
@import "framework/header";
@import "framework/highlight";
@import "framework/issue_box";
@import "framework/jquery";
@import "framework/lists";
@import "framework/logo";
@import "framework/markdown_area";
@import "framework/media_object";
@import "framework/mobile";
@import "framework/modal";
@import "framework/pagination";
@import "framework/panels";
@import "framework/popup";
@import "framework/secondary_navigation_elements";
@import "framework/selects";
@import "framework/sidebar";
@import "framework/contextual_sidebar";
@import "framework/tables";
@import "framework/notes";
@import "framework/tabs";
@import "framework/timeline";
@import "framework/tooltips";
@import "framework/toggle";
@import "framework/typography";
@import "framework/zen";
@import "framework/blank";
@import "framework/wells";
@import "framework/page_header";
@import "framework/awards";
@import "framework/images";
@import "framework/broadcast_messages";
@import "framework/emojis";
@import "framework/emoji_sprites";
@import "framework/icons";
@import "framework/snippets";
@import "framework/memory_graph";
@import "framework/responsive_tables";
@import "framework/stacked_progress_bar";
@import "framework/ci_variable_list";
@import "framework/feature_highlight";
@import 'framework/animations';
@import 'framework/vue_transitions';
@import 'framework/avatar';
@import 'framework/asciidoctor';
@import 'framework/banner';
@import 'framework/blocks';
@import 'framework/buttons';
@import 'framework/badges';
@import 'framework/calendar';
@import 'framework/callout';
@import 'framework/common';
@import 'framework/dropdowns';
@import 'framework/files';
@import 'framework/filters';
@import 'framework/flash';
@import 'framework/forms';
@import 'framework/gfm';
@import 'framework/gitlab_theme';
@import 'framework/header';
@import 'framework/highlight';
@import 'framework/issue_box';
@import 'framework/jquery';
@import 'framework/lists';
@import 'framework/logo';
@import 'framework/markdown_area';
@import 'framework/media_object';
@import 'framework/mobile';
@import 'framework/modal';
@import 'framework/pagination';
@import 'framework/panels';
@import 'framework/popup';
@import 'framework/secondary_navigation_elements';
@import 'framework/selects';
@import 'framework/sidebar';
@import 'framework/contextual_sidebar';
@import 'framework/tables';
@import 'framework/notes';
@import 'framework/tabs';
@import 'framework/timeline';
@import 'framework/tooltips';
@import 'framework/toggle';
@import 'framework/typography';
@import 'framework/zen';
@import 'framework/blank';
@import 'framework/wells';
@import 'framework/page_header';
@import 'framework/awards';
@import 'framework/images';
@import 'framework/broadcast_messages';
@import 'framework/emojis';
@import 'framework/icons';
@import 'framework/snippets';
@import 'framework/memory_graph';
@import 'framework/responsive_tables';
@import 'framework/stacked_progress_bar';
@import 'framework/ci_variable_list';
@import 'framework/feature_highlight';
......@@ -46,7 +46,7 @@
}
&.middle-block {
margin-top: 0;
margin-top: $gl-padding-24;
margin-bottom: 0;
}
......@@ -61,7 +61,7 @@
}
&.footer-block {
margin-top: 0;
margin-top: $gl-padding-24;
border-bottom: 0;
margin-bottom: -$gl-padding;
}
......
......@@ -452,6 +452,7 @@ img.emoji {
/** COMMON CLASSES **/
.prepend-top-0 { margin-top: 0; }
.prepend-top-2 { margin-top: 2px; }
.prepend-top-5 { margin-top: 5px; }
.prepend-top-8 { margin-top: $grid-size; }
.prepend-top-10 { margin-top: 10px; }
......
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -39,35 +39,10 @@
svg {
fill: currentColor;
&.s8 {
@include svg-size(8px);
}
&.s12 {
@include svg-size(12px);
}
&.s16 {
@include svg-size(16px);
}
&.s18 {
@include svg-size(18px);
}
&.s24 {
@include svg-size(24px);
}
&.s32 {
@include svg-size(32px);
}
&.s48 {
@include svg-size(48px);
}
&.s72 {
@include svg-size(72px);
$svg-sizes: 8 12 16 18 24 32 48 72;
@each $svg-size in $svg-sizes {
&.s#{$svg-size} {
@include svg-size(#{$svg-size}px);
}
}
}
......@@ -107,6 +107,16 @@
padding-top: 10px;
}
.referenced-commands {
background: $blue-50;
padding: $gl-padding-8 $gl-padding;
border-radius: $border-radius-default;
p {
margin: 0;
}
}
.md-preview-holder {
min-height: 167px;
padding: 10px 0;
......
......@@ -212,6 +212,7 @@ $tooltip-font-size: 12px;
/*
* Padding
*/
$gl-padding-24: 24px;
$gl-padding: 16px;
$gl-padding-8: 8px;
$gl-padding-4: 4px;
......
This diff is collapsed.
......@@ -110,7 +110,8 @@ class ApplicationController < ActionController::Base
def log_exception(exception)
Raven.capture_exception(exception) if sentry_enabled?
application_trace = ActionDispatch::ExceptionWrapper.new(env, exception).application_trace
backtrace_cleaner = Gitlab.rails5? ? env["action_dispatch.backtrace_cleaner"] : env
application_trace = ActionDispatch::ExceptionWrapper.new(backtrace_cleaner, exception).application_trace
application_trace.map! { |t| " #{t}\n" }
logger.error "\n#{exception.class.name} (#{exception.message}):\n#{application_trace.join}"
end
......
......@@ -57,7 +57,7 @@ module IssuableCollections
out_of_range = @issuables.current_page > total_pages # rubocop:disable Gitlab/ModuleWithInstanceVariables
if out_of_range
redirect_to(url_for(params.merge(page: total_pages, only_path: true)))
redirect_to(url_for(safe_params.merge(page: total_pages, only_path: true)))
end
out_of_range
......
......@@ -33,6 +33,6 @@ class Groups::ApplicationController < ApplicationController
def build_canonical_path(group)
params[:group_id] = group.to_param
url_for(params)
url_for(safe_params)
end
end
class Profiles::ActiveSessionsController < Profiles::ApplicationController
def index
@sessions = ActiveSession.list(current_user)
end
def destroy
ActiveSession.destroy(current_user, params[:id])
respond_to do |format|
format.html { redirect_to profile_active_sessions_url, status: 302 }
format.js { head :ok }
end
end
end
......@@ -25,7 +25,7 @@ class Projects::ApplicationController < ApplicationController
params[:namespace_id] = project.namespace.to_param
params[:project_id] = project.to_param
url_for(params)
url_for(safe_params)
end
def repository
......
......@@ -77,8 +77,7 @@ class Projects::LfsStorageController < Projects::GitHttpClientController
def link_to_project!(object)
if object && !object.projects.exists?(storage_project.id)
object.projects << storage_project
object.save!
object.lfs_objects_projects.create!(project: storage_project)
end
end
end
......@@ -83,13 +83,6 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
render layout: false
end
def update_branches
@target_project = selected_target_project
@target_branches = @target_project ? @target_project.repository.branch_names : []
render layout: false
end
private
def build_merge_request
......
module ActiveSessionsHelper
# Maps a device type as defined in `ActiveSession` to an svg icon name and
# outputs the icon html.
#
# see `DeviceDetector::Device::DEVICE_NAMES` about the available device types
def active_session_device_type_icon(active_session)
icon_name =
case active_session.device_type
when 'smartphone', 'feature phone', 'phablet'
'mobile'
when 'tablet'
'tablet'
when 'tv', 'smart display', 'camera', 'portable media player', 'console'
'media'
when 'car browser'
'car'
else
'monitor-o'
end
sprite_icon(icon_name, size: 16, css_class: 'prepend-top-2')
end
end
......@@ -442,7 +442,7 @@ module ProjectsHelper
visibilityHelpPath: help_page_path('public_access/public_access'),
registryAvailable: Gitlab.config.registry.enabled,
registryHelpPath: help_page_path('user/project/container_registry'),
lfsAvailable: Gitlab.config.lfs.enabled && current_user.admin?,
lfsAvailable: Gitlab.config.lfs.enabled,
lfsHelpPath: help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs')
}
......
class ActiveSession
include ActiveModel::Model
attr_accessor :created_at, :updated_at,
:session_id, :ip_address,
:browser, :os, :device_name, :device_type
def current?(session)
return false if session_id.nil? || session.id.nil?
session_id == session.id
end
def human_device_type
device_type&.titleize
end
def self.set(user, request)
Gitlab::Redis::SharedState.with do |redis|
session_id = request.session.id
client = DeviceDetector.new(request.user_agent)
timestamp = Time.current
active_user_session = new(
ip_address: request.ip,
browser: client.name,
os: client.os_name,
device_name: client.device_name,
device_type: client.device_type,
created_at: user.current_sign_in_at || timestamp,
updated_at: timestamp,
session_id: session_id
)
redis.pipelined do
redis.setex(
key_name(user.id, session_id),
Settings.gitlab['session_expire_delay'] * 60,
Marshal.dump(active_user_session)
)
redis.sadd(
lookup_key_name(user.id),
session_id
)
end
end
end
def self.list(user)
Gitlab::Redis::SharedState.with do |redis|
cleaned_up_lookup_entries(redis, user.id).map do |entry|
# rubocop:disable Security/MarshalLoad
Marshal.load(entry)
# rubocop:enable Security/MarshalLoad
end
end
end
def self.destroy(user, session_id)
Gitlab::Redis::SharedState.with do |redis|
redis.srem(lookup_key_name(user.id), session_id)
deleted_keys = redis.del(key_name(user.id, session_id))
# only allow deleting the devise session if we could actually find a
# related active session. this prevents another user from deleting
# someone else's session.
if deleted_keys > 0
redis.del("#{Gitlab::Redis::SharedState::SESSION_NAMESPACE}:#{session_id}")
end
end
end
def self.cleanup(user)
Gitlab::Redis::SharedState.with do |redis|
cleaned_up_lookup_entries(redis, user.id)
end
end
def self.key_name(user_id, session_id = '*')
"#{Gitlab::Redis::SharedState::USER_SESSIONS_NAMESPACE}:#{user_id}:#{session_id}"
end
def self.lookup_key_name(user_id)
"#{Gitlab::Redis::SharedState::USER_SESSIONS_LOOKUP_NAMESPACE}:#{user_id}"
end
def self.cleaned_up_lookup_entries(redis, user_id)
lookup_key = lookup_key_name(user_id)
session_ids = redis.smembers(lookup_key)
entry_keys = session_ids.map { |session_id| key_name(user_id, session_id) }
return [] if entry_keys.empty?
entries = redis.mget(entry_keys)
session_ids_and_entries = session_ids.zip(entries)
# remove expired keys.
# only the single key entries are automatically expired by redis, the
# lookup entries in the set need to be removed manually.
session_ids_and_entries.reject { |_session_id, entry| entry }.each do |session_id, _entry|
redis.srem(lookup_key, session_id)
end
session_ids_and_entries.select { |_session_id, entry| entry }.map { |_session_id, entry| entry }
end
end
......@@ -13,7 +13,7 @@ module Ci
after_save :update_project_statistics_after_save, if: :size_changed?
after_destroy :update_project_statistics_after_destroy, unless: :project_destroyed?
after_save :update_file_store
after_save :update_file_store, if: :file_changed?
scope :with_files_stored_locally, -> { where(file_store: [nil, ::JobArtifactUploader::Store::LOCAL]) }
......
......@@ -530,6 +530,17 @@ module Ci
@latest_builds_with_artifacts ||= builds.latest.with_artifacts_archive.to_a
end
# Rails 5.0 autogenerated question mark enum methods return wrong result if enum value is nil.
# They always return `false`.
# These methods overwrite autogenerated ones to return correct results.
def unknown?
Gitlab.rails5? ? source.nil? : super
end
def unknown_source?
Gitlab.rails5? ? config_source.nil? : super
end
private
def ci_yaml_from_repo
......
......@@ -13,14 +13,27 @@ module Ci
has_many :statuses, class_name: 'CommitStatus', foreign_key: :stage_id
has_many :builds, foreign_key: :stage_id
validates :project, presence: true, unless: :importing?
validates :pipeline, presence: true, unless: :importing?
validates :name, presence: true, unless: :importing?
with_options unless: :importing? do
validates :project, presence: true
validates :pipeline, presence: true
validates :name, presence: true
validates :position, presence: true
end
after_initialize do |stage|
after_initialize do
self.status = DEFAULT_STATUS if self.status.nil?
end
before_validation unless: :importing? do
next if position.present?
self.position = statuses.select(:stage_idx)
.where('stage_idx IS NOT NULL')
.group(:stage_idx)
.order('COUNT(*) DESC')
.first&.stage_idx.to_i
end
state_machine :status, initial: :created do
event :enqueue do
transition created: :pending
......
......@@ -424,6 +424,12 @@ class Commit
# no-op but needs to be defined since #persisted? is defined
end
def touch_later
# No-op.
# This method is called by ActiveRecord.
# We don't want to do anything for `Commit` model, so this is empty.
end
WIP_REGEX = /\A\s*(((?i)(\[WIP\]|WIP:|WIP)\s|WIP$))|(fixup!|squash!)\s/.freeze
def work_in_progress?
......
......@@ -189,4 +189,11 @@ class CommitStatus < ActiveRecord::Base
v =~ /\d+/ ? v.to_i : v
end
end
# Rails 5.0 autogenerated question mark enum methods return wrong result if enum value is nil.
# They always return `false`.
# This method overwrites the autogenerated one to return correct result.
def unknown_failure?
Gitlab.rails5? ? failure_reason.nil? : super
end
end
......@@ -11,7 +11,7 @@ class LfsObject < ActiveRecord::Base
mount_uploader :file, LfsObjectUploader
after_save :update_file_store
after_save :update_file_store, if: :file_changed?
def update_file_store
# The file.object_store is set during `uploader.store!`
......
......@@ -910,7 +910,7 @@ class User < ActiveRecord::Base
def delete_async(deleted_by:, params: {})
block if params[:hard_delete]
DeleteUserWorker.perform_async(deleted_by.id, id, params)
DeleteUserWorker.perform_async(deleted_by.id, id, params.to_h)
end
def notification_service
......
......@@ -42,6 +42,7 @@ module Ci
def create_stage
Ci::Stage.create!(name: @build.stage,
position: @build.stage_idx,
pipeline: @build.pipeline,
project: @build.project)
end
......
......@@ -50,21 +50,30 @@ module MergeRequests
end
def commit
message = params[:commit_message] || merge_request.merge_commit_message
log_info("Git merge started on JID #{merge_jid}")
commit_id = repository.merge(current_user, source, merge_request, message)
log_info("Git merge finished on JID #{merge_jid} commit #{commit_id}")
commit_id = try_merge
if commit_id
log_info("Git merge finished on JID #{merge_jid} commit #{commit_id}")
else
raise MergeError, 'Conflicts detected during merge'
end
raise MergeError, 'Conflicts detected during merge' unless commit_id
merge_request.update!(merge_commit_sha: commit_id)
end
def try_merge
message = params[:commit_message] || merge_request.merge_commit_message
merge_request.update(merge_commit_sha: commit_id)
repository.merge(current_user, source, merge_request, message)
rescue Gitlab::Git::HooksService::PreReceiveError => e
raise MergeError, e.message
rescue StandardError => e
raise MergeError, "Something went wrong during merge: #{e.message}"
handle_merge_error(log_message: e.message)
raise MergeError, 'Something went wrong during merge pre-receive hook'
rescue => e
handle_merge_error(log_message: e.message)
raise MergeError, 'Something went wrong during merge'
ensure
merge_request.update(in_progress_merge_commit_sha: nil)
merge_request.update!(in_progress_merge_commit_sha: nil)
end
def after_merge
......
......@@ -13,7 +13,7 @@
.panel
.panel-heading.alert.alert-danger
Last repository check
= "(#{time_ago_in_words(@project.last_repository_check_at)} ago)"
= "(#{time_ago_with_tooltip(@project.last_repository_check_at)})"
failed. See
= link_to 'repocheck.log', admin_logs_path
for error messages.
......
......@@ -31,7 +31,7 @@
= tag
%td
- if runner.contacted_at
#{time_ago_in_words(runner.contacted_at)} ago
= time_ago_with_tooltip runner.contacted_at
- else
Never
%td.admin-runner-btn-group-cell
......
......@@ -108,4 +108,4 @@
%td.timestamp
- if build.finished_at
%span #{time_ago_in_words build.finished_at} ago
%span= time_ago_with_tooltip build.finished_at
......@@ -20,5 +20,4 @@
%td
= service.description
%td.light
= time_ago_in_words service.updated_at
ago
= time_ago_with_tooltip service.updated_at
......@@ -2,7 +2,7 @@
= email_default_heading("Hello, #{@resource.name}!")
%p
Your GitLab account has been locked due to an excessive amount of unsuccessful
sign in attempts. Your account will automatically unlock in #{time_ago_in_words(Devise.unlock_in.from_now)}
sign in attempts. Your account will automatically unlock in #{distance_of_time_in_words(Devise.unlock_in)}
or you may click the link below to unlock now.
#cta
= link_to('Unlock account', unlock_url(@resource, unlock_token: @token))
Hello, <%= @resource.name %>!
Your GitLab account has been locked due to an excessive amount of unsuccessful
sign in attempts. Your account will automatically unlock in <%= time_ago_in_words(Devise.unlock_in.from_now) %>
sign in attempts. Your account will automatically unlock in <%= distance_of_time_in_words(Devise.unlock_in) %>
or you may click the link below to unlock now.
<%= unlock_url(@resource, unlock_token: @token) %>
- if current_user.admin?
.form-group
= f.label :lfs_enabled, 'Large File Storage', class: 'control-label'
.col-sm-10
.checkbox
= f.label :lfs_enabled do
= f.check_box :lfs_enabled, checked: @group.lfs_enabled?
%strong
Allow projects within this group to use Git LFS
= link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs')
%br/
%span.descr This setting can be overridden in each project.
.form-group
= f.label :lfs_enabled, 'Large File Storage', class: 'control-label'
.col-sm-10
.checkbox
= f.label :lfs_enabled do
= f.check_box :lfs_enabled, checked: @group.lfs_enabled?
%strong
Allow projects within this group to use Git LFS
= link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs')
%br/
%span.descr This setting can be overridden in each project.
- if can? current_user, :admin_group, @group
.form-group
= f.label :require_two_factor_authentication, 'Two-factor authentication', class: 'control-label col-sm-2'
.col-sm-10
.checkbox
= f.label :require_two_factor_authentication do
= f.check_box :require_two_factor_authentication
%strong
Require all users in this group to setup Two-factor authentication
= link_to icon('question-circle'), help_page_path('security/two_factor_authentication', anchor: 'enforcing-2fa-for-all-users-in-a-group')
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
= f.text_field :two_factor_grace_period, class: 'form-control'
.help-block Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication
.form-group
= f.label :require_two_factor_authentication, 'Two-factor authentication', class: 'control-label col-sm-2'
.col-sm-10
.checkbox
= f.label :require_two_factor_authentication do
= f.check_box :require_two_factor_authentication
%strong
Require all users in this group to setup Two-factor authentication
= link_to icon('question-circle'), help_page_path('security/two_factor_authentication', anchor: 'enforcing-2fa-for-all-users-in-a-group')
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
= f.text_field :two_factor_grace_period, class: 'form-control'
.help-block Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication
......@@ -129,6 +129,17 @@
= link_to profile_preferences_path do
%strong.fly-out-top-item-name
#{ _('Preferences') }
= nav_link(controller: :active_sessions) do
= link_to profile_active_sessions_path do
.nav-icon-container
= sprite_icon('monitor-lines')
%span.nav-item-name
Active Sessions
%ul.sidebar-sub-level-items.is-fly-out-only
= nav_link(controller: :active_sessions, html_options: { class: "fly-out-top-item" } ) do
= link_to profile_active_sessions_path do
%strong.fly-out-top-item-name
#{ _('Active Sessions') }
= nav_link(path: 'profiles#audit_log') do
= link_to audit_log_profile_path do
.nav-icon-container
......
......@@ -3,5 +3,5 @@
#js-peek{ data: { env: Peek.env,
request_id: Peek.request_id,
peek_url: peek_routes.results_url,
profile_url: url_for(params.merge(lineprofiler: 'true')) },
profile_url: url_for(safe_params.merge(lineprofiler: 'true')) },
class: Peek.env }
- is_current_session = active_session.current?(session)
%li
.pull-left.append-right-10{ data: { toggle: 'tooltip' }, title: active_session.human_device_type }
= active_session_device_type_icon(active_session)
.description.pull-left
%div
%strong= active_session.ip_address
- if is_current_session
%div This is your current session
- else
%div
Last accessed on
= l(active_session.updated_at, format: :short)
%div
%strong= active_session.browser
on
%strong= active_session.os
%div
%strong Signed in
on
= l(active_session.created_at, format: :short)
- unless is_current_session
.pull-right
= link_to profile_active_session_path(active_session.session_id), data: { confirm: 'Are you sure? The device will be signed out of GitLab.' }, method: :delete, class: "btn btn-danger prepend-left-10" do
%span.sr-only Revoke
Revoke
- page_title 'Active Sessions'
- @content_class = "limit-container-width" unless fluid_layout
.row.prepend-top-default
.col-lg-4.profile-settings-sidebar
%h4.prepend-top-0
= page_title
%p
This is a list of devices that have logged into your account. Revoke any sessions that you do not recognize.
.col-lg-8
.append-bottom-default
%ul.well-list
= render partial: 'profiles/active_sessions/active_session', collection: @sessions
......@@ -8,7 +8,7 @@
.files-changed-inner
.inline-parallel-buttons.hidden-xs.hidden-sm
- if !diffs_expanded? && diff_files.any? { |diff_file| diff_file.collapsed? }
= link_to 'Expand all', url_for(params.merge(expanded: 1, format: nil)), class: 'btn btn-default'
= link_to 'Expand all', url_for(safe_params.merge(expanded: 1, format: nil)), class: 'btn btn-default'
- if show_whitespace_toggle
- if current_controller?(:commit)
= commit_diff_whitespace_link(diffs.project, @commit, class: 'hidden-xs')
......
......@@ -15,7 +15,7 @@
- elsif @build.has_expiring_artifacts?
%p.build-detail-row
The artifacts will be removed in
%span= time_ago_in_words @build.artifacts_expire_at
%span= time_ago_with_tooltip @build.artifacts_expire_at
- if @build.artifacts?
.btn-group.btn-group-justified{ role: :group }
......
......@@ -3,7 +3,7 @@
= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], url: project_new_merge_request_path(@project), method: :get, html: { class: "merge-request-form form-inline js-requires-input" } do |f|
.hide.alert.alert-danger.mr-compare-errors
.js-merge-request-new-compare.row{ 'data-target-project-url': project_new_merge_request_update_branches_path(@source_project), 'data-source-branch-url': project_new_merge_request_branch_from_path(@source_project), 'data-target-branch-url': project_new_merge_request_branch_to_path(@source_project) }
.js-merge-request-new-compare.row{ 'data-source-branch-url': project_new_merge_request_branch_from_path(@source_project), 'data-target-branch-url': project_new_merge_request_branch_to_path(@source_project) }
.col-md-6
.panel.panel-default.panel-new-merge-request
.panel-heading
......@@ -11,7 +11,7 @@
.panel-body.clearfix
.merge-request-select.dropdown
= f.hidden_field :source_project_id
= dropdown_toggle @merge_request.source_project_path, { toggle: "dropdown", field_name: "#{f.object_name}[source_project_id]", disabled: @merge_request.persisted? }, { toggle_class: "js-compare-dropdown js-source-project" }
= dropdown_toggle @merge_request.source_project_path, { toggle: "dropdown", 'field-name': "#{f.object_name}[source_project_id]", disabled: @merge_request.persisted? }, { toggle_class: "js-compare-dropdown js-source-project" }
.dropdown-menu.dropdown-menu-selectable.dropdown-source-project
= dropdown_title("Select source project")
= dropdown_filter("Search projects")
......@@ -21,14 +21,12 @@
selected: f.object.source_project_id
.merge-request-select.dropdown
= f.hidden_field :source_branch
= dropdown_toggle f.object.source_branch || "Select source branch", { toggle: "dropdown", field_name: "#{f.object_name}[source_branch]" }, { toggle_class: "js-compare-dropdown js-source-branch git-revision-dropdown-toggle" }
.dropdown-menu.dropdown-menu-selectable.dropdown-source-branch.git-revision-dropdown
= dropdown_title("Select source branch")
= dropdown_filter("Search branches")
= dropdown_content do
= render 'projects/merge_requests/dropdowns/branch',
branches: @merge_request.source_branches,
selected: f.object.source_branch
= dropdown_toggle f.object.source_branch || _("Select source branch"), { toggle: "dropdown", 'field-name': "#{f.object_name}[source_branch]", 'refs-url': refs_project_path(@source_project), selected: f.object.source_branch }, { toggle_class: "js-compare-dropdown js-source-branch git-revision-dropdown-toggle" }
.dropdown-menu.dropdown-menu-selectable.js-source-branch-dropdown.git-revision-dropdown
= dropdown_title(_("Select source branch"))
= dropdown_filter(_("Search branches"))
= dropdown_content
= dropdown_loading
.panel-footer
.text-center= icon('spinner spin', class: 'js-source-loading')
%ul.list-unstyled.mr_source_commit
......@@ -41,7 +39,7 @@
- projects = target_projects(@project)
.merge-request-select.dropdown
= f.hidden_field :target_project_id
= dropdown_toggle f.object.target_project.full_path, { toggle: "dropdown", field_name: "#{f.object_name}[target_project_id]", disabled: @merge_request.persisted? }, { toggle_class: "js-compare-dropdown js-target-project" }
= dropdown_toggle f.object.target_project.full_path, { toggle: "dropdown", 'field-name': "#{f.object_name}[target_project_id]", disabled: @merge_request.persisted? }, { toggle_class: "js-compare-dropdown js-target-project" }
.dropdown-menu.dropdown-menu-selectable.dropdown-target-project
= dropdown_title("Select target project")
= dropdown_filter("Search projects")
......@@ -51,14 +49,12 @@
selected: f.object.target_project_id
.merge-request-select.dropdown
= f.hidden_field :target_branch
= dropdown_toggle f.object.target_branch, { toggle: "dropdown", field_name: "#{f.object_name}[target_branch]" }, { toggle_class: "js-compare-dropdown js-target-branch git-revision-dropdown-toggle" }
.dropdown-menu.dropdown-menu-selectable.dropdown-target-branch.js-target-branch-dropdown.git-revision-dropdown
= dropdown_title("Select target branch")
= dropdown_filter("Search branches")
= dropdown_content do
= render 'projects/merge_requests/dropdowns/branch',
branches: @merge_request.target_branches,
selected: f.object.target_branch
= dropdown_toggle f.object.target_branch, { toggle: "dropdown", 'field-name': "#{f.object_name}[target_branch]", 'refs-url': refs_project_path(f.object.target_project), selected: f.object.target_branch }, { toggle_class: "js-compare-dropdown js-target-branch git-revision-dropdown-toggle" }
.dropdown-menu.dropdown-menu-selectable.js-target-branch-dropdown.git-revision-dropdown
= dropdown_title(_("Select target branch"))
= dropdown_filter(_("Search branches"))
= dropdown_content
= dropdown_loading
.panel-footer
.text-center= icon('spinner spin', class: "js-target-loading")
%ul.list-unstyled.mr_target_commit
......
%ul
- projects.each do |project|
%li
%a{ href: "#", class: "#{('is-active' if selected == project.id)}", data: { id: project.id } }
%a{ href: "#", class: "#{('is-active' if selected == project.id)}", data: { id: project.id, 'refs-url': refs_project_path(project) } }
= project.full_path
......@@ -18,7 +18,7 @@
\-
%td
- if tag.created_at
= time_ago_in_words(tag.created_at)
= time_ago_with_tooltip tag.created_at
- else
.light
\-
......
......@@ -62,6 +62,6 @@
%td Last contact
%td
- if @runner.contacted_at
#{time_ago_in_words(@runner.contacted_at)} ago
= time_ago_with_tooltip @runner.contacted_at
- else
Never
......@@ -27,5 +27,4 @@
= service.description
%td.light
- if service.updated_at.present?
= time_ago_in_words service.updated_at
ago
= time_ago_with_tooltip service.updated_at
......@@ -25,7 +25,7 @@
%td
- if trigger.last_used
#{time_ago_in_words(trigger.last_used)} ago
= time_ago_with_tooltip trigger.last_used
- else
Never
......
......@@ -35,5 +35,4 @@
%span.light
#{t('sherlock.finished_at')}:
%strong
= time_ago_in_words(@transaction.finished_at)
= t('sherlock.ago')
= time_ago_with_tooltip @transaction.finished_at
......@@ -35,8 +35,7 @@
= t('sherlock.seconds')
%td= trans.queries.length
%td
= time_ago_in_words(trans.finished_at)
= t('sherlock.ago')
= time_ago_with_tooltip trans.finished_at
%td
= link_to(sherlock_transaction_path(trans), class: 'btn btn-xs') do
= t('sherlock.view')
---
title: Fixed wrong avatar URL when the avatar is on object storage.
merge_request: 18092
author:
type: fixed
---
title: Replace time_ago_in_words with JS-based one
merge_request: 18607
author: Takuya Noguchi
type: performance
---
title: Ports omniauth-jwt gem onto GitLab OmniAuth Strategies suite
merge_request: 18580
author:
type: fixed
---
title: Replace the `project/source/markdown_render.feature` spinach test with an rspec analog
merge_request: 18525
author: "@blackst0ne"
type: other
---
title: Fix errors on pushing to an empty repository
merge_request: 18462
author:
type: fixed
---
title: Fix redirection error for applications using OpenID
merge_request: 18599
author:
type: fixed
---
title: Fix commit trailer rendering when Gravatar is disabled
merge_request:
author:
type: fixed
---
title: Display active sessions and allow the user to revoke any of it
merge_request: 17867
author: Alexis Reigel
type: added
---
title: Improve quick actions summary preview
merge_request: 18659
author: George Tsiolis
type: changed
---
title: Increase new issue metadata form margin
merge_request: 18630
author: George Tsiolis
type: fixed
---
title: Fix users not seeing labels from private groups when being a member of a child project
merge_request:
author:
type: fixed
---
title: Display only generic message on merge error to avoid exposing any potentially
sensitive or user unfriendly backend messages.
merge_request:
author:
type: fixed
---
title: Show group and project LFS settings in the interface to Owners and Masters
merge_request: 18562
author:
type: changed
---
title: Move WorkInProgress vue component
merge_request: 17536
author: George Tsiolis
type: performance
---
title: Move TimeTrackingNoTrackingPane vue component
merge_request: 18676
author: George Tsiolis
type: performance
---
title: Move SidebarTimeTracking vue component
merge_request: 18677
author: George Tsiolis
type: performance
---
title: Update doorkeeper to 4.3.2 to fix GitLab OAuth authentication
merge_request: 18543
author:
type: fixed
---
title: Load branches on new merge request page asynchronously
merge_request: 18315
author:
type: changed
---
title: Finish NamespaceService migration to Gitaly
merge_request:
author:
type: performance
---
title: Compute Gitlab::Git::Repository#checksum on Gitaly by default
merge_request:
author:
type: performance
......@@ -115,6 +115,7 @@ module Gitlab
config.assets.precompile << "test.css"
config.assets.precompile << "snippets.css"
config.assets.precompile << "locale/**/app.js"
config.assets.precompile << "emoji_sprites.css"
# Import gitlab-svgs directly from vendored directory
config.assets.paths << "#{config.root}/node_modules/@gitlab-org/gitlab-svgs/dist"
......
......@@ -15,19 +15,15 @@ cookie_key = if Rails.env.development?
"_gitlab_session"
end
if Rails.env.test?
Gitlab::Application.config.session_store :cookie_store, key: "_gitlab_session"
else
sessions_config = Gitlab::Redis::SharedState.params
sessions_config[:namespace] = Gitlab::Redis::SharedState::SESSION_NAMESPACE
sessions_config = Gitlab::Redis::SharedState.params
sessions_config[:namespace] = Gitlab::Redis::SharedState::SESSION_NAMESPACE
Gitlab::Application.config.session_store(
:redis_store, # Using the cookie_store would enable session replay attacks.
servers: sessions_config,
key: cookie_key,
secure: Gitlab.config.gitlab.https,
httponly: true,
expires_in: Settings.gitlab['session_expire_delay'] * 60,
path: Rails.application.config.relative_url_root.nil? ? '/' : Gitlab::Application.config.relative_url_root
)
end
Gitlab::Application.config.session_store(
:redis_store, # Using the cookie_store would enable session replay attacks.
servers: sessions_config,
key: cookie_key,
secure: Gitlab.config.gitlab.https,
httponly: true,
expires_in: Settings.gitlab['session_expire_delay'] * 60,
path: Rails.application.config.relative_url_root.nil? ? '/' : Gitlab::Application.config.relative_url_root
)
......@@ -6,4 +6,16 @@ Rails.application.configure do |config|
Warden::Manager.before_failure do |env, opts|
Gitlab::Auth::BlockedUserTracker.log_if_user_blocked(env)
end
Warden::Manager.after_authentication do |user, auth, opts|
ActiveSession.cleanup(user)
end
Warden::Manager.after_set_user only: :fetch do |user, auth, opts|
ActiveSession.set(user, auth.request)
end
Warden::Manager.before_logout do |user, auth, opts|
ActiveSession.destroy(user || auth.user, auth.request.session.id)
end
end
......@@ -30,6 +30,7 @@ resource :profile, only: [:show, :update] do
put :revoke
end
end
resources :active_sessions, only: [:index, :destroy]
resources :emails, only: [:index, :create, :destroy] do
member do
put :resend_confirmation_instructions
......
......@@ -161,7 +161,6 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end
get :diff_for_path
get :update_branches
get :branch_from
get :branch_to
end
......
class AddTmpStagePriorityIndexToCiBuilds < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_concurrent_index(:ci_builds, [:stage_id, :stage_idx],
where: 'stage_idx IS NOT NULL', name: 'tmp_build_stage_position_index')
end
def down
remove_concurrent_index_by_name(:ci_builds, 'tmp_build_stage_position_index')
end
end
class AddIndexToCiStage < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def change
add_column :ci_stages, :position, :integer
end
end
class ScheduleStagesIndexMigration < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
MIGRATION = 'MigrateStageIndex'.freeze
BATCH_SIZE = 10000
disable_ddl_transaction!
class Stage < ActiveRecord::Base
include EachBatch
self.table_name = 'ci_stages'
end
def up
disable_statement_timeout
Stage.all.tap do |relation|
queue_background_migration_jobs_by_range_at_intervals(relation,
MIGRATION,
5.minutes,
batch_size: BATCH_SIZE)
end
end
def down
# noop
end
end
......@@ -331,6 +331,7 @@ ActiveRecord::Schema.define(version: 20180425131009) do
add_index "ci_builds", ["project_id", "id"], name: "index_ci_builds_on_project_id_and_id", using: :btree
add_index "ci_builds", ["protected"], name: "index_ci_builds_on_protected", using: :btree
add_index "ci_builds", ["runner_id"], name: "index_ci_builds_on_runner_id", using: :btree
add_index "ci_builds", ["stage_id", "stage_idx"], name: "tmp_build_stage_position_index", where: "(stage_idx IS NOT NULL)", using: :btree
add_index "ci_builds", ["stage_id"], name: "index_ci_builds_on_stage_id", using: :btree
add_index "ci_builds", ["status", "type", "runner_id"], name: "index_ci_builds_on_status_and_type_and_runner_id", using: :btree
add_index "ci_builds", ["status"], name: "index_ci_builds_on_status", using: :btree
......@@ -495,6 +496,7 @@ ActiveRecord::Schema.define(version: 20180425131009) do
t.string "name"
t.integer "status"
t.integer "lock_version"
t.integer "position"
end
add_index "ci_stages", ["pipeline_id", "name"], name: "index_ci_stages_on_pipeline_id_and_name", unique: true, using: :btree
......
......@@ -49,7 +49,7 @@ Please use the following function inside JS to render an icon :
All Icons and Illustrations are managed in the [gitlab-svgs](https://gitlab.com/gitlab-org/gitlab-svgs) repository which is added as a dev-dependency.
To upgrade to a new SVG Sprite version run `yarn upgrade @gitlab-org/gitlab-svgs` and then run `yarn run svg`. This task will copy the svg sprite and all illustrations in the correct folders. The updated files should be tracked in Git as those are referenced.
To upgrade to a new SVG Sprite version run `yarn upgrade @gitlab-org/gitlab-svgs`.
# SVG Illustrations
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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