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 ...@@ -326,11 +326,11 @@ group :development, :test do
gem 'pry-rails', '~> 0.3.4' gem 'pry-rails', '~> 0.3.4'
gem 'awesome_print', '~> 1.2.0', require: false 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 'database_cleaner', '~> 1.5.0'
gem 'factory_girl_rails', '~> 4.7.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 'rspec-retry', '~> 0.4.5'
gem 'spinach-rails', '~> 0.2.1' gem 'spinach-rails', '~> 0.2.1'
gem 'spinach-rerun-reporter', '~> 0.0.2' gem 'spinach-rerun-reporter', '~> 0.0.2'
...@@ -351,8 +351,8 @@ group :development, :test do ...@@ -351,8 +351,8 @@ group :development, :test do
gem 'spring-commands-rspec', '~> 1.0.4' gem 'spring-commands-rspec', '~> 1.0.4'
gem 'spring-commands-spinach', '~> 1.1.0' gem 'spring-commands-spinach', '~> 1.1.0'
gem 'rubocop', '~> 0.47.1', require: false gem 'rubocop', '~> 0.49.1', require: false
gem 'rubocop-rspec', '~> 1.15.0', require: false gem 'rubocop-rspec', '~> 1.15.1', require: false
gem 'scss_lint', '~> 0.54.0', require: false gem 'scss_lint', '~> 0.54.0', require: false
gem 'haml_lint', '~> 0.21.0', require: false gem 'haml_lint', '~> 0.21.0', require: false
gem 'simplecov', '~> 0.14.0', require: false gem 'simplecov', '~> 0.14.0', require: false
...@@ -406,7 +406,7 @@ gem 'sys-filesystem', '~> 1.1.6' ...@@ -406,7 +406,7 @@ gem 'sys-filesystem', '~> 1.1.6'
gem 'net-ntp' gem 'net-ntp'
# Gitaly GRPC client # Gitaly GRPC client
gem 'gitaly', '~> 0.21.0' gem 'gitaly', '~> 0.23.0'
gem 'toml-rb', '~> 0.3.15', require: false gem 'toml-rb', '~> 0.3.15', require: false
......
...@@ -164,7 +164,7 @@ GEM ...@@ -164,7 +164,7 @@ GEM
devise (~> 4.0) devise (~> 4.0)
railties railties
rotp (~> 2.0) rotp (~> 2.0)
diff-lcs (1.2.5) diff-lcs (1.3)
diffy (3.1.0) diffy (3.1.0)
docile (1.1.5) docile (1.1.5)
domain_name (0.5.20161021) domain_name (0.5.20161021)
...@@ -274,8 +274,8 @@ GEM ...@@ -274,8 +274,8 @@ GEM
foreman (0.78.0) foreman (0.78.0)
thor (~> 0.19.1) thor (~> 0.19.1)
formatador (0.2.5) formatador (0.2.5)
fuubar (2.0.0) fuubar (2.2.0)
rspec (~> 3.0) rspec-core (~> 3.0)
ruby-progressbar (~> 1.4) ruby-progressbar (~> 1.4)
gemnasium-gitlab-service (0.2.6) gemnasium-gitlab-service (0.2.6)
rugged (~> 0.21) rugged (~> 0.21)
...@@ -293,7 +293,7 @@ GEM ...@@ -293,7 +293,7 @@ GEM
po_to_json (>= 1.0.0) po_to_json (>= 1.0.0)
rails (>= 3.2.0) rails (>= 3.2.0)
gherkin-ruby (0.3.2) gherkin-ruby (0.3.2)
gitaly (0.21.0) gitaly (0.23.0)
google-protobuf (~> 3.1) google-protobuf (~> 3.1)
grpc (~> 1.0) grpc (~> 1.0)
github-linguist (4.7.6) github-linguist (4.7.6)
...@@ -348,7 +348,7 @@ GEM ...@@ -348,7 +348,7 @@ GEM
multi_json (~> 1.10) multi_json (~> 1.10)
retriable (~> 1.4) retriable (~> 1.4)
signet (~> 0.6) signet (~> 0.6)
google-protobuf (3.2.0.2) google-protobuf (3.3.0)
googleauth (0.5.1) googleauth (0.5.1)
faraday (~> 0.9) faraday (~> 0.9)
jwt (~> 1.4) jwt (~> 1.4)
...@@ -420,7 +420,7 @@ GEM ...@@ -420,7 +420,7 @@ GEM
json (~> 1.8) json (~> 1.8)
multi_xml (>= 0.5.2) multi_xml (>= 0.5.2)
httpclient (2.8.2) httpclient (2.8.2)
i18n (0.8.1) i18n (0.8.6)
ice_nine (0.11.2) ice_nine (0.11.2)
influxdb (0.2.3) influxdb (0.2.3)
cause cause
...@@ -574,6 +574,7 @@ GEM ...@@ -574,6 +574,7 @@ GEM
rubypants (~> 0.2) rubypants (~> 0.2)
orm_adapter (0.5.0) orm_adapter (0.5.0)
os (0.9.6) os (0.9.6)
parallel (1.11.2)
paranoia (2.3.1) paranoia (2.3.1)
activerecord (>= 4.0, < 5.2) activerecord (>= 4.0, < 5.2)
parser (2.4.0.0) parser (2.4.0.0)
...@@ -638,7 +639,7 @@ GEM ...@@ -638,7 +639,7 @@ GEM
pry-rails (0.3.5) pry-rails (0.3.5)
pry (>= 0.9.10) pry (>= 0.9.10)
pyu-ruby-sasl (0.0.3.3) pyu-ruby-sasl (0.0.3.3)
rack (1.6.5) rack (1.6.8)
rack-accept (0.4.5) rack-accept (0.4.5)
rack (>= 0.4) rack (>= 0.4)
rack-attack (4.4.1) rack-attack (4.4.1)
...@@ -686,7 +687,7 @@ GEM ...@@ -686,7 +687,7 @@ GEM
rainbow (2.2.2) rainbow (2.2.2)
rake rake
raindrops (0.18.0) raindrops (0.18.0)
rake (10.5.0) rake (12.0.0)
rblineprof (0.3.6) rblineprof (0.3.6)
debugger-ruby_core_source (~> 1.3) debugger-ruby_core_source (~> 1.3)
rdoc (4.2.2) rdoc (4.2.2)
...@@ -730,42 +731,39 @@ GEM ...@@ -730,42 +731,39 @@ GEM
chunky_png chunky_png
rqrcode-rails3 (0.1.7) rqrcode-rails3 (0.1.7)
rqrcode (>= 0.4.2) rqrcode (>= 0.4.2)
rspec (3.5.0) rspec-core (3.6.0)
rspec-core (~> 3.5.0) rspec-support (~> 3.6.0)
rspec-expectations (~> 3.5.0) rspec-expectations (3.6.0)
rspec-mocks (~> 3.5.0)
rspec-core (3.5.0)
rspec-support (~> 3.5.0)
rspec-expectations (3.5.0)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.5.0) rspec-support (~> 3.6.0)
rspec-mocks (3.5.0) rspec-mocks (3.6.0)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.5.0) rspec-support (~> 3.6.0)
rspec-rails (3.5.0) rspec-rails (3.6.0)
actionpack (>= 3.0) actionpack (>= 3.0)
activesupport (>= 3.0) activesupport (>= 3.0)
railties (>= 3.0) railties (>= 3.0)
rspec-core (~> 3.5.0) rspec-core (~> 3.6.0)
rspec-expectations (~> 3.5.0) rspec-expectations (~> 3.6.0)
rspec-mocks (~> 3.5.0) rspec-mocks (~> 3.6.0)
rspec-support (~> 3.5.0) rspec-support (~> 3.6.0)
rspec-retry (0.4.5) rspec-retry (0.4.5)
rspec-core rspec-core
rspec-set (0.1.3) rspec-set (0.1.3)
rspec-support (3.5.0) rspec-support (3.6.0)
rspec_profiling (0.0.5) rspec_profiling (0.0.5)
activerecord activerecord
pg pg
rails rails
sqlite3 sqlite3
rubocop (0.47.1) rubocop (0.49.1)
parallel (~> 1.10)
parser (>= 2.3.3.1, < 3.0) parser (>= 2.3.3.1, < 3.0)
powerpack (~> 0.1) powerpack (~> 0.1)
rainbow (>= 1.99.1, < 3.0) rainbow (>= 1.99.1, < 3.0)
ruby-progressbar (~> 1.7) ruby-progressbar (~> 1.7)
unicode-display_width (~> 1.0, >= 1.0.1) unicode-display_width (~> 1.0, >= 1.0.1)
rubocop-rspec (1.15.0) rubocop-rspec (1.15.1)
rubocop (>= 0.42.0) rubocop (>= 0.42.0)
ruby-fogbugz (0.2.1) ruby-fogbugz (0.2.1)
crack (~> 0.4) crack (~> 0.4)
...@@ -887,7 +885,7 @@ GEM ...@@ -887,7 +885,7 @@ GEM
truncato (0.7.8) truncato (0.7.8)
htmlentities (~> 4.3.1) htmlentities (~> 4.3.1)
nokogiri (~> 1.6.1) nokogiri (~> 1.6.1)
tzinfo (1.2.2) tzinfo (1.2.3)
thread_safe (~> 0.1) thread_safe (~> 0.1)
u2f (0.2.1) u2f (0.2.1)
uglifier (2.7.2) uglifier (2.7.2)
...@@ -897,7 +895,7 @@ GEM ...@@ -897,7 +895,7 @@ GEM
unf (0.1.4) unf (0.1.4)
unf_ext unf_ext
unf_ext (0.0.7.2) unf_ext (0.0.7.2)
unicode-display_width (1.1.3) unicode-display_width (1.3.0)
unicorn (5.1.0) unicorn (5.1.0)
kgio (~> 2.6) kgio (~> 2.6)
raindrops (~> 0.7) raindrops (~> 0.7)
...@@ -1004,13 +1002,13 @@ DEPENDENCIES ...@@ -1004,13 +1002,13 @@ DEPENDENCIES
fog-rackspace (~> 0.1.1) fog-rackspace (~> 0.1.1)
font-awesome-rails (~> 4.7) font-awesome-rails (~> 4.7)
foreman (~> 0.78.0) foreman (~> 0.78.0)
fuubar (~> 2.0.0) fuubar (~> 2.2.0)
gemnasium-gitlab-service (~> 0.2) gemnasium-gitlab-service (~> 0.2)
gemojione (~> 3.0) gemojione (~> 3.0)
gettext (~> 3.2.2) gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.2.0) gettext_i18n_rails_js (~> 1.2.0)
gitaly (~> 0.21.0) gitaly (~> 0.23.0)
github-linguist (~> 4.7.0) github-linguist (~> 4.7.0)
gitlab-flowdock-git-hook (~> 1.0.1) gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-license (~> 1.0) gitlab-license (~> 1.0)
...@@ -1111,12 +1109,12 @@ DEPENDENCIES ...@@ -1111,12 +1109,12 @@ DEPENDENCIES
responders (~> 2.0) responders (~> 2.0)
rouge (~> 2.0) rouge (~> 2.0)
rqrcode-rails3 (~> 0.1.7) rqrcode-rails3 (~> 0.1.7)
rspec-rails (~> 3.5.0) rspec-rails (~> 3.6.0)
rspec-retry (~> 0.4.5) rspec-retry (~> 0.4.5)
rspec-set (~> 0.1.3) rspec-set (~> 0.1.3)
rspec_profiling (~> 0.0.5) rspec_profiling (~> 0.0.5)
rubocop (~> 0.47.1) rubocop (~> 0.49.1)
rubocop-rspec (~> 1.15.0) rubocop-rspec (~> 1.15.1)
ruby-fogbugz (~> 0.2.1) ruby-fogbugz (~> 0.2.1)
ruby-prof (~> 0.16.2) ruby-prof (~> 0.16.2)
ruby_parser (~> 3.8) ruby_parser (~> 3.8)
...@@ -1165,4 +1163,4 @@ DEPENDENCIES ...@@ -1165,4 +1163,4 @@ DEPENDENCIES
wikicloth (= 0.8.1) wikicloth (= 0.8.1)
BUNDLED WITH BUNDLED WITH
1.15.1 1.15.3
...@@ -128,7 +128,7 @@ information, see ...@@ -128,7 +128,7 @@ information, see
### After the 7th ### 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. 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. 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. 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 ...@@ -158,6 +158,24 @@ release should have the correct milestone assigned _and_ have the label
Merge requests without a milestone and this label will Merge requests without a milestone and this label will
not be merged into any stable branches. 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 ## Release retrospective and kickoff
### Retrospective ### Retrospective
......
...@@ -10,7 +10,7 @@ class AjaxLoadingSpinner { ...@@ -10,7 +10,7 @@ class AjaxLoadingSpinner {
e.target.setAttribute('disabled', ''); e.target.setAttribute('disabled', '');
const iconElement = e.target.querySelector('i'); const iconElement = e.target.querySelector('i');
// get first fa- icon // 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; iconElement.dataset.icon = originalIcon;
AjaxLoadingSpinner.toggleLoadingIcon(iconElement); AjaxLoadingSpinner.toggleLoadingIcon(iconElement);
$(e.target).off('ajax:beforeSend', AjaxLoadingSpinner.ajaxBeforeSend); $(e.target).off('ajax:beforeSend', AjaxLoadingSpinner.ajaxBeforeSend);
......
...@@ -64,7 +64,7 @@ window.Build = (function () { ...@@ -64,7 +64,7 @@ window.Build = (function () {
$(window) $(window)
.off('scroll') .off('scroll')
.on('scroll', () => { .on('scroll', () => {
const contentHeight = this.$buildTraceOutput.prop('scrollHeight'); const contentHeight = this.$buildTraceOutput.height();
if (contentHeight > this.windowSize) { if (contentHeight > this.windowSize) {
// means the user did not scroll, the content was updated. // means the user did not scroll, the content was updated.
this.windowSize = contentHeight; this.windowSize = contentHeight;
...@@ -105,16 +105,17 @@ window.Build = (function () { ...@@ -105,16 +105,17 @@ window.Build = (function () {
}; };
Build.prototype.canScroll = function () { Build.prototype.canScroll = function () {
return document.body.scrollHeight > window.innerHeight; return $(document).height() > $(window).height();
}; };
Build.prototype.toggleScroll = function () { Build.prototype.toggleScroll = function () {
const currentPosition = document.body.scrollTop; const currentPosition = $(document).scrollTop();
const windowHeight = window.innerHeight; const scrollHeight = $(document).height();
const windowHeight = $(window).height();
if (this.canScroll()) { if (this.canScroll()) {
if (currentPosition > 0 && if (currentPosition > 0 &&
(document.body.scrollHeight - currentPosition !== windowHeight)) { (scrollHeight - currentPosition !== windowHeight)) {
// User is in the middle of the log // User is in the middle of the log
this.toggleDisableButton(this.$scrollTopBtn, false); this.toggleDisableButton(this.$scrollTopBtn, false);
...@@ -124,7 +125,7 @@ window.Build = (function () { ...@@ -124,7 +125,7 @@ window.Build = (function () {
this.toggleDisableButton(this.$scrollTopBtn, true); this.toggleDisableButton(this.$scrollTopBtn, true);
this.toggleDisableButton(this.$scrollBottomBtn, false); 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. // User is at the bottom of the build log.
this.toggleDisableButton(this.$scrollTopBtn, false); this.toggleDisableButton(this.$scrollTopBtn, false);
...@@ -137,7 +138,7 @@ window.Build = (function () { ...@@ -137,7 +138,7 @@ window.Build = (function () {
}; };
Build.prototype.scrollDown = function () { Build.prototype.scrollDown = function () {
document.body.scrollTop = document.body.scrollHeight; $(document).scrollTop($(document).height());
}; };
Build.prototype.scrollToBottom = function () { Build.prototype.scrollToBottom = function () {
...@@ -147,7 +148,7 @@ window.Build = (function () { ...@@ -147,7 +148,7 @@ window.Build = (function () {
}; };
Build.prototype.scrollToTop = function () { Build.prototype.scrollToTop = function () {
document.body.scrollTop = 0; $(document).scrollTop(0);
this.hasBeenScrolled = true; this.hasBeenScrolled = true;
this.toggleScroll(); this.toggleScroll();
}; };
...@@ -178,7 +179,7 @@ window.Build = (function () { ...@@ -178,7 +179,7 @@ window.Build = (function () {
this.state = log.state; this.state = log.state;
} }
this.windowSize = this.$buildTraceOutput.prop('scrollHeight'); this.windowSize = this.$buildTraceOutput.height();
if (log.append) { if (log.append) {
this.$buildTraceOutput.append(log.html); this.$buildTraceOutput.append(log.html);
......
...@@ -92,7 +92,7 @@ $(() => { ...@@ -92,7 +92,7 @@ $(() => {
}); });
}, },
selectDefaultStage() { selectDefaultStage() {
const stage = this.state.stages.first(); const stage = this.state.stages[0];
this.selectStage(stage); this.selectStage(stage);
}, },
selectStage(stage) { selectStage(stage) {
......
...@@ -94,7 +94,7 @@ const JumpToDiscussion = Vue.extend({ ...@@ -94,7 +94,7 @@ const JumpToDiscussion = Vue.extend({
hasDiscussionsToJumpTo = false; hasDiscussionsToJumpTo = false;
} }
} }
} else if (activeTab !== 'notes') { } else if (activeTab !== 'show') {
// If we are on the commits or builds tabs, // If we are on the commits or builds tabs,
// there are no discussions to jump to. // there are no discussions to jump to.
hasDiscussionsToJumpTo = false; hasDiscussionsToJumpTo = false;
...@@ -103,12 +103,12 @@ const JumpToDiscussion = Vue.extend({ ...@@ -103,12 +103,12 @@ const JumpToDiscussion = Vue.extend({
if (!hasDiscussionsToJumpTo) { if (!hasDiscussionsToJumpTo) {
// If there are no discussions to jump to on the current page, // If there are no discussions to jump to on the current page,
// switch to the notes tab and jump to the first disucssion there. // switch to the notes tab and jump to the first disucssion there.
window.mrTabs.activateTab('notes'); window.mrTabs.activateTab('show');
activeTab = 'notes'; activeTab = 'show';
jumpToFirstDiscussion = true; jumpToFirstDiscussion = true;
} }
if (activeTab === 'notes') { if (activeTab === 'show') {
discussionsSelector = '.discussion[data-discussion-id]'; discussionsSelector = '.discussion[data-discussion-id]';
discussionIdsInScope = discussionIdsForElements($(discussionsSelector)); discussionIdsInScope = discussionIdsForElements($(discussionsSelector));
} }
...@@ -156,7 +156,7 @@ const JumpToDiscussion = Vue.extend({ ...@@ -156,7 +156,7 @@ const JumpToDiscussion = Vue.extend({
let $target = $(`${discussionsSelector}[data-discussion-id="${nextUnresolvedDiscussionId}"]`); let $target = $(`${discussionsSelector}[data-discussion-id="${nextUnresolvedDiscussionId}"]`);
if (activeTab === 'notes') { if (activeTab === 'show') {
$target = $target.closest('.note-discussion'); $target = $target.closest('.note-discussion');
// If the next discussion is closed, toggle it open. // If the next discussion is closed, toggle it open.
......
...@@ -21,6 +21,8 @@ ...@@ -21,6 +21,8 @@
/* global Search */ /* global Search */
/* global Admin */ /* global Admin */
/* global NamespaceSelects */ /* global NamespaceSelects */
/* global NewCommitForm */
/* global NewBranchForm */
/* global Project */ /* global Project */
/* global ProjectAvatar */ /* global ProjectAvatar */
/* global MergeRequest */ /* global MergeRequest */
...@@ -229,7 +231,6 @@ import initGroupAnalytics from './init_group_analytics'; ...@@ -229,7 +231,6 @@ import initGroupAnalytics from './init_group_analytics';
break; break;
case 'explore:groups:index': case 'explore:groups:index':
new GroupsList(); new GroupsList();
const landingElement = document.querySelector('.js-explore-groups-landing'); const landingElement = document.querySelector('.js-explore-groups-landing');
if (!landingElement) break; if (!landingElement) break;
const exploreGroupsLanding = new Landing( const exploreGroupsLanding = new Landing(
...@@ -323,12 +324,14 @@ import initGroupAnalytics from './init_group_analytics'; ...@@ -323,12 +324,14 @@ import initGroupAnalytics from './init_group_analytics';
new gl.Diff(); new gl.Diff();
shortcut_handler = new ShortcutsIssuable(true); shortcut_handler = new ShortcutsIssuable(true);
new ZenMode(); new ZenMode();
initIssuableSidebar();
initNotes();
const mrShowNode = document.querySelector('.merge-request'); const mrShowNode = document.querySelector('.merge-request');
window.mergeRequest = new MergeRequest({ window.mergeRequest = new MergeRequest({
action: mrShowNode.dataset.mrAction, action: mrShowNode.dataset.mrAction,
}); });
initIssuableSidebar();
initNotes();
break; break;
case 'dashboard:activity': case 'dashboard:activity':
new gl.Activities(); new gl.Activities();
...@@ -382,6 +385,9 @@ import initGroupAnalytics from './init_group_analytics'; ...@@ -382,6 +385,9 @@ import initGroupAnalytics from './init_group_analytics';
initSettingsPanels(); initSettingsPanels();
new UsersSelect(); new UsersSelect();
break; break;
case 'projects:imports:show':
new ProjectImport();
break;
case 'projects:pipelines:new': case 'projects:pipelines:new':
new NewBranchForm($('.js-new-pipeline-form')); new NewBranchForm($('.js-new-pipeline-form'));
break; break;
...@@ -570,7 +576,7 @@ import initGroupAnalytics from './init_group_analytics'; ...@@ -570,7 +576,7 @@ import initGroupAnalytics from './init_group_analytics';
initGroupAnalytics(); initGroupAnalytics();
break; break;
} }
switch (path.first()) { switch (path[0]) {
case 'sessions': case 'sessions':
case 'omniauth_callbacks': case 'omniauth_callbacks':
if (!gon.u2f) break; if (!gon.u2f) break;
......
...@@ -217,7 +217,7 @@ window.DropzoneInput = (function() { ...@@ -217,7 +217,7 @@ window.DropzoneInput = (function() {
value = e.clipboardData.getData('text/plain'); value = e.clipboardData.getData('text/plain');
} }
value = value.split("\r"); value = value.split("\r");
return value.first(); return value[0];
}; };
const showSpinner = function(e) { 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 { ...@@ -51,31 +51,6 @@ class DropdownUtils {
return updatedItem; 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) { static mergeDuplicateLabels(dataMap, newLabel) {
const updatedMap = dataMap; const updatedMap = dataMap;
const key = newLabel.title; const key = newLabel.title;
...@@ -136,6 +111,31 @@ class DropdownUtils { ...@@ -136,6 +111,31 @@ class DropdownUtils {
return results; 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) { static setDataValueIfSelected(filter, selected) {
const dataValue = selected.getAttribute('data-value'); const dataValue = selected.getAttribute('data-value');
......
...@@ -179,7 +179,7 @@ class FilteredSearchDropdownManager { ...@@ -179,7 +179,7 @@ class FilteredSearchDropdownManager {
// Eg. token = 'label:' // Eg. token = 'label:'
const split = lastToken.split(':'); const split = lastToken.split(':');
const dropdownName = split[0].split(' ').last(); const dropdownName = _.last(split[0].split(' '));
this.loadDropdown(split.length > 1 ? dropdownName : ''); this.loadDropdown(split.length > 1 ? dropdownName : '');
} else if (lastToken) { } else if (lastToken) {
// Token has been initialized into an object because it has a value // Token has been initialized into an object because it has a value
......
...@@ -375,7 +375,7 @@ class FilteredSearchManager { ...@@ -375,7 +375,7 @@ class FilteredSearchManager {
const fragments = searchToken.split(':'); const fragments = searchToken.split(':');
if (fragments.length > 1) { if (fragments.length > 1) {
const inputValues = fragments[0].split(' '); const inputValues = fragments[0].split(' ');
const tokenKey = inputValues.last(); const tokenKey = _.last(inputValues);
if (inputValues.length > 1) { if (inputValues.length > 1) {
inputValues.pop(); 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> <script>
import eventHub from '../event_hub'; import eventHub from '../event_hub';
import groupIdenticon from './group_identicon.vue';
export default { export default {
components: {
groupIdenticon,
},
props: { props: {
group: { group: {
type: Object, type: Object,
...@@ -92,6 +96,9 @@ export default { ...@@ -92,6 +96,9 @@ export default {
hasGroups() { hasGroups() {
return Object.keys(this.group.subGroups).length > 0; return Object.keys(this.group.subGroups).length > 0;
}, },
hasAvatar() {
return this.group.avatarUrl && this.group.avatarUrl.indexOf('/assets/no_group_avatar') === -1;
},
}, },
}; };
</script> </script>
...@@ -194,9 +201,15 @@ export default { ...@@ -194,9 +201,15 @@ export default {
<a <a
:href="group.groupPath"> :href="group.groupPath">
<img <img
v-if="hasAvatar"
class="avatar s40" class="avatar s40"
:src="group.avatarUrl" :src="group.avatarUrl"
/> />
<group-identicon
v-else
:entity-id=group.id
:entity-name="group.name"
/>
</a> </a>
</div> </div>
<div <div
......
...@@ -16,9 +16,6 @@ import 'mousetrap'; ...@@ -16,9 +16,6 @@ import 'mousetrap';
import 'mousetrap/plugins/pause/mousetrap-pause'; import 'mousetrap/plugins/pause/mousetrap-pause';
import 'vendor/fuzzaldrin-plus'; import 'vendor/fuzzaldrin-plus';
// extensions
import './extensions/array';
// expose common libraries as globals (TODO: remove these) // expose common libraries as globals (TODO: remove these)
window.jQuery = jQuery; window.jQuery = jQuery;
window.$ = jQuery; window.$ = jQuery;
...@@ -149,8 +146,6 @@ import './subscription'; ...@@ -149,8 +146,6 @@ import './subscription';
import './subscription_select'; import './subscription_select';
import './syntax_highlight'; import './syntax_highlight';
import './dispatcher';
// EE-only scripts // EE-only scripts
import './admin_email_select'; import './admin_email_select';
import './application_settings'; import './application_settings';
...@@ -159,6 +154,8 @@ import './ldap_groups_select'; ...@@ -159,6 +154,8 @@ import './ldap_groups_select';
import './path_locks'; import './path_locks';
import './weight_select'; import './weight_select';
import './dispatcher';
// eslint-disable-next-line global-require, import/no-commonjs // eslint-disable-next-line global-require, import/no-commonjs
if (process.env.NODE_ENV !== 'production') require('./test_utils/'); if (process.env.NODE_ENV !== 'production') require('./test_utils/');
......
...@@ -530,6 +530,7 @@ export default class Notes { ...@@ -530,6 +530,7 @@ export default class Notes {
form.find('#note_line_code').remove(); form.find('#note_line_code').remove();
form.find('#note_position').remove(); form.find('#note_position').remove();
form.find('#note_type').val(''); form.find('#note_type').val('');
form.find('#note_project_id').remove();
form.find('#in_reply_to_discussion_id').remove(); form.find('#in_reply_to_discussion_id').remove();
form.find('.js-comment-resolve-button').closest('comment-and-resolve-btn').remove(); form.find('.js-comment-resolve-button').closest('comment-and-resolve-btn').remove();
this.parentTimeline = form.parents('.timeline'); this.parentTimeline = form.parents('.timeline');
...@@ -557,6 +558,7 @@ export default class Notes { ...@@ -557,6 +558,7 @@ export default class Notes {
form.find('#note_noteable_id').val(), form.find('#note_noteable_id').val(),
form.find('#note_commit_id').val(), form.find('#note_commit_id').val(),
form.find('#note_type').val(), form.find('#note_type').val(),
form.find('#note_project_id').val(),
form.find('#in_reply_to_discussion_id').val(), form.find('#in_reply_to_discussion_id').val(),
// LegacyDiffNote // LegacyDiffNote
...@@ -849,6 +851,8 @@ export default class Notes { ...@@ -849,6 +851,8 @@ export default class Notes {
form.find('#in_reply_to_discussion_id').val(discussionID); 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.attr('data-line-code', dataHolder.data('lineCode'));
form.find('#line_type').val(dataHolder.data('lineType')); 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 */ /* 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 _ from 'underscore';
import 'vendor/cropper';
((global) => { ((global) => {
// Matches everything but the file name // 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 importBtnTooltip = 'Please enter a valid project name.';
const $importBtnWrapper = $('.import_gitlab_project'); 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) => { $('.how_to_import_link').on('click', (e) => {
e.preventDefault(); e.preventDefault();
...@@ -13,19 +47,19 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -13,19 +47,19 @@ document.addEventListener('DOMContentLoaded', () => {
$('.btn_import_gitlab_project').on('click', () => { $('.btn_import_gitlab_project').on('click', () => {
const importHref = $('a.btn_import_gitlab_project').attr('href'); 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); $importBtnWrapper.attr('title', importBtnTooltip);
$('#new_project').on('submit', () => { $newProjectForm.on('submit', () => {
const $path = $('#project_path'); $projectPath.val($projectPath.val().trim());
$path.val($path.val().trim());
}); });
$('#project_path').on('keyup', () => { $projectPath.on('keyup', () => {
if ($('#project_path').val().trim().length) { hasUserDefinedProjectPath = $projectPath.val().trim().length > 0;
if (hasUserDefinedProjectPath) {
$('.btn_import_gitlab_project').attr('disabled', false); $('.btn_import_gitlab_project').attr('disabled', false);
$importBtnWrapper.attr('title', ''); $importBtnWrapper.attr('title', '');
$importBtnWrapper.removeClass('has-tooltip'); $importBtnWrapper.removeClass('has-tooltip');
...@@ -35,9 +69,17 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -35,9 +69,17 @@ document.addEventListener('DOMContentLoaded', () => {
} }
}); });
$('#project_import_url').disable(); $projectImportUrl.disable();
$projectImportUrl.keyup(() => deriveProjectPathFromUrl($projectImportUrl, $projectPath));
$('.import_git').on('click', () => { $('.import_git').on('click', () => {
const $projectImportUrl = $('#project_import_url');
$projectImportUrl.attr('disabled', !$projectImportUrl.attr('disabled')); $projectImportUrl.attr('disabled', !$projectImportUrl.attr('disabled'));
}); });
}); };
document.addEventListener('DOMContentLoaded', bindEvents);
export default {
bindEvents,
deriveProjectPathFromUrl,
};
...@@ -92,6 +92,10 @@ ...@@ -92,6 +92,10 @@
overflow: hidden; overflow: hidden;
display: flex; display: flex;
a {
display: flex;
}
.avatar { .avatar {
border-radius: 0; border-radius: 0;
border: none; border: none;
......
...@@ -772,4 +772,8 @@ ...@@ -772,4 +772,8 @@
} }
} }
} }
.dropdown-menu-align-right {
margin-top: 2px;
}
} }
...@@ -414,13 +414,16 @@ ...@@ -414,13 +414,16 @@
background-color: $dropdown-hover-color; background-color: $dropdown-hover-color;
color: $white-light; color: $white-light;
text-decoration: none; text-decoration: none;
outline: 0;
.avatar { .avatar {
border-color: $white-light; border-color: $white-light;
} }
} }
.filter-dropdown-item { .droplab-dropdown .dropdown-menu .filter-dropdown-item {
padding: 0;
.btn { .btn {
border: none; border: none;
width: 100%; width: 100%;
...@@ -455,14 +458,11 @@ ...@@ -455,14 +458,11 @@
} }
.dropdown-user { .dropdown-user {
display: -webkit-flex;
display: flex; display: flex;
} }
.dropdown-user-details { .dropdown-user-details {
display: -webkit-flex;
display: flex; display: flex;
-webkit-flex-direction: column;
flex-direction: column; flex-direction: column;
> span { > span {
......
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
*/ */
header { header {
@include new-style-dropdown;
transition: padding $sidebar-transition-duration; transition: padding $sidebar-transition-duration;
&.navbar-empty { &.navbar-empty {
...@@ -319,6 +321,10 @@ header { ...@@ -319,6 +321,10 @@ header {
top: $performance-bar-height; top: $performance-bar-height;
} }
.with-performance-bar header.navbar-gitlab {
top: $performance-bar-height;
}
.navbar-nav { .navbar-nav {
li { li {
.badge { .badge {
......
...@@ -375,6 +375,10 @@ ul.indent-list { ...@@ -375,6 +375,10 @@ ul.indent-list {
background-color: $row-hover; background-color: $row-hover;
cursor: pointer; cursor: pointer;
} }
.avatar-container > a {
width: 100%;
}
} }
} }
......
...@@ -185,3 +185,28 @@ ...@@ -185,3 +185,28 @@
text-overflow: ellipsis; 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 @@ ...@@ -19,9 +19,9 @@
} }
img.js-lazy-loaded { img.js-lazy-loaded {
min-width: none; min-width: inherit;
min-height: none; min-height: inherit;
background-color: none; background-color: inherit;
} }
p a:not(.no-attachment-icon) img { p a:not(.no-attachment-icon) img {
......
...@@ -348,6 +348,7 @@ header.navbar-gitlab-new { ...@@ -348,6 +348,7 @@ header.navbar-gitlab-new {
.breadcrumbs-links { .breadcrumbs-links {
flex: 1; flex: 1;
min-width: 0;
align-self: center; align-self: center;
color: $gl-text-color-quaternary; color: $gl-text-color-quaternary;
...@@ -366,7 +367,7 @@ header.navbar-gitlab-new { ...@@ -366,7 +367,7 @@ header.navbar-gitlab-new {
} }
.title { .title {
white-space: nowrap; display: inline-block;
> a { > a {
&:last-of-type:not(:first-child) { &:last-of-type:not(:first-child) {
......
...@@ -86,6 +86,7 @@ ...@@ -86,6 +86,7 @@
position: absolute; position: absolute;
right: 0; right: 0;
left: 0; left: 0;
top: 0;
} }
.truncated-info { .truncated-info {
...@@ -310,9 +311,7 @@ ...@@ -310,9 +311,7 @@
} }
.dropdown-menu { .dropdown-menu {
right: $gl-padding; margin-top: -$gl-padding;
left: $gl-padding;
width: auto;
} }
svg { svg {
......
#cycle-analytics { #cycle-analytics {
@include new-style-dropdown;
max-width: 1000px; max-width: 1000px;
margin: 24px auto 0; margin: 24px auto 0;
position: relative; position: relative;
......
.tree-holder { .tree-holder {
@include new-style-dropdown;
.nav-block { .nav-block {
margin: 10px 0; margin: 10px 0;
......
...@@ -4,6 +4,7 @@ module NotesActions ...@@ -4,6 +4,7 @@ module NotesActions
included do included do
before_action :authorize_admin_note!, only: [:update, :destroy] before_action :authorize_admin_note!, only: [:update, :destroy]
before_action :note_project, only: [:create]
end end
def index def index
...@@ -28,7 +29,8 @@ module NotesActions ...@@ -28,7 +29,8 @@ module NotesActions
merge_request_diff_head_sha: params[:merge_request_diff_head_sha], merge_request_diff_head_sha: params[:merge_request_diff_head_sha],
in_reply_to_discussion_id: params[:in_reply_to_discussion_id] 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) if @note.is_a?(Note)
Banzai::NoteRenderer.render([@note], @project, current_user) Banzai::NoteRenderer.render([@note], @project, current_user)
...@@ -177,4 +179,22 @@ module NotesActions ...@@ -177,4 +179,22 @@ module NotesActions
def notes_finder def notes_finder
@notes_finder ||= NotesFinder.new(project, current_user, finder_params) @notes_finder ||= NotesFinder.new(project, current_user, finder_params)
end 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 end
...@@ -13,7 +13,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController ...@@ -13,7 +13,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController
end end
def destroy 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| respond_to do |format|
format.html do format.html do
...@@ -37,7 +37,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController ...@@ -37,7 +37,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController
end end
def restore 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 render json: todos_counts
end end
......
...@@ -66,7 +66,8 @@ module Projects ...@@ -66,7 +66,8 @@ module Projects
end end
def filter_params 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 end
def move_params def move_params
......
...@@ -8,7 +8,7 @@ class Projects::BranchesController < Projects::ApplicationController ...@@ -8,7 +8,7 @@ class Projects::BranchesController < Projects::ApplicationController
before_action :authorize_push_code!, only: [:new, :create, :destroy, :destroy_all_merged] before_action :authorize_push_code!, only: [:new, :create, :destroy, :destroy_all_merged]
def index def index
@sort = params[:sort].presence || sort_value_name @sort = params[:sort].presence || sort_value_recently_updated
@branches = BranchesFinder.new(@repository, params).execute @branches = BranchesFinder.new(@repository, params).execute
@branches = Kaminari.paginate_array(@branches).page(params[:page]) @branches = Kaminari.paginate_array(@branches).page(params[:page])
......
...@@ -95,9 +95,18 @@ class TodosFinder ...@@ -95,9 +95,18 @@ class TodosFinder
@project @project
end 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) def projects(items)
item_project_ids = items.reorder(nil).select(:project_id) ProjectsFinder.new(current_user: current_user, project_ids_relation: project_ids(items)).execute
ProjectsFinder.new(current_user: current_user, project_ids_relation: item_project_ids).execute
end end
def type? def type?
......
...@@ -15,7 +15,7 @@ module DiffHelper ...@@ -15,7 +15,7 @@ module DiffHelper
def diff_view def diff_view
@diff_view ||= begin @diff_view ||= begin
diff_views = %w(inline parallel) 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 = diff_views.first unless diff_views.include?(diff_view)
diff_view.to_sym diff_view.to_sym
end end
......
...@@ -50,8 +50,12 @@ module GitlabRoutingHelper ...@@ -50,8 +50,12 @@ module GitlabRoutingHelper
end end
def milestone_path(entity, *args) def milestone_path(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) project_milestone_path(entity.project, entity, *args)
end end
end
def issue_url(entity, *args) def issue_url(entity, *args)
project_issue_url(entity.project, entity, *args) project_issue_url(entity.project, entity, *args)
...@@ -65,6 +69,14 @@ module GitlabRoutingHelper ...@@ -65,6 +69,14 @@ module GitlabRoutingHelper
project_pipeline_url(pipeline.project, pipeline.id, *args) project_pipeline_url(pipeline.project, pipeline.id, *args)
end 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) def pipeline_job_url(pipeline, build, *args)
project_job_url(pipeline.project, build.id, *args) project_job_url(pipeline.project, build.id, *args)
end end
......
...@@ -62,7 +62,11 @@ module NotesHelper ...@@ -62,7 +62,11 @@ module NotesHelper
def link_to_reply_discussion(discussion, line_type = nil) def link_to_reply_discussion(discussion, line_type = nil)
return unless current_user 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', button_tag 'Reply...', class: 'btn btn-text-field js-discussion-reply-button',
data: data, title: 'Add a reply' data: data, title: 'Add a reply'
......
...@@ -34,6 +34,8 @@ module WebpackHelper ...@@ -34,6 +34,8 @@ module WebpackHelper
end end
def webpack_public_path 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
end end
module ProtectedBranchAccess module ProtectedBranchAccess
extend ActiveSupport::Concern extend ActiveSupport::Concern
ALLOWED_ACCESS_LEVELS ||= [
Gitlab::Access::MASTER,
Gitlab::Access::DEVELOPER,
Gitlab::Access::NO_ACCESS
].freeze
included do included do
include ProtectedRefAccess include ProtectedRefAccess
include EE::ProtectedRefAccess include EE::ProtectedRefAccess
...@@ -10,11 +16,7 @@ module ProtectedBranchAccess ...@@ -10,11 +16,7 @@ module ProtectedBranchAccess
delegate :project, to: :protected_branch delegate :project, to: :protected_branch
validates :access_level, presence: true, inclusion: { validates :access_level, presence: true, inclusion: {
in: [ in: ALLOWED_ACCESS_LEVELS
Gitlab::Access::MASTER,
Gitlab::Access::DEVELOPER,
Gitlab::Access::NO_ACCESS
]
} }
def self.human_access_levels def self.human_access_levels
......
...@@ -25,6 +25,18 @@ module Referable ...@@ -25,6 +25,18 @@ module Referable
to_reference(from_project) to_reference(from_project)
end 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 module ClassMethods
# The character that prefixes the actual reference identifier # The character that prefixes the actual reference identifier
# #
......
...@@ -16,8 +16,6 @@ class Key < ActiveRecord::Base ...@@ -16,8 +16,6 @@ class Key < ActiveRecord::Base
presence: true, presence: true,
length: { maximum: 5000 }, length: { maximum: 5000 },
format: { with: /\A(ssh|ecdsa)-.*\Z/ } format: { with: /\A(ssh|ecdsa)-.*\Z/ }
validates :key,
format: { without: /\n|\r/, message: 'should be a single line' }
validates :fingerprint, validates :fingerprint,
uniqueness: true, uniqueness: true,
presence: { message: 'cannot be generated' } presence: { message: 'cannot be generated' }
...@@ -33,6 +31,7 @@ class Key < ActiveRecord::Base ...@@ -33,6 +31,7 @@ class Key < ActiveRecord::Base
after_destroy :post_destroy_hook after_destroy :post_destroy_hook
def key=(value) def key=(value)
value&.delete!("\n\r")
value.strip! unless value.blank? value.strip! unless value.blank?
write_attribute(:key, value) write_attribute(:key, value)
end end
......
...@@ -85,11 +85,7 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -85,11 +85,7 @@ class MergeRequestDiff < ActiveRecord::Base
def raw_diffs(options = {}) def raw_diffs(options = {})
if options[:ignore_whitespace_change] if options[:ignore_whitespace_change]
@diffs_no_whitespace ||= @diffs_no_whitespace ||= compare.diffs(options)
Gitlab::Git::Compare.new(
repository.raw_repository,
safe_start_commit_sha,
head_commit_sha).diffs(options)
else else
@raw_diffs ||= {} @raw_diffs ||= {}
@raw_diffs[options] ||= load_diffs(options) @raw_diffs[options] ||= load_diffs(options)
......
class NotificationSetting < ActiveRecord::Base class NotificationSetting < ActiveRecord::Base
include IgnorableColumn
ignore_column :events
enum level: { global: 3, watch: 2, mention: 4, participating: 1, disabled: 0, custom: 5 } enum level: { global: 3, watch: 2, mention: 4, participating: 1, disabled: 0, custom: 5 }
default_value_for :level, NotificationSetting.levels[:global] default_value_for :level, NotificationSetting.levels[:global]
...@@ -41,9 +45,6 @@ class NotificationSetting < ActiveRecord::Base ...@@ -41,9 +45,6 @@ class NotificationSetting < ActiveRecord::Base
:success_pipeline :success_pipeline
].freeze ].freeze
store :events, coder: JSON
before_save :convert_events
def self.find_or_create_for(source) def self.find_or_create_for(source)
setting = find_or_initialize_by(source: source) setting = find_or_initialize_by(source: source)
...@@ -54,42 +55,17 @@ class NotificationSetting < ActiveRecord::Base ...@@ -54,42 +55,17 @@ class NotificationSetting < ActiveRecord::Base
setting setting
end 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 # Allow people to receive failed pipeline notifications if they already have
# custom notifications enabled, as these are more like mentions than the other # custom notifications enabled, as these are more like mentions than the other
# custom settings. # custom settings.
def failed_pipeline def failed_pipeline
bool = super bool = super
bool = events[:failed_pipeline] if bool.nil?
bool.nil? || bool bool.nil? || bool
end end
alias_method :failed_pipeline?, :failed_pipeline alias_method :failed_pipeline?, :failed_pipeline
def event_enabled?(event) def event_enabled?(event)
respond_to?(event) && public_send(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)
end end
end end
...@@ -1206,7 +1206,18 @@ class Project < ActiveRecord::Base ...@@ -1206,7 +1206,18 @@ class Project < ActiveRecord::Base
end end
def remove_private_deploy_keys 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 end
# TODO: what to do here when not using Legacy Storage? Do we still need to rename and delay removal? # 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 ...@@ -104,7 +104,7 @@ class JiraService < IssueTrackerService
def close_issue(entity, external_issue) def close_issue(entity, external_issue)
issue = jira_request { client.Issue.find(external_issue.iid) } 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) commit_id = if entity.is_a?(Commit)
entity.id entity.id
...@@ -118,7 +118,7 @@ class JiraService < IssueTrackerService ...@@ -118,7 +118,7 @@ class JiraService < IssueTrackerService
# may or may not be allowed. Refresh the issue after transition and check # 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. # 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) 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 end
def create_cross_reference_note(mentioned, noteable, author) def create_cross_reference_note(mentioned, noteable, author)
...@@ -216,6 +216,10 @@ class JiraService < IssueTrackerService ...@@ -216,6 +216,10 @@ class JiraService < IssueTrackerService
end end
end end
def has_resolution?(issue)
issue.respond_to?(:resolution) && issue.resolution.present?
end
def comment_exists?(issue, message) def comment_exists?(issue, message)
comments = jira_request { issue.comments } comments = jira_request { issue.comments }
......
class ProjectWiki class ProjectWiki
include Gitlab::ShellAdapter include Gitlab::ShellAdapter
include Storage::LegacyProjectWiki
# EE only modules
include Elastic::WikiRepositoriesSearch include Elastic::WikiRepositoriesSearch
include Gitlab::CurrentSettings include Gitlab::CurrentSettings
include Storage::LegacyProjectWiki
MARKUPS = { MARKUPS = {
'Markdown' => :markdown, 'Markdown' => :markdown,
......
...@@ -1018,7 +1018,7 @@ class Repository ...@@ -1018,7 +1018,7 @@ class Repository
if is_enabled if is_enabled
raw_repository.is_ancestor?(ancestor_id, descendant_id) raw_repository.is_ancestor?(ancestor_id, descendant_id)
else else
merge_base_commit(ancestor_id, descendant_id) == ancestor_id rugged_is_ancestor?(ancestor_id, descendant_id)
end end
end end
end end
......
...@@ -50,6 +50,11 @@ class User < ActiveRecord::Base ...@@ -50,6 +50,11 @@ class User < ActiveRecord::Base
devise :lockable, :recoverable, :rememberable, :trackable, devise :lockable, :recoverable, :rememberable, :trackable,
:validatable, :omniauthable, :confirmable, :registerable :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! # Override Devise::Models::Trackable#update_tracked_fields!
# to limit database writes to at most once every hour # to limit database writes to at most once every hour
def update_tracked_fields!(request) def update_tracked_fields!(request)
......
...@@ -44,7 +44,7 @@ class GlobalPolicy < BasePolicy ...@@ -44,7 +44,7 @@ class GlobalPolicy < BasePolicy
prevent :log_in prevent :log_in
end end
rule { admin | ~restricted_public_level }.policy do rule { ~(anonymous & restricted_public_level) }.policy do
enable :read_users_list enable :read_users_list
end end
end end
...@@ -11,7 +11,7 @@ class DeleteMergedBranchesService < BaseService ...@@ -11,7 +11,7 @@ class DeleteMergedBranchesService < BaseService
# Prevent deletion of branches relevant to open merge requests # Prevent deletion of branches relevant to open merge requests
branches -= merge_request_branch_names branches -= merge_request_branch_names
# Prevent deletion of protected branches # Prevent deletion of protected branches
branches -= project.protected_branches.pluck(:name) branches = branches.reject { |branch| project.protected_for?(branch) }
branches.each do |branch| branches.each do |branch|
DeleteBranchService.new(project, current_user).execute(branch) DeleteBranchService.new(project, current_user).execute(branch)
......
...@@ -45,6 +45,7 @@ class GitPushService < BaseService ...@@ -45,6 +45,7 @@ class GitPushService < BaseService
elsif push_to_existing_branch? elsif push_to_existing_branch?
# Collect data for this git push # Collect data for this git push
@push_commits = @project.repository.commits_between(params[:oldrev], params[:newrev]) @push_commits = @project.repository.commits_between(params[:oldrev], params[:newrev])
process_commit_messages process_commit_messages
# Update the bare repositories info/attributes file using the contents of the default branches # Update the bare repositories info/attributes file using the contents of the default branches
...@@ -71,15 +72,21 @@ class GitPushService < BaseService ...@@ -71,15 +72,21 @@ class GitPushService < BaseService
def update_caches def update_caches
if is_default_branch? if is_default_branch?
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 paths = Set.new
@push_commits.each do |commit| @push_commits.last(PROCESS_COMMIT_LIMIT).each do |commit|
commit.raw_deltas.each do |diff| commit.raw_deltas.each do |diff|
paths << diff.new_path paths << diff.new_path
end end
end end
types = Gitlab::FileDetector.types_in_paths(paths.to_a) types = Gitlab::FileDetector.types_in_paths(paths.to_a)
end
else else
types = [] types = []
end end
...@@ -97,7 +104,7 @@ class GitPushService < BaseService ...@@ -97,7 +104,7 @@ class GitPushService < BaseService
def process_commit_messages def process_commit_messages
default = is_default_branch? 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? if commit.matches_cross_reference_regex?
ProcessCommitWorker ProcessCommitWorker
.perform_async(project.id, current_user.id, commit.to_hash, default) .perform_async(project.id, current_user.id, commit.to_hash, default)
...@@ -145,7 +152,10 @@ class GitPushService < BaseService ...@@ -145,7 +152,10 @@ class GitPushService < BaseService
end end
def process_default_branch 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 # Ensure HEAD points to the default branch in case it is not master
project.change_head(branch_name) project.change_head(branch_name)
...@@ -174,7 +184,8 @@ class GitPushService < BaseService ...@@ -174,7 +184,8 @@ class GitPushService < BaseService
params[:oldrev], params[:oldrev],
params[:newrev], params[:newrev],
params[:ref], params[:ref],
push_commits) @push_commits,
commits_count: @push_commits_count)
end end
def push_to_existing_branch? def push_to_existing_branch?
......
...@@ -290,7 +290,7 @@ class IssuableBaseService < BaseService ...@@ -290,7 +290,7 @@ class IssuableBaseService < BaseService
todo_service.mark_todo(issuable, current_user) todo_service.mark_todo(issuable, current_user)
when 'done' when 'done'
todo = TodosFinder.new(current_user).execute.find_by(target: issuable) 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
end end
......
...@@ -5,6 +5,9 @@ module QuickActions ...@@ -5,6 +5,9 @@ module QuickActions
attr_reader :issuable attr_reader :issuable
SHRUG = '¯\\_(ツ)_/¯'.freeze
TABLEFLIP = '(╯°□°)╯︵ ┻━┻'.freeze
# Takes a text and interprets the commands that are extracted from it. # 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. # Returns the content without commands, and hash of changes to be applied to a record.
def execute(content, issuable) def execute(content, issuable)
...@@ -15,6 +18,7 @@ module QuickActions ...@@ -15,6 +18,7 @@ module QuickActions
content, commands = extractor.extract_commands(content, context) content, commands = extractor.extract_commands(content, context)
extract_updates(commands, context) extract_updates(commands, context)
[content, @updates] [content, @updates]
end end
...@@ -424,6 +428,18 @@ module QuickActions ...@@ -424,6 +428,18 @@ module QuickActions
@updates[:spend_time] = { duration: :reset, user: current_user } @updates[:spend_time] = { duration: :reset, user: current_user }
end 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 # This is a dummy command, so that it appears in the autocomplete commands
desc 'CC' desc 'CC'
params '@user' params '@user'
......
...@@ -178,20 +178,22 @@ class TodoService ...@@ -178,20 +178,22 @@ class TodoService
# When user marks some todos as done # When user marks some todos as done
def mark_todos_as_done(todos, current_user) 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 end
def mark_todos_as_done_by_ids(ids, current_user) 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 end
# When user marks some todos as pending # When user marks some todos as pending
def mark_todos_as_pending(todos, current_user) 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 end
def mark_todos_as_pending_by_ids(ids, current_user) 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 end
# When user marks an issue as todo # When user marks an issue as todo
...@@ -206,9 +208,11 @@ class TodoService ...@@ -206,9 +208,11 @@ class TodoService
private private
def update_todos_state_by_ids(ids, current_user, state) def todos_by_ids(ids, current_user)
todos = current_user.todos.where(id: ids) 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 # Only update those that are not really on that state
todos = todos.where.not(state: state) todos = todos.where.not(state: state)
todos_ids = todos.pluck(:id) todos_ids = todos.pluck(:id)
......
...@@ -44,7 +44,7 @@ class WebHookService ...@@ -44,7 +44,7 @@ class WebHookService
http_status: response.code, http_status: response.code,
message: response.to_s 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( log_execution(
trigger: hook_name, trigger: hook_name,
url: hook.url, url: hook.url,
...@@ -101,7 +101,7 @@ class WebHookService ...@@ -101,7 +101,7 @@ class WebHookService
request_headers: build_headers(hook_name), request_headers: build_headers(hook_name),
request_data: request_data, request_data: request_data,
response_headers: format_response_headers(response), response_headers: format_response_headers(response),
response_body: response.body, response_body: safe_response_body(response),
response_status: response.code, response_status: response.code,
internal_error_message: error_message internal_error_message: error_message
) )
...@@ -124,4 +124,10 @@ class WebHookService ...@@ -124,4 +124,10 @@ class WebHookService
def format_response_headers(response) def format_response_headers(response)
response.headers.each_capitalized.to_h response.headers.each_capitalized.to_h
end end
def safe_response_body(response)
return '' unless response.body
response.body.encode('UTF-8', invalid: :replace, undef: :replace, replace: '')
end
end end
...@@ -356,7 +356,7 @@ ...@@ -356,7 +356,7 @@
\. This setting requires a \. This setting requires a
= link_to 'restart', help_page_path('administration/restart_gitlab') = link_to 'restart', help_page_path('administration/restart_gitlab')
to take effect. 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 .form-group
.col-sm-offset-2.col-sm-10 .col-sm-offset-2.col-sm-10
.checkbox .checkbox
......
- @breadcrumb_title = "Help"
- page_title "Help" - page_title "Help"
- header_title "Help", help_path - header_title "Help", help_path
......
...@@ -67,7 +67,9 @@ ...@@ -67,7 +67,9 @@
SSH Keys SSH Keys
= nav_link(controller: :gpg_keys) do = nav_link(controller: :gpg_keys) do
= link_to profile_gpg_keys_path, title: '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 GPG Keys
= nav_link(controller: :preferences) do = nav_link(controller: :preferences) do
= link_to profile_preferences_path, title: 'Preferences' do = link_to profile_preferences_path, title: 'Preferences' do
......
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
= link_to _("Create merge request"), create_mr_path(@repository.root_ref, @ref), class: 'btn btn-success' = link_to _("Create merge request"), create_mr_path(@repository.root_ref, @ref), class: 'btn btn-success'
.control .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 } = 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 .control
= link_to project_commits_path(@project, @ref, rss_url_options), title: _("Commits feed"), class: 'btn' do = 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 @@ ...@@ -37,7 +37,7 @@
= link_to 'Edit', '#', class: 'edit-link pull-right' = link_to 'Edit', '#', class: 'edit-link pull-right'
.value.hide-collapsed .value.hide-collapsed
- if issuable.milestone - 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 - else
%span.no-value None %span.no-value None
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
= hidden_field_tag :line_type = hidden_field_tag :line_type
= hidden_field_tag :merge_request_diff_head_sha, @note.noteable.try(:diff_head_sha) = 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 :in_reply_to_discussion_id
= hidden_field_tag :note_project_id
= note_target_fields(@note) = note_target_fields(@note)
= f.hidden_field :noteable_type = f.hidden_field :noteable_type
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
- if avatar - if avatar
.avatar-container.s40 .avatar-container.s40
= link_to project_path(project), class: dom_class(project) do = 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:'' = image_tag avatar_icon(project.creator.email, 40), class: "avatar s40", alt:''
- else - else
= project_icon(project, alt: '', class: 'avatar project-avatar s40') = project_icon(project, alt: '', class: 'avatar project-avatar s40')
......
...@@ -31,8 +31,6 @@ class EmailReceiverWorker ...@@ -31,8 +31,6 @@ class EmailReceiverWorker
when Gitlab::Email::EmptyEmailError when Gitlab::Email::EmptyEmailError
can_retry = true 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." "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 when Gitlab::Email::UserNotFoundError
"We couldn't figure out what user corresponds to the email. Please create your comment through the web interface." "We couldn't figure out what user corresponds to the email. Please create your comment through the web interface."
when Gitlab::Email::UserBlockedError when Gitlab::Email::UserBlockedError
......
...@@ -5,6 +5,12 @@ class GitGarbageCollectWorker ...@@ -5,6 +5,12 @@ class GitGarbageCollectWorker
sidekiq_options retry: false 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) def perform(project_id, task = :gc, lease_key = nil, lease_uuid = nil)
project = Project.find(project_id) project = Project.find(project_id)
task = task.to_sym task = task.to_sym
...@@ -15,8 +21,14 @@ class GitGarbageCollectWorker ...@@ -15,8 +21,14 @@ class GitGarbageCollectWorker
Gitlab::GitLogger.info(description) Gitlab::GitLogger.info(description)
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) output, status = Gitlab::Popen.popen(cmd, repo_path)
Gitlab::GitLogger.error("#{description} failed:\n#{output}") unless status.zero? 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 # Refresh the branch cache in case garbage collection caused a ref lookup to fail
flush_ref_caches(project) if task == :gc flush_ref_caches(project) if task == :gc
...@@ -26,6 +38,19 @@ class GitGarbageCollectWorker ...@@ -26,6 +38,19 @@ class GitGarbageCollectWorker
private 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) def command(task)
case task case task
when :gc when :gc
...@@ -55,4 +80,14 @@ class GitGarbageCollectWorker ...@@ -55,4 +80,14 @@ class GitGarbageCollectWorker
config_value = write_bitmaps ? 'true' : 'false' config_value = write_bitmaps ? 'true' : 'false'
%W[git -c repack.writeBitmaps=#{config_value}] %W[git -c repack.writeBitmaps=#{config_value}]
end 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 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: merge_request:
author: 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: merge_request:
author: 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