Commit 859b693f authored by Nick Thomas's avatar Nick Thomas

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

parents 49d2d4be 1a449b24
...@@ -68,7 +68,7 @@ stages: ...@@ -68,7 +68,7 @@ stages:
.only-master-and-ee-or-mysql: &only-master-and-ee-or-mysql .only-master-and-ee-or-mysql: &only-master-and-ee-or-mysql
only: only:
- /mysql/ - /mysql/
- /-stable$/ - /-stable/
- master@gitlab-org/gitlab-ce - master@gitlab-org/gitlab-ce
- master@gitlab/gitlabhq - master@gitlab/gitlabhq
- tags@gitlab-org/gitlab-ce - tags@gitlab-org/gitlab-ce
...@@ -460,9 +460,10 @@ codeclimate: ...@@ -460,9 +460,10 @@ codeclimate:
services: services:
- docker:dind - docker:dind
script: script:
- docker pull stedolan/jq
- docker pull codeclimate/codeclimate - docker pull codeclimate/codeclimate
- docker run --env CODECLIMATE_CODE="$PWD" --volume "$PWD":/code --volume /var/run/docker.sock:/var/run/docker.sock --volume /tmp/cc:/tmp/cc codeclimate/codeclimate analyze -f json > codeclimate.json - docker run --env CODECLIMATE_CODE="$PWD" --volume "$PWD":/code --volume /var/run/docker.sock:/var/run/docker.sock --volume /tmp/cc:/tmp/cc codeclimate/codeclimate analyze -f json > raw_codeclimate.json
- sed -i.bak 's/\({"body":"\)[^"]*\("}\)/\1\2/g' codeclimate.json - cat raw_codeclimate.json | docker run -i stedolan/jq -c 'map({check_name,fingerprint,location})' > codeclimate.json
artifacts: artifacts:
paths: [codeclimate.json] paths: [codeclimate.json]
......
...@@ -4,6 +4,7 @@ entry. ...@@ -4,6 +4,7 @@ entry.
## 9.3.3 (2017-06-30) ## 9.3.3 (2017-06-30)
<<<<<<< HEAD
- No changes. - No changes.
- Fix head pipeline stored in merge request for external pipelines. !12478 - Fix head pipeline stored in merge request for external pipelines. !12478
- Bring back branches badge to main project page. !12548 - Bring back branches badge to main project page. !12548
...@@ -12,6 +13,14 @@ entry. ...@@ -12,6 +13,14 @@ entry.
- Fixed multi-line markdown tooltip buttons in issue edit form. - Fixed multi-line markdown tooltip buttons in issue edit form.
- Fix shared runners minutes query to update only projects with used allowance. - Fix shared runners minutes query to update only projects with used allowance.
- Perform housekeeping only when an import of a fresh project is completed. - Perform housekeeping only when an import of a fresh project is completed.
=======
- Fix head pipeline stored in merge request for external pipelines. !12478
- Bring back branches badge to main project page. !12548
- Fix diff of requirements.txt file by not matching newlines as part of package names.
- Perform housekeeping only when an import of a fresh project is completed.
- Fixed issue boards closed list not showing all closed issues.
- Fixed multi-line markdown tooltip buttons in issue edit form.
>>>>>>> ce/master
## 9.3.2 (2017-06-27) ## 9.3.2 (2017-06-27)
......
...@@ -85,9 +85,8 @@ window.Build = (function () { ...@@ -85,9 +85,8 @@ window.Build = (function () {
if (!this.hasBeenScrolled) { if (!this.hasBeenScrolled) {
this.scrollToBottom(); this.scrollToBottom();
} }
}); })
.then(() => this.verifyTopPosition());
this.verifyTopPosition();
} }
Build.prototype.canScroll = function () { Build.prototype.canScroll = function () {
...@@ -176,7 +175,7 @@ window.Build = (function () { ...@@ -176,7 +175,7 @@ window.Build = (function () {
} }
if ($flashError.length) { if ($flashError.length) {
topPostion += $flashError.outerHeight(); topPostion += $flashError.outerHeight() + prependTopDefault;
} }
this.$buildTrace.css({ this.$buildTrace.css({
...@@ -196,6 +195,7 @@ window.Build = (function () { ...@@ -196,6 +195,7 @@ window.Build = (function () {
}) })
.done((log) => { .done((log) => {
gl.utils.setCiStatusFavicon(`${this.pageUrl}/status.json`); gl.utils.setCiStatusFavicon(`${this.pageUrl}/status.json`);
if (log.state) { if (log.state) {
this.state = log.state; this.state = log.state;
} }
...@@ -220,7 +220,11 @@ window.Build = (function () { ...@@ -220,7 +220,11 @@ window.Build = (function () {
} }
if (!log.complete) { if (!log.complete) {
this.toggleScrollAnimation(true); if (!this.hasBeenScrolled) {
this.toggleScrollAnimation(true);
} else {
this.toggleScrollAnimation(false);
}
Build.timeout = setTimeout(() => { Build.timeout = setTimeout(() => {
//eslint-disable-next-line //eslint-disable-next-line
...@@ -229,7 +233,8 @@ window.Build = (function () { ...@@ -229,7 +233,8 @@ window.Build = (function () {
if (!this.hasBeenScrolled) { if (!this.hasBeenScrolled) {
this.scrollToBottom(); this.scrollToBottom();
} }
}); })
.then(() => this.verifyTopPosition());
}, 4000); }, 4000);
} else { } else {
this.$buildRefreshAnimation.remove(); this.$buildRefreshAnimation.remove();
......
/* eslint-disable class-methods-use-this */ /* eslint-disable class-methods-use-this */
import './lib/utils/url_utility'; import './lib/utils/url_utility';
import FilesCommentButton from './files_comment_button';
const UNFOLD_COUNT = 20; const UNFOLD_COUNT = 20;
let isBound = false; let isBound = false;
...@@ -8,8 +9,10 @@ let isBound = false; ...@@ -8,8 +9,10 @@ let isBound = false;
class Diff { class Diff {
constructor() { constructor() {
const $diffFile = $('.files .diff-file'); const $diffFile = $('.files .diff-file');
$diffFile.singleFileDiff(); $diffFile.singleFileDiff();
$diffFile.filesCommentButton();
FilesCommentButton.init($diffFile);
$diffFile.each((index, file) => new gl.ImageFile(file)); $diffFile.each((index, file) => new gl.ImageFile(file));
......
...@@ -139,9 +139,9 @@ const DiffNoteAvatars = Vue.extend({ ...@@ -139,9 +139,9 @@ const DiffNoteAvatars = Vue.extend({
const notesCount = this.notesCount; const notesCount = this.notesCount;
$(this.$el).closest('.js-avatar-container') $(this.$el).closest('.js-avatar-container')
.toggleClass('js-no-comment-btn', notesCount > 0) .toggleClass('no-comment-btn', notesCount > 0)
.nextUntil('.js-avatar-container') .nextUntil('.js-avatar-container')
.toggleClass('js-no-comment-btn', notesCount > 0); .toggleClass('no-comment-btn', notesCount > 0);
}, },
toggleDiscussionsToggleState() { toggleDiscussionsToggleState() {
const $notesHolders = $(this.$el).closest('.code').find('.notes_holder'); const $notesHolders = $(this.$el).closest('.code').find('.notes_holder');
......
...@@ -393,6 +393,7 @@ import AuditLogs from './audit_logs'; ...@@ -393,6 +393,7 @@ import AuditLogs from './audit_logs';
case 'search:show': case 'search:show':
new Search(); new Search();
break; break;
<<<<<<< HEAD
case 'projects:mirrors:show': case 'projects:mirrors:show':
case 'projects:mirrors:update': case 'projects:mirrors:update':
new UsersSelect(); new UsersSelect();
...@@ -403,6 +404,8 @@ import AuditLogs from './audit_logs'; ...@@ -403,6 +404,8 @@ import AuditLogs from './audit_logs';
case 'admin:audit_logs:index': case 'admin:audit_logs:index':
new AuditLogs(); new AuditLogs();
break; break;
=======
>>>>>>> ce/master
case 'projects:settings:repository:show': case 'projects:settings:repository:show':
// Initialize Protected Branch Settings // Initialize Protected Branch Settings
new gl.ProtectedBranchCreate(); new gl.ProtectedBranchCreate();
......
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, max-len, one-var, one-var-declaration-per-line, quotes, prefer-template, newline-per-chained-call, comma-dangle, new-cap, no-else-return, consistent-return */ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, max-len, one-var, one-var-declaration-per-line, quotes, prefer-template, newline-per-chained-call, comma-dangle, new-cap, no-else-return, consistent-return */
/* global FilesCommentButton */
/* global notes */ /* global notes */
let $commentButtonTemplate; /* Developer beware! Do not add logic to showButton or hideButton
* that will force a reflow. Doing so will create a signficant performance
window.FilesCommentButton = (function() { * bottleneck for pages with large diffs. For a comprehensive list of what
var COMMENT_BUTTON_CLASS, EMPTY_CELL_CLASS, LINE_COLUMN_CLASSES, LINE_CONTENT_CLASS, LINE_HOLDER_CLASS, LINE_NUMBER_CLASS, OLD_LINE_CLASS, TEXT_FILE_SELECTOR, UNFOLDABLE_LINE_CLASS; * causes reflows, visit https://gist.github.com/paulirish/5d52fb081b3570c81e3a
*/
COMMENT_BUTTON_CLASS = '.add-diff-note';
const LINE_NUMBER_CLASS = 'diff-line-num';
LINE_HOLDER_CLASS = '.line_holder'; const UNFOLDABLE_LINE_CLASS = 'js-unfold';
const NO_COMMENT_CLASS = 'no-comment-btn';
LINE_NUMBER_CLASS = 'diff-line-num'; const EMPTY_CELL_CLASS = 'empty-cell';
const OLD_LINE_CLASS = 'old_line';
LINE_CONTENT_CLASS = 'line_content'; const LINE_COLUMN_CLASSES = `.${LINE_NUMBER_CLASS}, .line_content`;
const DIFF_CONTAINER_SELECTOR = '.files';
UNFOLDABLE_LINE_CLASS = 'js-unfold'; const DIFF_EXPANDED_CLASS = 'diff-expanded';
EMPTY_CELL_CLASS = 'empty-cell'; export default {
init($diffFile) {
OLD_LINE_CLASS = 'old_line'; /* Caching is used only when the following members are *true*. This is because there are likely to be
* differently configured versions of diffs in the same session. However if these values are true, they
LINE_COLUMN_CLASSES = "." + LINE_NUMBER_CLASS + ", .line_content"; * will be true in all cases */
TEXT_FILE_SELECTOR = '.text-file'; if (!this.userCanCreateNote) {
// data-can-create-note is an empty string when true, otherwise undefined
function FilesCommentButton(filesContainerElement) { this.userCanCreateNote = $diffFile.closest(DIFF_CONTAINER_SELECTOR).data('can-create-note') === '';
this.render = this.render.bind(this);
this.hideButton = this.hideButton.bind(this);
this.isParallelView = notes.isParallelView();
filesContainerElement.on('mouseover', LINE_COLUMN_CLASSES, this.render)
.on('mouseleave', LINE_COLUMN_CLASSES, this.hideButton);
}
FilesCommentButton.prototype.render = function(e) {
var $currentTarget, buttonParentElement, lineContentElement, textFileElement, $button;
$currentTarget = $(e.currentTarget);
if ($currentTarget.hasClass('js-no-comment-btn')) return;
lineContentElement = this.getLineContent($currentTarget);
buttonParentElement = this.getButtonParent($currentTarget);
if (!this.validateButtonParent(buttonParentElement) || !this.validateLineContent(lineContentElement)) return;
$button = $(COMMENT_BUTTON_CLASS, buttonParentElement);
buttonParentElement.addClass('is-over')
.nextUntil(`.${LINE_CONTENT_CLASS}`).addClass('is-over');
if ($button.length) {
return;
} }
textFileElement = this.getTextFileElement($currentTarget); if (typeof notes !== 'undefined' && !this.isParallelView) {
buttonParentElement.append(this.buildButton({ this.isParallelView = notes.isParallelView && notes.isParallelView();
discussionID: lineContentElement.attr('data-discussion-id'),
lineType: lineContentElement.attr('data-line-type'),
noteableType: textFileElement.attr('data-noteable-type'),
noteableID: textFileElement.attr('data-noteable-id'),
commitID: textFileElement.attr('data-commit-id'),
noteType: lineContentElement.attr('data-note-type'),
// LegacyDiffNote
lineCode: lineContentElement.attr('data-line-code'),
// DiffNote
position: lineContentElement.attr('data-position')
}));
};
FilesCommentButton.prototype.hideButton = function(e) {
var $currentTarget = $(e.currentTarget);
var buttonParentElement = this.getButtonParent($currentTarget);
buttonParentElement.removeClass('is-over')
.nextUntil(`.${LINE_CONTENT_CLASS}`).removeClass('is-over');
};
FilesCommentButton.prototype.buildButton = function(buttonAttributes) {
return $commentButtonTemplate.clone().attr({
'data-discussion-id': buttonAttributes.discussionID,
'data-line-type': buttonAttributes.lineType,
'data-noteable-type': buttonAttributes.noteableType,
'data-noteable-id': buttonAttributes.noteableID,
'data-commit-id': buttonAttributes.commitID,
'data-note-type': buttonAttributes.noteType,
// LegacyDiffNote
'data-line-code': buttonAttributes.lineCode,
// DiffNote
'data-position': buttonAttributes.position
});
};
FilesCommentButton.prototype.getTextFileElement = function(hoveredElement) {
return hoveredElement.closest(TEXT_FILE_SELECTOR);
};
FilesCommentButton.prototype.getLineContent = function(hoveredElement) {
if (hoveredElement.hasClass(LINE_CONTENT_CLASS)) {
return hoveredElement;
}
if (!this.isParallelView) {
return $(hoveredElement).closest(LINE_HOLDER_CLASS).find("." + LINE_CONTENT_CLASS);
} else {
return $(hoveredElement).next("." + LINE_CONTENT_CLASS);
} }
};
FilesCommentButton.prototype.getButtonParent = function(hoveredElement) { if (this.userCanCreateNote) {
if (!this.isParallelView) { $diffFile.on('mouseover', LINE_COLUMN_CLASSES, e => this.showButton(this.isParallelView, e))
if (hoveredElement.hasClass(OLD_LINE_CLASS)) { .on('mouseleave', LINE_COLUMN_CLASSES, e => this.hideButton(this.isParallelView, e));
return hoveredElement;
}
return hoveredElement.parent().find("." + OLD_LINE_CLASS);
} else {
if (hoveredElement.hasClass(LINE_NUMBER_CLASS)) {
return hoveredElement;
}
return $(hoveredElement).prev("." + LINE_NUMBER_CLASS);
} }
}; },
FilesCommentButton.prototype.validateButtonParent = function(buttonParentElement) { showButton(isParallelView, e) {
return !buttonParentElement.hasClass(EMPTY_CELL_CLASS) && !buttonParentElement.hasClass(UNFOLDABLE_LINE_CLASS); const buttonParentElement = this.getButtonParent(e.currentTarget, isParallelView);
};
FilesCommentButton.prototype.validateLineContent = function(lineContentElement) { if (!this.validateButtonParent(buttonParentElement)) return;
return lineContentElement.attr('data-note-type') && lineContentElement.attr('data-note-type') !== '';
};
return FilesCommentButton; buttonParentElement.classList.add('is-over');
})(); buttonParentElement.nextElementSibling.classList.add('is-over');
},
$.fn.filesCommentButton = function() { hideButton(isParallelView, e) {
$commentButtonTemplate = $('<button name="button" type="submit" class="add-diff-note js-add-diff-note-button" title="Add a comment to this line"><i class="fa fa-comment-o"></i></button>'); const buttonParentElement = this.getButtonParent(e.currentTarget, isParallelView);
if (!(this && (this.parent().data('can-create-note') != null))) { buttonParentElement.classList.remove('is-over');
return; buttonParentElement.nextElementSibling.classList.remove('is-over');
} },
return this.each(function() {
if (!$.data(this, 'filesCommentButton')) { getButtonParent(hoveredElement, isParallelView) {
return $.data(this, 'filesCommentButton', new FilesCommentButton($(this))); if (isParallelView) {
if (!hoveredElement.classList.contains(LINE_NUMBER_CLASS)) {
return hoveredElement.previousElementSibling;
}
} else if (!hoveredElement.classList.contains(OLD_LINE_CLASS)) {
return hoveredElement.parentNode.querySelector(`.${OLD_LINE_CLASS}`);
} }
}); return hoveredElement;
},
validateButtonParent(buttonParentElement) {
return !buttonParentElement.classList.contains(EMPTY_CELL_CLASS) &&
!buttonParentElement.classList.contains(UNFOLDABLE_LINE_CLASS) &&
!buttonParentElement.classList.contains(NO_COMMENT_CLASS) &&
!buttonParentElement.parentNode.classList.contains(DIFF_EXPANDED_CLASS);
},
}; };
...@@ -396,6 +396,13 @@ class GfmAutoComplete { ...@@ -396,6 +396,13 @@ class GfmAutoComplete {
this.cachedData = {}; this.cachedData = {};
} }
destroy() {
this.input.each((i, input) => {
const $input = $(input);
$input.atwho('destroy');
});
}
static isLoading(data) { static isLoading(data) {
let dataToInspect = data; let dataToInspect = data;
if (data && data.length > 0) { if (data && data.length > 0) {
......
...@@ -21,6 +21,9 @@ function GLForm(form, enableGFM = false) { ...@@ -21,6 +21,9 @@ function GLForm(form, enableGFM = false) {
GLForm.prototype.destroy = function() { GLForm.prototype.destroy = function() {
// Clean form listeners // Clean form listeners
this.clearEventListeners(); this.clearEventListeners();
if (this.autoComplete) {
this.autoComplete.destroy();
}
return this.form.data('gl-form', null); return this.form.data('gl-form', null);
}; };
...@@ -33,7 +36,8 @@ GLForm.prototype.setupForm = function() { ...@@ -33,7 +36,8 @@ GLForm.prototype.setupForm = function() {
this.form.addClass('gfm-form'); this.form.addClass('gfm-form');
// remove notify commit author checkbox for non-commit notes // remove notify commit author checkbox for non-commit notes
gl.utils.disableButtonIfEmptyField(this.form.find('.js-note-text'), this.form.find('.js-comment-button, .js-note-new-discussion')); gl.utils.disableButtonIfEmptyField(this.form.find('.js-note-text'), this.form.find('.js-comment-button, .js-note-new-discussion'));
new GfmAutoComplete(gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources).setup(this.form.find('.js-gfm-input'), { this.autoComplete = new GfmAutoComplete(gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources);
this.autoComplete.setup(this.form.find('.js-gfm-input'), {
emojis: true, emojis: true,
members: this.enableGFM, members: this.enableGFM,
issues: this.enableGFM, issues: this.enableGFM,
......
...@@ -39,6 +39,17 @@ ...@@ -39,6 +39,17 @@
runnerId() { runnerId() {
return `#${this.job.runner.id}`; return `#${this.job.runner.id}`;
}, },
renderBlock() {
return this.job.merge_request ||
this.job.duration ||
this.job.finished_data ||
this.job.erased_at ||
this.job.queued ||
this.job.runner ||
this.job.coverage ||
this.job.tags.length ||
this.job.cancel_path;
},
}, },
}; };
</script> </script>
...@@ -63,7 +74,7 @@ ...@@ -63,7 +74,7 @@
Retry Retry
</a> </a>
</div> </div>
<div class="block"> <div :class="{block : renderBlock }">
<p <p
class="build-detail-row js-job-mr" class="build-detail-row js-job-mr"
v-if="job.merge_request"> v-if="job.merge_request">
......
...@@ -144,7 +144,9 @@ import BlobForkSuggestion from './blob/blob_fork_suggestion'; ...@@ -144,7 +144,9 @@ import BlobForkSuggestion from './blob/blob_fork_suggestion';
this.resetViewContainer(); this.resetViewContainer();
this.mountPipelinesView(); this.mountPipelinesView();
} else { } else {
this.expandView(); if (Breakpoints.get().getBreakpointSize() !== 'xs') {
this.expandView();
}
this.resetViewContainer(); this.resetViewContainer();
this.destroyPipelinesView(); this.destroyPipelinesView();
} }
......
...@@ -829,6 +829,8 @@ export default class Notes { ...@@ -829,6 +829,8 @@ export default class Notes {
*/ */
setupDiscussionNoteForm(dataHolder, form) { setupDiscussionNoteForm(dataHolder, form) {
// setup note target // setup note target
const diffFileData = dataHolder.closest('.text-file');
var discussionID = dataHolder.data('discussionId'); var discussionID = dataHolder.data('discussionId');
if (discussionID) { if (discussionID) {
...@@ -839,9 +841,10 @@ export default class Notes { ...@@ -839,9 +841,10 @@ export default class Notes {
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'));
form.find('#note_noteable_type').val(dataHolder.data('noteableType')); form.find('#note_noteable_type').val(diffFileData.data('noteableType'));
form.find('#note_noteable_id').val(dataHolder.data('noteableId')); form.find('#note_noteable_id').val(diffFileData.data('noteableId'));
form.find('#note_commit_id').val(dataHolder.data('commitId')); form.find('#note_commit_id').val(diffFileData.data('commitId'));
form.find('#note_type').val(dataHolder.data('noteType')); form.find('#note_type').val(dataHolder.data('noteType'));
// LegacyDiffNote // LegacyDiffNote
......
...@@ -10,6 +10,8 @@ import Cookies from 'js-cookie'; ...@@ -10,6 +10,8 @@ import Cookies from 'js-cookie';
this.$sidebarInner = this.sidebar.find('.issuable-sidebar'); this.$sidebarInner = this.sidebar.find('.issuable-sidebar');
this.$navGitlab = $('.navbar-gitlab'); this.$navGitlab = $('.navbar-gitlab');
this.$layoutNav = $('.layout-nav');
this.$subScroll = $('.sub-nav-scroll');
this.$rightSidebar = $('.js-right-sidebar'); this.$rightSidebar = $('.js-right-sidebar');
this.removeListeners(); this.removeListeners();
...@@ -27,14 +29,14 @@ import Cookies from 'js-cookie'; ...@@ -27,14 +29,14 @@ import Cookies from 'js-cookie';
Sidebar.prototype.addEventListeners = function() { Sidebar.prototype.addEventListeners = function() {
const $document = $(document); const $document = $(document);
const throttledSetSidebarHeight = _.throttle(this.setSidebarHeight.bind(this), 20); const throttledSetSidebarHeight = _.throttle(this.setSidebarHeight.bind(this), 20);
const debouncedSetSidebarHeight = _.debounce(this.setSidebarHeight.bind(this), 200); const slowerThrottledSetSidebarHeight = _.throttle(this.setSidebarHeight.bind(this), 200);
this.sidebar.on('click', '.sidebar-collapsed-icon', this, this.sidebarCollapseClicked); this.sidebar.on('click', '.sidebar-collapsed-icon', this, this.sidebarCollapseClicked);
$('.dropdown').on('hidden.gl.dropdown', this, this.onSidebarDropdownHidden); $('.dropdown').on('hidden.gl.dropdown', this, this.onSidebarDropdownHidden);
$('.dropdown').on('loading.gl.dropdown', this.sidebarDropdownLoading); $('.dropdown').on('loading.gl.dropdown', this.sidebarDropdownLoading);
$('.dropdown').on('loaded.gl.dropdown', this.sidebarDropdownLoaded); $('.dropdown').on('loaded.gl.dropdown', this.sidebarDropdownLoaded);
$(window).on('resize', () => throttledSetSidebarHeight()); $(window).on('resize', () => throttledSetSidebarHeight());
$document.on('scroll', () => debouncedSetSidebarHeight()); $document.on('scroll', () => slowerThrottledSetSidebarHeight());
$document.on('click', '.js-sidebar-toggle', function(e, triggered) { $document.on('click', '.js-sidebar-toggle', function(e, triggered) {
var $allGutterToggleIcons, $this, $thisIcon; var $allGutterToggleIcons, $this, $thisIcon;
e.preventDefault(); e.preventDefault();
...@@ -213,7 +215,7 @@ import Cookies from 'js-cookie'; ...@@ -213,7 +215,7 @@ import Cookies from 'js-cookie';
}; };
Sidebar.prototype.setSidebarHeight = function() { Sidebar.prototype.setSidebarHeight = function() {
const $navHeight = this.$navGitlab.outerHeight(); const $navHeight = this.$navGitlab.outerHeight() + this.$layoutNav.outerHeight() + (this.$subScroll ? this.$subScroll.outerHeight() : 0);
const diff = $navHeight - $(window).scrollTop(); const diff = $navHeight - $(window).scrollTop();
if (diff > 0) { if (diff > 0) {
this.$rightSidebar.outerHeight($(window).height() - diff); this.$rightSidebar.outerHeight($(window).height() - diff);
......
/* eslint-disable func-names, prefer-arrow-callback, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, one-var, one-var-declaration-per-line, consistent-return, no-param-reassign, max-len */ /* eslint-disable func-names, prefer-arrow-callback, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, one-var, one-var-declaration-per-line, consistent-return, no-param-reassign, max-len */
import FilesCommentButton from './files_comment_button';
(function() { (function() {
window.SingleFileDiff = (function() { window.SingleFileDiff = (function() {
var COLLAPSED_HTML, ERROR_HTML, LOADING_HTML, WRAPPER; var COLLAPSED_HTML, ERROR_HTML, LOADING_HTML, WRAPPER;
...@@ -78,6 +80,8 @@ ...@@ -78,6 +80,8 @@
gl.diffNotesCompileComponents(); gl.diffNotesCompileComponents();
} }
FilesCommentButton.init($(_this.file));
if (cb) cb(); if (cb) cb();
}; };
})(this)); })(this));
......
...@@ -64,6 +64,12 @@ ...@@ -64,6 +64,12 @@
*/ */
return new gl.GLForm($(this.$refs['gl-form']), true); return new gl.GLForm($(this.$refs['gl-form']), true);
}, },
beforeDestroy() {
const glForm = $(this.$refs['gl-form']).data('gl-form');
if (glForm) {
glForm.destroy();
}
},
}; };
</script> </script>
......
...@@ -11,20 +11,19 @@ header.navbar-gitlab-new { ...@@ -11,20 +11,19 @@ header.navbar-gitlab-new {
padding-left: 0; padding-left: 0;
.title-container { .title-container {
align-items: stretch;
padding-top: 0; padding-top: 0;
overflow: visible; overflow: visible;
} }
.title { .title {
display: block; display: flex;
height: 100%;
padding-right: 0; padding-right: 0;
color: currentColor; color: currentColor;
> a { > a {
display: flex; display: flex;
align-items: center; align-items: center;
height: 100%;
padding-top: 3px; padding-top: 3px;
padding-right: $gl-padding; padding-right: $gl-padding;
padding-left: $gl-padding; padding-left: $gl-padding;
......
...@@ -5,17 +5,46 @@ ...@@ -5,17 +5,46 @@
$new-sidebar-width: 220px; $new-sidebar-width: 220px;
.page-with-new-sidebar { .page-with-new-sidebar {
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
padding-left: $new-sidebar-width; padding-left: $new-sidebar-width;
} }
// Override position: absolute
.right-sidebar { .right-sidebar {
position: fixed; position: fixed;
height: 100%; height: 100%;
} }
} }
.context-header {
background-color: $gray-normal;
border-bottom: 1px solid $border-color;
font-weight: 600;
display: flex;
align-items: center;
padding: 10px 14px;
.avatar-container {
flex: 0 0 40px;
}
&:hover {
background-color: $border-color;
}
}
.settings-avatar {
background-color: $white-light;
i {
font-size: 20px;
width: 100%;
color: $gl-text-color-secondary;
text-align: center;
align-self: center;
}
}
.nav-sidebar { .nav-sidebar {
position: fixed; position: fixed;
z-index: 400; z-index: 400;
......
...@@ -147,10 +147,9 @@ ...@@ -147,10 +147,9 @@
top: 35px; top: 35px;
left: 10px; left: 10px;
bottom: 0; bottom: 0;
overflow-y: scroll;
overflow-x: hidden;
padding: 10px 20px 20px 5px; padding: 10px 20px 20px 5px;
white-space: pre; white-space: pre-wrap;
overflow: auto;
} }
.environment-information { .environment-information {
...@@ -399,6 +398,7 @@ ...@@ -399,6 +398,7 @@
.build-light-text { .build-light-text {
color: $gl-text-color-secondary; color: $gl-text-color-secondary;
word-wrap: break-word;
} }
.build-gutter-toggle { .build-gutter-toggle {
......
...@@ -20,8 +20,6 @@ ...@@ -20,8 +20,6 @@
} }
.diff-content { .diff-content {
overflow: auto;
overflow-y: hidden;
background: $white-light; background: $white-light;
color: $gl-text-color; color: $gl-text-color;
border-radius: 0 0 3px 3px; border-radius: 0 0 3px 3px;
...@@ -476,6 +474,7 @@ ...@@ -476,6 +474,7 @@
height: 19px; height: 19px;
width: 19px; width: 19px;
margin-left: -15px; margin-left: -15px;
z-index: 100;
&:hover { &:hover {
.diff-comment-avatar, .diff-comment-avatar,
...@@ -491,7 +490,7 @@ ...@@ -491,7 +490,7 @@
transform: translateX((($i * $x-pos) - $x-pos)); transform: translateX((($i * $x-pos) - $x-pos));
&:hover { &:hover {
transform: translateX((($i * $x-pos) - $x-pos)) scale(1.2); transform: translateX((($i * $x-pos) - $x-pos));
} }
} }
} }
...@@ -542,6 +541,7 @@ ...@@ -542,6 +541,7 @@
height: 19px; height: 19px;
padding: 0; padding: 0;
transition: transform .1s ease-out; transition: transform .1s ease-out;
z-index: 100;
svg { svg {
position: absolute; position: absolute;
...@@ -555,10 +555,6 @@ ...@@ -555,10 +555,6 @@
fill: $white-light; fill: $white-light;
} }
&:hover {
transform: scale(1.2);
}
&:focus { &:focus {
outline: 0; outline: 0;
} }
......
...@@ -659,8 +659,12 @@ ...@@ -659,8 +659,12 @@
@media(max-width: $screen-md-max) { @media(max-width: $screen-md-max) {
.task-status, .task-status,
.issuable-due-date, .issuable-due-date,
<<<<<<< HEAD
.project-ref-path, .project-ref-path,
.issuable-weight { .issuable-weight {
=======
.project-ref-path {
>>>>>>> ce/master
display: none; display: none;
} }
} }
......
...@@ -628,8 +628,14 @@ ul.notes { ...@@ -628,8 +628,14 @@ ul.notes {
* Line note button on the side of diffs * Line note button on the side of diffs
*/ */
.line_holder .is-over:not(.no-comment-btn) {
.add-diff-note {
opacity: 1;
}
}
.add-diff-note { .add-diff-note {
display: none; opacity: 0;
margin-top: -2px; margin-top: -2px;
border-radius: 50%; border-radius: 50%;
background: $white-light; background: $white-light;
...@@ -642,13 +648,11 @@ ul.notes { ...@@ -642,13 +648,11 @@ ul.notes {
width: 23px; width: 23px;
height: 23px; height: 23px;
border: 1px solid $blue-500; border: 1px solid $blue-500;
transition: transform .1s ease-in-out;
&:hover { &:hover {
background: $blue-500; background: $blue-500;
border-color: $blue-600; border-color: $blue-600;
color: $white-light; color: $white-light;
transform: scale(1.15);
} }
&:active { &:active {
......
.tree-holder { .tree-holder {
.nav-block { .nav-block {
margin: 10px 0; margin: 10px 0;
...@@ -15,6 +16,11 @@ ...@@ -15,6 +16,11 @@
.btn-group { .btn-group {
margin-left: 10px; margin-left: 10px;
} }
.control {
float: left;
margin-left: 10px;
}
} }
.tree-ref-holder { .tree-ref-holder {
......
class AbuseReportsController < ApplicationController class AbuseReportsController < ApplicationController
before_action :set_user, only: [:new]
def new def new
@abuse_report = AbuseReport.new @abuse_report = AbuseReport.new
@abuse_report.user_id = params[:user_id] @abuse_report.user_id = @user.id
@ref_url = params.fetch(:ref_url, '') @ref_url = params.fetch(:ref_url, '')
end end
...@@ -27,4 +29,14 @@ class AbuseReportsController < ApplicationController ...@@ -27,4 +29,14 @@ class AbuseReportsController < ApplicationController
user_id user_id
)) ))
end end
def set_user
@user = User.find_by(id: params[:user_id])
if @user.nil?
redirect_to root_path, alert: "Cannot create the abuse report. The user has been deleted."
elsif @user.blocked?
redirect_to @user, alert: "Cannot create the abuse report. This user has been blocked."
end
end
end end
class Projects::MergeRequests::ApplicationController < Projects::ApplicationController class Projects::MergeRequests::ApplicationController < Projects::ApplicationController
<<<<<<< HEAD
prepend ::EE::Projects::MergeRequests::ApplicationController prepend ::EE::Projects::MergeRequests::ApplicationController
=======
>>>>>>> ce/master
before_action :check_merge_requests_available! before_action :check_merge_requests_available!
before_action :merge_request before_action :merge_request
before_action :authorize_read_merge_request! before_action :authorize_read_merge_request!
......
class Projects::MergeRequests::CreationsController < Projects::MergeRequests::ApplicationController class Projects::MergeRequests::CreationsController < Projects::MergeRequests::ApplicationController
include DiffForPath include DiffForPath
include DiffHelper include DiffHelper
<<<<<<< HEAD
prepend ::EE::Projects::MergeRequests::CreationsController prepend ::EE::Projects::MergeRequests::CreationsController
=======
>>>>>>> ce/master
skip_before_action :merge_request skip_before_action :merge_request
skip_before_action :ensure_ref_fetched skip_before_action :ensure_ref_fetched
......
...@@ -9,6 +9,12 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo ...@@ -9,6 +9,12 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
skip_before_action :merge_request, only: [:index, :bulk_update] skip_before_action :merge_request, only: [:index, :bulk_update]
skip_before_action :ensure_ref_fetched, only: [:index, :bulk_update] skip_before_action :ensure_ref_fetched, only: [:index, :bulk_update]
<<<<<<< HEAD
=======
skip_before_action :merge_request, only: [:index, :bulk_update]
skip_before_action :ensure_ref_fetched, only: [:index, :bulk_update]
>>>>>>> ce/master
before_action :authorize_update_merge_request!, only: [:close, :edit, :update, :remove_wip, :sort] before_action :authorize_update_merge_request!, only: [:close, :edit, :update, :remove_wip, :sort]
before_action :authenticate_user!, only: [:assign_related_issues] before_action :authenticate_user!, only: [:assign_related_issues]
......
class Projects::PipelineSchedulesController < Projects::ApplicationController class Projects::PipelineSchedulesController < Projects::ApplicationController
before_action :authorize_read_pipeline_schedule! before_action :authorize_read_pipeline_schedule!
before_action :authorize_create_pipeline_schedule!, only: [:new, :create, :edit, :take_ownership, :update] before_action :authorize_create_pipeline_schedule!, only: [:new, :create]
before_action :authorize_update_pipeline_schedule!, only: [:edit, :take_ownership, :update]
before_action :authorize_admin_pipeline_schedule!, only: [:destroy] before_action :authorize_admin_pipeline_schedule!, only: [:destroy]
before_action :schedule, only: [:edit, :update, :destroy, :take_ownership] before_action :schedule, only: [:edit, :update, :destroy, :take_ownership]
......
...@@ -98,7 +98,7 @@ class ProjectsController < Projects::ApplicationController ...@@ -98,7 +98,7 @@ class ProjectsController < Projects::ApplicationController
end end
if @project.pending_delete? if @project.pending_delete?
flash[:alert] = _("Project '%{project_name}' queued for deletion.") % { project_name: @project.name } flash.now[:alert] = _("Project '%{project_name}' queued for deletion.") % { project_name: @project.name }
end end
respond_to do |format| respond_to do |format|
......
...@@ -83,6 +83,8 @@ class TodosFinder ...@@ -83,6 +83,8 @@ class TodosFinder
if project? if project?
@project = Project.find(params[:project_id]) @project = Project.find(params[:project_id])
@project = nil if @project.pending_delete?
unless Ability.allowed?(current_user, :read_project, @project) unless Ability.allowed?(current_user, :read_project, @project)
@project = nil @project = nil
end end
......
...@@ -47,6 +47,18 @@ module NotesHelper ...@@ -47,6 +47,18 @@ module NotesHelper
data data
end end
def add_diff_note_button(line_code, position, line_type)
return if @diff_notes_disabled
button_tag '',
class: 'add-diff-note js-add-diff-note-button',
type: 'submit', name: 'button',
data: diff_view_line_data(line_code, position, line_type),
title: 'Add a comment to this line' do
icon('comment-o')
end
end
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
......
...@@ -73,6 +73,7 @@ module SubmoduleHelper ...@@ -73,6 +73,7 @@ module SubmoduleHelper
end end
def relative_self_links(url, commit) def relative_self_links(url, commit)
url.rstrip!
# Map relative links to a namespace and project # Map relative links to a namespace and project
# For example: # For example:
# ../bar.git -> same namespace, repo bar # ../bar.git -> same namespace, repo bar
......
<<<<<<< HEAD
require 'declarative_policy' require 'declarative_policy'
=======
require_dependency 'declarative_policy'
>>>>>>> ce/master
class Ability class Ability
class << self class << self
......
...@@ -151,6 +151,7 @@ module Ci ...@@ -151,6 +151,7 @@ module Ci
where(id: max_id) where(id: max_id)
end end
end end
scope :internal, -> { where(source: internal_sources) }
def self.latest_status(ref = nil) def self.latest_status(ref = nil)
latest(ref).status latest(ref).status
...@@ -174,6 +175,10 @@ module Ci ...@@ -174,6 +175,10 @@ module Ci
where.not(duration: nil).sum(:duration) where.not(duration: nil).sum(:duration)
end end
def self.internal_sources
sources.reject { |source| source == "external" }.values
end
def stages_count def stages_count
statuses.select(:stage).distinct.count statuses.select(:stage).distinct.count
end end
......
module Ci module Ci
class Variable < ActiveRecord::Base class Variable < ActiveRecord::Base
extend Ci::Model extend Ci::Model
include HasVariable
belongs_to :project belongs_to :project
validates :key, validates :key, uniqueness: { scope: :project_id }
presence: true,
uniqueness: { scope: :project_id },
length: { maximum: 255 },
format: { with: /\A[a-zA-Z0-9_]+\z/,
message: "can contain only letters, digits and '_'." }
scope :order_key_asc, -> { reorder(key: :asc) }
scope :unprotected, -> { where(protected: false) } scope :unprotected, -> { where(protected: false) }
attr_encrypted :value,
mode: :per_attribute_iv_and_salt,
insecure_mode: true,
key: Gitlab::Application.secrets.db_key_base,
algorithm: 'aes-256-cbc'
def to_runner_variable
{ key: key, value: value, public: false }
end
end end
end end
module HasVariable
extend ActiveSupport::Concern
included do
validates :key,
presence: true,
length: { maximum: 255 },
format: { with: /\A[a-zA-Z0-9_]+\z/,
message: "can contain only letters, digits and '_'." }
scope :order_key_asc, -> { reorder(key: :asc) }
attr_encrypted :value,
mode: :per_attribute_iv_and_salt,
insecure_mode: true,
key: Gitlab::Application.secrets.db_key_base,
algorithm: 'aes-256-cbc'
def to_runner_variable
{ key: key, value: value, public: false }
end
end
end
module ShaAttribute
extend ActiveSupport::Concern
module ClassMethods
def sha_attribute(name)
column = columns.find { |c| c.name == name.to_s }
# In case the table doesn't exist we won't be able to find the column,
# thus we will only check the type if the column is present.
if column && column.type != :binary
raise ArgumentError,
"sha_attribute #{name.inspect} is invalid since the column type is not :binary"
end
attribute(name, Gitlab::Database::ShaAttribute.new)
end
end
end
class Namespace < ActiveRecord::Base class Namespace < ActiveRecord::Base
acts_as_paranoid acts_as_paranoid without_default_scope: true
prepend EE::Namespace prepend EE::Namespace
include CacheMarkdownField include CacheMarkdownField
...@@ -224,6 +224,12 @@ class Namespace < ActiveRecord::Base ...@@ -224,6 +224,12 @@ class Namespace < ActiveRecord::Base
parent.present? parent.present?
end end
def soft_delete_without_removing_associations
# We can't use paranoia's `#destroy` since this will hard-delete projects.
# Project uses `pending_delete` instead of the acts_as_paranoia gem.
self.deleted_at = Time.now
end
private private
def repository_storage_paths def repository_storage_paths
......
...@@ -19,7 +19,7 @@ class NotificationSetting < ActiveRecord::Base ...@@ -19,7 +19,7 @@ class NotificationSetting < ActiveRecord::Base
# pending delete). # pending delete).
# #
scope :for_projects, -> do scope :for_projects, -> do
includes(:project).references(:projects).where(source_type: 'Project').where.not(projects: { id: nil }) includes(:project).references(:projects).where(source_type: 'Project').where.not(projects: { id: nil, pending_delete: true })
end end
EMAIL_EVENTS = [ EMAIL_EVENTS = [
......
...@@ -228,9 +228,8 @@ class Project < ActiveRecord::Base ...@@ -228,9 +228,8 @@ class Project < ActiveRecord::Base
has_many :uploads, as: :model, dependent: :destroy has_many :uploads, as: :model, dependent: :destroy
# Scopes # Scopes
default_scope { where(pending_delete: false) } scope :pending_delete, -> { where(pending_delete: true) }
scope :without_deleted, -> { where(pending_delete: false) }
scope :with_deleted, -> { unscope(where: :pending_delete) }
scope :sorted_by_activity, -> { reorder(last_activity_at: :desc) } scope :sorted_by_activity, -> { reorder(last_activity_at: :desc) }
scope :sorted_by_stars, -> { reorder('projects.star_count DESC') } scope :sorted_by_stars, -> { reorder('projects.star_count DESC') }
...@@ -353,7 +352,16 @@ class Project < ActiveRecord::Base ...@@ -353,7 +352,16 @@ class Project < ActiveRecord::Base
after_transition started: :finished do |project, _| after_transition started: :finished do |project, _|
project.reset_cache_and_import_attrs project.reset_cache_and_import_attrs
project.perform_housekeeping
if Gitlab::ImportSources.importer_names.include?(project.import_type) && project.repo_exists?
project.run_after_commit do
begin
Projects::HousekeepingService.new(project).execute
rescue Projects::HousekeepingService::LeaseTaken => e
Rails.logger.info("Could not perform housekeeping for project #{project.path_with_namespace} (#{project.id}): #{e}")
end
end
end
end end
end end
...@@ -511,22 +519,6 @@ class Project < ActiveRecord::Base ...@@ -511,22 +519,6 @@ class Project < ActiveRecord::Base
ProjectCacheWorker.perform_async(self.id) ProjectCacheWorker.perform_async(self.id)
end end
remove_import_data
end
def perform_housekeeping
return unless repo_exists?
run_after_commit do
begin
Projects::HousekeepingService.new(self).execute
rescue Projects::HousekeepingService::LeaseTaken => e
Rails.logger.info("Could not perform housekeeping for project #{self.path_with_namespace} (#{self.id}): #{e}")
end
end
end
def remove_import_data
import_data&.destroy import_data&.destroy
end end
...@@ -1461,7 +1453,7 @@ class Project < ActiveRecord::Base ...@@ -1461,7 +1453,7 @@ class Project < ActiveRecord::Base
def pending_delete_twin def pending_delete_twin
return false unless path return false unless path
Project.unscoped.where(pending_delete: true).find_by_full_path(path_with_namespace) Project.pending_delete.find_by_full_path(path_with_namespace)
end end
## ##
......
...@@ -12,8 +12,11 @@ class User < ActiveRecord::Base ...@@ -12,8 +12,11 @@ class User < ActiveRecord::Base
include TokenAuthenticatable include TokenAuthenticatable
include IgnorableColumn include IgnorableColumn
include FeatureGate include FeatureGate
<<<<<<< HEAD
prepend EE::GeoAwareAvatar prepend EE::GeoAwareAvatar
prepend EE::User prepend EE::User
=======
>>>>>>> ce/master
DEFAULT_NOTIFICATION_LEVEL = :participating DEFAULT_NOTIFICATION_LEVEL = :participating
...@@ -322,11 +325,20 @@ class User < ActiveRecord::Base ...@@ -322,11 +325,20 @@ class User < ActiveRecord::Base
table = arel_table table = arel_table
pattern = "%#{query}%" pattern = "%#{query}%"
order = <<~SQL
CASE
WHEN users.name = %{query} THEN 0
WHEN users.username = %{query} THEN 1
WHEN users.email = %{query} THEN 2
ELSE 3
END
SQL
where( where(
table[:name].matches(pattern) table[:name].matches(pattern)
.or(table[:email].matches(pattern)) .or(table[:email].matches(pattern))
.or(table[:username].matches(pattern)) .or(table[:username].matches(pattern))
) ).reorder(order % { query: ActiveRecord::Base.connection.quote(query) }, id: :desc)
end end
# searches user by given pattern # searches user by given pattern
......
<<<<<<< HEAD
require 'declarative_policy' require 'declarative_policy'
=======
require_dependency 'declarative_policy'
>>>>>>> ce/master
class BasePolicy < DeclarativePolicy::Base class BasePolicy < DeclarativePolicy::Base
desc "User is an instance admin" desc "User is an instance admin"
...@@ -10,6 +14,7 @@ class BasePolicy < DeclarativePolicy::Base ...@@ -10,6 +14,7 @@ class BasePolicy < DeclarativePolicy::Base
with_options scope: :user, score: 0 with_options scope: :user, score: 0
condition(:can_create_group) { @user&.can_create_group } condition(:can_create_group) { @user&.can_create_group }
<<<<<<< HEAD
# EE Extensions # EE Extensions
with_scope :user with_scope :user
...@@ -20,4 +25,6 @@ class BasePolicy < DeclarativePolicy::Base ...@@ -20,4 +25,6 @@ class BasePolicy < DeclarativePolicy::Base
with_scope :global with_scope :global
condition(:license_block) { License.block_changes? } condition(:license_block) { License.block_changes? }
=======
>>>>>>> ce/master
end end
class GroupPolicy < BasePolicy class GroupPolicy < BasePolicy
<<<<<<< HEAD
prepend EE::GroupPolicy prepend EE::GroupPolicy
=======
>>>>>>> ce/master
desc "Group is public" desc "Group is public"
with_options scope: :subject, score: 0 with_options scope: :subject, score: 0
condition(:public_group) { @subject.public? } condition(:public_group) { @subject.public? }
with_score 0 with_score 0
condition(:logged_in_viewable) { @user && @subject.internal? && !@user.external? } condition(:logged_in_viewable) { @user && @subject.internal? && !@user.external? }
<<<<<<< HEAD
condition(:has_access) { access_level != GroupMember::NO_ACCESS } condition(:has_access) { access_level != GroupMember::NO_ACCESS }
...@@ -15,6 +19,16 @@ class GroupPolicy < BasePolicy ...@@ -15,6 +19,16 @@ class GroupPolicy < BasePolicy
condition(:master) { access_level >= GroupMember::MASTER } condition(:master) { access_level >= GroupMember::MASTER }
condition(:reporter) { access_level >= GroupMember::REPORTER } condition(:reporter) { access_level >= GroupMember::REPORTER }
=======
condition(:has_access) { access_level != GroupMember::NO_ACCESS }
condition(:guest) { access_level >= GroupMember::GUEST }
condition(:owner) { access_level >= GroupMember::OWNER }
condition(:master) { access_level >= GroupMember::MASTER }
condition(:reporter) { access_level >= GroupMember::REPORTER }
>>>>>>> ce/master
condition(:has_projects) do condition(:has_projects) do
GroupProjectsFinder.new(group: @subject, current_user: @user).execute.any? GroupProjectsFinder.new(group: @subject, current_user: @user).execute.any?
end end
......
class ProjectPolicy < BasePolicy class ProjectPolicy < BasePolicy
<<<<<<< HEAD
prepend EE::ProjectPolicy prepend EE::ProjectPolicy
def self.create_read_update_admin(name) def self.create_read_update_admin(name)
...@@ -29,6 +30,36 @@ class ProjectPolicy < BasePolicy ...@@ -29,6 +30,36 @@ class ProjectPolicy < BasePolicy
desc "User has reporter access" desc "User has reporter access"
condition(:reporter) { team_access_level >= Gitlab::Access::REPORTER } condition(:reporter) { team_access_level >= Gitlab::Access::REPORTER }
=======
def self.create_read_update_admin(name)
[
:"create_#{name}",
:"read_#{name}",
:"update_#{name}",
:"admin_#{name}"
]
end
desc "User is a project owner"
condition :owner do
@user && project.owner == @user || (project.group && project.group.has_owner?(@user))
end
desc "Project has public builds enabled"
condition(:public_builds, scope: :subject) { project.public_builds? }
# For guest access we use #is_team_member? so we can use
# project.members, which gets cached in subject scope.
# This is safe because team_access_level is guaranteed
# by ProjectAuthorization's validation to be at minimum
# GUEST
desc "User has guest access"
condition(:guest) { is_team_member? }
desc "User has reporter access"
condition(:reporter) { team_access_level >= Gitlab::Access::REPORTER }
>>>>>>> ce/master
desc "User has developer access" desc "User has developer access"
condition(:developer) { team_access_level >= Gitlab::Access::DEVELOPER } condition(:developer) { team_access_level >= Gitlab::Access::DEVELOPER }
......
...@@ -27,7 +27,10 @@ class ProjectSnippetPolicy < BasePolicy ...@@ -27,7 +27,10 @@ class ProjectSnippetPolicy < BasePolicy
all?(private_snippet | (internal & external_user), all?(private_snippet | (internal & external_user),
~project.guest, ~project.guest,
~admin, ~admin,
<<<<<<< HEAD
~auditor, ~auditor,
=======
>>>>>>> ce/master
~is_author) ~is_author)
end.prevent :read_project_snippet end.prevent :read_project_snippet
......
...@@ -55,7 +55,7 @@ module Ci ...@@ -55,7 +55,7 @@ module Ci
def builds_for_shared_runner def builds_for_shared_runner
new_builds. new_builds.
# don't run projects which have not enabled shared runners and builds # don't run projects which have not enabled shared runners and builds
joins(:project).where(projects: { shared_runners_enabled: true }) joins(:project).where(projects: { shared_runners_enabled: true, pending_delete: false })
.joins('LEFT JOIN project_features ON ci_builds.project_id = project_features.project_id') .joins('LEFT JOIN project_features ON ci_builds.project_id = project_features.project_id')
.where('project_features.builds_access_level IS NULL or project_features.builds_access_level > 0'). .where('project_features.builds_access_level IS NULL or project_features.builds_access_level > 0').
...@@ -67,7 +67,7 @@ module Ci ...@@ -67,7 +67,7 @@ module Ci
end end
def builds_for_specific_runner def builds_for_specific_runner
new_builds.where(project: runner.projects.with_builds_enabled).order('created_at ASC') new_builds.where(project: runner.projects.without_deleted.with_builds_enabled).order('created_at ASC')
end end
def running_builds_for_shared_runners def running_builds_for_shared_runners
......
...@@ -3,8 +3,8 @@ class GitHooksService ...@@ -3,8 +3,8 @@ class GitHooksService
attr_accessor :oldrev, :newrev, :ref attr_accessor :oldrev, :newrev, :ref
def execute(user, repo_path, oldrev, newrev, ref) def execute(user, project, oldrev, newrev, ref)
@repo_path = repo_path @project = project
@user = Gitlab::GlId.gl_id(user) @user = Gitlab::GlId.gl_id(user)
@oldrev = oldrev @oldrev = oldrev
@newrev = newrev @newrev = newrev
...@@ -26,7 +26,7 @@ class GitHooksService ...@@ -26,7 +26,7 @@ class GitHooksService
private private
def run_hook(name) def run_hook(name)
hook = Gitlab::Git::Hook.new(name, @repo_path) hook = Gitlab::Git::Hook.new(name, @project)
hook.trigger(@user, oldrev, newrev, ref) hook.trigger(@user, oldrev, newrev, ref)
end end
end end
...@@ -120,7 +120,7 @@ class GitOperationService ...@@ -120,7 +120,7 @@ class GitOperationService
def with_hooks(ref, newrev, oldrev) def with_hooks(ref, newrev, oldrev)
GitHooksService.new.execute( GitHooksService.new.execute(
user, user,
repository.path_to_repo, repository.project,
oldrev, oldrev,
newrev, newrev,
ref) do |service| ref) do |service|
......
module Groups module Groups
class DestroyService < Groups::BaseService class DestroyService < Groups::BaseService
def async_execute def async_execute
# Soft delete via paranoia gem group.soft_delete_without_removing_associations
group.destroy
job_id = GroupDestroyWorker.perform_async(group.id, current_user.id) job_id = GroupDestroyWorker.perform_async(group.id, current_user.id)
Rails.logger.info("User #{current_user.id} scheduled a deletion of group ID #{group.id} with job ID #{job_id}") Rails.logger.info("User #{current_user.id} scheduled a deletion of group ID #{group.id} with job ID #{job_id}")
end end
...@@ -10,7 +9,7 @@ module Groups ...@@ -10,7 +9,7 @@ module Groups
def execute def execute
group.prepare_for_destroy group.prepare_for_destroy
group.projects.with_deleted.each do |project| group.projects.each do |project|
# Execute the destruction of the models immediately to ensure atomic cleanup. # Execute the destruction of the models immediately to ensure atomic cleanup.
# Skip repository removal because we remove directory with namespace # Skip repository removal because we remove directory with namespace
# that contain all these repositories # that contain all these repositories
......
...@@ -95,13 +95,19 @@ module Projects ...@@ -95,13 +95,19 @@ module Projects
end end
# Requires UnZip at least 6.00 Info-ZIP. # Requires UnZip at least 6.00 Info-ZIP.
# -qq be (very) quiet
# -n never overwrite existing files # -n never overwrite existing files
# We add * to end of SITE_PATH, because we want to extract SITE_PATH and all subdirectories # We add * to end of SITE_PATH, because we want to extract SITE_PATH and all subdirectories
site_path = File.join(SITE_PATH, '*') site_path = File.join(SITE_PATH, '*')
<<<<<<< HEAD
build.artifacts_file.use_file do |artifacts_path| build.artifacts_file.use_file do |artifacts_path|
unless system(*%W(unzip -n #{artifacts_path} #{site_path} -d #{temp_path})) unless system(*%W(unzip -n #{artifacts_path} #{site_path} -d #{temp_path}))
raise 'pages failed to extract' raise 'pages failed to extract'
end end
=======
unless system(*%W(unzip -qq -n #{artifacts} #{site_path} -d #{temp_path}))
raise 'pages failed to extract'
>>>>>>> ce/master
end end
end end
......
...@@ -35,7 +35,7 @@ module Users ...@@ -35,7 +35,7 @@ module Users
Groups::DestroyService.new(group, current_user).execute Groups::DestroyService.new(group, current_user).execute
end end
user.personal_projects.with_deleted.each do |project| user.personal_projects.each do |project|
# Skip repository removal because we remove directory with namespace # Skip repository removal because we remove directory with namespace
# that contain all this repositories # that contain all this repositories
::Projects::DestroyService.new(project, current_user, skip_repo: true).execute ::Projects::DestroyService.new(project, current_user, skip_repo: true).execute
......
...@@ -11,6 +11,8 @@ ...@@ -11,6 +11,8 @@
%meta{ property: 'og:title', content: page_title } %meta{ property: 'og:title', content: page_title }
%meta{ property: 'og:description', content: page_description } %meta{ property: 'og:description', content: page_description }
%meta{ property: 'og:image', content: page_image } %meta{ property: 'og:image', content: page_image }
%meta{ property: 'og:image:width', content: '64' }
%meta{ property: 'og:image:height', content: '64' }
%meta{ property: 'og:url', content: request.base_url + request.fullpath } %meta{ property: 'og:url', content: request.base_url + request.fullpath }
-# Twitter Card - https://dev.twitter.com/cards/types/summary -# Twitter Card - https://dev.twitter.com/cards/types/summary
......
.nav-sidebar .nav-sidebar
= link_to admin_root_path, title: 'Admin Overview', class: 'context-header' do
.avatar-container.s40.settings-avatar
= icon('wrench')
.project-title Admin Area
%ul.sidebar-top-level-items %ul.sidebar-top-level-items
= nav_link(controller: %w(dashboard admin projects users groups builds runners cohorts), html_options: {class: 'home'}) do = nav_link(controller: %w(dashboard admin projects users groups builds runners cohorts), html_options: {class: 'home'}) do
= link_to admin_root_path, title: 'Overview', class: 'shortcuts-tree' do = link_to admin_root_path, title: 'Overview', class: 'shortcuts-tree' do
......
.nav-sidebar .nav-sidebar
= link_to group_path(@group), title: 'Group', class: 'context-header' do
.avatar-container.s40.group-avatar
= image_tag group_icon(@group), class: "avatar s40 avatar-tile"
.group-title
= @group.name
%ul.sidebar-top-level-items %ul.sidebar-top-level-items
= nav_link(path: ['groups#show', 'groups#activity', 'groups#subgroups'], html_options: { class: 'home' }) do = nav_link(path: ['groups#show', 'groups#activity', 'groups#subgroups'], html_options: { class: 'home' }) do
= link_to group_path(@group), title: 'Home' do = link_to group_path(@group), title: 'Home' do
......
.nav-sidebar .nav-sidebar
= link_to profile_path, title: 'Profile Settings', class: 'context-header' do
.avatar-container.s40.settings-avatar
= icon('user')
.project-title User Settings
%ul.sidebar-top-level-items %ul.sidebar-top-level-items
= nav_link(path: 'profiles#show', html_options: {class: 'home'}) do = nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
= link_to profile_path, title: 'Profile Settings' do = link_to profile_path, title: 'Profile Settings' do
......
.nav-sidebar .nav-sidebar
- can_edit = can?(current_user, :admin_project, @project) - can_edit = can?(current_user, :admin_project, @project)
= link_to project_path(@project), title: 'Project', class: 'context-header' do
.avatar-container.s40.project-avatar
= project_icon(@project, alt: @project.name, class: 'avatar s40 avatar-tile')
.project-title
= @project.name
%ul.sidebar-top-level-items %ul.sidebar-top-level-items
= nav_link(path: ['projects#show', 'projects#activity', 'cycle_analytics#show'], html_options: { class: 'home' }) do = nav_link(path: ['projects#show', 'projects#activity', 'cycle_analytics#show'], html_options: { class: 'home' }) do
= link_to project_path(@project), title: 'Project', class: 'shortcuts-project' do = link_to project_path(@project), title: 'Project', class: 'shortcuts-project' do
......
...@@ -8,27 +8,28 @@ ...@@ -8,27 +8,28 @@
= render "head" = render "head"
%div{ class: container_class } %div{ class: container_class }
.row-content-block.second-block.content-component-block.flex-container-block .tree-holder
.tree-ref-holder .nav-block
= render 'shared/ref_switcher', destination: 'commits' .tree-ref-container
.tree-ref-holder
= render 'shared/ref_switcher', destination: 'commits'
%ul.breadcrumb.repo-breadcrumb
= commits_breadcrumbs
.tree-controls.hidden-xs.hidden-sm
- if @merge_request.present?
.control
= link_to _("View open merge request"), namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'btn'
- elsif create_mr_button?(@repository.root_ref, @ref)
.control
= link_to _("Create merge request"), create_mr_path(@repository.root_ref, @ref), class: 'btn btn-success'
%ul.breadcrumb.repo-breadcrumb
= commits_breadcrumbs
.block-controls.hidden-xs.hidden-sm
- if @merge_request.present?
.control .control
= link_to _("View open merge request"), namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'btn' = form_tag(namespace_project_commits_path(@project.namespace, @project, @id), method: :get, class: 'commits-search-form') do
- elsif create_mr_button?(@repository.root_ref, @ref) = 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 _("Create merge request"), create_mr_path(@repository.root_ref, @ref), class: 'btn btn-success' = link_to namespace_project_commits_path(@project.namespace, @project, @ref, rss_url_options), title: _("Commits feed"), class: 'btn' do
= icon("rss")
.control
= form_tag(namespace_project_commits_path(@project.namespace, @project, @id), method: :get, class: 'commits-search-form') 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 namespace_project_commits_path(@project.namespace, @project, @ref, rss_url_options), title: _("Commits feed"), class: 'btn' do
= icon("rss")
= render 'projects/commits/mirror_status' = render 'projects/commits/mirror_status'
......
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
- if plain - if plain
= link_text = link_text
- else - else
= add_diff_note_button(line_code, diff_file.position(line), type)
%a{ href: "##{line_code}", data: { linenumber: link_text } } %a{ href: "##{line_code}", data: { linenumber: link_text } }
- discussion = line_discussions.try(:first) - discussion = line_discussions.try(:first)
- if discussion && discussion.resolvable? && !plain - if discussion && discussion.resolvable? && !plain
...@@ -29,7 +30,7 @@ ...@@ -29,7 +30,7 @@
= link_text = link_text
- else - else
%a{ href: "##{line_code}", data: { linenumber: link_text } } %a{ href: "##{line_code}", data: { linenumber: link_text } }
%td.line_content.noteable_line{ class: type, data: (diff_view_line_data(line_code, diff_file.position(line), type) unless plain) }< %td.line_content.noteable_line{ class: type }<
- if email - if email
%pre= line.text %pre= line.text
- else - else
......
/ Side-by-side diff view / Side-by-side diff view
.text-file.diff-wrap-lines.code.js-syntax-highlight{ data: diff_view_data } .text-file.diff-wrap-lines.code.js-syntax-highlight{ data: diff_view_data }
%table %table
- diff_file.parallel_diff_lines.each do |line| - diff_file.parallel_diff_lines.each do |line|
...@@ -18,11 +19,12 @@ ...@@ -18,11 +19,12 @@
- left_line_code = diff_file.line_code(left) - left_line_code = diff_file.line_code(left)
- left_position = diff_file.position(left) - left_position = diff_file.position(left)
%td.old_line.diff-line-num.js-avatar-container{ id: left_line_code, class: left.type, data: { linenumber: left.old_pos } } %td.old_line.diff-line-num.js-avatar-container{ id: left_line_code, class: left.type, data: { linenumber: left.old_pos } }
= add_diff_note_button(left_line_code, left_position, 'old')
%a{ href: "##{left_line_code}", data: { linenumber: left.old_pos } } %a{ href: "##{left_line_code}", data: { linenumber: left.old_pos } }
- discussion_left = discussions_left.try(:first) - discussion_left = discussions_left.try(:first)
- if discussion_left && discussion_left.resolvable? - if discussion_left && discussion_left.resolvable?
%diff-note-avatars{ "discussion-id" => discussion_left.id } %diff-note-avatars{ "discussion-id" => discussion_left.id }
%td.line_content.parallel.noteable_line{ class: left.type, data: diff_view_line_data(left_line_code, left_position, 'old') }= diff_line_content(left.text) %td.line_content.parallel.noteable_line{ class: left.type }= diff_line_content(left.text)
- else - else
%td.old_line.diff-line-num.empty-cell %td.old_line.diff-line-num.empty-cell
%td.line_content.parallel %td.line_content.parallel
...@@ -38,11 +40,12 @@ ...@@ -38,11 +40,12 @@
- right_line_code = diff_file.line_code(right) - right_line_code = diff_file.line_code(right)
- right_position = diff_file.position(right) - right_position = diff_file.position(right)
%td.new_line.diff-line-num.js-avatar-container{ id: right_line_code, class: right.type, data: { linenumber: right.new_pos } } %td.new_line.diff-line-num.js-avatar-container{ id: right_line_code, class: right.type, data: { linenumber: right.new_pos } }
= add_diff_note_button(right_line_code, right_position, 'new')
%a{ href: "##{right_line_code}", data: { linenumber: right.new_pos } } %a{ href: "##{right_line_code}", data: { linenumber: right.new_pos } }
- discussion_right = discussions_right.try(:first) - discussion_right = discussions_right.try(:first)
- if discussion_right && discussion_right.resolvable? - if discussion_right && discussion_right.resolvable?
%diff-note-avatars{ "discussion-id" => discussion_right.id } %diff-note-avatars{ "discussion-id" => discussion_right.id }
%td.line_content.parallel.noteable_line{ class: right.type, data: diff_view_line_data(right_line_code, right_position, 'new') }= diff_line_content(right.text) %td.line_content.parallel.noteable_line{ class: right.type }= diff_line_content(right.text)
- else - else
%td.old_line.diff-line-num.empty-cell %td.old_line.diff-line-num.empty-cell
%td.line_content.parallel %td.line_content.parallel
......
...@@ -36,11 +36,14 @@ ...@@ -36,11 +36,14 @@
&nbsp; &nbsp;
- issue.labels.each do |label| - issue.labels.each do |label|
= link_to_label(label, subject: issue.project, css_class: 'label-link') = link_to_label(label, subject: issue.project, css_class: 'label-link')
<<<<<<< HEAD
- if issue.weight - if issue.weight
%span.issuable-weight %span.issuable-weight
&nbsp; &nbsp;
= icon('balance-scale') = icon('balance-scale')
= issue.weight = issue.weight
=======
>>>>>>> ce/master
.issuable-meta .issuable-meta
%ul.controls %ul.controls
......
...@@ -13,6 +13,9 @@ ...@@ -13,6 +13,9 @@
= render 'projects/last_push' = render 'projects/last_push'
- merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project))
- new_merge_request_path = namespace_project_new_merge_request_path(merge_project.namespace, merge_project) if merge_project
- if @project.merge_requests.exists? - if @project.merge_requests.exists?
%div{ class: container_class } %div{ class: container_class }
.top-area .top-area
...@@ -20,9 +23,12 @@ ...@@ -20,9 +23,12 @@
.nav-controls .nav-controls
- if @can_bulk_update - if @can_bulk_update
= button_tag "Edit Merge Requests", class: "btn js-bulk-update-toggle" = button_tag "Edit Merge Requests", class: "btn js-bulk-update-toggle"
- merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project))
- if merge_project - if merge_project
<<<<<<< HEAD
= link_to namespace_project_new_merge_request_path(merge_project.namespace, merge_project), class: "btn btn-new", title: "New merge request" do = link_to namespace_project_new_merge_request_path(merge_project.namespace, merge_project), class: "btn btn-new", title: "New merge request" do
=======
= link_to new_merge_request_path, class: "btn btn-new", title: "New merge request" do
>>>>>>> ce/master
New merge request New merge request
= render 'shared/issuable/search_bar', type: :merge_requests = render 'shared/issuable/search_bar', type: :merge_requests
...@@ -33,4 +39,8 @@ ...@@ -33,4 +39,8 @@
.merge-requests-holder .merge-requests-holder
= render 'merge_requests' = render 'merge_requests'
- else - else
<<<<<<< HEAD
= render 'shared/empty_states/merge_requests', button_path: namespace_project_new_merge_request_path(@project.namespace, @project) = render 'shared/empty_states/merge_requests', button_path: namespace_project_new_merge_request_path(@project.namespace, @project)
=======
= render 'shared/empty_states/merge_requests', button_path: new_merge_request_path
>>>>>>> ce/master
...@@ -5,7 +5,10 @@ ...@@ -5,7 +5,10 @@
- content_for :page_specific_javascripts do - content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('common_vue') = page_specific_javascript_bundle_tag('common_vue')
= page_specific_javascript_bundle_tag('diff_notes') = page_specific_javascript_bundle_tag('diff_notes')
<<<<<<< HEAD
= webpack_bundle_tag('issuable') = webpack_bundle_tag('issuable')
=======
>>>>>>> ce/master
.merge-request{ 'data-url' => merge_request_path(@merge_request, format: :json), 'data-project-path' => project_path(@merge_request.project) } .merge-request{ 'data-url' => merge_request_path(@merge_request, format: :json), 'data-project-path' => project_path(@merge_request.project) }
= render "projects/merge_requests/mr_title" = render "projects/merge_requests/mr_title"
...@@ -19,6 +22,7 @@ ...@@ -19,6 +22,7 @@
:javascript :javascript
window.gl.mrWidgetData = #{serialize_issuable(@merge_request)} window.gl.mrWidgetData = #{serialize_issuable(@merge_request)}
<<<<<<< HEAD
// Append static, server-generated data not included in merge request entity (EE-Only) // Append static, server-generated data not included in merge request entity (EE-Only)
// Object.assign would be useful here, but it blows up Phantom.js in tests // Object.assign would be useful here, but it blows up Phantom.js in tests
window.gl.mrWidgetData.is_geo_secondary_node = '#{Gitlab::Geo.secondary?}' === 'true'; window.gl.mrWidgetData.is_geo_secondary_node = '#{Gitlab::Geo.secondary?}' === 'true';
...@@ -26,6 +30,8 @@ ...@@ -26,6 +30,8 @@
window.gl.mrWidgetData.enable_squash_before_merge = '#{@merge_request.project.feature_available?(:merge_request_squash)}' === 'true'; window.gl.mrWidgetData.enable_squash_before_merge = '#{@merge_request.project.feature_available?(:merge_request_squash)}' === 'true';
window.gl.mrWidgetData.squash_before_merge_help_path = '#{help_page_path("user/project/merge_requests/squash_and_merge")}'; window.gl.mrWidgetData.squash_before_merge_help_path = '#{help_page_path("user/project/merge_requests/squash_and_merge")}';
=======
>>>>>>> ce/master
#js-vue-mr-widget.mr-widget #js-vue-mr-widget.mr-widget
- content_for :page_specific_javascripts do - content_for :page_specific_javascripts do
......
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
%li %li
= link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do = link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do
#{n_('Commit', 'Commits', @project.statistics.commit_count)} (#{number_with_delimiter(@project.statistics.commit_count)}) #{n_('Commit', 'Commits', @project.statistics.commit_count)} (#{number_with_delimiter(@project.statistics.commit_count)})
%l %li
= link_to namespace_project_branches_path(@project.namespace, @project) do = link_to namespace_project_branches_path(@project.namespace, @project) do
#{n_('Branch', 'Branches', @repository.branch_count)} (#{number_with_delimiter(@repository.branch_count)}) #{n_('Branch', 'Branches', @repository.branch_count)} (#{number_with_delimiter(@repository.branch_count)})
%li %li
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
- if projects.any? - if projects.any?
%ul.projects-list %ul.projects-list
- projects.each_with_index do |project, i| - projects.each_with_index do |project, i|
- css_class = (i >= projects_limit) ? 'hide' : nil - css_class = (i >= projects_limit) || project.pending_delete? ? 'hide' : nil
= render "shared/projects/project", project: project, skip_namespace: skip_namespace, = render "shared/projects/project", project: project, skip_namespace: skip_namespace,
avatar: avatar, stars: stars, css_class: css_class, ci: ci, use_creator_avatar: use_creator_avatar, avatar: avatar, stars: stars, css_class: css_class, ci: ci, use_creator_avatar: use_creator_avatar,
forks: forks, show_last_commit_as_description: show_last_commit_as_description forks: forks, show_last_commit_as_description: show_last_commit_as_description
......
...@@ -45,7 +45,7 @@ class StuckCiJobsWorker ...@@ -45,7 +45,7 @@ class StuckCiJobsWorker
def search(status, timeout) def search(status, timeout)
builds = Ci::Build.where(status: status).where('ci_builds.updated_at < ?', timeout.ago) builds = Ci::Build.where(status: status).where('ci_builds.updated_at < ?', timeout.ago)
builds.joins(:project).includes(:tags, :runner, project: :namespace).find_each(batch_size: 50).each do |build| builds.joins(:project).merge(Project.without_deleted).includes(:tags, :runner, project: :namespace).find_each(batch_size: 50).each do |build|
yield(build) yield(build)
end end
end end
......
---
title: Inserts exact matches of name, username and email to the top of the search
list
merge_request: 12525
author:
---
title: Removes deleted_at and pending_delete occurrences in Project related queries
merge_request: 12091
author:
---
title: Use authorize_update_pipeline_schedule in PipelineSchedulesController
merge_request: 11846
author:
---
title: Supplement Traditional Chinese in Taiwan translation of Project Page & Repository Page
merge_request: 12514
author: Huang Tao
---
title: Closes any open Autocomplete of the markdown editor when the form is closed
merge_request: 12521
author:
---
title: Rename all reserved paths that could have been created
merge_request: 11713
author:
---
title: Fix diff of requirements.txt file by not matching newlines as part of package
names
merge_request:
author:
---
title: Fix 'New merge request' button for users who don't have push access to canonical
project
merge_request:
author:
---
title: Limit OpenGraph image size to 64x64
merge_request:
author:
---
title: Strip trailing whitespace in relative submodule URL
merge_request:
author:
---
title: Fix head pipeline stored in merge request for external pipelines
merge_request: 12478
author:
---
title: Fixed sidebar not collapsing on merge requests in mobile screens
merge_request:
author:
---
title: Fix errors caused by attempts to report already blocked or deleted users
merge_request: 12502
author: Horacio Bertorello
---
title: Fixed issue boards closed list not showing all closed issues
merge_request:
author:
---
title: Fixed multi-line markdown tooltip buttons in issue edit form
merge_request:
author:
---
title: Defer project destroys within a namespace in Groups::DestroyService#async_execute
merge_request:
author:
---
title: Split pipelines as internal and external in the usage data
merge_request: 12277
author:
required_version = Gitlab::VersionInfo.parse(Gitlab::Shell.version_required) unless Rails.env.test?
current_version = Gitlab::VersionInfo.parse(Gitlab::Shell.new.version) required_version = Gitlab::VersionInfo.parse(Gitlab::Shell.version_required)
current_version = Gitlab::VersionInfo.parse(Gitlab::Shell.new.version)
unless current_version.valid? && required_version <= current_version unless current_version.valid? && required_version <= current_version
warn "WARNING: This version of GitLab depends on gitlab-shell #{required_version}, but you're running #{current_version}. Please update gitlab-shell." warn "WARNING: This version of GitLab depends on gitlab-shell #{required_version}, but you're running #{current_version}. Please update gitlab-shell."
end
end end
...@@ -169,6 +169,7 @@ constraints(ProjectUrlConstrainer.new) do ...@@ -169,6 +169,7 @@ constraints(ProjectUrlConstrainer.new) do
end end
end end
<<<<<<< HEAD
## EE-specific ## EE-specific
resources :path_locks, only: [:index, :destroy] do resources :path_locks, only: [:index, :destroy] do
collection do collection do
...@@ -180,6 +181,8 @@ constraints(ProjectUrlConstrainer.new) do ...@@ -180,6 +181,8 @@ constraints(ProjectUrlConstrainer.new) do
get '/service_desk' => 'service_desk#show', as: :service_desk get '/service_desk' => 'service_desk#show', as: :service_desk
put '/service_desk' => 'service_desk#update', as: :service_desk_refresh put '/service_desk' => 'service_desk#update', as: :service_desk_refresh
=======
>>>>>>> ce/master
resources :variables, only: [:index, :show, :update, :create, :destroy] resources :variables, only: [:index, :show, :update, :create, :destroy]
resources :triggers, only: [:index, :create, :edit, :update, :destroy] do resources :triggers, only: [:index, :create, :edit, :update, :destroy] do
member do member do
......
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class RenameAllReservedPathsAgain < ActiveRecord::Migration
include Gitlab::Database::RenameReservedPathsMigration::V1
DOWNTIME = false
disable_ddl_transaction!
TOP_LEVEL_ROUTES = %w[
-
.well-known
abuse_reports
admin
all
api
assets
autocomplete
ci
dashboard
explore
files
groups
health_check
help
hooks
import
invites
issues
jwt
koding
member
merge_requests
new
notes
notification_settings
oauth
profile
projects
public
repository
robots.txt
s
search
sent_notifications
services
snippets
teams
u
unicorn_test
unsubscribes
uploads
users
].freeze
PROJECT_WILDCARD_ROUTES = %w[
badges
blame
blob
builds
commits
create
create_dir
edit
environments/folders
files
find_file
gitlab-lfs/objects
info/lfs/objects
new
preview
raw
refs
tree
update
wikis
].freeze
GROUP_ROUTES = %w[
activity
analytics
audit_events
avatar
edit
group_members
hooks
issues
labels
ldap
ldap_group_links
merge_requests
milestones
notification_setting
pipeline_quota
projects
subgroups
].freeze
def up
disable_statement_timeout
TOP_LEVEL_ROUTES.each { |route| rename_root_paths(route) }
PROJECT_WILDCARD_ROUTES.each { |route| rename_wildcard_paths(route) }
GROUP_ROUTES.each { |route| rename_child_paths(route) }
end
def down
disable_statement_timeout
revert_renames
end
end
...@@ -35,11 +35,18 @@ Example response: ...@@ -35,11 +35,18 @@ Example response:
{ {
"id": 2, "id": 2,
"path": "group1", "path": "group1",
<<<<<<< HEAD
"kind": "group" "kind": "group"
"full_path": "group1", "full_path": "group1",
"parent_id": "null", "parent_id": "null",
"members_count_with_descendants": 2, "members_count_with_descendants": 2,
"plan": "bronze" "plan": "bronze"
=======
"kind": "group",
"full_path": "group1",
"parent_id": "null",
"members_count_with_descendants": 2
>>>>>>> ce/master
}, },
{ {
"id": 3, "id": 3,
...@@ -52,7 +59,11 @@ Example response: ...@@ -52,7 +59,11 @@ Example response:
] ]
``` ```
<<<<<<< HEAD
**Note**: `members_count_with_descendants` and `plan` are presented only for group masters/owners. **Note**: `members_count_with_descendants` and `plan` are presented only for group masters/owners.
=======
**Note**: `members_count_with_descendants` are presented only for group masters/owners.
>>>>>>> ce/master
## Search for namespace ## Search for namespace
......
...@@ -34,9 +34,9 @@ instructions to [generate an SSH key](../../ssh/README.md). Do not add a ...@@ -34,9 +34,9 @@ instructions to [generate an SSH key](../../ssh/README.md). Do not add a
passphrase to the SSH key, or the `before_script` will prompt for it. passphrase to the SSH key, or the `before_script` will prompt for it.
Then, create a new **Secret Variable** in your project settings on GitLab Then, create a new **Secret Variable** in your project settings on GitLab
following **Settings > Variables**. As **Key** add the name `SSH_PRIVATE_KEY` following **Settings > Pipelines** and look for the "Secret Variables" section.
and in the **Value** field paste the content of your _private_ key that you As **Key** add the name `SSH_PRIVATE_KEY` and in the **Value** field paste the
created earlier. content of your _private_ key that you created earlier.
It is also good practice to check the server's own public key to make sure you It is also good practice to check the server's own public key to make sure you
are not being targeted by a man-in-the-middle attack. To do this, add another are not being targeted by a man-in-the-middle attack. To do this, add another
......
...@@ -55,6 +55,7 @@ ...@@ -55,6 +55,7 @@
- [Polymorphic Associations](polymorphic_associations.md) - [Polymorphic Associations](polymorphic_associations.md)
- [Single Table Inheritance](single_table_inheritance.md) - [Single Table Inheritance](single_table_inheritance.md)
- [Background Migrations](background_migrations.md) - [Background Migrations](background_migrations.md)
- [Storing SHA1 Hashes As Binary](sha1_as_binary.md)
## i18n ## i18n
......
# Storing SHA1 Hashes As Binary
Storing SHA1 hashes as strings is not very space efficient. A SHA1 as a string
requires at least 40 bytes, an additional byte to store the encoding, and
perhaps more space depending on the internals of PostgreSQL and MySQL.
On the other hand, if one were to store a SHA1 as binary one would only need 20
bytes for the actual SHA1, and 1 or 4 bytes of additional space (again depending
on database internals). This means that in the best case scenario we can reduce
the space usage by 50%.
To make this easier to work with you can include the concern `ShaAttribute` into
a model and define a SHA attribute using the `sha_attribute` class method. For
example:
```ruby
class Commit < ActiveRecord::Base
include ShaAttribute
sha_attribute :sha
end
```
This allows you to use the value of the `sha` attribute as if it were a string,
while storing it as binary. This means that you can do something like this,
without having to worry about converting data to the right binary format:
```ruby
commit = Commit.find_by(sha: '88c60307bd1f215095834f09a1a5cb18701ac8ad')
commit.sha = '971604de4cfa324d91c41650fabc129420c8d1cc'
commit.save
```
There is however one requirement: the column used to store the SHA has _must_ be
a binary type. For Rails this means you need to use the `:binary` type instead
of `:text` or `:string`.
doc/user/project/img/issue_board.png

74.7 KB | W: | H:

doc/user/project/img/issue_board.png

50.2 KB | W: | H:

doc/user/project/img/issue_board.png
doc/user/project/img/issue_board.png
doc/user/project/img/issue_board.png
doc/user/project/img/issue_board.png
  • 2-up
  • Swipe
  • Onion skin
...@@ -232,7 +232,7 @@ module SharedDiffNote ...@@ -232,7 +232,7 @@ module SharedDiffNote
end end
def click_parallel_diff_line(code, line_type) def click_parallel_diff_line(code, line_type)
find(".line_content.parallel.#{line_type}[data-line-code='#{code}']").trigger 'mouseover' find(".line_holder.parallel .diff-line-num[id='#{code}']").trigger 'mouseover'
find(".line_holder.parallel button[data-line-code='#{code}']").trigger 'click' find(".line_holder.parallel button[data-line-code='#{code}']").trigger 'click'
end end
end end
...@@ -519,10 +519,13 @@ module API ...@@ -519,10 +519,13 @@ module API
def expose_members_count_with_descendants?(namespace, opts) def expose_members_count_with_descendants?(namespace, opts)
namespace.kind == 'group' && Ability.allowed?(opts[:current_user], :admin_group, namespace) namespace.kind == 'group' && Ability.allowed?(opts[:current_user], :admin_group, namespace)
end end
<<<<<<< HEAD
# EE-only # EE-only
expose :shared_runners_minutes_limit, if: lambda { |_, options| options[:current_user]&.admin? } expose :shared_runners_minutes_limit, if: lambda { |_, options| options[:current_user]&.admin? }
expose :plan, if: -> (namespace, opts) { Ability.allowed?(opts[:current_user], :admin_namespace, namespace) } expose :plan, if: -> (namespace, opts) { Ability.allowed?(opts[:current_user], :admin_namespace, namespace) }
=======
>>>>>>> ce/master
end end
class MemberAccess < Grape::Entity class MemberAccess < Grape::Entity
......
...@@ -48,7 +48,8 @@ module API ...@@ -48,7 +48,8 @@ module API
yield if block_given? yield if block_given?
forbidden!('Project has been deleted!') unless job.project project = job.project
forbidden!('Project has been deleted!') if project.nil? || project.pending_delete?
forbidden!('Job has been erased!') if job.erased? forbidden!('Job has been erased!') if job.erased?
end end
......
...@@ -18,6 +18,7 @@ module API ...@@ -18,6 +18,7 @@ module API
namespaces = namespaces.search(params[:search]) if params[:search].present? namespaces = namespaces.search(params[:search]) if params[:search].present?
present paginate(namespaces), with: Entities::Namespace, current_user: current_user present paginate(namespaces), with: Entities::Namespace, current_user: current_user
<<<<<<< HEAD
end end
desc 'Update a namespace' do desc 'Update a namespace' do
...@@ -39,6 +40,8 @@ module API ...@@ -39,6 +40,8 @@ module API
else else
render_validation_error!(namespace) render_validation_error!(namespace)
end end
=======
>>>>>>> ce/master
end end
end end
end end
......
<<<<<<< HEAD
require 'declarative_policy' require 'declarative_policy'
=======
require_dependency 'declarative_policy'
>>>>>>> ce/master
module API module API
# Projects API # Projects API
......
...@@ -30,7 +30,8 @@ module Ci ...@@ -30,7 +30,8 @@ module Ci
yield if block_given? yield if block_given?
forbidden!('Project has been deleted!') unless build.project project = build.project
forbidden!('Project has been deleted!') if project.nil? || project.pending_delete?
forbidden!('Build has been erased!') if build.erased? forbidden!('Build has been erased!') if build.erased?
end end
......
...@@ -29,6 +29,11 @@ module Gitlab ...@@ -29,6 +29,11 @@ module Gitlab
paths = Array(paths) paths = Array(paths)
RenameNamespaces.new(paths, self).rename_namespaces(type: :top_level) RenameNamespaces.new(paths, self).rename_namespaces(type: :top_level)
end end
def revert_renames
RenameProjects.new([], self).revert_renames
RenameNamespaces.new([], self).revert_renames
end
end end
end end
end end
......
...@@ -6,7 +6,10 @@ module Gitlab ...@@ -6,7 +6,10 @@ module Gitlab
attr_reader :paths, :migration attr_reader :paths, :migration
delegate :update_column_in_batches, delegate :update_column_in_batches,
:execute,
:replace_sql, :replace_sql,
:quote_string,
:say,
to: :migration to: :migration
def initialize(paths, migration) def initialize(paths, migration)
...@@ -26,24 +29,45 @@ module Gitlab ...@@ -26,24 +29,45 @@ module Gitlab
new_path = rename_path(namespace_path, old_path) new_path = rename_path(namespace_path, old_path)
new_full_path = join_routable_path(namespace_path, new_path) new_full_path = join_routable_path(namespace_path, new_path)
perform_rename(routable, old_full_path, new_full_path)
[old_full_path, new_full_path]
end
def perform_rename(routable, old_full_path, new_full_path)
# skips callbacks & validations # skips callbacks & validations
new_path = new_full_path.split('/').last
routable.class.where(id: routable) routable.class.where(id: routable)
.update_all(path: new_path) .update_all(path: new_path)
rename_routes(old_full_path, new_full_path) rename_routes(old_full_path, new_full_path)
[old_full_path, new_full_path]
end end
def rename_routes(old_full_path, new_full_path) def rename_routes(old_full_path, new_full_path)
routes = Route.arel_table
quoted_old_full_path = quote_string(old_full_path)
quoted_old_wildcard_path = quote_string("#{old_full_path}/%")
filter = if Database.mysql?
"lower(routes.path) = lower('#{quoted_old_full_path}') "\
"OR routes.path LIKE '#{quoted_old_wildcard_path}'"
else
"routes.id IN "\
"( SELECT routes.id FROM routes WHERE lower(routes.path) = lower('#{quoted_old_full_path}') "\
"UNION SELECT routes.id FROM routes WHERE routes.path ILIKE '#{quoted_old_wildcard_path}' )"
end
replace_statement = replace_sql(Route.arel_table[:path], replace_statement = replace_sql(Route.arel_table[:path],
old_full_path, old_full_path,
new_full_path) new_full_path)
update_column_in_batches(:routes, :path, replace_statement) do |table, query| update = Arel::UpdateManager.new(ActiveRecord::Base)
path_or_children = table[:path].matches_any([old_full_path, "#{old_full_path}/%"]) .table(routes)
query.where(path_or_children) .set([[routes[:path], replace_statement]])
end .where(Arel::Nodes::SqlLiteral.new(filter))
execute(update.to_sql)
end end
def rename_path(namespace_path, path_was) def rename_path(namespace_path, path_was)
...@@ -86,32 +110,74 @@ module Gitlab ...@@ -86,32 +110,74 @@ module Gitlab
def move_folders(directory, old_relative_path, new_relative_path) def move_folders(directory, old_relative_path, new_relative_path)
old_path = File.join(directory, old_relative_path) old_path = File.join(directory, old_relative_path)
return unless File.directory?(old_path) unless File.directory?(old_path)
say "#{old_path} doesn't exist, skipping"
return
end
new_path = File.join(directory, new_relative_path) new_path = File.join(directory, new_relative_path)
FileUtils.mv(old_path, new_path) FileUtils.mv(old_path, new_path)
end end
def remove_cached_html_for_projects(project_ids) def remove_cached_html_for_projects(project_ids)
update_column_in_batches(:projects, :description_html, nil) do |table, query| project_ids.each do |project_id|
query.where(table[:id].in(project_ids)) update_column_in_batches(:projects, :description_html, nil) do |table, query|
end query.where(table[:id].eq(project_id))
end
update_column_in_batches(:issues, :description_html, nil) do |table, query|
query.where(table[:project_id].in(project_ids)) update_column_in_batches(:issues, :description_html, nil) do |table, query|
query.where(table[:project_id].eq(project_id))
end
update_column_in_batches(:merge_requests, :description_html, nil) do |table, query|
query.where(table[:target_project_id].eq(project_id))
end
update_column_in_batches(:notes, :note_html, nil) do |table, query|
query.where(table[:project_id].eq(project_id))
end
update_column_in_batches(:milestones, :description_html, nil) do |table, query|
query.where(table[:project_id].eq(project_id))
end
end end
end
update_column_in_batches(:merge_requests, :description_html, nil) do |table, query| def track_rename(type, old_path, new_path)
query.where(table[:target_project_id].in(project_ids)) key = redis_key_for_type(type)
Gitlab::Redis.with do |redis|
redis.lpush(key, [old_path, new_path].to_json)
redis.expire(key, 2.weeks.to_i)
end end
say "tracked rename: #{key}: #{old_path} -> #{new_path}"
end
update_column_in_batches(:notes, :note_html, nil) do |table, query| def reverts_for_type(type)
query.where(table[:project_id].in(project_ids)) key = redis_key_for_type(type)
Gitlab::Redis.with do |redis|
failed_reverts = []
while rename_info = redis.lpop(key)
path_before_rename, path_after_rename = JSON.parse(rename_info)
say "renaming #{type} from #{path_after_rename} back to #{path_before_rename}"
begin
yield(path_before_rename, path_after_rename)
rescue StandardError => e
failed_reverts << rename_info
say "Renaming #{type} from #{path_after_rename} back to "\
"#{path_before_rename} failed. Review the error and try "\
"again by running the `down` action. \n"\
"#{e.message}: \n #{e.backtrace.join("\n")}"
end
end
failed_reverts.each { |rename_info| redis.lpush(key, rename_info) }
end end
end
update_column_in_batches(:milestones, :description_html, nil) do |table, query| def redis_key_for_type(type)
query.where(table[:project_id].in(project_ids)) "rename:#{migration.name}:#{type}"
end
end end
def file_storage? def file_storage?
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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