Commit 802079a4 authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Merge branch 'ce-to-ee-2017-08-03' into 'master'

CE upstream: Thursday

Closes gitaly#415, gitlab-ce#35592, gitaly#398, and #2604

See merge request !2589
parents b38cc0c2 570813d6
This diff is collapsed.
This diff is collapsed.
......@@ -326,11 +326,11 @@ group :development, :test do
gem 'pry-rails', '~> 0.3.4'
gem 'awesome_print', '~> 1.2.0', require: false
gem 'fuubar', '~> 2.0.0'
gem 'fuubar', '~> 2.2.0'
gem 'database_cleaner', '~> 1.5.0'
gem 'factory_girl_rails', '~> 4.7.0'
gem 'rspec-rails', '~> 3.5.0'
gem 'rspec-rails', '~> 3.6.0'
gem 'rspec-retry', '~> 0.4.5'
gem 'spinach-rails', '~> 0.2.1'
gem 'spinach-rerun-reporter', '~> 0.0.2'
......@@ -351,8 +351,8 @@ group :development, :test do
gem 'spring-commands-rspec', '~> 1.0.4'
gem 'spring-commands-spinach', '~> 1.1.0'
gem 'rubocop', '~> 0.47.1', require: false
gem 'rubocop-rspec', '~> 1.15.0', require: false
gem 'rubocop', '~> 0.49.1', require: false
gem 'rubocop-rspec', '~> 1.15.1', require: false
gem 'scss_lint', '~> 0.54.0', require: false
gem 'haml_lint', '~> 0.21.0', require: false
gem 'simplecov', '~> 0.14.0', require: false
......@@ -406,7 +406,7 @@ gem 'sys-filesystem', '~> 1.1.6'
gem 'net-ntp'
# Gitaly GRPC client
gem 'gitaly', '~> 0.21.0'
gem 'gitaly', '~> 0.23.0'
gem 'toml-rb', '~> 0.3.15', require: false
......
......@@ -164,7 +164,7 @@ GEM
devise (~> 4.0)
railties
rotp (~> 2.0)
diff-lcs (1.2.5)
diff-lcs (1.3)
diffy (3.1.0)
docile (1.1.5)
domain_name (0.5.20161021)
......@@ -274,8 +274,8 @@ GEM
foreman (0.78.0)
thor (~> 0.19.1)
formatador (0.2.5)
fuubar (2.0.0)
rspec (~> 3.0)
fuubar (2.2.0)
rspec-core (~> 3.0)
ruby-progressbar (~> 1.4)
gemnasium-gitlab-service (0.2.6)
rugged (~> 0.21)
......@@ -293,7 +293,7 @@ GEM
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
gherkin-ruby (0.3.2)
gitaly (0.21.0)
gitaly (0.23.0)
google-protobuf (~> 3.1)
grpc (~> 1.0)
github-linguist (4.7.6)
......@@ -348,7 +348,7 @@ GEM
multi_json (~> 1.10)
retriable (~> 1.4)
signet (~> 0.6)
google-protobuf (3.2.0.2)
google-protobuf (3.3.0)
googleauth (0.5.1)
faraday (~> 0.9)
jwt (~> 1.4)
......@@ -420,7 +420,7 @@ GEM
json (~> 1.8)
multi_xml (>= 0.5.2)
httpclient (2.8.2)
i18n (0.8.1)
i18n (0.8.6)
ice_nine (0.11.2)
influxdb (0.2.3)
cause
......@@ -574,6 +574,7 @@ GEM
rubypants (~> 0.2)
orm_adapter (0.5.0)
os (0.9.6)
parallel (1.11.2)
paranoia (2.3.1)
activerecord (>= 4.0, < 5.2)
parser (2.4.0.0)
......@@ -638,7 +639,7 @@ GEM
pry-rails (0.3.5)
pry (>= 0.9.10)
pyu-ruby-sasl (0.0.3.3)
rack (1.6.5)
rack (1.6.8)
rack-accept (0.4.5)
rack (>= 0.4)
rack-attack (4.4.1)
......@@ -686,7 +687,7 @@ GEM
rainbow (2.2.2)
rake
raindrops (0.18.0)
rake (10.5.0)
rake (12.0.0)
rblineprof (0.3.6)
debugger-ruby_core_source (~> 1.3)
rdoc (4.2.2)
......@@ -730,42 +731,39 @@ GEM
chunky_png
rqrcode-rails3 (0.1.7)
rqrcode (>= 0.4.2)
rspec (3.5.0)
rspec-core (~> 3.5.0)
rspec-expectations (~> 3.5.0)
rspec-mocks (~> 3.5.0)
rspec-core (3.5.0)
rspec-support (~> 3.5.0)
rspec-expectations (3.5.0)
rspec-core (3.6.0)
rspec-support (~> 3.6.0)
rspec-expectations (3.6.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.5.0)
rspec-mocks (3.5.0)
rspec-support (~> 3.6.0)
rspec-mocks (3.6.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.5.0)
rspec-rails (3.5.0)
rspec-support (~> 3.6.0)
rspec-rails (3.6.0)
actionpack (>= 3.0)
activesupport (>= 3.0)
railties (>= 3.0)
rspec-core (~> 3.5.0)
rspec-expectations (~> 3.5.0)
rspec-mocks (~> 3.5.0)
rspec-support (~> 3.5.0)
rspec-core (~> 3.6.0)
rspec-expectations (~> 3.6.0)
rspec-mocks (~> 3.6.0)
rspec-support (~> 3.6.0)
rspec-retry (0.4.5)
rspec-core
rspec-set (0.1.3)
rspec-support (3.5.0)
rspec-support (3.6.0)
rspec_profiling (0.0.5)
activerecord
pg
rails
sqlite3
rubocop (0.47.1)
rubocop (0.49.1)
parallel (~> 1.10)
parser (>= 2.3.3.1, < 3.0)
powerpack (~> 0.1)
rainbow (>= 1.99.1, < 3.0)
ruby-progressbar (~> 1.7)
unicode-display_width (~> 1.0, >= 1.0.1)
rubocop-rspec (1.15.0)
rubocop-rspec (1.15.1)
rubocop (>= 0.42.0)
ruby-fogbugz (0.2.1)
crack (~> 0.4)
......@@ -887,7 +885,7 @@ GEM
truncato (0.7.8)
htmlentities (~> 4.3.1)
nokogiri (~> 1.6.1)
tzinfo (1.2.2)
tzinfo (1.2.3)
thread_safe (~> 0.1)
u2f (0.2.1)
uglifier (2.7.2)
......@@ -897,7 +895,7 @@ GEM
unf (0.1.4)
unf_ext
unf_ext (0.0.7.2)
unicode-display_width (1.1.3)
unicode-display_width (1.3.0)
unicorn (5.1.0)
kgio (~> 2.6)
raindrops (~> 0.7)
......@@ -1004,13 +1002,13 @@ DEPENDENCIES
fog-rackspace (~> 0.1.1)
font-awesome-rails (~> 4.7)
foreman (~> 0.78.0)
fuubar (~> 2.0.0)
fuubar (~> 2.2.0)
gemnasium-gitlab-service (~> 0.2)
gemojione (~> 3.0)
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.2.0)
gitaly (~> 0.21.0)
gitaly (~> 0.23.0)
github-linguist (~> 4.7.0)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-license (~> 1.0)
......@@ -1111,12 +1109,12 @@ DEPENDENCIES
responders (~> 2.0)
rouge (~> 2.0)
rqrcode-rails3 (~> 0.1.7)
rspec-rails (~> 3.5.0)
rspec-rails (~> 3.6.0)
rspec-retry (~> 0.4.5)
rspec-set (~> 0.1.3)
rspec_profiling (~> 0.0.5)
rubocop (~> 0.47.1)
rubocop-rspec (~> 1.15.0)
rubocop (~> 0.49.1)
rubocop-rspec (~> 1.15.1)
ruby-fogbugz (~> 0.2.1)
ruby-prof (~> 0.16.2)
ruby_parser (~> 3.8)
......@@ -1165,4 +1163,4 @@ DEPENDENCIES
wikicloth (= 0.8.1)
BUNDLED WITH
1.15.1
1.15.3
......@@ -128,7 +128,7 @@ information, see
### After the 7th
Once the stable branch is frozen, only fixes for regressions (bugs introduced in that same release)
Once the stable branch is frozen, only fixes for [regressions](#regressions)
and security issues will be cherry-picked into the stable branch.
Any merge requests cherry-picked into the stable branch for a previous release will also be picked into the latest stable branch.
These fixes will be shipped in the next RC for that release if it is before the 22nd.
......@@ -158,6 +158,24 @@ release should have the correct milestone assigned _and_ have the label
Merge requests without a milestone and this label will
not be merged into any stable branches.
### Regressions
A regression for a particular monthly release is a bug that exists in that
release, but wasn't present in the release before. This includes bugs in
features that were only added in that monthly release. Every regression **must**
have the milestone of the release it was introduced in - if a regression doesn't
have a milestone, it might be 'just' a bug!
For instance, if 10.5.0 adds a feature, and that feature doesn't work correctly,
then this is a regression in 10.5. If 10.5.1 then fixes that, but 10.5.3 somehow
reintroduces the bug, then this bug is still a regression in 10.5.
Because GitLab.com runs release candidates of new releases, a regression can be
reported in a release before its 'official' release date on the 22nd of the
month. When we say 'the most recent monthly release', this can refer to either
the version currently running on GitLab.com, or the most recent version
available in the package repositories.
## Release retrospective and kickoff
### Retrospective
......
......@@ -10,7 +10,7 @@ class AjaxLoadingSpinner {
e.target.setAttribute('disabled', '');
const iconElement = e.target.querySelector('i');
// get first fa- icon
const originalIcon = iconElement.className.match(/(fa-)([^\s]+)/g).first();
const originalIcon = iconElement.className.match(/(fa-)([^\s]+)/g)[0];
iconElement.dataset.icon = originalIcon;
AjaxLoadingSpinner.toggleLoadingIcon(iconElement);
$(e.target).off('ajax:beforeSend', AjaxLoadingSpinner.ajaxBeforeSend);
......
......@@ -64,7 +64,7 @@ window.Build = (function () {
$(window)
.off('scroll')
.on('scroll', () => {
const contentHeight = this.$buildTraceOutput.prop('scrollHeight');
const contentHeight = this.$buildTraceOutput.height();
if (contentHeight > this.windowSize) {
// means the user did not scroll, the content was updated.
this.windowSize = contentHeight;
......@@ -105,16 +105,17 @@ window.Build = (function () {
};
Build.prototype.canScroll = function () {
return document.body.scrollHeight > window.innerHeight;
return $(document).height() > $(window).height();
};
Build.prototype.toggleScroll = function () {
const currentPosition = document.body.scrollTop;
const windowHeight = window.innerHeight;
const currentPosition = $(document).scrollTop();
const scrollHeight = $(document).height();
const windowHeight = $(window).height();
if (this.canScroll()) {
if (currentPosition > 0 &&
(document.body.scrollHeight - currentPosition !== windowHeight)) {
(scrollHeight - currentPosition !== windowHeight)) {
// User is in the middle of the log
this.toggleDisableButton(this.$scrollTopBtn, false);
......@@ -124,7 +125,7 @@ window.Build = (function () {
this.toggleDisableButton(this.$scrollTopBtn, true);
this.toggleDisableButton(this.$scrollBottomBtn, false);
} else if (document.body.scrollHeight - currentPosition === windowHeight) {
} else if (scrollHeight - currentPosition === windowHeight) {
// User is at the bottom of the build log.
this.toggleDisableButton(this.$scrollTopBtn, false);
......@@ -137,7 +138,7 @@ window.Build = (function () {
};
Build.prototype.scrollDown = function () {
document.body.scrollTop = document.body.scrollHeight;
$(document).scrollTop($(document).height());
};
Build.prototype.scrollToBottom = function () {
......@@ -147,7 +148,7 @@ window.Build = (function () {
};
Build.prototype.scrollToTop = function () {
document.body.scrollTop = 0;
$(document).scrollTop(0);
this.hasBeenScrolled = true;
this.toggleScroll();
};
......@@ -178,7 +179,7 @@ window.Build = (function () {
this.state = log.state;
}
this.windowSize = this.$buildTraceOutput.prop('scrollHeight');
this.windowSize = this.$buildTraceOutput.height();
if (log.append) {
this.$buildTraceOutput.append(log.html);
......
......@@ -92,7 +92,7 @@ $(() => {
});
},
selectDefaultStage() {
const stage = this.state.stages.first();
const stage = this.state.stages[0];
this.selectStage(stage);
},
selectStage(stage) {
......
......@@ -94,7 +94,7 @@ const JumpToDiscussion = Vue.extend({
hasDiscussionsToJumpTo = false;
}
}
} else if (activeTab !== 'notes') {
} else if (activeTab !== 'show') {
// If we are on the commits or builds tabs,
// there are no discussions to jump to.
hasDiscussionsToJumpTo = false;
......@@ -103,12 +103,12 @@ const JumpToDiscussion = Vue.extend({
if (!hasDiscussionsToJumpTo) {
// If there are no discussions to jump to on the current page,
// switch to the notes tab and jump to the first disucssion there.
window.mrTabs.activateTab('notes');
activeTab = 'notes';
window.mrTabs.activateTab('show');
activeTab = 'show';
jumpToFirstDiscussion = true;
}
if (activeTab === 'notes') {
if (activeTab === 'show') {
discussionsSelector = '.discussion[data-discussion-id]';
discussionIdsInScope = discussionIdsForElements($(discussionsSelector));
}
......@@ -156,7 +156,7 @@ const JumpToDiscussion = Vue.extend({
let $target = $(`${discussionsSelector}[data-discussion-id="${nextUnresolvedDiscussionId}"]`);
if (activeTab === 'notes') {
if (activeTab === 'show') {
$target = $target.closest('.note-discussion');
// If the next discussion is closed, toggle it open.
......
......@@ -21,6 +21,8 @@
/* global Search */
/* global Admin */
/* global NamespaceSelects */
/* global NewCommitForm */
/* global NewBranchForm */
/* global Project */
/* global ProjectAvatar */
/* global MergeRequest */
......@@ -229,7 +231,6 @@ import initGroupAnalytics from './init_group_analytics';
break;
case 'explore:groups:index':
new GroupsList();
const landingElement = document.querySelector('.js-explore-groups-landing');
if (!landingElement) break;
const exploreGroupsLanding = new Landing(
......@@ -323,12 +324,14 @@ import initGroupAnalytics from './init_group_analytics';
new gl.Diff();
shortcut_handler = new ShortcutsIssuable(true);
new ZenMode();
initIssuableSidebar();
initNotes();
const mrShowNode = document.querySelector('.merge-request');
window.mergeRequest = new MergeRequest({
action: mrShowNode.dataset.mrAction,
});
initIssuableSidebar();
initNotes();
break;
case 'dashboard:activity':
new gl.Activities();
......@@ -382,6 +385,9 @@ import initGroupAnalytics from './init_group_analytics';
initSettingsPanels();
new UsersSelect();
break;
case 'projects:imports:show':
new ProjectImport();
break;
case 'projects:pipelines:new':
new NewBranchForm($('.js-new-pipeline-form'));
break;
......@@ -570,7 +576,7 @@ import initGroupAnalytics from './init_group_analytics';
initGroupAnalytics();
break;
}
switch (path.first()) {
switch (path[0]) {
case 'sessions':
case 'omniauth_callbacks':
if (!gon.u2f) break;
......
......@@ -217,7 +217,7 @@ window.DropzoneInput = (function() {
value = e.clipboardData.getData('text/plain');
}
value = value.split("\r");
return value.first();
return value[0];
};
const showSpinner = function(e) {
......
// TODO: remove this
// eslint-disable-next-line no-extend-native
Array.prototype.first = function first() {
return this[0];
};
// eslint-disable-next-line no-extend-native
Array.prototype.last = function last() {
return this[this.length - 1];
};
......@@ -51,31 +51,6 @@ class DropdownUtils {
return updatedItem;
}
static filterHint(config, item) {
const { input, allowedKeys } = config;
const updatedItem = item;
const searchInput = gl.DropdownUtils.getSearchQuery(input);
const { lastToken, tokens } =
gl.FilteredSearchTokenizer.processTokens(searchInput, allowedKeys);
const lastKey = lastToken.key || lastToken || '';
const allowMultiple = item.type === 'array';
const itemInExistingTokens = tokens.some(t => t.key === item.hint);
if (!allowMultiple && itemInExistingTokens) {
updatedItem.droplab_hidden = true;
} else if (!lastKey || searchInput.split('').last() === ' ') {
updatedItem.droplab_hidden = false;
} else if (lastKey) {
const split = lastKey.split(':');
const tokenName = split[0].split(' ').last();
const match = updatedItem.hint.indexOf(tokenName.toLowerCase()) === -1;
updatedItem.droplab_hidden = tokenName ? match : false;
}
return updatedItem;
}
static mergeDuplicateLabels(dataMap, newLabel) {
const updatedMap = dataMap;
const key = newLabel.title;
......@@ -136,6 +111,31 @@ class DropdownUtils {
return results;
}
static filterHint(config, item) {
const { input, allowedKeys } = config;
const updatedItem = item;
const searchInput = gl.DropdownUtils.getSearchQuery(input);
const { lastToken, tokens } =
gl.FilteredSearchTokenizer.processTokens(searchInput, allowedKeys);
const lastKey = lastToken.key || lastToken || '';
const allowMultiple = item.type === 'array';
const itemInExistingTokens = tokens.some(t => t.key === item.hint);
if (!allowMultiple && itemInExistingTokens) {
updatedItem.droplab_hidden = true;
} else if (!lastKey || _.last(searchInput.split('')) === ' ') {
updatedItem.droplab_hidden = false;
} else if (lastKey) {
const split = lastKey.split(':');
const tokenName = _.last(split[0].split(' '));
const match = updatedItem.hint.indexOf(tokenName.toLowerCase()) === -1;
updatedItem.droplab_hidden = tokenName ? match : false;
}
return updatedItem;
}
static setDataValueIfSelected(filter, selected) {
const dataValue = selected.getAttribute('data-value');
......
......@@ -179,7 +179,7 @@ class FilteredSearchDropdownManager {
// Eg. token = 'label:'
const split = lastToken.split(':');
const dropdownName = split[0].split(' ').last();
const dropdownName = _.last(split[0].split(' '));
this.loadDropdown(split.length > 1 ? dropdownName : '');
} else if (lastToken) {
// Token has been initialized into an object because it has a value
......
......@@ -375,7 +375,7 @@ class FilteredSearchManager {
const fragments = searchToken.split(':');
if (fragments.length > 1) {
const inputValues = fragments[0].split(' ');
const tokenKey = inputValues.last();
const tokenKey = _.last(inputValues);
if (inputValues.length > 1) {
inputValues.pop();
......
<script>
export default {
props: {
entityId: {
type: Number,
required: true,
},
entityName: {
type: String,
required: true,
},
},
computed: {
/**
* This method is based on app/helpers/application_helper.rb#project_identicon
*/
identiconStyles() {
const allowedColors = [
'#FFEBEE',
'#F3E5F5',
'#E8EAF6',
'#E3F2FD',
'#E0F2F1',
'#FBE9E7',
'#EEEEEE',
];
const backgroundColor = allowedColors[this.entityId % 7];
return `background-color: ${backgroundColor}; color: #555;`;
},
identiconTitle() {
return this.entityName.charAt(0).toUpperCase();
},
},
};
</script>
<template>
<div
class="avatar s40 identicon"
:style="identiconStyles">
{{identiconTitle}}
</div>
</template>
<script>
import eventHub from '../event_hub';
import groupIdenticon from './group_identicon.vue';
export default {
components: {
groupIdenticon,
},
props: {
group: {
type: Object,
......@@ -92,6 +96,9 @@ export default {
hasGroups() {
return Object.keys(this.group.subGroups).length > 0;
},
hasAvatar() {
return this.group.avatarUrl && this.group.avatarUrl.indexOf('/assets/no_group_avatar') === -1;
},
},
};
</script>
......@@ -194,9 +201,15 @@ export default {
<a
:href="group.groupPath">
<img
v-if="hasAvatar"
class="avatar s40"
:src="group.avatarUrl"
/>
<group-identicon
v-else
:entity-id=group.id
:entity-name="group.name"
/>
</a>
</div>
<div
......
......@@ -16,9 +16,6 @@ import 'mousetrap';
import 'mousetrap/plugins/pause/mousetrap-pause';
import 'vendor/fuzzaldrin-plus';
// extensions
import './extensions/array';
// expose common libraries as globals (TODO: remove these)
window.jQuery = jQuery;
window.$ = jQuery;
......@@ -149,8 +146,6 @@ import './subscription';
import './subscription_select';
import './syntax_highlight';
import './dispatcher';
// EE-only scripts
import './admin_email_select';
import './application_settings';
......@@ -159,6 +154,8 @@ import './ldap_groups_select';
import './path_locks';
import './weight_select';
import './dispatcher';
// eslint-disable-next-line global-require, import/no-commonjs
if (process.env.NODE_ENV !== 'production') require('./test_utils/');
......
......@@ -530,6 +530,7 @@ export default class Notes {
form.find('#note_line_code').remove();
form.find('#note_position').remove();
form.find('#note_type').val('');
form.find('#note_project_id').remove();
form.find('#in_reply_to_discussion_id').remove();
form.find('.js-comment-resolve-button').closest('comment-and-resolve-btn').remove();
this.parentTimeline = form.parents('.timeline');
......@@ -557,6 +558,7 @@ export default class Notes {
form.find('#note_noteable_id').val(),
form.find('#note_commit_id').val(),
form.find('#note_type').val(),
form.find('#note_project_id').val(),
form.find('#in_reply_to_discussion_id').val(),
// LegacyDiffNote
......@@ -849,6 +851,8 @@ export default class Notes {
form.find('#in_reply_to_discussion_id').val(discussionID);
}
form.find('#note_project_id').val(dataHolder.data('discussionProjectId'));
form.attr('data-line-code', dataHolder.data('lineCode'));
form.find('#line_type').val(dataHolder.data('lineType'));
......
/* eslint-disable no-useless-escape, max-len, quotes, no-var, no-underscore-dangle, func-names, space-before-function-paren, no-unused-vars, no-return-assign, object-shorthand, one-var, one-var-declaration-per-line, comma-dangle, consistent-return, class-methods-use-this, new-parens */
import 'cropper';
import _ from 'underscore';
import 'vendor/cropper';
((global) => {
// Matches everything but the file name
......
document.addEventListener('DOMContentLoaded', () => {
let hasUserDefinedProjectPath = false;
const deriveProjectPathFromUrl = ($projectImportUrl, $projectPath) => {
if ($projectImportUrl.attr('disabled') || hasUserDefinedProjectPath) {
return;
}
let importUrl = $projectImportUrl.val().trim();
if (importUrl.length === 0) {
return;
}
/*
\/?: remove trailing slash
(\.git\/?)?: remove trailing .git (with optional trailing slash)
(\?.*)?: remove query string
(#.*)?: remove fragment identifier
*/
importUrl = importUrl.replace(/\/?(\.git\/?)?(\?.*)?(#.*)?$/, '');
// extract everything after the last slash
const pathMatch = /\/([^/]+)$/.exec(importUrl);
if (pathMatch) {
$projectPath.val(pathMatch[1]);
}
};
const bindEvents = () => {
const $newProjectForm = $('#new_project');
const importBtnTooltip = 'Please enter a valid project name.';
const $importBtnWrapper = $('.import_gitlab_project');
const $projectImportUrl = $('#project_import_url');
const $projectPath = $('#project_path');
if ($newProjectForm.length !== 1) {
return;
}
$('.how_to_import_link').on('click', (e) => {
e.preventDefault();
......@@ -13,19 +47,19 @@ document.addEventListener('DOMContentLoaded', () => {
$('.btn_import_gitlab_project').on('click', () => {
const importHref = $('a.btn_import_gitlab_project').attr('href');
$('.btn_import_gitlab_project').attr('href', `${importHref}?namespace_id=${$('#project_namespace_id').val()}&path=${$('#project_path').val()}`);
$('.btn_import_gitlab_project').attr('href', `${importHref}?namespace_id=${$('#project_namespace_id').val()}&path=${$projectPath.val()}`);
});
$('.btn_import_gitlab_project').attr('disabled', !$('#project_path').val().trim().length);
$('.btn_import_gitlab_project').attr('disabled', !$projectPath.val().trim().length);
$importBtnWrapper.attr('title', importBtnTooltip);
$('#new_project').on('submit', () => {
const $path = $('#project_path');
$path.val($path.val().trim());
$newProjectForm.on('submit', () => {
$projectPath.val($projectPath.val().trim());
});
$('#project_path').on('keyup', () => {
if ($('#project_path').val().trim().length) {
$projectPath.on('keyup', () => {
hasUserDefinedProjectPath = $projectPath.val().trim().length > 0;
if (hasUserDefinedProjectPath) {
$('.btn_import_gitlab_project').attr('disabled', false);
$importBtnWrapper.attr('title', '');
$importBtnWrapper.removeClass('has-tooltip');
......@@ -35,9 +69,17 @@ document.addEventListener('DOMContentLoaded', () => {
}
});
$('#project_import_url').disable();
$projectImportUrl.disable();
$projectImportUrl.keyup(() => deriveProjectPathFromUrl($projectImportUrl, $projectPath));
$('.import_git').on('click', () => {
const $projectImportUrl = $('#project_import_url');
$projectImportUrl.attr('disabled', !$projectImportUrl.attr('disabled'));
});
});
};
document.addEventListener('DOMContentLoaded', bindEvents);
export default {
bindEvents,
deriveProjectPathFromUrl,
};
......@@ -92,6 +92,10 @@
overflow: hidden;
display: flex;
a {
display: flex;
}
.avatar {
border-radius: 0;
border: none;
......
......@@ -772,4 +772,8 @@
}
}
}
.dropdown-menu-align-right {
margin-top: 2px;
}
}
......@@ -414,13 +414,16 @@
background-color: $dropdown-hover-color;
color: $white-light;
text-decoration: none;
outline: 0;
.avatar {
border-color: $white-light;
}
}
.filter-dropdown-item {
.droplab-dropdown .dropdown-menu .filter-dropdown-item {
padding: 0;
.btn {
border: none;
width: 100%;
......@@ -455,14 +458,11 @@
}
.dropdown-user {
display: -webkit-flex;
display: flex;
}
.dropdown-user-details {
display: -webkit-flex;
display: flex;
-webkit-flex-direction: column;
flex-direction: column;
> span {
......
......@@ -4,6 +4,8 @@
*/
header {
@include new-style-dropdown;
transition: padding $sidebar-transition-duration;
&.navbar-empty {
......@@ -319,6 +321,10 @@ header {
top: $performance-bar-height;
}
.with-performance-bar header.navbar-gitlab {
top: $performance-bar-height;
}
.navbar-nav {
li {
.badge {
......
......@@ -375,6 +375,10 @@ ul.indent-list {
background-color: $row-hover;
cursor: pointer;
}
.avatar-container > a {
width: 100%;
}
}
}
......
......@@ -185,3 +185,28 @@
text-overflow: ellipsis;
}
}
// TODO: fallback to global style
.atwho-view {
.atwho-view-ul {
padding: 8px 1px;
li {
padding: 8px 16px;
border: 0;
&.cur {
background-color: $gray-darker;
color: $gl-text-color;
small {
color: inherit;
}
}
strong {
color: $gl-text-color;
}
}
}
}
......@@ -19,9 +19,9 @@
}
img.js-lazy-loaded {
min-width: none;
min-height: none;
background-color: none;
min-width: inherit;
min-height: inherit;
background-color: inherit;
}
p a:not(.no-attachment-icon) img {
......
......@@ -348,6 +348,7 @@ header.navbar-gitlab-new {
.breadcrumbs-links {
flex: 1;
min-width: 0;
align-self: center;
color: $gl-text-color-quaternary;
......@@ -366,7 +367,7 @@ header.navbar-gitlab-new {
}
.title {
white-space: nowrap;
display: inline-block;
> a {
&:last-of-type:not(:first-child) {
......
......@@ -86,6 +86,7 @@
position: absolute;
right: 0;
left: 0;
top: 0;
}
.truncated-info {
......@@ -310,9 +311,7 @@
}
.dropdown-menu {
right: $gl-padding;
left: $gl-padding;
width: auto;
margin-top: -$gl-padding;
}
svg {
......
#cycle-analytics {
@include new-style-dropdown;
max-width: 1000px;
margin: 24px auto 0;
position: relative;
......
.tree-holder {
@include new-style-dropdown;
.nav-block {
margin: 10px 0;
......
......@@ -4,6 +4,7 @@ module NotesActions
included do
before_action :authorize_admin_note!, only: [:update, :destroy]
before_action :note_project, only: [:create]
end
def index
......@@ -28,7 +29,8 @@ module NotesActions
merge_request_diff_head_sha: params[:merge_request_diff_head_sha],
in_reply_to_discussion_id: params[:in_reply_to_discussion_id]
)
@note = Notes::CreateService.new(project, current_user, create_params).execute
@note = Notes::CreateService.new(note_project, current_user, create_params).execute
if @note.is_a?(Note)
Banzai::NoteRenderer.render([@note], @project, current_user)
......@@ -177,4 +179,22 @@ module NotesActions
def notes_finder
@notes_finder ||= NotesFinder.new(project, current_user, finder_params)
end
def note_project
return @note_project if defined?(@note_project)
return nil unless project
note_project_id = params[:note_project_id]
@note_project =
if note_project_id.present?
Project.find(note_project_id)
else
project
end
return access_denied! unless can?(current_user, :create_note, @note_project)
@note_project
end
end
......@@ -13,7 +13,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController
end
def destroy
TodoService.new.mark_todos_as_done_by_ids([params[:id]], current_user)
TodoService.new.mark_todos_as_done_by_ids(params[:id], current_user)
respond_to do |format|
format.html do
......@@ -37,7 +37,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController
end
def restore
TodoService.new.mark_todos_as_pending_by_ids([params[:id]], current_user)
TodoService.new.mark_todos_as_pending_by_ids(params[:id], current_user)
render json: todos_counts
end
......
......@@ -66,7 +66,8 @@ module Projects
end
def filter_params
params.merge(board_id: params[:board_id], id: params[:list_id]).compact
params.merge(board_id: params[:board_id], id: params[:list_id])
.reject { |_, value| value.nil? }
end
def move_params
......
......@@ -8,7 +8,7 @@ class Projects::BranchesController < Projects::ApplicationController
before_action :authorize_push_code!, only: [:new, :create, :destroy, :destroy_all_merged]
def index
@sort = params[:sort].presence || sort_value_name
@sort = params[:sort].presence || sort_value_recently_updated
@branches = BranchesFinder.new(@repository, params).execute
@branches = Kaminari.paginate_array(@branches).page(params[:page])
......
......@@ -95,9 +95,18 @@ class TodosFinder
@project
end
def project_ids(items)
ids = items.except(:order).select(:project_id)
if Gitlab::Database.mysql?
# To make UPDATE work on MySQL, wrap it in a SELECT with an alias
ids = Todo.except(:order).select('*').from("(#{ids.to_sql}) AS t")
end
ids
end
def projects(items)
item_project_ids = items.reorder(nil).select(:project_id)
ProjectsFinder.new(current_user: current_user, project_ids_relation: item_project_ids).execute
ProjectsFinder.new(current_user: current_user, project_ids_relation: project_ids(items)).execute
end
def type?
......
......@@ -15,7 +15,7 @@ module DiffHelper
def diff_view
@diff_view ||= begin
diff_views = %w(inline parallel)
diff_view = cookies[:diff_view]
diff_view = params[:view] || cookies[:diff_view]
diff_view = diff_views.first unless diff_views.include?(diff_view)
diff_view.to_sym
end
......
......@@ -50,7 +50,11 @@ module GitlabRoutingHelper
end
def milestone_path(entity, *args)
project_milestone_path(entity.project, entity, *args)
if entity.is_group_milestone?
group_milestone_path(entity.group, entity, *args)
elsif entity.is_project_milestone?
project_milestone_path(entity.project, entity, *args)
end
end
def issue_url(entity, *args)
......@@ -65,6 +69,14 @@ module GitlabRoutingHelper
project_pipeline_url(pipeline.project, pipeline.id, *args)
end
def milestone_url(entity, *args)
if entity.is_group_milestone?
group_milestone_url(entity.group, entity, *args)
elsif entity.is_project_milestone?
project_milestone_url(entity.project, entity, *args)
end
end
def pipeline_job_url(pipeline, build, *args)
project_job_url(pipeline.project, build.id, *args)
end
......
......@@ -62,7 +62,11 @@ module NotesHelper
def link_to_reply_discussion(discussion, line_type = nil)
return unless current_user
data = { discussion_id: discussion.reply_id, line_type: line_type }
data = {
discussion_id: discussion.reply_id,
discussion_project_id: discussion.project&.id,
line_type: line_type
}
button_tag 'Reply...', class: 'btn btn-text-field js-discussion-reply-button',
data: data, title: 'Add a reply'
......
......@@ -34,6 +34,8 @@ module WebpackHelper
end
def webpack_public_path
"#{webpack_public_host}/#{Rails.application.config.webpack.public_path}/"
relative_path = Rails.application.config.relative_url_root
webpack_path = Rails.application.config.webpack.public_path
File.join(webpack_public_host.to_s, relative_path.to_s, webpack_path.to_s, '')
end
end
module ProtectedBranchAccess
extend ActiveSupport::Concern
ALLOWED_ACCESS_LEVELS ||= [
Gitlab::Access::MASTER,
Gitlab::Access::DEVELOPER,
Gitlab::Access::NO_ACCESS
].freeze
included do
include ProtectedRefAccess
include EE::ProtectedRefAccess
......@@ -10,11 +16,7 @@ module ProtectedBranchAccess
delegate :project, to: :protected_branch
validates :access_level, presence: true, inclusion: {
in: [
Gitlab::Access::MASTER,
Gitlab::Access::DEVELOPER,
Gitlab::Access::NO_ACCESS
]
in: ALLOWED_ACCESS_LEVELS
}
def self.human_access_levels
......
......@@ -25,6 +25,18 @@ module Referable
to_reference(from_project)
end
def referable_inspect
if respond_to?(:id)
"#<#{self.class.name} id:#{id} #{to_reference(full: true)}>"
else
"#<#{self.class.name} #{to_reference(full: true)}>"
end
end
def inspect
referable_inspect
end
module ClassMethods
# The character that prefixes the actual reference identifier
#
......
......@@ -16,8 +16,6 @@ class Key < ActiveRecord::Base
presence: true,
length: { maximum: 5000 },
format: { with: /\A(ssh|ecdsa)-.*\Z/ }
validates :key,
format: { without: /\n|\r/, message: 'should be a single line' }
validates :fingerprint,
uniqueness: true,
presence: { message: 'cannot be generated' }
......@@ -33,6 +31,7 @@ class Key < ActiveRecord::Base
after_destroy :post_destroy_hook
def key=(value)
value&.delete!("\n\r")
value.strip! unless value.blank?
write_attribute(:key, value)
end
......
......@@ -85,11 +85,7 @@ class MergeRequestDiff < ActiveRecord::Base
def raw_diffs(options = {})
if options[:ignore_whitespace_change]
@diffs_no_whitespace ||=
Gitlab::Git::Compare.new(
repository.raw_repository,
safe_start_commit_sha,
head_commit_sha).diffs(options)
@diffs_no_whitespace ||= compare.diffs(options)
else
@raw_diffs ||= {}
@raw_diffs[options] ||= load_diffs(options)
......
class NotificationSetting < ActiveRecord::Base
include IgnorableColumn
ignore_column :events
enum level: { global: 3, watch: 2, mention: 4, participating: 1, disabled: 0, custom: 5 }
default_value_for :level, NotificationSetting.levels[:global]
......@@ -41,9 +45,6 @@ class NotificationSetting < ActiveRecord::Base
:success_pipeline
].freeze
store :events, coder: JSON
before_save :convert_events
def self.find_or_create_for(source)
setting = find_or_initialize_by(source: source)
......@@ -54,42 +55,17 @@ class NotificationSetting < ActiveRecord::Base
setting
end
# 1. Check if this event has a value stored in its database column.
# 2. If it does, return that value.
# 3. If it doesn't (the value is nil), return the value from the serialized
# JSON hash in `events`.
(EMAIL_EVENTS - [:failed_pipeline]).each do |event|
define_method(event) do
bool = super()
bool.nil? ? !!events[event] : bool
end
alias_method :"#{event}?", event
end
# Allow people to receive failed pipeline notifications if they already have
# custom notifications enabled, as these are more like mentions than the other
# custom settings.
def failed_pipeline
bool = super
bool = events[:failed_pipeline] if bool.nil?
bool.nil? || bool
end
alias_method :failed_pipeline?, :failed_pipeline
def event_enabled?(event)
respond_to?(event) && public_send(event)
end
def convert_events
return if events_before_type_cast.nil?
EMAIL_EVENTS.each do |event|
write_attribute(event, public_send(event))
end
write_attribute(:events, nil)
respond_to?(event) && !!public_send(event)
end
end
......@@ -1206,7 +1206,18 @@ class Project < ActiveRecord::Base
end
def remove_private_deploy_keys
deploy_keys.where(public: false).delete_all
exclude_keys_linked_to_other_projects = <<-SQL
NOT EXISTS (
SELECT 1
FROM deploy_keys_projects dkp2
WHERE dkp2.deploy_key_id = deploy_keys_projects.deploy_key_id
AND dkp2.project_id != deploy_keys_projects.project_id
)
SQL
deploy_keys.where(public: false)
.where(exclude_keys_linked_to_other_projects)
.delete_all
end
# TODO: what to do here when not using Legacy Storage? Do we still need to rename and delay removal?
......
......@@ -104,7 +104,7 @@ class JiraService < IssueTrackerService
def close_issue(entity, external_issue)
issue = jira_request { client.Issue.find(external_issue.iid) }
return if issue.nil? || issue.resolution.present? || !jira_issue_transition_id.present?
return if issue.nil? || has_resolution?(issue) || !jira_issue_transition_id.present?
commit_id = if entity.is_a?(Commit)
entity.id
......@@ -118,7 +118,7 @@ class JiraService < IssueTrackerService
# may or may not be allowed. Refresh the issue after transition and check
# if it is closed, so we don't have one comment for every commit.
issue = jira_request { client.Issue.find(issue.key) } if transition_issue(issue)
add_issue_solved_comment(issue, commit_id, commit_url) if issue.resolution
add_issue_solved_comment(issue, commit_id, commit_url) if has_resolution?(issue)
end
def create_cross_reference_note(mentioned, noteable, author)
......@@ -216,6 +216,10 @@ class JiraService < IssueTrackerService
end
end
def has_resolution?(issue)
issue.respond_to?(:resolution) && issue.resolution.present?
end
def comment_exists?(issue, message)
comments = jira_request { issue.comments }
......
class ProjectWiki
include Gitlab::ShellAdapter
include Storage::LegacyProjectWiki
# EE only modules
include Elastic::WikiRepositoriesSearch
include Gitlab::CurrentSettings
include Storage::LegacyProjectWiki
MARKUPS = {
'Markdown' => :markdown,
......
......@@ -1018,7 +1018,7 @@ class Repository
if is_enabled
raw_repository.is_ancestor?(ancestor_id, descendant_id)
else
merge_base_commit(ancestor_id, descendant_id) == ancestor_id
rugged_is_ancestor?(ancestor_id, descendant_id)
end
end
end
......
......@@ -50,6 +50,11 @@ class User < ActiveRecord::Base
devise :lockable, :recoverable, :rememberable, :trackable,
:validatable, :omniauthable, :confirmable, :registerable
# devise overrides #inspect, so we manually use the Referable one
def inspect
referable_inspect
end
# Override Devise::Models::Trackable#update_tracked_fields!
# to limit database writes to at most once every hour
def update_tracked_fields!(request)
......
......@@ -44,7 +44,7 @@ class GlobalPolicy < BasePolicy
prevent :log_in
end
rule { admin | ~restricted_public_level }.policy do
rule { ~(anonymous & restricted_public_level) }.policy do
enable :read_users_list
end
end
......@@ -11,7 +11,7 @@ class DeleteMergedBranchesService < BaseService
# 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 = branches.reject { |branch| project.protected_for?(branch) }
branches.each do |branch|
DeleteBranchService.new(project, current_user).execute(branch)
......
......@@ -45,6 +45,7 @@ class GitPushService < BaseService
elsif push_to_existing_branch?
# Collect data for this git push
@push_commits = @project.repository.commits_between(params[:oldrev], params[:newrev])
process_commit_messages
# Update the bare repositories info/attributes file using the contents of the default branches
......@@ -71,15 +72,21 @@ class GitPushService < BaseService
def update_caches
if is_default_branch?
paths = Set.new
if push_to_new_branch?
# If this is the initial push into the default branch, the file type caches
# will already be reset as a result of `Project#change_head`.
types = []
else
paths = Set.new
@push_commits.each do |commit|
commit.raw_deltas.each do |diff|
paths << diff.new_path
@push_commits.last(PROCESS_COMMIT_LIMIT).each do |commit|
commit.raw_deltas.each do |diff|
paths << diff.new_path
end
end
end
types = Gitlab::FileDetector.types_in_paths(paths.to_a)
types = Gitlab::FileDetector.types_in_paths(paths.to_a)
end
else
types = []
end
......@@ -97,7 +104,7 @@ class GitPushService < BaseService
def process_commit_messages
default = is_default_branch?
push_commits.last(PROCESS_COMMIT_LIMIT).each do |commit|
@push_commits.last(PROCESS_COMMIT_LIMIT).each do |commit|
if commit.matches_cross_reference_regex?
ProcessCommitWorker
.perform_async(project.id, current_user.id, commit.to_hash, default)
......@@ -125,7 +132,7 @@ class GitPushService < BaseService
EventCreateService.new.push(@project, current_user, build_push_data)
Ci::CreatePipelineService.new(@project, current_user, build_push_data).execute(:push, mirror_update: mirror_update)
SystemHookPushWorker.perform_async(build_push_data.dup, :push_hooks)
@project.execute_hooks(build_push_data.dup, :push_hooks)
@project.execute_services(build_push_data.dup, :push_hooks)
......@@ -145,7 +152,10 @@ class GitPushService < BaseService
end
def process_default_branch
@push_commits = project.repository.commits(params[:newrev])
@push_commits_count = project.repository.commit_count_for_ref(params[:ref])
offset = [@push_commits_count - PROCESS_COMMIT_LIMIT, 0].max
@push_commits = project.repository.commits(params[:newrev], offset: offset, limit: PROCESS_COMMIT_LIMIT)
# Ensure HEAD points to the default branch in case it is not master
project.change_head(branch_name)
......@@ -174,7 +184,8 @@ class GitPushService < BaseService
params[:oldrev],
params[:newrev],
params[:ref],
push_commits)
@push_commits,
commits_count: @push_commits_count)
end
def push_to_existing_branch?
......
......@@ -290,7 +290,7 @@ class IssuableBaseService < BaseService
todo_service.mark_todo(issuable, current_user)
when 'done'
todo = TodosFinder.new(current_user).execute.find_by(target: issuable)
todo_service.mark_todos_as_done([todo], current_user) if todo
todo_service.mark_todos_as_done_by_ids(todo, current_user) if todo
end
end
......
......@@ -5,6 +5,9 @@ module QuickActions
attr_reader :issuable
SHRUG = '¯\\_(ツ)_/¯'.freeze
TABLEFLIP = '(╯°□°)╯︵ ┻━┻'.freeze
# Takes a text and interprets the commands that are extracted from it.
# Returns the content without commands, and hash of changes to be applied to a record.
def execute(content, issuable)
......@@ -15,6 +18,7 @@ module QuickActions
content, commands = extractor.extract_commands(content, context)
extract_updates(commands, context)
[content, @updates]
end
......@@ -424,6 +428,18 @@ module QuickActions
@updates[:spend_time] = { duration: :reset, user: current_user }
end
desc "Append the comment with #{SHRUG}"
params '<Comment>'
substitution :shrug do |comment|
"#{comment} #{SHRUG}"
end
desc "Append the comment with #{TABLEFLIP}"
params '<Comment>'
substitution :tableflip do |comment|
"#{comment} #{TABLEFLIP}"
end
# This is a dummy command, so that it appears in the autocomplete commands
desc 'CC'
params '@user'
......
......@@ -178,20 +178,22 @@ class TodoService
# When user marks some todos as done
def mark_todos_as_done(todos, current_user)
update_todos_state_by_ids(todos.select(&:id), current_user, :done)
update_todos_state(todos, current_user, :done)
end
def mark_todos_as_done_by_ids(ids, current_user)
update_todos_state_by_ids(ids, current_user, :done)
todos = todos_by_ids(ids, current_user)
mark_todos_as_done(todos, current_user)
end
# When user marks some todos as pending
def mark_todos_as_pending(todos, current_user)
update_todos_state_by_ids(todos.select(&:id), current_user, :pending)
update_todos_state(todos, current_user, :pending)
end
def mark_todos_as_pending_by_ids(ids, current_user)
update_todos_state_by_ids(ids, current_user, :pending)
todos = todos_by_ids(ids, current_user)
mark_todos_as_pending(todos, current_user)
end
# When user marks an issue as todo
......@@ -206,9 +208,11 @@ class TodoService
private
def update_todos_state_by_ids(ids, current_user, state)
todos = current_user.todos.where(id: ids)
def todos_by_ids(ids, current_user)
current_user.todos.where(id: Array(ids))
end
def update_todos_state(todos, current_user, state)
# Only update those that are not really on that state
todos = todos.where.not(state: state)
todos_ids = todos.pluck(:id)
......
......@@ -44,7 +44,7 @@ class WebHookService
http_status: response.code,
message: response.to_s
}
rescue SocketError, OpenSSL::SSL::SSLError, Errno::ECONNRESET, Errno::ECONNREFUSED, Net::OpenTimeout => e
rescue SocketError, OpenSSL::SSL::SSLError, Errno::ECONNRESET, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Net::OpenTimeout, Net::ReadTimeout => e
log_execution(
trigger: hook_name,
url: hook.url,
......@@ -101,7 +101,7 @@ class WebHookService
request_headers: build_headers(hook_name),
request_data: request_data,
response_headers: format_response_headers(response),
response_body: response.body,
response_body: safe_response_body(response),
response_status: response.code,
internal_error_message: error_message
)
......@@ -124,4 +124,10 @@ class WebHookService
def format_response_headers(response)
response.headers.each_capitalized.to_h
end
def safe_response_body(response)
return '' unless response.body
response.body.encode('UTF-8', invalid: :replace, undef: :replace, replace: '')
end
end
......@@ -356,7 +356,7 @@
\. This setting requires a
= link_to 'restart', help_page_path('administration/restart_gitlab')
to take effect.
= link_to icon('question-circle'), help_page_path('administration/monitoring/performance/introduction')
= link_to icon('question-circle'), help_page_path('administration/monitoring/prometheus/index')
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
......
- @breadcrumb_title = "Help"
- page_title "Help"
- header_title "Help", help_path
......
......@@ -67,7 +67,9 @@
SSH Keys
= nav_link(controller: :gpg_keys) do
= link_to profile_gpg_keys_path, title: 'GPG Keys' do
%span
.nav-icon-container
= custom_icon('key_2')
%span.nav-item-name
GPG Keys
= nav_link(controller: :preferences) do
= link_to profile_preferences_path, title: 'Preferences' do
......
......@@ -30,7 +30,7 @@
= link_to _("Create merge request"), create_mr_path(@repository.root_ref, @ref), class: 'btn btn-success'
.control
= form_tag(project_commits_path(@project, @id), method: :get, class: 'commits-search-form') do
= form_tag(project_commits_path(@project, @id), method: :get, class: 'commits-search-form', data: { 'signatures-path' => namespace_project_signatures_path }) do
= search_field_tag :search, params[:search], { placeholder: _('Filter by commit message'), id: 'commits-search', class: 'form-control search-text-input input-short', spellcheck: false }
.control
= link_to project_commits_path(@project, @ref, rss_url_options), title: _("Commits feed"), class: 'btn' do
......
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M5.172 14.157l-.344.344-2.485.133a.462.462 0 0 1-.497-.503l.14-2.24a.599.599 0 0 1 .177-.382l5.155-5.155a4 4 0 1 1 2.828 2.828l-1.439 1.44-1.06-.354-.708.707.354 1.06-.707.708-1.06-.354-.708.707.354 1.06zm6.01-8.839a1 1 0 1 0 1.414-1.414 1 1 0 0 0-1.414 1.414z"/></svg>
......@@ -37,7 +37,7 @@
= link_to 'Edit', '#', class: 'edit-link pull-right'
.value.hide-collapsed
- if issuable.milestone
= link_to issuable.milestone.title, project_milestone_path(@project, issuable.milestone), class: "bold has-tooltip", title: milestone_remaining_days(issuable.milestone), data: { container: "body", html: 1 }
= link_to issuable.milestone.title, milestone_path(issuable.milestone), class: "bold has-tooltip", title: milestone_remaining_days(issuable.milestone), data: { container: "body", html: 1 }
- else
%span.no-value None
......
......@@ -10,6 +10,7 @@
= hidden_field_tag :line_type
= hidden_field_tag :merge_request_diff_head_sha, @note.noteable.try(:diff_head_sha)
= hidden_field_tag :in_reply_to_discussion_id
= hidden_field_tag :note_project_id
= note_target_fields(@note)
= f.hidden_field :noteable_type
......
......@@ -14,7 +14,7 @@
- if avatar
.avatar-container.s40
= link_to project_path(project), class: dom_class(project) do
- if use_creator_avatar
- if project.creator && use_creator_avatar
= image_tag avatar_icon(project.creator.email, 40), class: "avatar s40", alt:''
- else
= project_icon(project, alt: '', class: 'avatar project-avatar s40')
......
......@@ -31,8 +31,6 @@ class EmailReceiverWorker
when Gitlab::Email::EmptyEmailError
can_retry = true
"It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
when Gitlab::Email::AutoGeneratedEmailError
"The email was marked as 'auto generated', which we can't accept. Please create your comment through the web interface."
when Gitlab::Email::UserNotFoundError
"We couldn't figure out what user corresponds to the email. Please create your comment through the web interface."
when Gitlab::Email::UserBlockedError
......
......@@ -5,6 +5,12 @@ class GitGarbageCollectWorker
sidekiq_options retry: false
GITALY_MIGRATED_TASKS = {
gc: :garbage_collect,
full_repack: :repack_full,
incremental_repack: :repack_incremental
}.freeze
def perform(project_id, task = :gc, lease_key = nil, lease_uuid = nil)
project = Project.find(project_id)
task = task.to_sym
......@@ -15,8 +21,14 @@ class GitGarbageCollectWorker
Gitlab::GitLogger.info(description)
output, status = Gitlab::Popen.popen(cmd, repo_path)
Gitlab::GitLogger.error("#{description} failed:\n#{output}") unless status.zero?
gitaly_migrate(GITALY_MIGRATED_TASKS[task]) do |is_enabled|
if is_enabled
gitaly_call(task, project.repository.raw_repository)
else
output, status = Gitlab::Popen.popen(cmd, repo_path)
Gitlab::GitLogger.error("#{description} failed:\n#{output}") unless status.zero?
end
end
# Refresh the branch cache in case garbage collection caused a ref lookup to fail
flush_ref_caches(project) if task == :gc
......@@ -26,6 +38,19 @@ class GitGarbageCollectWorker
private
## `repository` has to be a Gitlab::Git::Repository
def gitaly_call(task, repository)
client = Gitlab::GitalyClient::RepositoryService.new(repository)
case task
when :gc
client.garbage_collect(bitmaps_enabled?)
when :full_repack
client.repack_full(bitmaps_enabled?)
when :incremental_repack
client.repack_incremental
end
end
def command(task)
case task
when :gc
......@@ -55,4 +80,14 @@ class GitGarbageCollectWorker
config_value = write_bitmaps ? 'true' : 'false'
%W[git -c repack.writeBitmaps=#{config_value}]
end
def gitaly_migrate(method, &block)
Gitlab::GitalyClient.migrate(method, &block)
rescue GRPC::NotFound => e
Gitlab::GitLogger.error("#{method} failed:\nRepository not found")
raise Gitlab::Git::Repository::NoRepository.new(e)
rescue GRPC::BadStatus => e
Gitlab::GitLogger.error("#{method} failed:\n#{e}")
raise Gitlab::Git::CommandError.new(e)
end
end
---
title: "Fix v3 api project_hooks POST and PUT operations for build_events"
merge_request: 12673
author: Richard Clamp
---
title: Expose target_iid in Events API
merge_request: 13247
author: sue445
---
title: Don't send rejection mails for all auto-generated mails
merge_request: 13254
author:
---
title: Add /shrug and /tableflip commands
merge_request: 10068
author: Alex Ives
---
title: Remove events column from notification settings table
merge_request:
author:
---
title: Use jQuery to control scroll behavior in job log for cross browser consistency
merge_request:
author:
---
title: Extend API for Group Secret Variable
merge_request: 12936
author:
---
title: Bump rubocop to 0.49.1 and rubocop-rspec to 1.15.1
merge_request:
author: Takuya Noguchi
---
title: Fix project logos that are not centered vertically on list pages
merge_request: 13124
author: Florian Lemaitre
---
title: Add LDAP SSL certificate verification option
title: fix jump to next discussion button
merge_request:
author:
---
title: Show auto-generated avatars for Groups without avatars
merge_request: 13188
author:
---
title: Fix creating merge request diffs when diff contains bytes that are invalid
in UTF-8
merge_request:
author:
---
title: Fix display of new diff comments after changing b between diff views
merge_request:
author:
---
title: Allow any logged in users to read_users_list even if it's restricted
merge_request: 13201
author:
---
title: Fix Issue board when using Ruby 2.4
merge_request: 13220
author:
---
title: Fix encoding error for WebHook logging
merge_request: 13230
author: Alexander Randa (@randaalex)
---
title: repository archive download url now ends with selected file extension
merge_request: 13178
author: haseebeqx
---
title: Ensure filesystem metrics test files are deleted
title: Add filtered search to group issue dashboard
merge_request:
author:
---
title: Fixed breadcrumbs title aggressively collapsing
merge_request:
author:
---
title: Improve performance of large (initial) push into default branch
merge_request:
author:
---
title: Add API for protected branches to allow for wildcard matching and no access
restrictions
merge_request: 12756
author: Eric Yu
---
title: Modify if condition to be more readable
merge_request:
author:
---
title: Fix links to group milestones from issue and merge request sidebar
merge_request:
author:
---
title: Fix replying to commit comments on merge requests created from forks
merge_request:
author:
---
title: Uniquify reserved word usernames on OAuth user creation
merge_request: 13244
author: Robin Bobbitt
---
title: Improve redirect route query performance
merge_request: 13062
author:
---
title: Fix deletion of deploy keys linked to other projects
merge_request: 13162
author:
---
title: Fix improperly skipped backups of wikis.
merge_request: 13096
author:
---
title: Add support for kube_namespace in Metrics queries
merge_request: 16169
author:
---
title: Fix Prometheus client PID reuse bug
merge_request: 13130
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.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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