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:
.only-master-and-ee-or-mysql: &only-master-and-ee-or-mysql
only:
- /mysql/
- /-stable$/
- /-stable/
- master@gitlab-org/gitlab-ce
- master@gitlab/gitlabhq
- tags@gitlab-org/gitlab-ce
......@@ -460,9 +460,10 @@ codeclimate:
services:
- docker:dind
script:
- docker pull stedolan/jq
- 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
- sed -i.bak 's/\({"body":"\)[^"]*\("}\)/\1\2/g' 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
- cat raw_codeclimate.json | docker run -i stedolan/jq -c 'map({check_name,fingerprint,location})' > codeclimate.json
artifacts:
paths: [codeclimate.json]
......
......@@ -4,6 +4,7 @@ entry.
## 9.3.3 (2017-06-30)
<<<<<<< HEAD
- No changes.
- Fix head pipeline stored in merge request for external pipelines. !12478
- Bring back branches badge to main project page. !12548
......@@ -12,6 +13,14 @@ entry.
- Fixed multi-line markdown tooltip buttons in issue edit form.
- Fix shared runners minutes query to update only projects with used allowance.
- 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)
......
......@@ -85,9 +85,8 @@ window.Build = (function () {
if (!this.hasBeenScrolled) {
this.scrollToBottom();
}
});
this.verifyTopPosition();
})
.then(() => this.verifyTopPosition());
}
Build.prototype.canScroll = function () {
......@@ -176,7 +175,7 @@ window.Build = (function () {
}
if ($flashError.length) {
topPostion += $flashError.outerHeight();
topPostion += $flashError.outerHeight() + prependTopDefault;
}
this.$buildTrace.css({
......@@ -196,6 +195,7 @@ window.Build = (function () {
})
.done((log) => {
gl.utils.setCiStatusFavicon(`${this.pageUrl}/status.json`);
if (log.state) {
this.state = log.state;
}
......@@ -220,7 +220,11 @@ window.Build = (function () {
}
if (!log.complete) {
this.toggleScrollAnimation(true);
if (!this.hasBeenScrolled) {
this.toggleScrollAnimation(true);
} else {
this.toggleScrollAnimation(false);
}
Build.timeout = setTimeout(() => {
//eslint-disable-next-line
......@@ -229,7 +233,8 @@ window.Build = (function () {
if (!this.hasBeenScrolled) {
this.scrollToBottom();
}
});
})
.then(() => this.verifyTopPosition());
}, 4000);
} else {
this.$buildRefreshAnimation.remove();
......
/* eslint-disable class-methods-use-this */
import './lib/utils/url_utility';
import FilesCommentButton from './files_comment_button';
const UNFOLD_COUNT = 20;
let isBound = false;
......@@ -8,8 +9,10 @@ let isBound = false;
class Diff {
constructor() {
const $diffFile = $('.files .diff-file');
$diffFile.singleFileDiff();
$diffFile.filesCommentButton();
FilesCommentButton.init($diffFile);
$diffFile.each((index, file) => new gl.ImageFile(file));
......
......@@ -139,9 +139,9 @@ const DiffNoteAvatars = Vue.extend({
const notesCount = this.notesCount;
$(this.$el).closest('.js-avatar-container')
.toggleClass('js-no-comment-btn', notesCount > 0)
.toggleClass('no-comment-btn', notesCount > 0)
.nextUntil('.js-avatar-container')
.toggleClass('js-no-comment-btn', notesCount > 0);
.toggleClass('no-comment-btn', notesCount > 0);
},
toggleDiscussionsToggleState() {
const $notesHolders = $(this.$el).closest('.code').find('.notes_holder');
......
......@@ -393,6 +393,7 @@ import AuditLogs from './audit_logs';
case 'search:show':
new Search();
break;
<<<<<<< HEAD
case 'projects:mirrors:show':
case 'projects:mirrors:update':
new UsersSelect();
......@@ -403,6 +404,8 @@ import AuditLogs from './audit_logs';
case 'admin:audit_logs:index':
new AuditLogs();
break;
=======
>>>>>>> ce/master
case 'projects:settings:repository:show':
// Initialize Protected Branch Settings
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 */
/* global FilesCommentButton */
/* global notes */
let $commentButtonTemplate;
window.FilesCommentButton = (function() {
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;
COMMENT_BUTTON_CLASS = '.add-diff-note';
LINE_HOLDER_CLASS = '.line_holder';
LINE_NUMBER_CLASS = 'diff-line-num';
LINE_CONTENT_CLASS = 'line_content';
UNFOLDABLE_LINE_CLASS = 'js-unfold';
EMPTY_CELL_CLASS = 'empty-cell';
OLD_LINE_CLASS = 'old_line';
LINE_COLUMN_CLASSES = "." + LINE_NUMBER_CLASS + ", .line_content";
TEXT_FILE_SELECTOR = '.text-file';
function FilesCommentButton(filesContainerElement) {
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;
/* Developer beware! Do not add logic to showButton or hideButton
* that will force a reflow. Doing so will create a signficant performance
* bottleneck for pages with large diffs. For a comprehensive list of what
* causes reflows, visit https://gist.github.com/paulirish/5d52fb081b3570c81e3a
*/
const LINE_NUMBER_CLASS = 'diff-line-num';
const UNFOLDABLE_LINE_CLASS = 'js-unfold';
const NO_COMMENT_CLASS = 'no-comment-btn';
const EMPTY_CELL_CLASS = 'empty-cell';
const OLD_LINE_CLASS = 'old_line';
const LINE_COLUMN_CLASSES = `.${LINE_NUMBER_CLASS}, .line_content`;
const DIFF_CONTAINER_SELECTOR = '.files';
const DIFF_EXPANDED_CLASS = 'diff-expanded';
export default {
init($diffFile) {
/* 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
* will be true in all cases */
if (!this.userCanCreateNote) {
// data-can-create-note is an empty string when true, otherwise undefined
this.userCanCreateNote = $diffFile.closest(DIFF_CONTAINER_SELECTOR).data('can-create-note') === '';
}
textFileElement = this.getTextFileElement($currentTarget);
buttonParentElement.append(this.buildButton({
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);
if (typeof notes !== 'undefined' && !this.isParallelView) {
this.isParallelView = notes.isParallelView && notes.isParallelView();
}
};
FilesCommentButton.prototype.getButtonParent = function(hoveredElement) {
if (!this.isParallelView) {
if (hoveredElement.hasClass(OLD_LINE_CLASS)) {
return hoveredElement;
}
return hoveredElement.parent().find("." + OLD_LINE_CLASS);
} else {
if (hoveredElement.hasClass(LINE_NUMBER_CLASS)) {
return hoveredElement;
}
return $(hoveredElement).prev("." + LINE_NUMBER_CLASS);
if (this.userCanCreateNote) {
$diffFile.on('mouseover', LINE_COLUMN_CLASSES, e => this.showButton(this.isParallelView, e))
.on('mouseleave', LINE_COLUMN_CLASSES, e => this.hideButton(this.isParallelView, e));
}
};
},
FilesCommentButton.prototype.validateButtonParent = function(buttonParentElement) {
return !buttonParentElement.hasClass(EMPTY_CELL_CLASS) && !buttonParentElement.hasClass(UNFOLDABLE_LINE_CLASS);
};
showButton(isParallelView, e) {
const buttonParentElement = this.getButtonParent(e.currentTarget, isParallelView);
FilesCommentButton.prototype.validateLineContent = function(lineContentElement) {
return lineContentElement.attr('data-note-type') && lineContentElement.attr('data-note-type') !== '';
};
if (!this.validateButtonParent(buttonParentElement)) return;
return FilesCommentButton;
})();
buttonParentElement.classList.add('is-over');
buttonParentElement.nextElementSibling.classList.add('is-over');
},
$.fn.filesCommentButton = function() {
$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>');
hideButton(isParallelView, e) {
const buttonParentElement = this.getButtonParent(e.currentTarget, isParallelView);
if (!(this && (this.parent().data('can-create-note') != null))) {
return;
}
return this.each(function() {
if (!$.data(this, 'filesCommentButton')) {
return $.data(this, 'filesCommentButton', new FilesCommentButton($(this)));
buttonParentElement.classList.remove('is-over');
buttonParentElement.nextElementSibling.classList.remove('is-over');
},
getButtonParent(hoveredElement, isParallelView) {
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 {
this.cachedData = {};
}
destroy() {
this.input.each((i, input) => {
const $input = $(input);
$input.atwho('destroy');
});
}
static isLoading(data) {
let dataToInspect = data;
if (data && data.length > 0) {
......
......@@ -21,6 +21,9 @@ function GLForm(form, enableGFM = false) {
GLForm.prototype.destroy = function() {
// Clean form listeners
this.clearEventListeners();
if (this.autoComplete) {
this.autoComplete.destroy();
}
return this.form.data('gl-form', null);
};
......@@ -33,7 +36,8 @@ GLForm.prototype.setupForm = function() {
this.form.addClass('gfm-form');
// 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'));
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,
members: this.enableGFM,
issues: this.enableGFM,
......
......@@ -39,6 +39,17 @@
runnerId() {
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>
......@@ -63,7 +74,7 @@
Retry
</a>
</div>
<div class="block">
<div :class="{block : renderBlock }">
<p
class="build-detail-row js-job-mr"
v-if="job.merge_request">
......
......@@ -144,7 +144,9 @@ import BlobForkSuggestion from './blob/blob_fork_suggestion';
this.resetViewContainer();
this.mountPipelinesView();
} else {
this.expandView();
if (Breakpoints.get().getBreakpointSize() !== 'xs') {
this.expandView();
}
this.resetViewContainer();
this.destroyPipelinesView();
}
......
......@@ -829,6 +829,8 @@ export default class Notes {
*/
setupDiscussionNoteForm(dataHolder, form) {
// setup note target
const diffFileData = dataHolder.closest('.text-file');
var discussionID = dataHolder.data('discussionId');
if (discussionID) {
......@@ -839,9 +841,10 @@ export default class Notes {
form.attr('data-line-code', dataHolder.data('lineCode'));
form.find('#line_type').val(dataHolder.data('lineType'));
form.find('#note_noteable_type').val(dataHolder.data('noteableType'));
form.find('#note_noteable_id').val(dataHolder.data('noteableId'));
form.find('#note_commit_id').val(dataHolder.data('commitId'));
form.find('#note_noteable_type').val(diffFileData.data('noteableType'));
form.find('#note_noteable_id').val(diffFileData.data('noteableId'));
form.find('#note_commit_id').val(diffFileData.data('commitId'));
form.find('#note_type').val(dataHolder.data('noteType'));
// LegacyDiffNote
......
......@@ -10,6 +10,8 @@ import Cookies from 'js-cookie';
this.$sidebarInner = this.sidebar.find('.issuable-sidebar');
this.$navGitlab = $('.navbar-gitlab');
this.$layoutNav = $('.layout-nav');
this.$subScroll = $('.sub-nav-scroll');
this.$rightSidebar = $('.js-right-sidebar');
this.removeListeners();
......@@ -27,14 +29,14 @@ import Cookies from 'js-cookie';
Sidebar.prototype.addEventListeners = function() {
const $document = $(document);
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);
$('.dropdown').on('hidden.gl.dropdown', this, this.onSidebarDropdownHidden);
$('.dropdown').on('loading.gl.dropdown', this.sidebarDropdownLoading);
$('.dropdown').on('loaded.gl.dropdown', this.sidebarDropdownLoaded);
$(window).on('resize', () => throttledSetSidebarHeight());
$document.on('scroll', () => debouncedSetSidebarHeight());
$document.on('scroll', () => slowerThrottledSetSidebarHeight());
$document.on('click', '.js-sidebar-toggle', function(e, triggered) {
var $allGutterToggleIcons, $this, $thisIcon;
e.preventDefault();
......@@ -213,7 +215,7 @@ import Cookies from 'js-cookie';
};
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();
if (diff > 0) {
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 */
import FilesCommentButton from './files_comment_button';
(function() {
window.SingleFileDiff = (function() {
var COLLAPSED_HTML, ERROR_HTML, LOADING_HTML, WRAPPER;
......@@ -78,6 +80,8 @@
gl.diffNotesCompileComponents();
}
FilesCommentButton.init($(_this.file));
if (cb) cb();
};
})(this));
......
......@@ -64,6 +64,12 @@
*/
return new gl.GLForm($(this.$refs['gl-form']), true);
},
beforeDestroy() {
const glForm = $(this.$refs['gl-form']).data('gl-form');
if (glForm) {
glForm.destroy();
}
},
};
</script>
......
......@@ -11,20 +11,19 @@ header.navbar-gitlab-new {
padding-left: 0;
.title-container {
align-items: stretch;
padding-top: 0;
overflow: visible;
}
.title {
display: block;
height: 100%;
display: flex;
padding-right: 0;
color: currentColor;
> a {
display: flex;
align-items: center;
height: 100%;
padding-top: 3px;
padding-right: $gl-padding;
padding-left: $gl-padding;
......
......@@ -5,17 +5,46 @@
$new-sidebar-width: 220px;
.page-with-new-sidebar {
@media (min-width: $screen-sm-min) {
padding-left: $new-sidebar-width;
}
// Override position: absolute
.right-sidebar {
position: fixed;
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 {
position: fixed;
z-index: 400;
......
......@@ -147,10 +147,9 @@
top: 35px;
left: 10px;
bottom: 0;
overflow-y: scroll;
overflow-x: hidden;
padding: 10px 20px 20px 5px;
white-space: pre;
white-space: pre-wrap;
overflow: auto;
}
.environment-information {
......@@ -399,6 +398,7 @@
.build-light-text {
color: $gl-text-color-secondary;
word-wrap: break-word;
}
.build-gutter-toggle {
......
......@@ -20,8 +20,6 @@
}
.diff-content {
overflow: auto;
overflow-y: hidden;
background: $white-light;
color: $gl-text-color;
border-radius: 0 0 3px 3px;
......@@ -476,6 +474,7 @@
height: 19px;
width: 19px;
margin-left: -15px;
z-index: 100;
&:hover {
.diff-comment-avatar,
......@@ -491,7 +490,7 @@
transform: translateX((($i * $x-pos) - $x-pos));
&:hover {
transform: translateX((($i * $x-pos) - $x-pos)) scale(1.2);
transform: translateX((($i * $x-pos) - $x-pos));
}
}
}
......@@ -542,6 +541,7 @@
height: 19px;
padding: 0;
transition: transform .1s ease-out;
z-index: 100;
svg {
position: absolute;
......@@ -555,10 +555,6 @@
fill: $white-light;
}
&:hover {
transform: scale(1.2);
}
&:focus {
outline: 0;
}
......
......@@ -659,8 +659,12 @@
@media(max-width: $screen-md-max) {
.task-status,
.issuable-due-date,
<<<<<<< HEAD
.project-ref-path,
.issuable-weight {
=======
.project-ref-path {
>>>>>>> ce/master
display: none;
}
}
......
......@@ -628,8 +628,14 @@ ul.notes {
* Line note button on the side of diffs
*/
.line_holder .is-over:not(.no-comment-btn) {
.add-diff-note {
opacity: 1;
}
}
.add-diff-note {
display: none;
opacity: 0;
margin-top: -2px;
border-radius: 50%;
background: $white-light;
......@@ -642,13 +648,11 @@ ul.notes {
width: 23px;
height: 23px;
border: 1px solid $blue-500;
transition: transform .1s ease-in-out;
&:hover {
background: $blue-500;
border-color: $blue-600;
color: $white-light;
transform: scale(1.15);
}
&:active {
......
.tree-holder {
.nav-block {
margin: 10px 0;
......@@ -15,6 +16,11 @@
.btn-group {
margin-left: 10px;
}
.control {
float: left;
margin-left: 10px;
}
}
.tree-ref-holder {
......
class AbuseReportsController < ApplicationController
before_action :set_user, only: [:new]
def new
@abuse_report = AbuseReport.new
@abuse_report.user_id = params[:user_id]
@abuse_report.user_id = @user.id
@ref_url = params.fetch(:ref_url, '')
end
......@@ -27,4 +29,14 @@ class AbuseReportsController < ApplicationController
user_id
))
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
class Projects::MergeRequests::ApplicationController < Projects::ApplicationController
<<<<<<< HEAD
prepend ::EE::Projects::MergeRequests::ApplicationController
=======
>>>>>>> ce/master
before_action :check_merge_requests_available!
before_action :merge_request
before_action :authorize_read_merge_request!
......
class Projects::MergeRequests::CreationsController < Projects::MergeRequests::ApplicationController
include DiffForPath
include DiffHelper
<<<<<<< HEAD
prepend ::EE::Projects::MergeRequests::CreationsController
=======
>>>>>>> ce/master
skip_before_action :merge_request
skip_before_action :ensure_ref_fetched
......
......@@ -9,6 +9,12 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
skip_before_action :merge_request, 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 :authenticate_user!, only: [:assign_related_issues]
......
class Projects::PipelineSchedulesController < Projects::ApplicationController
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 :schedule, only: [:edit, :update, :destroy, :take_ownership]
......
......@@ -98,7 +98,7 @@ class ProjectsController < Projects::ApplicationController
end
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
respond_to do |format|
......
......@@ -83,6 +83,8 @@ class TodosFinder
if project?
@project = Project.find(params[:project_id])
@project = nil if @project.pending_delete?
unless Ability.allowed?(current_user, :read_project, @project)
@project = nil
end
......
......@@ -47,6 +47,18 @@ module NotesHelper
data
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)
return unless current_user
......
......@@ -73,6 +73,7 @@ module SubmoduleHelper
end
def relative_self_links(url, commit)
url.rstrip!
# Map relative links to a namespace and project
# For example:
# ../bar.git -> same namespace, repo bar
......
<<<<<<< HEAD
require 'declarative_policy'
=======
require_dependency 'declarative_policy'
>>>>>>> ce/master
class Ability
class << self
......
......@@ -151,6 +151,7 @@ module Ci
where(id: max_id)
end
end
scope :internal, -> { where(source: internal_sources) }
def self.latest_status(ref = nil)
latest(ref).status
......@@ -174,6 +175,10 @@ module Ci
where.not(duration: nil).sum(:duration)
end
def self.internal_sources
sources.reject { |source| source == "external" }.values
end
def stages_count
statuses.select(:stage).distinct.count
end
......
module Ci
class Variable < ActiveRecord::Base
extend Ci::Model
include HasVariable
belongs_to :project
validates :key,
presence: true,
uniqueness: { scope: :project_id },
length: { maximum: 255 },
format: { with: /\A[a-zA-Z0-9_]+\z/,
message: "can contain only letters, digits and '_'." }
validates :key, uniqueness: { scope: :project_id }
scope :order_key_asc, -> { reorder(key: :asc) }
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
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
acts_as_paranoid
acts_as_paranoid without_default_scope: true
prepend EE::Namespace
include CacheMarkdownField
......@@ -224,6 +224,12 @@ class Namespace < ActiveRecord::Base
parent.present?
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
def repository_storage_paths
......
......@@ -19,7 +19,7 @@ class NotificationSetting < ActiveRecord::Base
# pending delete).
#
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
EMAIL_EVENTS = [
......
......@@ -228,9 +228,8 @@ class Project < ActiveRecord::Base
has_many :uploads, as: :model, dependent: :destroy
# Scopes
default_scope { where(pending_delete: false) }
scope :with_deleted, -> { unscope(where: :pending_delete) }
scope :pending_delete, -> { where(pending_delete: true) }
scope :without_deleted, -> { where(pending_delete: false) }
scope :sorted_by_activity, -> { reorder(last_activity_at: :desc) }
scope :sorted_by_stars, -> { reorder('projects.star_count DESC') }
......@@ -353,7 +352,16 @@ class Project < ActiveRecord::Base
after_transition started: :finished do |project, _|
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
......@@ -511,22 +519,6 @@ class Project < ActiveRecord::Base
ProjectCacheWorker.perform_async(self.id)
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
end
......@@ -1461,7 +1453,7 @@ class Project < ActiveRecord::Base
def pending_delete_twin
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
##
......
......@@ -12,8 +12,11 @@ class User < ActiveRecord::Base
include TokenAuthenticatable
include IgnorableColumn
include FeatureGate
<<<<<<< HEAD
prepend EE::GeoAwareAvatar
prepend EE::User
=======
>>>>>>> ce/master
DEFAULT_NOTIFICATION_LEVEL = :participating
......@@ -322,11 +325,20 @@ class User < ActiveRecord::Base
table = arel_table
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(
table[:name].matches(pattern)
.or(table[:email].matches(pattern))
.or(table[:username].matches(pattern))
)
).reorder(order % { query: ActiveRecord::Base.connection.quote(query) }, id: :desc)
end
# searches user by given pattern
......
<<<<<<< HEAD
require 'declarative_policy'
=======
require_dependency 'declarative_policy'
>>>>>>> ce/master
class BasePolicy < DeclarativePolicy::Base
desc "User is an instance admin"
......@@ -10,6 +14,7 @@ class BasePolicy < DeclarativePolicy::Base
with_options scope: :user, score: 0
condition(:can_create_group) { @user&.can_create_group }
<<<<<<< HEAD
# EE Extensions
with_scope :user
......@@ -20,4 +25,6 @@ class BasePolicy < DeclarativePolicy::Base
with_scope :global
condition(:license_block) { License.block_changes? }
=======
>>>>>>> ce/master
end
class GroupPolicy < BasePolicy
<<<<<<< HEAD
prepend EE::GroupPolicy
=======
>>>>>>> ce/master
desc "Group is public"
with_options scope: :subject, score: 0
condition(:public_group) { @subject.public? }
with_score 0
condition(:logged_in_viewable) { @user && @subject.internal? && !@user.external? }
<<<<<<< HEAD
condition(:has_access) { access_level != GroupMember::NO_ACCESS }
......@@ -15,6 +19,16 @@ class GroupPolicy < BasePolicy
condition(:master) { access_level >= GroupMember::MASTER }
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
GroupProjectsFinder.new(group: @subject, current_user: @user).execute.any?
end
......
class ProjectPolicy < BasePolicy
<<<<<<< HEAD
prepend EE::ProjectPolicy
def self.create_read_update_admin(name)
......@@ -29,6 +30,36 @@ class ProjectPolicy < BasePolicy
desc "User has reporter access"
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"
condition(:developer) { team_access_level >= Gitlab::Access::DEVELOPER }
......
......@@ -27,7 +27,10 @@ class ProjectSnippetPolicy < BasePolicy
all?(private_snippet | (internal & external_user),
~project.guest,
~admin,
<<<<<<< HEAD
~auditor,
=======
>>>>>>> ce/master
~is_author)
end.prevent :read_project_snippet
......
......@@ -55,7 +55,7 @@ module Ci
def builds_for_shared_runner
new_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')
.where('project_features.builds_access_level IS NULL or project_features.builds_access_level > 0').
......@@ -67,7 +67,7 @@ module Ci
end
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
def running_builds_for_shared_runners
......
......@@ -3,8 +3,8 @@ class GitHooksService
attr_accessor :oldrev, :newrev, :ref
def execute(user, repo_path, oldrev, newrev, ref)
@repo_path = repo_path
def execute(user, project, oldrev, newrev, ref)
@project = project
@user = Gitlab::GlId.gl_id(user)
@oldrev = oldrev
@newrev = newrev
......@@ -26,7 +26,7 @@ class GitHooksService
private
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)
end
end
......@@ -120,7 +120,7 @@ class GitOperationService
def with_hooks(ref, newrev, oldrev)
GitHooksService.new.execute(
user,
repository.path_to_repo,
repository.project,
oldrev,
newrev,
ref) do |service|
......
module Groups
class DestroyService < Groups::BaseService
def async_execute
# Soft delete via paranoia gem
group.destroy
group.soft_delete_without_removing_associations
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}")
end
......@@ -10,7 +9,7 @@ module Groups
def execute
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.
# Skip repository removal because we remove directory with namespace
# that contain all these repositories
......
......@@ -95,13 +95,19 @@ module Projects
end
# Requires UnZip at least 6.00 Info-ZIP.
# -qq be (very) quiet
# -n never overwrite existing files
# We add * to end of SITE_PATH, because we want to extract SITE_PATH and all subdirectories
site_path = File.join(SITE_PATH, '*')
<<<<<<< HEAD
build.artifacts_file.use_file do |artifacts_path|
unless system(*%W(unzip -n #{artifacts_path} #{site_path} -d #{temp_path}))
raise 'pages failed to extract'
end
=======
unless system(*%W(unzip -qq -n #{artifacts} #{site_path} -d #{temp_path}))
raise 'pages failed to extract'
>>>>>>> ce/master
end
end
......
......@@ -35,7 +35,7 @@ module Users
Groups::DestroyService.new(group, current_user).execute
end
user.personal_projects.with_deleted.each do |project|
user.personal_projects.each do |project|
# Skip repository removal because we remove directory with namespace
# that contain all this repositories
::Projects::DestroyService.new(project, current_user, skip_repo: true).execute
......
......@@ -11,6 +11,8 @@
%meta{ property: 'og:title', content: page_title }
%meta{ property: 'og:description', content: page_description }
%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 }
-# Twitter Card - https://dev.twitter.com/cards/types/summary
......
.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
= 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
......
.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
= nav_link(path: ['groups#show', 'groups#activity', 'groups#subgroups'], html_options: { class: 'home' }) do
= link_to group_path(@group), title: 'Home' do
......
.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
= nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
= link_to profile_path, title: 'Profile Settings' do
......
.nav-sidebar
- 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
= 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
......
......@@ -8,27 +8,28 @@
= render "head"
%div{ class: container_class }
.row-content-block.second-block.content-component-block.flex-container-block
.tree-ref-holder
= render 'shared/ref_switcher', destination: 'commits'
.tree-holder
.nav-block
.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
= 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)
= 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 _("Create merge request"), create_mr_path(@repository.root_ref, @ref), class: 'btn btn-success'
.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")
= 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'
......
......@@ -19,6 +19,7 @@
- if plain
= link_text
- else
= add_diff_note_button(line_code, diff_file.position(line), type)
%a{ href: "##{line_code}", data: { linenumber: link_text } }
- discussion = line_discussions.try(:first)
- if discussion && discussion.resolvable? && !plain
......@@ -29,7 +30,7 @@
= link_text
- else
%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
%pre= line.text
- else
......
/ Side-by-side diff view
.text-file.diff-wrap-lines.code.js-syntax-highlight{ data: diff_view_data }
%table
- diff_file.parallel_diff_lines.each do |line|
......@@ -18,11 +19,12 @@
- left_line_code = diff_file.line_code(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 } }
= add_diff_note_button(left_line_code, left_position, 'old')
%a{ href: "##{left_line_code}", data: { linenumber: left.old_pos } }
- discussion_left = discussions_left.try(:first)
- if discussion_left && discussion_left.resolvable?
%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
%td.old_line.diff-line-num.empty-cell
%td.line_content.parallel
......@@ -38,11 +40,12 @@
- right_line_code = diff_file.line_code(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 } }
= add_diff_note_button(right_line_code, right_position, 'new')
%a{ href: "##{right_line_code}", data: { linenumber: right.new_pos } }
- discussion_right = discussions_right.try(:first)
- if discussion_right && discussion_right.resolvable?
%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
%td.old_line.diff-line-num.empty-cell
%td.line_content.parallel
......
......@@ -36,11 +36,14 @@
&nbsp;
- issue.labels.each do |label|
= link_to_label(label, subject: issue.project, css_class: 'label-link')
<<<<<<< HEAD
- if issue.weight
%span.issuable-weight
&nbsp;
= icon('balance-scale')
= issue.weight
=======
>>>>>>> ce/master
.issuable-meta
%ul.controls
......
......@@ -13,6 +13,9 @@
= 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?
%div{ class: container_class }
.top-area
......@@ -20,9 +23,12 @@
.nav-controls
- if @can_bulk_update
= 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
<<<<<<< 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 new_merge_request_path, class: "btn btn-new", title: "New merge request" do
>>>>>>> ce/master
New merge request
= render 'shared/issuable/search_bar', type: :merge_requests
......@@ -33,4 +39,8 @@
.merge-requests-holder
= render 'merge_requests'
- 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: new_merge_request_path
>>>>>>> ce/master
......@@ -5,7 +5,10 @@
- content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('common_vue')
= page_specific_javascript_bundle_tag('diff_notes')
<<<<<<< HEAD
= 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) }
= render "projects/merge_requests/mr_title"
......@@ -19,6 +22,7 @@
:javascript
window.gl.mrWidgetData = #{serialize_issuable(@merge_request)}
<<<<<<< HEAD
// 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
window.gl.mrWidgetData.is_geo_secondary_node = '#{Gitlab::Geo.secondary?}' === 'true';
......@@ -26,6 +30,8 @@
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")}';
=======
>>>>>>> ce/master
#js-vue-mr-widget.mr-widget
- content_for :page_specific_javascripts do
......
......@@ -24,7 +24,7 @@
%li
= 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)})
%l
%li
= link_to namespace_project_branches_path(@project.namespace, @project) do
#{n_('Branch', 'Branches', @repository.branch_count)} (#{number_with_delimiter(@repository.branch_count)})
%li
......
......@@ -13,7 +13,7 @@
- if projects.any?
%ul.projects-list
- 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,
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
......
......@@ -45,7 +45,7 @@ class StuckCiJobsWorker
def search(status, timeout)
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)
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)
current_version = Gitlab::VersionInfo.parse(Gitlab::Shell.new.version)
unless Rails.env.test?
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
warn "WARNING: This version of GitLab depends on gitlab-shell #{required_version}, but you're running #{current_version}. Please update gitlab-shell."
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."
end
end
......@@ -169,6 +169,7 @@ constraints(ProjectUrlConstrainer.new) do
end
end
<<<<<<< HEAD
## EE-specific
resources :path_locks, only: [:index, :destroy] do
collection do
......@@ -180,6 +181,8 @@ constraints(ProjectUrlConstrainer.new) do
get '/service_desk' => 'service_desk#show', as: :service_desk
put '/service_desk' => 'service_desk#update', as: :service_desk_refresh
=======
>>>>>>> ce/master
resources :variables, only: [:index, :show, :update, :create, :destroy]
resources :triggers, only: [:index, :create, :edit, :update, :destroy] 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:
{
"id": 2,
"path": "group1",
<<<<<<< HEAD
"kind": "group"
"full_path": "group1",
"parent_id": "null",
"members_count_with_descendants": 2,
"plan": "bronze"
=======
"kind": "group",
"full_path": "group1",
"parent_id": "null",
"members_count_with_descendants": 2
>>>>>>> ce/master
},
{
"id": 3,
......@@ -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` are presented only for group masters/owners.
>>>>>>> ce/master
## Search for namespace
......
......@@ -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.
Then, create a new **Secret Variable** in your project settings on GitLab
following **Settings > Variables**. As **Key** add the name `SSH_PRIVATE_KEY`
and in the **Value** field paste the content of your _private_ key that you
created earlier.
following **Settings > Pipelines** and look for the "Secret Variables" section.
As **Key** add the name `SSH_PRIVATE_KEY` and in the **Value** field paste the
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
are not being targeted by a man-in-the-middle attack. To do this, add another
......
......@@ -55,6 +55,7 @@
- [Polymorphic Associations](polymorphic_associations.md)
- [Single Table Inheritance](single_table_inheritance.md)
- [Background Migrations](background_migrations.md)
- [Storing SHA1 Hashes As Binary](sha1_as_binary.md)
## 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
end
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'
end
end
......@@ -519,10 +519,13 @@ module API
def expose_members_count_with_descendants?(namespace, opts)
namespace.kind == 'group' && Ability.allowed?(opts[:current_user], :admin_group, namespace)
end
<<<<<<< HEAD
# EE-only
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) }
=======
>>>>>>> ce/master
end
class MemberAccess < Grape::Entity
......
......@@ -48,7 +48,8 @@ module API
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?
end
......
......@@ -18,6 +18,7 @@ module API
namespaces = namespaces.search(params[:search]) if params[:search].present?
present paginate(namespaces), with: Entities::Namespace, current_user: current_user
<<<<<<< HEAD
end
desc 'Update a namespace' do
......@@ -39,6 +40,8 @@ module API
else
render_validation_error!(namespace)
end
=======
>>>>>>> ce/master
end
end
end
......
<<<<<<< HEAD
require 'declarative_policy'
=======
require_dependency 'declarative_policy'
>>>>>>> ce/master
module API
# Projects API
......
......@@ -30,7 +30,8 @@ module Ci
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?
end
......
......@@ -29,6 +29,11 @@ module Gitlab
paths = Array(paths)
RenameNamespaces.new(paths, self).rename_namespaces(type: :top_level)
end
def revert_renames
RenameProjects.new([], self).revert_renames
RenameNamespaces.new([], self).revert_renames
end
end
end
end
......
......@@ -6,7 +6,10 @@ module Gitlab
attr_reader :paths, :migration
delegate :update_column_in_batches,
:execute,
:replace_sql,
:quote_string,
:say,
to: :migration
def initialize(paths, migration)
......@@ -26,24 +29,45 @@ module Gitlab
new_path = rename_path(namespace_path, old_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
new_path = new_full_path.split('/').last
routable.class.where(id: routable)
.update_all(path: new_path)
rename_routes(old_full_path, new_full_path)
[old_full_path, new_full_path]
end
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],
old_full_path,
new_full_path)
update_column_in_batches(:routes, :path, replace_statement) do |table, query|
path_or_children = table[:path].matches_any([old_full_path, "#{old_full_path}/%"])
query.where(path_or_children)
end
update = Arel::UpdateManager.new(ActiveRecord::Base)
.table(routes)
.set([[routes[:path], replace_statement]])
.where(Arel::Nodes::SqlLiteral.new(filter))
execute(update.to_sql)
end
def rename_path(namespace_path, path_was)
......@@ -86,32 +110,74 @@ module Gitlab
def move_folders(directory, old_relative_path, new_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)
FileUtils.mv(old_path, new_path)
end
def remove_cached_html_for_projects(project_ids)
update_column_in_batches(:projects, :description_html, nil) do |table, query|
query.where(table[:id].in(project_ids))
end
update_column_in_batches(:issues, :description_html, nil) do |table, query|
query.where(table[:project_id].in(project_ids))
project_ids.each do |project_id|
update_column_in_batches(:projects, :description_html, nil) do |table, query|
query.where(table[:id].eq(project_id))
end
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
update_column_in_batches(:merge_requests, :description_html, nil) do |table, query|
query.where(table[:target_project_id].in(project_ids))
def track_rename(type, old_path, new_path)
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
say "tracked rename: #{key}: #{old_path} -> #{new_path}"
end
update_column_in_batches(:notes, :note_html, nil) do |table, query|
query.where(table[:project_id].in(project_ids))
def reverts_for_type(type)
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
update_column_in_batches(:milestones, :description_html, nil) do |table, query|
query.where(table[:project_id].in(project_ids))
end
def redis_key_for_type(type)
"rename:#{migration.name}:#{type}"
end
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