Commit 79aaf516 authored by Rémy Coutable's avatar Rémy Coutable

Merge remote-tracking branch 'origin/master' into ce-to-ee-2017-10-18

Signed-off-by: default avatarRémy Coutable <remy@rymai.me>
parents 74a2c395 f9b3cf17
Please view this file on the master branch, on stable branches it's out of date. Please view this file on the master branch, on stable branches it's out of date.
## 10.0.4 (2017-10-16)
- [SECURITY] Prevent Related Issues from leaking confidential issues. !541
- [SECURITY] Escape user name in filtered search bar.
## 10.0.3 (2017-10-05) ## 10.0.3 (2017-10-05)
- [FIXED] Rewrite Geo database rake tasks so they operate on the correct database. !3052 - [FIXED] Rewrite Geo database rake tasks so they operate on the correct database. !3052
...@@ -61,6 +66,11 @@ Please view this file on the master branch, on stable branches it's out of date. ...@@ -61,6 +66,11 @@ Please view this file on the master branch, on stable branches it's out of date.
- Add group issue boards. - Add group issue boards.
- Ports style changes fixed in a conflict in ce to ee upstream to master for new projects page. - Ports style changes fixed in a conflict in ce to ee upstream to master for new projects page.
## 9.5.9 (2017-10-16)
- [SECURITY] Prevent Related Issues from leaking confidential issues.
- Escape user name in filtered search bar.
## 9.5.8 (2017-10-04) ## 9.5.8 (2017-10-04)
- [FIXED] Fix EE delta size check handling with annotated tags. - [FIXED] Fix EE delta size check handling with annotated tags.
...@@ -137,6 +147,12 @@ Please view this file on the master branch, on stable branches it's out of date. ...@@ -137,6 +147,12 @@ Please view this file on the master branch, on stable branches it's out of date.
- Fix rebase button when merge request is created from a fork. - Fix rebase button when merge request is created from a fork.
- Skip oAuth authorization for trusted applications. - Skip oAuth authorization for trusted applications.
## 9.4.7 (2017-10-16)
- [SECURITY] Prevent Related Issues from leaking confidential issues.
- Fix when pushing without a branch name. !2879
- Escape user name in filtered search bar.
## 9.4.6 (2017-09-06) ## 9.4.6 (2017-09-06)
- [FIXED] Validate branch name push rule when pushing branch without commits. !2685 - [FIXED] Validate branch name push rule when pushing branch without commits. !2685
......
...@@ -2,6 +2,12 @@ ...@@ -2,6 +2,12 @@
documentation](doc/development/changelog.md) for instructions on adding your own documentation](doc/development/changelog.md) for instructions on adding your own
entry. entry.
## 10.0.4 (2017-10-16)
- [SECURITY] Move project repositories between namespaces when renaming users.
- [SECURITY] Prevent an open redirect on project pages.
- [SECURITY] Prevent a persistent XSS in user-provided markup.
## 10.0.3 (2017-10-05) ## 10.0.3 (2017-10-05)
- [FIXED] find_user Users helper method no longer overrides find_user API helper method. !14418 - [FIXED] find_user Users helper method no longer overrides find_user API helper method. !14418
...@@ -212,6 +218,14 @@ entry. ...@@ -212,6 +218,14 @@ entry.
- Added type to CHANGELOG entries. (Jacopo Beschi @jacopo-beschi) - Added type to CHANGELOG entries. (Jacopo Beschi @jacopo-beschi)
- [BUGIFX] Improves subgroup creation permissions. !13418 - [BUGIFX] Improves subgroup creation permissions. !13418
## 9.5.9 (2017-10-16)
- [SECURITY] Move project repositories between namespaces when renaming users.
- [SECURITY] Prevent an open redirect on project pages.
- [SECURITY] Prevent a persistent XSS in user-provided markup.
- [FIXED] Allow using newlines in pipeline email service recipients. !14250
- Escape user name in filtered search bar.
## 9.5.8 (2017-10-04) ## 9.5.8 (2017-10-04)
- [FIXED] Fixed fork button being disabled for users who can fork to a group. - [FIXED] Fixed fork button being disabled for users who can fork to a group.
...@@ -457,6 +471,15 @@ entry. ...@@ -457,6 +471,15 @@ entry.
- Use a specialized class for querying events to improve performance. - Use a specialized class for querying events to improve performance.
- Update build badges to be pipeline badges and display passing instead of success. - Update build badges to be pipeline badges and display passing instead of success.
## 9.4.7 (2017-10-16)
- [SECURITY] Upgrade mail and nokogiri gems due to security issues. !13662 (Markus Koller)
- [SECURITY] Move project repositories between namespaces when renaming users.
- [SECURITY] Prevent an open redirect on project pages.
- [SECURITY] Prevent a persistent XSS in user-provided markup.
- [FIXED] Allow using newlines in pipeline email service recipients. !14250
- Escape user name in filtered search bar.
## 9.4.6 (2017-09-06) ## 9.4.6 (2017-09-06)
- [SECURITY] Upgrade mail and nokogiri gems due to security issues. !13662 (Markus Koller) - [SECURITY] Upgrade mail and nokogiri gems due to security issues. !13662 (Markus Koller)
......
...@@ -9,6 +9,7 @@ import Flash from '../../flash'; ...@@ -9,6 +9,7 @@ import Flash from '../../flash';
import eventHub from '../../sidebar/event_hub'; import eventHub from '../../sidebar/event_hub';
import AssigneeTitle from '../../sidebar/components/assignees/assignee_title'; import AssigneeTitle from '../../sidebar/components/assignees/assignee_title';
import Assignees from '../../sidebar/components/assignees/assignees'; import Assignees from '../../sidebar/components/assignees/assignees';
import DueDateSelectors from '../../due_date_select';
import './sidebar/remove_issue'; import './sidebar/remove_issue';
const Store = gl.issueBoards.BoardsStore; const Store = gl.issueBoards.BoardsStore;
...@@ -113,7 +114,7 @@ gl.issueBoards.BoardSidebar = Vue.extend({ ...@@ -113,7 +114,7 @@ gl.issueBoards.BoardSidebar = Vue.extend({
mounted () { mounted () {
new IssuableContext(this.currentUser); new IssuableContext(this.currentUser);
new MilestoneSelect(); new MilestoneSelect();
new gl.DueDateSelectors(); new DueDateSelectors();
new LabelsSelect(); new LabelsSelect();
new Sidebar(); new Sidebar();
gl.Subscription.bindAll('.subscription'); gl.Subscription.bindAll('.subscription');
......
...@@ -89,6 +89,7 @@ import ShortcutsIssuable from './shortcuts_issuable'; ...@@ -89,6 +89,7 @@ import ShortcutsIssuable from './shortcuts_issuable';
import U2FAuthenticate from './u2f/authenticate'; import U2FAuthenticate from './u2f/authenticate';
import Members from './members'; import Members from './members';
import memberExpirationDate from './member_expiration_date'; import memberExpirationDate from './member_expiration_date';
import DueDateSelectors from './due_date_select';
// EE-only // EE-only
import ApproversSelect from './approvers_select'; import ApproversSelect from './approvers_select';
...@@ -257,7 +258,7 @@ import initGroupAnalytics from './init_group_analytics'; ...@@ -257,7 +258,7 @@ import initGroupAnalytics from './init_group_analytics';
case 'groups:milestones:edit': case 'groups:milestones:edit':
case 'groups:milestones:update': case 'groups:milestones:update':
new ZenMode(); new ZenMode();
new gl.DueDateSelectors(); new DueDateSelectors();
new GLForm($('.milestone-form'), true); new GLForm($('.milestone-form'), true);
break; break;
case 'projects:compare:show': case 'projects:compare:show':
...@@ -590,7 +591,7 @@ import initGroupAnalytics from './init_group_analytics'; ...@@ -590,7 +591,7 @@ import initGroupAnalytics from './init_group_analytics';
break; break;
case 'profiles:personal_access_tokens:index': case 'profiles:personal_access_tokens:index':
case 'admin:impersonation_tokens:index': case 'admin:impersonation_tokens:index':
new gl.DueDateSelectors(); new DueDateSelectors();
break; break;
case 'projects:clusters:show': case 'projects:clusters:show':
import(/* webpackChunkName: "clusters" */ './clusters') import(/* webpackChunkName: "clusters" */ './clusters')
......
/* eslint-disable wrap-iife, func-names, space-before-function-paren, comma-dangle, prefer-template, consistent-return, class-methods-use-this, arrow-body-style, no-unused-vars, no-underscore-dangle, no-new, max-len, no-sequences, no-unused-expressions, no-param-reassign */
/* global dateFormat */ /* global dateFormat */
import Pikaday from 'pikaday'; import Pikaday from 'pikaday';
import DateFix from './lib/utils/datefix'; import { parsePikadayDate, pikadayToString } from './lib/utils/datefix';
class DueDateSelect { class DueDateSelect {
constructor({ $dropdown, $loading } = {}) { constructor({ $dropdown, $loading } = {}) {
...@@ -17,8 +16,8 @@ class DueDateSelect { ...@@ -17,8 +16,8 @@ class DueDateSelect {
this.$value = $block.find('.value'); this.$value = $block.find('.value');
this.$valueContent = $block.find('.value-content'); this.$valueContent = $block.find('.value-content');
this.$sidebarValue = $('.js-due-date-sidebar-value', $block); this.$sidebarValue = $('.js-due-date-sidebar-value', $block);
this.fieldName = $dropdown.data('field-name'), this.fieldName = $dropdown.data('field-name');
this.abilityName = $dropdown.data('ability-name'), this.abilityName = $dropdown.data('ability-name');
this.issueUpdateURL = $dropdown.data('issue-update'); this.issueUpdateURL = $dropdown.data('issue-update');
this.rawSelectedDate = null; this.rawSelectedDate = null;
...@@ -39,20 +38,20 @@ class DueDateSelect { ...@@ -39,20 +38,20 @@ class DueDateSelect {
hidden: () => { hidden: () => {
this.$selectbox.hide(); this.$selectbox.hide();
this.$value.css('display', ''); this.$value.css('display', '');
} },
}); });
} }
initDatePicker() { initDatePicker() {
const $dueDateInput = $(`input[name='${this.fieldName}']`); const $dueDateInput = $(`input[name='${this.fieldName}']`);
const dateFix = DateFix.dashedFix($dueDateInput.val());
const calendar = new Pikaday({ const calendar = new Pikaday({
field: $dueDateInput.get(0), field: $dueDateInput.get(0),
theme: 'gitlab-theme', theme: 'gitlab-theme',
format: 'yyyy-mm-dd', format: 'yyyy-mm-dd',
parse: dateString => parsePikadayDate(dateString),
toString: date => pikadayToString(date),
onSelect: (dateText) => { onSelect: (dateText) => {
const formattedDate = dateFormat(new Date(dateText), 'yyyy-mm-dd'); $dueDateInput.val(calendar.toString(dateText));
$dueDateInput.val(formattedDate);
if (this.$dropdown.hasClass('js-issue-boards-due-date')) { if (this.$dropdown.hasClass('js-issue-boards-due-date')) {
gl.issueBoards.BoardsStore.detail.issue.dueDate = $dueDateInput.val(); gl.issueBoards.BoardsStore.detail.issue.dueDate = $dueDateInput.val();
...@@ -60,10 +59,10 @@ class DueDateSelect { ...@@ -60,10 +59,10 @@ class DueDateSelect {
} else { } else {
this.saveDueDate(true); this.saveDueDate(true);
} }
} },
}); });
calendar.setDate(dateFix); calendar.setDate(parsePikadayDate($dueDateInput.val()));
this.$datePicker.append(calendar.el); this.$datePicker.append(calendar.el);
this.$datePicker.data('pikaday', calendar); this.$datePicker.data('pikaday', calendar);
} }
...@@ -79,8 +78,8 @@ class DueDateSelect { ...@@ -79,8 +78,8 @@ class DueDateSelect {
gl.issueBoards.BoardsStore.detail.issue.dueDate = ''; gl.issueBoards.BoardsStore.detail.issue.dueDate = '';
this.updateIssueBoardIssue(); this.updateIssueBoardIssue();
} else { } else {
$("input[name='" + this.fieldName + "']").val(''); $(`input[name='${this.fieldName}']`).val('');
return this.saveDueDate(false); this.saveDueDate(false);
} }
}); });
} }
...@@ -111,7 +110,7 @@ class DueDateSelect { ...@@ -111,7 +110,7 @@ class DueDateSelect {
this.datePayload = datePayload; this.datePayload = datePayload;
} }
updateIssueBoardIssue () { updateIssueBoardIssue() {
this.$loading.fadeIn(); this.$loading.fadeIn();
this.$dropdown.trigger('loading.gl.dropdown'); this.$dropdown.trigger('loading.gl.dropdown');
this.$selectbox.hide(); this.$selectbox.hide();
...@@ -149,8 +148,8 @@ class DueDateSelect { ...@@ -149,8 +148,8 @@ class DueDateSelect {
return selectedDateValue.length ? return selectedDateValue.length ?
$('.js-remove-due-date-holder').removeClass('hidden') : $('.js-remove-due-date-holder').removeClass('hidden') :
$('.js-remove-due-date-holder').addClass('hidden'); $('.js-remove-due-date-holder').addClass('hidden');
} },
}).done((data) => { }).done(() => {
if (isDropdown) { if (isDropdown) {
this.$dropdown.trigger('loaded.gl.dropdown'); this.$dropdown.trigger('loaded.gl.dropdown');
this.$dropdown.dropdown('toggle'); this.$dropdown.dropdown('toggle');
...@@ -160,27 +159,28 @@ class DueDateSelect { ...@@ -160,27 +159,28 @@ class DueDateSelect {
} }
} }
class DueDateSelectors { export default class DueDateSelectors {
constructor() { constructor() {
this.initMilestoneDatePicker(); this.initMilestoneDatePicker();
this.initIssuableSelect(); this.initIssuableSelect();
} }
// eslint-disable-next-line class-methods-use-this
initMilestoneDatePicker() { initMilestoneDatePicker() {
$('.datepicker').each(function() { $('.datepicker').each(function initPikadayMilestone() {
const $datePicker = $(this); const $datePicker = $(this);
const dateFix = DateFix.dashedFix($datePicker.val());
const calendar = new Pikaday({ const calendar = new Pikaday({
field: $datePicker.get(0), field: $datePicker.get(0),
theme: 'gitlab-theme animate-picker', theme: 'gitlab-theme animate-picker',
format: 'yyyy-mm-dd', format: 'yyyy-mm-dd',
container: $datePicker.parent().get(0), container: $datePicker.parent().get(0),
parse: dateString => parsePikadayDate(dateString),
toString: date => pikadayToString(date),
onSelect(dateText) { onSelect(dateText) {
$datePicker.val(dateFormat(new Date(dateText), 'yyyy-mm-dd')); $datePicker.val(calendar.toString(dateText));
} },
}); });
calendar.setDate(dateFix); calendar.setDate(parsePikadayDate($datePicker.val()));
$datePicker.data('pikaday', calendar); $datePicker.data('pikaday', calendar);
}); });
...@@ -191,19 +191,17 @@ class DueDateSelectors { ...@@ -191,19 +191,17 @@ class DueDateSelectors {
calendar.setDate(null); calendar.setDate(null);
}); });
} }
// eslint-disable-next-line class-methods-use-this
initIssuableSelect() { initIssuableSelect() {
const $loading = $('.js-issuable-update .due_date').find('.block-loading').hide(); const $loading = $('.js-issuable-update .due_date').find('.block-loading').hide();
$('.js-due-date-select').each((i, dropdown) => { $('.js-due-date-select').each((i, dropdown) => {
const $dropdown = $(dropdown); const $dropdown = $(dropdown);
// eslint-disable-next-line no-new
new DueDateSelect({ new DueDateSelect({
$dropdown, $dropdown,
$loading $loading,
}); });
}); });
} }
} }
window.gl = window.gl || {};
window.gl.DueDateSelectors = DueDateSelectors;
...@@ -123,8 +123,8 @@ class FilteredSearchVisualTokens { ...@@ -123,8 +123,8 @@ class FilteredSearchVisualTokens {
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
tokenValueContainer.dataset.originalValue = tokenValue; tokenValueContainer.dataset.originalValue = tokenValue;
tokenValueElement.innerHTML = ` tokenValueElement.innerHTML = `
<img class="avatar s20" src="${user.avatar_url}" alt="${user.name}'s avatar"> <img class="avatar s20" src="${user.avatar_url}" alt="">
${user.name} ${_.escape(user.name)}
`; `;
/* eslint-enable no-param-reassign */ /* eslint-enable no-param-reassign */
}) })
......
...@@ -5,6 +5,8 @@ ...@@ -5,6 +5,8 @@
/* global IssuableContext */ /* global IssuableContext */
/* global Sidebar */ /* global Sidebar */
import DueDateSelectors from './due_date_select';
export default () => { export default () => {
const sidebarOptions = JSON.parse(document.querySelector('.js-sidebar-options').innerHTML); const sidebarOptions = JSON.parse(document.querySelector('.js-sidebar-options').innerHTML);
...@@ -15,6 +17,6 @@ export default () => { ...@@ -15,6 +17,6 @@ export default () => {
new WeightSelect(); new WeightSelect();
new IssuableContext(sidebarOptions.currentUser); new IssuableContext(sidebarOptions.currentUser);
gl.Subscription.bindAll('.subscription'); gl.Subscription.bindAll('.subscription');
new gl.DueDateSelectors(); new DueDateSelectors();
window.sidebar = new Sidebar(); window.sidebar = new Sidebar();
}; };
...@@ -2,12 +2,12 @@ ...@@ -2,12 +2,12 @@
/* global GitLab */ /* global GitLab */
/* global Autosave */ /* global Autosave */
/* global GroupsSelect */ /* global GroupsSelect */
/* global dateFormat */
import Pikaday from 'pikaday'; import Pikaday from 'pikaday';
import UsersSelect from './users_select'; import UsersSelect from './users_select';
import GfmAutoComplete from './gfm_auto_complete'; import GfmAutoComplete from './gfm_auto_complete';
import ZenMode from './zen_mode'; import ZenMode from './zen_mode';
import { parsePikadayDate, pikadayToString } from './lib/utils/datefix';
(function() { (function() {
this.IssuableForm = (function() { this.IssuableForm = (function() {
...@@ -40,11 +40,13 @@ import ZenMode from './zen_mode'; ...@@ -40,11 +40,13 @@ import ZenMode from './zen_mode';
theme: 'gitlab-theme animate-picker', theme: 'gitlab-theme animate-picker',
format: 'yyyy-mm-dd', format: 'yyyy-mm-dd',
container: $issuableDueDate.parent().get(0), container: $issuableDueDate.parent().get(0),
parse: dateString => parsePikadayDate(dateString),
toString: date => pikadayToString(date),
onSelect: function(dateText) { onSelect: function(dateText) {
$issuableDueDate.val(dateFormat(new Date(dateText), 'yyyy-mm-dd')); $issuableDueDate.val(calendar.toString(dateText));
} }
}); });
calendar.setDate(new Date($issuableDueDate.val())); calendar.setDate(parsePikadayDate($issuableDueDate.val()));
} }
} }
......
const DateFix = {
dashedFix(val) { export const pad = (val, len = 2) => (`0${val}`).slice(-len);
const [y, m, d] = val.split('-');
return new Date(y, m - 1, d); /**
}, * Formats dates in Pickaday
* @param {String} dateString Date in yyyy-mm-dd format
* @return {Date} UTC format
*/
export const parsePikadayDate = (dateString) => {
const parts = dateString.split('-');
const year = parseInt(parts[0], 10);
const month = parseInt(parts[1] - 1, 10);
const day = parseInt(parts[2], 10);
return new Date(year, month, day);
}; };
export default DateFix; /**
* Used `onSelect` method in pickaday
* @param {Date} date UTC format
* @return {String} Date formated in yyyy-mm-dd
*/
export const pikadayToString = (date) => {
const day = pad(date.getDate());
const month = pad(date.getMonth() + 1);
const year = date.getFullYear();
return `${year}-${month}-${day}`;
};
...@@ -51,8 +51,6 @@ import './confirm_danger_modal'; ...@@ -51,8 +51,6 @@ import './confirm_danger_modal';
import './copy_as_gfm'; import './copy_as_gfm';
import './copy_to_clipboard'; import './copy_to_clipboard';
import './diff'; import './diff';
import './dropzone_input';
import './due_date_select';
import './files_comment_button'; import './files_comment_button';
import Flash, { removeFlashClickListener } from './flash'; import Flash, { removeFlashClickListener } from './flash';
import './gl_dropdown'; import './gl_dropdown';
......
/* global dateFormat */
import Pikaday from 'pikaday'; import Pikaday from 'pikaday';
import { parsePikadayDate, pikadayToString } from './lib/utils/datefix';
// Add datepickers to all `js-access-expiration-date` elements. If those elements are // Add datepickers to all `js-access-expiration-date` elements. If those elements are
// children of an element with the `clearable-input` class, and have a sibling // children of an element with the `clearable-input` class, and have a sibling
...@@ -22,8 +21,10 @@ export default function memberExpirationDate(selector = '.js-access-expiration-d ...@@ -22,8 +21,10 @@ export default function memberExpirationDate(selector = '.js-access-expiration-d
format: 'yyyy-mm-dd', format: 'yyyy-mm-dd',
minDate: new Date(), minDate: new Date(),
container: $input.parent().get(0), container: $input.parent().get(0),
parse: dateString => parsePikadayDate(dateString),
toString: date => pikadayToString(date),
onSelect(dateText) { onSelect(dateText) {
$input.val(dateFormat(new Date(dateText), 'yyyy-mm-dd')); $input.val(calendar.toString(dateText));
$input.trigger('change'); $input.trigger('change');
...@@ -31,7 +32,7 @@ export default function memberExpirationDate(selector = '.js-access-expiration-d ...@@ -31,7 +32,7 @@ export default function memberExpirationDate(selector = '.js-access-expiration-d
}, },
}); });
calendar.setDate(new Date($input.val())); calendar.setDate(parsePikadayDate($input.val()));
$input.data('pikaday', calendar); $input.data('pikaday', calendar);
}); });
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
Sample configuration: Sample configuration:
<user-avatar-image <user-avatar-image
:lazy="true"
:img-src="userAvatarSrc" :img-src="userAvatarSrc"
:img-alt="tooltipText" :img-alt="tooltipText"
:tooltip-text="tooltipText" :tooltip-text="tooltipText"
...@@ -16,11 +17,17 @@ ...@@ -16,11 +17,17 @@
*/ */
import defaultAvatarUrl from 'images/no_avatar.png'; import defaultAvatarUrl from 'images/no_avatar.png';
import { placeholderImage } from '../../../lazy_loader';
import tooltip from '../../directives/tooltip'; import tooltip from '../../directives/tooltip';
export default { export default {
name: 'UserAvatarImage', name: 'UserAvatarImage',
props: { props: {
lazy: {
type: Boolean,
required: false,
default: false,
},
imgSrc: { imgSrc: {
type: String, type: String,
required: false, required: false,
...@@ -56,18 +63,21 @@ export default { ...@@ -56,18 +63,21 @@ export default {
tooltip, tooltip,
}, },
computed: { computed: {
// API response sends null when gravatar is disabled and
// we provide an empty string when we use it inside user avatar link.
// In both cases we should render the defaultAvatarUrl
sanitizedSource() {
return this.imgSrc === '' || this.imgSrc === null ? defaultAvatarUrl : this.imgSrc;
},
resultantSrcAttribute() {
return this.lazy ? placeholderImage : this.sanitizedSource;
},
tooltipContainer() { tooltipContainer() {
return this.tooltipText ? 'body' : null; return this.tooltipText ? 'body' : null;
}, },
avatarSizeClass() { avatarSizeClass() {
return `s${this.size}`; return `s${this.size}`;
}, },
// API response sends null when gravatar is disabled and
// we provide an empty string when we use it inside user avatar link.
// In both cases we should render the defaultAvatarUrl
imageSource() {
return this.imgSrc === '' || this.imgSrc === null ? defaultAvatarUrl : this.imgSrc;
},
}, },
}; };
</script> </script>
...@@ -76,11 +86,16 @@ export default { ...@@ -76,11 +86,16 @@ export default {
<img <img
v-tooltip v-tooltip
class="avatar" class="avatar"
:class="[avatarSizeClass, cssClasses]" :class="{
:src="imageSource" lazy,
[avatarSizeClass]: true,
[cssClasses]: true
}"
:src="resultantSrcAttribute"
:width="size" :width="size"
:height="size" :height="size"
:alt="imgAlt" :alt="imgAlt"
:data-src="sanitizedSource"
:data-container="tooltipContainer" :data-container="tooltipContainer"
:data-placement="tooltipPlacement" :data-placement="tooltipPlacement"
:title="tooltipText" :title="tooltipText"
......
...@@ -61,6 +61,11 @@ ...@@ -61,6 +61,11 @@
border: 1px solid $dropdown-border-color; border: 1px solid $dropdown-border-color;
min-width: 175px; min-width: 175px;
color: $gl-text-color; color: $gl-text-color;
z-index: 999;
}
.select2-drop-mask {
z-index: 998;
} }
.select2-drop.select2-drop-above.select2-drop-active { .select2-drop.select2-drop-above.select2-drop-active {
......
...@@ -2,7 +2,6 @@ class Projects::ApplicationController < ApplicationController ...@@ -2,7 +2,6 @@ class Projects::ApplicationController < ApplicationController
include RoutableActions include RoutableActions
skip_before_action :authenticate_user! skip_before_action :authenticate_user!
before_action :redirect_git_extension
before_action :project before_action :project
before_action :repository before_action :repository
layout 'project' layout 'project'
...@@ -11,15 +10,6 @@ class Projects::ApplicationController < ApplicationController ...@@ -11,15 +10,6 @@ class Projects::ApplicationController < ApplicationController
private private
def redirect_git_extension
# Redirect from
# localhost/group/project.git
# to
# localhost/group/project
#
redirect_to url_for(params.merge(format: nil)) if params[:format] == 'git'
end
def project def project
return @project if @project return @project if @project
return nil unless params[:project_id] || params[:id] return nil unless params[:project_id] || params[:id]
......
...@@ -75,17 +75,36 @@ class Projects::MirrorsController < Projects::ApplicationController ...@@ -75,17 +75,36 @@ class Projects::MirrorsController < Projects::ApplicationController
@remote_mirror = @project.remote_mirrors.first_or_initialize @remote_mirror = @project.remote_mirrors.first_or_initialize
end end
def mirror_params def remote_mirror_attributes
params.require(:project) { remote_mirrors_attributes: %i[url id enabled] }
.permit( end
def mirror_params_attributes
attributes = [
:mirror, :mirror,
:import_url, :import_url,
:username_only_import_url, :username_only_import_url,
:mirror_user_id, :mirror_user_id,
:mirror_trigger_builds, :mirror_trigger_builds,
import_data_attributes: [:id, :auth_method, :password, :ssh_known_hosts, :regenerate_ssh_private_key],
remote_mirrors_attributes: [:url, :id, :enabled] import_data_attributes: %i[
) id
auth_method
password
ssh_known_hosts
regenerate_ssh_private_key
]
]
if can?(current_user, :admin_remote_mirror, project)
attributes << remote_mirror_attributes
end
attributes
end
def mirror_params
params.require(:project).permit(mirror_params_attributes)
end end
def safe_mirror_params def safe_mirror_params
......
...@@ -5,6 +5,7 @@ class ProjectsController < Projects::ApplicationController ...@@ -5,6 +5,7 @@ class ProjectsController < Projects::ApplicationController
prepend EE::ProjectsController prepend EE::ProjectsController
before_action :authenticate_user!, except: [:index, :show, :activity, :refs] before_action :authenticate_user!, except: [:index, :show, :activity, :refs]
before_action :redirect_git_extension, only: [:show]
before_action :project, except: [:index, :new, :create] before_action :project, except: [:index, :new, :create]
before_action :repository, except: [:index, :new, :create] before_action :repository, except: [:index, :new, :create]
before_action :assign_ref_vars, only: [:show], if: :repo_exists? before_action :assign_ref_vars, only: [:show], if: :repo_exists?
...@@ -399,4 +400,13 @@ class ProjectsController < Projects::ApplicationController ...@@ -399,4 +400,13 @@ class ProjectsController < Projects::ApplicationController
def project_export_enabled def project_export_enabled
render_404 unless current_application_settings.project_export_enabled? render_404 unless current_application_settings.project_export_enabled?
end end
def redirect_git_extension
# Redirect from
# localhost/group/project.git
# to
# localhost/group/project
#
redirect_to request.original_url.sub(/\.git\/?\Z/, '') if params[:format] == 'git'
end
end end
...@@ -7,6 +7,8 @@ module Storage ...@@ -7,6 +7,8 @@ module Storage
raise Gitlab::UpdatePathError.new('Namespace cannot be moved, because at least one project has tags in container registry') raise Gitlab::UpdatePathError.new('Namespace cannot be moved, because at least one project has tags in container registry')
end end
expires_full_path_cache
# Move the namespace directory in all storage paths used by member projects # Move the namespace directory in all storage paths used by member projects
repository_storage_paths.each do |repository_storage_path| repository_storage_paths.each do |repository_storage_path|
# Ensure old directory exists before moving it # Ensure old directory exists before moving it
......
class Geo::BaseFdw < Geo::TrackingBase
self.abstract_class = true
end
class Geo::BaseRegistry < ActiveRecord::Base class Geo::BaseRegistry < Geo::TrackingBase
self.abstract_class = true self.abstract_class = true
if Gitlab::Geo.geo_database_configured?
establish_connection Rails.configuration.geo_database
end
def self.connection
raise 'Geo secondary database is not configured' unless Gitlab::Geo.geo_database_configured?
super
end
end end
module Geo
module Fdw
class LfsObject < ::Geo::BaseFdw
self.table_name = Gitlab::Geo.fdw_table('lfs_objects')
end
end
end
module Geo
module Fdw
class Upload < ::Geo::BaseFdw
self.table_name = Gitlab::Geo.fdw_table('uploads')
end
end
end
# This module is intended to centralize all database access to the secondary
# tracking database for Geo.
module Geo
class TrackingBase < ActiveRecord::Base
self.abstract_class = true
if ::Gitlab::Geo.geo_database_configured?
establish_connection Rails.configuration.geo_database
end
def self.connection
raise 'Geo secondary database is not configured' unless ::Gitlab::Geo.geo_database_configured?
super
end
end
end
...@@ -47,6 +47,7 @@ class License < ActiveRecord::Base ...@@ -47,6 +47,7 @@ class License < ActiveRecord::Base
object_storage object_storage
service_desk service_desk
variable_environment_scope variable_environment_scope
reject_unsigned_commits
].freeze ].freeze
EEU_FEATURES = EEP_FEATURES EEU_FEATURES = EEP_FEATURES
......
...@@ -175,7 +175,7 @@ class Note < ActiveRecord::Base ...@@ -175,7 +175,7 @@ class Note < ActiveRecord::Base
end end
def cross_reference? def cross_reference?
system? && SystemNoteService.cross_reference?(note) system? && matches_cross_reference_regex?
end end
def diff_note? def diff_note?
......
...@@ -26,8 +26,8 @@ class ProjectMirrorData < ActiveRecord::Base ...@@ -26,8 +26,8 @@ class ProjectMirrorData < ActiveRecord::Base
def set_next_execution_timestamp! def set_next_execution_timestamp!
timestamp = Time.now timestamp = Time.now
retry_factor = [1, self.retry_count].max retry_factor = [1, self.retry_count].max
delay = [base_delay(timestamp) * retry_factor, Gitlab::Mirror.max_delay].min delay = [base_delay(timestamp), Gitlab::Mirror.min_delay].max
delay = [delay, Gitlab::Mirror.min_delay].max delay = [delay * retry_factor, Gitlab::Mirror.max_delay].min
self.next_execution_timestamp = timestamp + delay self.next_execution_timestamp = timestamp + delay
end end
......
...@@ -22,6 +22,7 @@ class PushRule < ActiveRecord::Base ...@@ -22,6 +22,7 @@ class PushRule < ActiveRecord::Base
end end
def commit_signature_allowed?(commit) def commit_signature_allowed?(commit)
return true unless available?(:reject_unsigned_commits)
return true unless reject_unsigned_commits return true unless reject_unsigned_commits
commit.has_signature? commit.has_signature?
...@@ -74,6 +75,14 @@ class PushRule < ActiveRecord::Base ...@@ -74,6 +75,14 @@ class PushRule < ActiveRecord::Base
is_sample? is_sample?
end end
def available?(feature_sym)
if global?
License.feature_available?(feature_sym)
else
project&.feature_available?(feature_sym)
end
end
private private
def data_match?(data, regex) def data_match?(data, regex)
......
...@@ -17,6 +17,7 @@ class RemoteMirror < ActiveRecord::Base ...@@ -17,6 +17,7 @@ class RemoteMirror < ActiveRecord::Base
validate :url_availability, if: -> (mirror) { mirror.url_changed? || mirror.enabled? } validate :url_availability, if: -> (mirror) { mirror.url_changed? || mirror.enabled? }
after_save :set_override_remote_mirror_available, unless: -> { Gitlab::CurrentSettings.current_application_settings.remote_mirror_available }
after_save :refresh_remote, if: :mirror_url_changed? after_save :refresh_remote, if: :mirror_url_changed?
after_update :reset_fields, if: :mirror_url_changed? after_update :reset_fields, if: :mirror_url_changed?
after_destroy :remove_remote after_destroy :remove_remote
...@@ -85,6 +86,7 @@ class RemoteMirror < ActiveRecord::Base ...@@ -85,6 +86,7 @@ class RemoteMirror < ActiveRecord::Base
def enabled def enabled
return false unless project && super return false unless project && super
return false unless project.remote_mirror_available?
return false unless project.repository_exists? return false unless project.repository_exists?
return false if project.pending_delete? return false if project.pending_delete?
...@@ -147,6 +149,12 @@ class RemoteMirror < ActiveRecord::Base ...@@ -147,6 +149,12 @@ class RemoteMirror < ActiveRecord::Base
) )
end end
def set_override_remote_mirror_available
enabled = read_attribute(:enabled)
project.update(remote_mirror_available_overridden: enabled)
end
def refresh_remote def refresh_remote
return unless project return unless project
......
...@@ -10,7 +10,7 @@ module Geo ...@@ -10,7 +10,7 @@ module Geo
repository_storage_name: project.repository.storage, repository_storage_name: project.repository.storage,
repository_storage_path: project.repository_storage_path, repository_storage_path: project.repository_storage_path,
repo_path: project.disk_path, repo_path: project.disk_path,
wiki_path: ("#{project.disk_path}.wiki" if project.wiki_enabled?), wiki_path: (project.wiki.disk_path if project.wiki_enabled?),
project_name: project.name project_name: project.name
) )
end end
......
...@@ -31,7 +31,7 @@ module Geo ...@@ -31,7 +31,7 @@ module Geo
end end
def new_wiki_path_with_namespace def new_wiki_path_with_namespace
"#{project.disk_path}.wiki" project.wiki.disk_path
end end
end end
end end
...@@ -126,7 +126,7 @@ class GitPushService < BaseService ...@@ -126,7 +126,7 @@ class GitPushService < BaseService
protected protected
def update_remote_mirrors def update_remote_mirrors
return if @project.remote_mirrors.empty? return unless @project.has_remote_mirror?
@project.mark_stuck_remote_mirrors_as_failed! @project.mark_stuck_remote_mirrors_as_failed!
@project.update_remote_mirrors @project.update_remote_mirrors
......
...@@ -11,7 +11,7 @@ module Projects ...@@ -11,7 +11,7 @@ module Projects
result = move_storage(project.disk_path, new_storage_path) result = move_storage(project.disk_path, new_storage_path)
if project.wiki.repository_exists? if project.wiki.repository_exists?
result &&= move_storage("#{project.disk_path}.wiki", new_storage_path) result &&= move_storage(project.wiki.disk_path, new_storage_path)
end end
if result if result
...@@ -44,7 +44,7 @@ module Projects ...@@ -44,7 +44,7 @@ module Projects
if wiki.repository_exists? if wiki.repository_exists?
GitlabShellWorker.perform_async(:mv_repository, GitlabShellWorker.perform_async(:mv_repository,
old_repository_storage_path, old_repository_storage_path,
"#{disk_path}.wiki", wiki.disk_path,
"#{new_project_path}.wiki") "#{new_project_path}.wiki")
end end
end end
......
...@@ -162,7 +162,6 @@ module SystemNoteService ...@@ -162,7 +162,6 @@ module SystemNoteService
# "changed time estimate to 3d 5h" # "changed time estimate to 3d 5h"
# #
# Returns the created Note object # Returns the created Note object
def change_time_estimate(noteable, project, author) def change_time_estimate(noteable, project, author)
parsed_time = Gitlab::TimeTrackingFormatter.output(noteable.time_estimate) parsed_time = Gitlab::TimeTrackingFormatter.output(noteable.time_estimate)
body = if noteable.time_estimate == 0 body = if noteable.time_estimate == 0
...@@ -188,7 +187,6 @@ module SystemNoteService ...@@ -188,7 +187,6 @@ module SystemNoteService
# "added 2h 30m of time spent" # "added 2h 30m of time spent"
# #
# Returns the created Note object # Returns the created Note object
def change_time_spent(noteable, project, author) def change_time_spent(noteable, project, author)
time_spent = noteable.time_spent time_spent = noteable.time_spent
...@@ -453,10 +451,6 @@ module SystemNoteService ...@@ -453,10 +451,6 @@ module SystemNoteService
end end
end end
def cross_reference?(note_text)
note_text =~ /\A#{cross_reference_note_prefix}/i
end
# Check if a cross-reference is disallowed # Check if a cross-reference is disallowed
# #
# This method prevents adding a "mentioned in !1" note on every single commit # This method prevents adding a "mentioned in !1" note on every single commit
...@@ -486,7 +480,6 @@ module SystemNoteService ...@@ -486,7 +480,6 @@ module SystemNoteService
# mentioner - Mentionable object # mentioner - Mentionable object
# #
# Returns Boolean # Returns Boolean
def cross_reference_exists?(noteable, mentioner) def cross_reference_exists?(noteable, mentioner)
# Initial scope should be system notes of this noteable type # Initial scope should be system notes of this noteable type
notes = Note.system.where(noteable_type: noteable.class) notes = Note.system.where(noteable_type: noteable.class)
......
...@@ -126,7 +126,9 @@ ...@@ -126,7 +126,9 @@
Enabling this will only make licensed EE features available to projects if the project namespace's plan Enabling this will only make licensed EE features available to projects if the project namespace's plan
includes the feature or if the project is public. includes the feature or if the project is public.
- if License.feature_available?(:repository_mirrors)
= render partial: 'repository_mirrors_form', locals: { f: f } = render partial: 'repository_mirrors_form', locals: { f: f }
= render partial: 'repository_remote_mirrors_form', locals: { f: f }
%fieldset %fieldset
%legend Sign-up Restrictions %legend Sign-up Restrictions
......
%fieldset
%legend Repository Remote mirror settings
.form-group
= f.label :remote_mirror_available, 'Enable remote mirror configuration', class: 'control-label col-sm-2'
.col-sm-10
.checkbox
= f.label :remote_mirror_available do
= f.check_box :remote_mirror_available
Allow remote mirrors to be setup for projects
%span.help-block
If disabled, only admins will be able to setup remote mirrors in projects.
= link_to icon('question-circle'), help_page_path('workflow/repository_mirroring', anchor: 'pushing-to-a-remote-repository')
...@@ -11,4 +11,4 @@ ...@@ -11,4 +11,4 @@
.alert.alert-danger .alert.alert-danger
- @push_rule.errors.full_messages.each do |msg| - @push_rule.errors.full_messages.each do |msg|
%p= msg %p= msg
= render "shared/push_rules_form", f: f = render "shared/push_rules/form", f: f
...@@ -3,4 +3,5 @@ ...@@ -3,4 +3,5 @@
- if @project.feature_available?(:repository_mirrors) - if @project.feature_available?(:repository_mirrors)
= render 'projects/mirrors/pull' = render 'projects/mirrors/pull'
- if can?(current_user, :admin_remote_mirror, @project)
= render 'projects/mirrors/push' = render 'projects/mirrors/push'
...@@ -15,4 +15,4 @@ ...@@ -15,4 +15,4 @@
= form_for [@project.namespace.becomes(Namespace), @project, @push_rule] do |f| = form_for [@project.namespace.becomes(Namespace), @project, @push_rule] do |f|
= form_errors(@push_rule) = form_errors(@push_rule)
= render "shared/push_rules_form", f: f = render "shared/push_rules/form", f: f
.form-group = render 'shared/push_rules/reject_unsigned_commits_setting', form: f, push_rule: f.object
= f.check_box :reject_unsigned_commits, class: "pull-left", disabled: !can_change_reject_unsigned_commits?(f.object)
.prepend-left-20
= f.label :reject_unsigned_commits, class: "label-light append-bottom-0" do
Reject unsigned commits
%p.light.append-bottom-0
= reject_unsigned_commits_description(f.object)
.form-group .form-group
= f.check_box :deny_delete_tag, class: "pull-left" = f.check_box :deny_delete_tag, class: "pull-left"
......
- return unless push_rule.available?(:reject_unsigned_commits)
- form = local_assigns.fetch(:form)
- push_rule = local_assigns.fetch(:push_rule)
.form-group
= form.check_box :reject_unsigned_commits, class: "pull-left", disabled: !can_change_reject_unsigned_commits?(push_rule)
.prepend-left-20
= form.label :reject_unsigned_commits, class: "label-light append-bottom-0" do
Reject unsigned commits
%p.light.append-bottom-0
= reject_unsigned_commits_description(push_rule)
...@@ -21,9 +21,9 @@ module Geo ...@@ -21,9 +21,9 @@ module Geo
def find_unsynced_objects def find_unsynced_objects
lfs_object_ids = find_lfs_object_ids lfs_object_ids = find_lfs_object_ids
objects_ids = find_object_ids upload_objects_ids = find_upload_object_ids
interleave(lfs_object_ids, objects_ids) interleave(lfs_object_ids, upload_objects_ids)
end end
def find_failed_objects def find_failed_objects
...@@ -33,38 +33,82 @@ module Geo ...@@ -33,38 +33,82 @@ module Geo
.pluck(:file_id, :file_type) .pluck(:file_id, :file_type)
end end
def find_object_ids def selective_sync
unsynced_downloads = filter_registry_ids( current_node.restricted_project_ids
current_node.uploads, end
Geo::FileService::DEFAULT_OBJECT_TYPES,
Upload.table_name def find_lfs_object_ids
) # Selective project replication adds a wrinkle to FDW queries, so
# we fallback to the legacy version for now.
relation =
if Gitlab::Geo.fdw? && !selective_sync
fdw_find_lfs_object_ids
else
legacy_find_lfs_object_ids
end
unsynced_downloads relation
.limit(db_retrieve_batch_size)
.pluck(:id)
.map { |id| [id, :lfs] }
end
def find_upload_object_ids
# Selective project replication adds a wrinkle to FDW queries, so
# we fallback to the legacy version for now.
relation =
if Gitlab::Geo.fdw? && !selective_sync
fdw_find_upload_object_ids
else
legacy_find_upload_object_ids
end
relation
.limit(db_retrieve_batch_size) .limit(db_retrieve_batch_size)
.pluck(:id, :uploader) .pluck(:id, :uploader)
.map { |id, uploader| [id, uploader.sub(/Uploader\z/, '').underscore] } .map { |id, uploader| [id, uploader.sub(/Uploader\z/, '').underscore] }
end end
def find_lfs_object_ids def fdw_find_lfs_object_ids
unsynced_downloads = filter_registry_ids( fdw_table = Geo::Fdw::LfsObject.table_name
# Filter out objects in object storage (this is done in GeoNode#lfs_objects)
Geo::Fdw::LfsObject.joins("LEFT OUTER JOIN file_registry ON file_registry.file_id = #{fdw_table}.id AND file_registry.file_type = 'lfs'")
.where("#{fdw_table}.file_store IS NULL OR #{fdw_table}.file_store = #{LfsObjectUploader::LOCAL_STORE}")
.where('file_registry.file_id IS NULL')
end
def fdw_find_upload_object_ids
fdw_table = Geo::Fdw::Upload.table_name
obj_types = Geo::FileService::DEFAULT_OBJECT_TYPES.map { |val| "'#{val}'" }.join(',')
Geo::Fdw::Upload.joins("LEFT OUTER JOIN file_registry ON file_registry.file_id = #{fdw_table}.id AND file_registry.file_type IN (#{obj_types})")
.where('file_registry.file_id IS NULL')
.order(created_at: :desc)
end
def legacy_find_upload_object_ids
legacy_filter_registry_ids(
current_node.uploads,
Geo::FileService::DEFAULT_OBJECT_TYPES,
Upload.table_name
)
end
def legacy_find_lfs_object_ids
legacy_filter_registry_ids(
current_node.lfs_objects, current_node.lfs_objects,
[:lfs], [:lfs],
LfsObject.table_name LfsObject.table_name
) )
unsynced_downloads
.limit(db_retrieve_batch_size)
.pluck(:id)
.map { |id| [id, :lfs] }
end end
# This query requires data from two different databases, and unavoidably # This query requires data from two different databases, and unavoidably
# plucks a list of file IDs from one into the other. This will not scale # plucks a list of file IDs from one into the other. This will not scale
# well with the number of synchronized files--the query will increase # well with the number of synchronized files--the query will increase
# linearly in size--so this should be replaced with postgres_fdw ASAP. # linearly in size--so this should be replaced with postgres_fdw ASAP.
def filter_registry_ids(objects, file_types, table_name) def legacy_filter_registry_ids(objects, file_types, table_name)
registry_ids = pluck_registry_ids(Geo::FileRegistry, file_types) registry_ids = legacy_pluck_registry_ids(Geo::FileRegistry, file_types)
return objects if registry_ids.empty? return objects if registry_ids.empty?
...@@ -78,7 +122,7 @@ module Geo ...@@ -78,7 +122,7 @@ module Geo
joined_relation.where(file_registry: { registry_present: [nil, false] }) joined_relation.where(file_registry: { registry_present: [nil, false] })
end end
def pluck_registry_ids(relation, file_types) def legacy_pluck_registry_ids(relation, file_types)
ids = relation.where(file_type: file_types).pluck(:file_id) ids = relation.where(file_type: file_types).pluck(:file_id)
(ids + scheduled_file_ids(file_types)).uniq (ids + scheduled_file_ids(file_types)).uniq
end end
......
---
title: Allow admins to globally disable all remote mirrors from application settings
page.
merge_request:
author:
type: added
---
title: Reconfigure the Geo tracking database pool size when running as Sidekiq
merge_request: 3181
author:
type: fixed
---
title: Use PostgreSQL FDW for Geo downloads
merge_request:
author:
type: added
---
title: Decreases z-index of select2 to a lower number of our navigation bar
merge_request:
author:
type: fixed
---
title: Fix timezone bug in Pikaday and upgrade Pikaday version
merge_request:
author:
type: fixed
---
title: Add lazy option to UserAvatarImage
merge_request: 14895
author:
type: changed
...@@ -464,3 +464,10 @@ ...@@ -464,3 +464,10 @@
:why: Our own library - https://gitlab.com/gitlab-org/gitlab-svgs :why: Our own library - https://gitlab.com/gitlab-org/gitlab-svgs
:versions: [] :versions: []
:when: 2017-09-19 14:36:32.795496000 Z :when: 2017-09-19 14:36:32.795496000 Z
- - :license
- pikaday
- MIT
- :who:
:why:
:versions: []
:when: 2017-10-17 17:46:12.367554000 Z
...@@ -52,6 +52,14 @@ Sidekiq.configure_server do |config| ...@@ -52,6 +52,14 @@ Sidekiq.configure_server do |config|
ActiveRecord::Base.establish_connection(config) ActiveRecord::Base.establish_connection(config)
Rails.logger.debug("Connection Pool size for Sidekiq Server is now: #{ActiveRecord::Base.connection.pool.instance_variable_get('@size')}") Rails.logger.debug("Connection Pool size for Sidekiq Server is now: #{ActiveRecord::Base.connection.pool.instance_variable_get('@size')}")
# EE only
if Gitlab::Geo.geo_database_configured?
Rails.configuration.geo_database['pool'] = Sidekiq.options[:concurrency]
Geo::TrackingBase.establish_connection(Rails.configuration.geo_database)
Rails.logger.debug("Connection Pool size for Sidekiq Server is now: #{Geo::TrackingBase.connection_pool.size} (Geo tracking database)")
end
# Avoid autoload issue such as 'Mail::Parsers::AddressStruct' # Avoid autoload issue such as 'Mail::Parsers::AddressStruct'
# https://github.com/mikel/mail/issues/912#issuecomment-214850355 # https://github.com/mikel/mail/issues/912#issuecomment-214850355
Mail.eager_autoload! Mail.eager_autoload!
......
class AddRemoteMirrorAvailableToApplicationSettings < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_column_with_default(:application_settings, :remote_mirror_available, :boolean, default: true, allow_null: false)
end
def down
remove_column(:application_settings, :remote_mirror_available)
end
end
class AddRemoteMirrorAvailableOverriddenToProjects < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_column(:projects, :remote_mirror_available_overridden, :boolean)
end
def down
remove_column(:projects, :remote_mirror_available_overridden)
end
end
...@@ -11,7 +11,11 @@ ...@@ -11,7 +11,11 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
<<<<<<< HEAD
ActiveRecord::Schema.define(version: 20171012101043) do ActiveRecord::Schema.define(version: 20171012101043) do
=======
ActiveRecord::Schema.define(version: 20171017130239) do
>>>>>>> origin/master
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -157,10 +161,14 @@ ActiveRecord::Schema.define(version: 20171012101043) do ...@@ -157,10 +161,14 @@ ActiveRecord::Schema.define(version: 20171012101043) do
t.boolean "hashed_storage_enabled", default: false, null: false t.boolean "hashed_storage_enabled", default: false, null: false
t.boolean "project_export_enabled", default: true, null: false t.boolean "project_export_enabled", default: true, null: false
t.boolean "auto_devops_enabled", default: false, null: false t.boolean "auto_devops_enabled", default: false, null: false
<<<<<<< HEAD
t.integer "circuitbreaker_failure_count_threshold", default: 160 t.integer "circuitbreaker_failure_count_threshold", default: 160
t.integer "circuitbreaker_failure_wait_time", default: 30 t.integer "circuitbreaker_failure_wait_time", default: 30
t.integer "circuitbreaker_failure_reset_time", default: 1800 t.integer "circuitbreaker_failure_reset_time", default: 1800
t.integer "circuitbreaker_storage_timeout", default: 30 t.integer "circuitbreaker_storage_timeout", default: 30
=======
t.boolean "remote_mirror_available", default: true, null: false
>>>>>>> origin/master
end end
create_table "approvals", force: :cascade do |t| create_table "approvals", force: :cascade do |t|
...@@ -1607,6 +1615,7 @@ ActiveRecord::Schema.define(version: 20171012101043) do ...@@ -1607,6 +1615,7 @@ ActiveRecord::Schema.define(version: 20171012101043) do
t.boolean "disable_overriding_approvers_per_merge_request" t.boolean "disable_overriding_approvers_per_merge_request"
t.integer "storage_version", limit: 2 t.integer "storage_version", limit: 2
t.boolean "resolve_outdated_diff_discussions" t.boolean "resolve_outdated_diff_discussions"
t.boolean "remote_mirror_available_overridden"
end end
add_index "projects", ["ci_id"], name: "index_projects_on_ci_id", using: :btree add_index "projects", ["ci_id"], name: "index_projects_on_ci_id", using: :btree
......
...@@ -18,7 +18,7 @@ in the merge request widget area: ...@@ -18,7 +18,7 @@ in the merge request widget area:
For instance, consider the following workflow: For instance, consider the following workflow:
1. Your backend team member starts a new implementation for making certain feature in your app faster 1. Your backend team member starts a new implementation for making certain feature in your app faster
1. With Code Quality reports, they analize how their implementation is impacting the code quality 1. With Code Quality reports, they analyze how their implementation is impacting the code quality
1. The metrics show that their code degrade the quality in 10 points 1. The metrics show that their code degrade the quality in 10 points
1. You ask a co-worker to help them with this modification 1. You ask a co-worker to help them with this modification
1. They both work on the changes until Code Quality report displays no degradations, only improvements 1. They both work on the changes until Code Quality report displays no degradations, only improvements
......
...@@ -22,7 +22,8 @@ module EE ...@@ -22,7 +22,8 @@ module EE
:slack_app_id, :slack_app_id,
:slack_app_secret, :slack_app_secret,
:slack_app_verification_token, :slack_app_verification_token,
:allow_group_owners_to_manage_ldap :allow_group_owners_to_manage_ldap,
:remote_mirror_available
] ]
end end
......
...@@ -40,7 +40,8 @@ module EE ...@@ -40,7 +40,8 @@ module EE
mirror_max_delay: Settings.gitlab['mirror_max_delay'], mirror_max_delay: Settings.gitlab['mirror_max_delay'],
mirror_max_capacity: Settings.gitlab['mirror_max_capacity'], mirror_max_capacity: Settings.gitlab['mirror_max_capacity'],
mirror_capacity_threshold: Settings.gitlab['mirror_capacity_threshold'], mirror_capacity_threshold: Settings.gitlab['mirror_capacity_threshold'],
allow_group_owners_to_manage_ldap: true allow_group_owners_to_manage_ldap: true,
remote_mirror_available: true
) )
end end
end end
......
...@@ -132,7 +132,9 @@ module EE ...@@ -132,7 +132,9 @@ module EE
end end
def has_remote_mirror? def has_remote_mirror?
feature_available?(:repository_mirrors) && remote_mirrors.enabled.exists? feature_available?(:repository_mirrors) &&
remote_mirror_available? &&
remote_mirrors.enabled.exists?
end end
def updating_remote_mirror? def updating_remote_mirror?
...@@ -140,7 +142,7 @@ module EE ...@@ -140,7 +142,7 @@ module EE
end end
def update_remote_mirrors def update_remote_mirrors
return unless feature_available?(:repository_mirrors) return unless feature_available?(:repository_mirrors) && remote_mirror_available?
remote_mirrors.enabled.each(&:sync) remote_mirrors.enabled.each(&:sync)
end end
...@@ -463,6 +465,11 @@ module EE ...@@ -463,6 +465,11 @@ module EE
@disabled_services @disabled_services
end end
def remote_mirror_available?
remote_mirror_available_overridden ||
current_application_settings.remote_mirror_available
end
private private
def licensed_feature_available?(feature) def licensed_feature_available?(feature)
......
...@@ -20,6 +20,11 @@ module EE ...@@ -20,6 +20,11 @@ module EE
!PushRule.global&.reject_unsigned_commits !PushRule.global&.reject_unsigned_commits
end end
with_scope :global
condition(:remote_mirror_available) do
::Gitlab::CurrentSettings.current_application_settings.remote_mirror_available
end
rule { admin }.enable :change_repository_storage rule { admin }.enable :change_repository_storage
rule { support_bot }.enable :guest_access rule { support_bot }.enable :guest_access
...@@ -49,6 +54,8 @@ module EE ...@@ -49,6 +54,8 @@ module EE
rule { can?(:developer_access) }.enable :admin_board rule { can?(:developer_access) }.enable :admin_board
rule { (remote_mirror_available & can?(:admin_project)) | admin }.enable :admin_remote_mirror
rule { deploy_board_disabled & ~is_development }.prevent :read_deploy_board rule { deploy_board_disabled & ~is_development }.prevent :read_deploy_board
rule { can?(:master_access) }.policy do rule { can?(:master_access) }.policy do
......
...@@ -17,7 +17,7 @@ module EE ...@@ -17,7 +17,7 @@ module EE
def process_wiki_repository_update def process_wiki_repository_update
if ::Gitlab::Geo.primary? if ::Gitlab::Geo.primary?
# Create wiki repository updated event on Geo event log # Create wiki repository updated event on Geo event log
::Geo::RepositoryUpdatedEventStore.new(project, source: Geo::RepositoryUpdatedEvent::WIKI).create ::Geo::RepositoryUpdatedEventStore.new(project, source: ::Geo::RepositoryUpdatedEvent::WIKI).create
end end
end end
end end
......
...@@ -22,7 +22,7 @@ module EE ...@@ -22,7 +22,7 @@ module EE
if ::Gitlab::Geo.enabled? if ::Gitlab::Geo.enabled?
# Create wiki repository updated event on Geo event log # Create wiki repository updated event on Geo event log
::Geo::RepositoryUpdatedEventStore.new(post_received.project, source: Geo::RepositoryUpdatedEvent::WIKI).create ::Geo::RepositoryUpdatedEventStore.new(post_received.project, source: ::Geo::RepositoryUpdatedEvent::WIKI).create
end end
end end
......
...@@ -101,8 +101,7 @@ module Backup ...@@ -101,8 +101,7 @@ module Backup
end end
def unpack def unpack
Dir.chdir(backup_path) Dir.chdir(backup_path) do
# check for existing backups in the backup dir # check for existing backups in the backup dir
if backup_file_list.empty? if backup_file_list.empty?
$progress.puts "No backups found in #{backup_path}" $progress.puts "No backups found in #{backup_path}"
...@@ -147,6 +146,7 @@ module Backup ...@@ -147,6 +146,7 @@ module Backup
exit 1 exit 1
end end
end end
end
def tar_version def tar_version
tar_version, _ = Gitlab::Popen.popen(%w(tar --version)) tar_version, _ = Gitlab::Popen.popen(%w(tar --version))
......
...@@ -75,9 +75,19 @@ module Banzai ...@@ -75,9 +75,19 @@ module Banzai
begin begin
node['href'] = node['href'].strip node['href'] = node['href'].strip
uri = Addressable::URI.parse(node['href']) uri = Addressable::URI.parse(node['href'])
uri.scheme = uri.scheme.downcase if uri.scheme
node.remove_attribute('href') if UNSAFE_PROTOCOLS.include?(uri.scheme) return unless uri.scheme
# Remove all invalid scheme characters before checking against the
# list of unsafe protocols.
#
# See https://tools.ietf.org/html/rfc3986#section-3.1
scheme = uri.scheme
.strip
.downcase
.gsub(/[^A-Za-z0-9\+\.\-]+/, '')
node.remove_attribute('href') if UNSAFE_PROTOCOLS.include?(scheme)
rescue Addressable::URI::InvalidURIError rescue Addressable::URI::InvalidURIError
node.remove_attribute('href') node.remove_attribute('href')
end end
......
...@@ -14,6 +14,8 @@ module Gitlab ...@@ -14,6 +14,8 @@ module Gitlab
SECONDARY_JOBS = %i(repository_sync_job file_download_job).freeze SECONDARY_JOBS = %i(repository_sync_job file_download_job).freeze
FDW_SCHEMA = 'gitlab_secondary'.freeze
def self.current_node def self.current_node
self.cache_value(:geo_node_current) do self.cache_value(:geo_node_current) do
GeoNode.find_by(host: Gitlab.config.gitlab.host, GeoNode.find_by(host: Gitlab.config.gitlab.host,
...@@ -77,13 +79,13 @@ module Gitlab ...@@ -77,13 +79,13 @@ module Gitlab
def self.fdw? def self.fdw?
self.cache_value(:geo_fdw?) do self.cache_value(:geo_fdw?) do
::Geo::BaseRegistry.connection.execute( ::Geo::BaseRegistry.connection.execute(
"SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = '#{self.fdw_schema}' AND table_type = 'FOREIGN TABLE'" "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = '#{FDW_SCHEMA}' AND table_type = 'FOREIGN TABLE'"
).first.fetch('count').to_i.positive? ).first.fetch('count').to_i.positive?
end end
end end
def self.fdw_schema def self.fdw_table(table_name)
'gitlab_secondary'.freeze FDW_SCHEMA + ".#{table_name}"
end end
def self.repository_sync_job def self.repository_sync_job
......
...@@ -105,6 +105,7 @@ excluded_attributes: ...@@ -105,6 +105,7 @@ excluded_attributes:
- :mirror_user_id - :mirror_user_id
- :mirror_trigger_builds - :mirror_trigger_builds
- :storage_version - :storage_version
- :remote_mirror_available_overridden
snippets: snippets:
- :expired_at - :expired_at
merge_request_diff: merge_request_diff:
......
...@@ -64,9 +64,9 @@ module Gitlab ...@@ -64,9 +64,9 @@ module Gitlab
deployments: Deployment.count, deployments: Deployment.count,
environments: ::Environment.count, environments: ::Environment.count,
gcp_clusters: ::Gcp::Cluster.count, gcp_clusters: ::Gcp::Cluster.count,
geo_nodes: GeoNode.count,
gcp_clusters_enabled: ::Gcp::Cluster.enabled.count, gcp_clusters_enabled: ::Gcp::Cluster.enabled.count,
gcp_clusters_disabled: ::Gcp::Cluster.disabled.count, gcp_clusters_disabled: ::Gcp::Cluster.disabled.count,
geo_nodes: GeoNode.count,
in_review_folder: ::Environment.in_review_folder.count, in_review_folder: ::Environment.in_review_folder.count,
groups: Group.count, groups: Group.count,
issues: Issue.count, issues: Issue.count,
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
require 'rubocop-rspec'
require_relative '../../spec_helpers'
module RuboCop
module Cop
module RSpec
# This cop checks for ENV assignment in specs
#
# @example
#
# # bad
# before do
# ENV['FOO'] = 'bar'
# end
#
# # good
# before do
# stub_env('FOO', 'bar')
# end
class EnvAssignment < Cop
include SpecHelpers
MESSAGE = "Don't assign to ENV, use `stub_env` instead.".freeze
def_node_search :env_assignment?, <<~PATTERN
(send (const nil? :ENV) :[]= ...)
PATTERN
# Following is what node.children looks like on a match
# [s(:const, nil, :ENV), :[]=, s(:str, "key"), s(:str, "value")]
def on_send(node)
return unless in_spec?(node)
return unless env_assignment?(node)
add_offense(node, :expression, MESSAGE)
end
def autocorrect(node)
lambda do |corrector|
corrector.replace(node.loc.expression, stub_env(env_key(node), env_value(node)))
end
end
def env_key(node)
node.children[2].source
end
def env_value(node)
node.children[3].source
end
def stub_env(key, value)
"stub_env(#{key}, #{value})"
end
end
end
end
end
require_relative 'cop/active_record_dependent'
require_relative 'cop/active_record_serialize'
require_relative 'cop/custom_error_class' require_relative 'cop/custom_error_class'
require_relative 'cop/gem_fetcher' require_relative 'cop/gem_fetcher'
require_relative 'cop/active_record_serialize' require_relative 'cop/in_batches'
require_relative 'cop/redirect_with_status'
require_relative 'cop/polymorphic_associations' require_relative 'cop/polymorphic_associations'
require_relative 'cop/project_path_helper' require_relative 'cop/project_path_helper'
require_relative 'cop/active_record_dependent' require_relative 'cop/redirect_with_status'
require_relative 'cop/in_batches'
require_relative 'cop/migration/add_column' require_relative 'cop/migration/add_column'
require_relative 'cop/migration/add_column_with_default_to_large_table' require_relative 'cop/migration/add_column_with_default_to_large_table'
require_relative 'cop/migration/add_concurrent_foreign_key' require_relative 'cop/migration/add_concurrent_foreign_key'
...@@ -13,12 +13,13 @@ require_relative 'cop/migration/add_concurrent_index' ...@@ -13,12 +13,13 @@ require_relative 'cop/migration/add_concurrent_index'
require_relative 'cop/migration/add_index' require_relative 'cop/migration/add_index'
require_relative 'cop/migration/add_timestamps' require_relative 'cop/migration/add_timestamps'
require_relative 'cop/migration/datetime' require_relative 'cop/migration/datetime'
require_relative 'cop/migration/safer_boolean_column'
require_relative 'cop/migration/hash_index' require_relative 'cop/migration/hash_index'
require_relative 'cop/migration/remove_concurrent_index' require_relative 'cop/migration/remove_concurrent_index'
require_relative 'cop/migration/remove_index' require_relative 'cop/migration/remove_index'
require_relative 'cop/migration/reversible_add_column_with_default' require_relative 'cop/migration/reversible_add_column_with_default'
require_relative 'cop/migration/safer_boolean_column'
require_relative 'cop/migration/timestamps' require_relative 'cop/migration/timestamps'
require_relative 'cop/migration/update_column_in_batches' require_relative 'cop/migration/update_column_in_batches'
require_relative 'cop/rspec/env_assignment'
require_relative 'cop/rspec/single_line_hook' require_relative 'cop/rspec/single_line_hook'
require_relative 'cop/rspec/verbose_include_metadata' require_relative 'cop/rspec/verbose_include_metadata'
module RuboCop
module SpecHelpers
SPEC_HELPERS = %w[spec_helper.rb rails_helper.rb].freeze
# Returns true if the given node originated from the spec directory.
def in_spec?(node)
path = node.location.expression.source_buffer.name
!SPEC_HELPERS.include?(File.basename(path)) && path.start_with?(File.join(Dir.pwd, 'spec'))
end
end
end
require('spec_helper') require('spec_helper')
describe ProfilesController do describe ProfilesController, :request_store do
describe "PUT update" do let(:user) { create(:user) }
it "allows an email update from a user without an external email address" do
user = create(:user) describe 'PUT update' do
it 'allows an email update from a user without an external email address' do
sign_in(user) sign_in(user)
put :update, put :update,
...@@ -29,7 +30,7 @@ describe ProfilesController do ...@@ -29,7 +30,7 @@ describe ProfilesController do
expect(user.unconfirmed_email).to eq nil expect(user.unconfirmed_email).to eq nil
end end
it "ignores an email update from a user with an external email address" do it 'ignores an email update from a user with an external email address' do
stub_omniauth_setting(sync_profile_from_provider: ['ldap']) stub_omniauth_setting(sync_profile_from_provider: ['ldap'])
stub_omniauth_setting(sync_profile_attributes: true) stub_omniauth_setting(sync_profile_attributes: true)
...@@ -46,7 +47,7 @@ describe ProfilesController do ...@@ -46,7 +47,7 @@ describe ProfilesController do
expect(ldap_user.unconfirmed_email).not_to eq('john@gmail.com') expect(ldap_user.unconfirmed_email).not_to eq('john@gmail.com')
end end
it "ignores an email and name update but allows a location update from a user with external email and name, but not external location" do it 'ignores an email and name update but allows a location update from a user with external email and name, but not external location' do
stub_omniauth_setting(sync_profile_from_provider: ['ldap']) stub_omniauth_setting(sync_profile_from_provider: ['ldap'])
stub_omniauth_setting(sync_profile_attributes: true) stub_omniauth_setting(sync_profile_attributes: true)
...@@ -65,4 +66,35 @@ describe ProfilesController do ...@@ -65,4 +66,35 @@ describe ProfilesController do
expect(ldap_user.location).to eq('City, Country') expect(ldap_user.location).to eq('City, Country')
end end
end end
describe 'PUT update_username' do
let(:namespace) { user.namespace }
let(:project) { create(:project_empty_repo, namespace: namespace) }
let(:gitlab_shell) { Gitlab::Shell.new }
let(:new_username) { 'renamedtosomethingelse' }
it 'allows username change' do
sign_in(user)
put :update_username,
user: { username: new_username }
user.reload
expect(response.status).to eq(302)
expect(user.username).to eq(new_username)
end
it 'moves dependent projects to new namespace' do
sign_in(user)
put :update_username,
user: { username: new_username }
user.reload
expect(response.status).to eq(302)
expect(gitlab_shell.exists?(project.repository_storage_path, "#{new_username}/#{project.path}.git")).to be_truthy
end
end
end end
...@@ -850,7 +850,7 @@ describe Projects::IssuesController do ...@@ -850,7 +850,7 @@ describe Projects::IssuesController do
describe 'GET #discussions' do describe 'GET #discussions' do
let!(:discussion) { create(:discussion_note_on_issue, noteable: issue, project: issue.project) } let!(:discussion) { create(:discussion_note_on_issue, noteable: issue, project: issue.project) }
context 'when authenticated' do
before do before do
project.add_developer(user) project.add_developer(user)
sign_in(user) sign_in(user)
...@@ -859,7 +859,7 @@ describe Projects::IssuesController do ...@@ -859,7 +859,7 @@ describe Projects::IssuesController do
it 'returns discussion json' do it 'returns discussion json' do
get :discussions, namespace_id: project.namespace, project_id: project, id: issue.iid get :discussions, namespace_id: project.namespace, project_id: project, id: issue.iid
expect(JSON.parse(response.body).first.keys).to match_array(%w[id reply_id expanded notes individual_note]) expect(json_response.first.keys).to match_array(%w[id reply_id expanded notes individual_note])
end end
context 'with cross-reference system note', :request_store do context 'with cross-reference system note', :request_store do
...@@ -894,4 +894,64 @@ describe Projects::IssuesController do ...@@ -894,4 +894,64 @@ describe Projects::IssuesController do
end end
end end
end end
context 'with a related system note' do
let(:confidential_issue) { create(:issue, :confidential, project: project) }
let!(:system_note) { SystemNoteService.relate_issue(issue, confidential_issue, user) }
shared_examples 'user can see confidential issue' do |access_level|
context "when a user is a #{access_level}" do
before do
project.add_user(user, access_level)
end
it 'displays related notes' do
get :discussions, namespace_id: project.namespace, project_id: project, id: issue.iid
discussions = json_response
notes = discussions.flat_map {|d| d['notes']}
expect(discussions.count).to equal(2)
expect(notes).to include(a_hash_including('id' => system_note.id))
end
end
end
shared_examples 'user cannot see confidential issue' do |access_level|
context "when a user is a #{access_level}" do
before do
project.add_user(user, access_level)
end
it 'redacts note related to a confidential issue' do
get :discussions, namespace_id: project.namespace, project_id: project, id: issue.iid
discussions = json_response
notes = discussions.flat_map {|d| d['notes']}
expect(discussions.count).to equal(1)
expect(notes).not_to include(a_hash_including('id' => system_note.id))
end
end
end
context 'when authenticated' do
before do
sign_in(user)
end
%i(reporter developer master).each do |access|
it_behaves_like 'user can see confidential issue', access
end
it_behaves_like 'user cannot see confidential issue', :guest
end
context 'when unauthenticated' do
let(:project) { create(:project, :public) }
it_behaves_like 'user cannot see confidential issue', Gitlab::Access::NO_ACCESS
end
end
end
end end
...@@ -4,6 +4,37 @@ describe Projects::MirrorsController do ...@@ -4,6 +4,37 @@ describe Projects::MirrorsController do
include ReactiveCachingHelpers include ReactiveCachingHelpers
describe 'setting up a remote mirror' do describe 'setting up a remote mirror' do
set(:project) { create(:project, :repository) }
let(:url) { 'http://foo.com' }
context 'when remote mirrors are disabled' do
before do
stub_application_setting(remote_mirror_available: false)
end
context 'when user is admin' do
let(:admin) { create(:user, :admin) }
it 'creates a new remote mirror' do
sign_in(admin)
expect do
do_put(project, remote_mirrors_attributes: { '0' => { 'enabled' => 1, 'url' => url } })
end.to change { RemoteMirror.count }.to(1)
end
end
context 'when user is not admin' do
it 'does not create a new remote mirror' do
sign_in(project.owner)
expect do
do_put(project, remote_mirrors_attributes: { '0' => { 'enabled' => 1, 'url' => url } })
end.not_to change { RemoteMirror.count }
end
end
end
context 'when the current project is a mirror' do context 'when the current project is a mirror' do
let(:project) { create(:project, :repository, :mirror) } let(:project) { create(:project, :repository, :mirror) }
...@@ -38,7 +69,6 @@ describe Projects::MirrorsController do ...@@ -38,7 +69,6 @@ describe Projects::MirrorsController do
context 'when the current project is not a mirror' do context 'when the current project is not a mirror' do
it 'allows to create a remote mirror' do it 'allows to create a remote mirror' do
project = create(:project, :repository)
sign_in(project.owner) sign_in(project.owner)
expect do expect do
...@@ -48,7 +78,6 @@ describe Projects::MirrorsController do ...@@ -48,7 +78,6 @@ describe Projects::MirrorsController do
end end
context 'when the current project has a remote mirror' do context 'when the current project has a remote mirror' do
let(:project) { create(:project, :repository) }
let(:remote_mirror) { project.remote_mirrors.create!(enabled: 1, url: 'http://local.dev') } let(:remote_mirror) { project.remote_mirrors.create!(enabled: 1, url: 'http://local.dev') }
before do before do
...@@ -117,7 +146,7 @@ describe Projects::MirrorsController do ...@@ -117,7 +146,7 @@ describe Projects::MirrorsController do
end end
end end
describe 'forcing an update' do describe 'forcing an update on a pull mirror' do
it 'forces update' do it 'forces update' do
expect_any_instance_of(EE::Project).to receive(:force_import_job!) expect_any_instance_of(EE::Project).to receive(:force_import_job!)
...@@ -128,6 +157,25 @@ describe Projects::MirrorsController do ...@@ -128,6 +157,25 @@ describe Projects::MirrorsController do
end end
end end
describe 'forcing an update on a push mirror' do
context 'when remote mirrors are disabled' do
let(:project) { create(:project, :repository, :remote_mirror) }
before do
stub_application_setting(remote_mirror_available: false)
sign_in(project.owner)
end
it 'updates now when overridden' do
project.update(remote_mirror_available_overridden: true)
expect_any_instance_of(EE::Project).to receive(:update_remote_mirrors)
put :update_now, { namespace_id: project.namespace.to_param, project_id: project.to_param, sync_remote: 1 }
end
end
end
describe '#update' do describe '#update' do
let(:project) { create(:project, :repository, :mirror, :remote_mirror) } let(:project) { create(:project, :repository, :mirror, :remote_mirror) }
......
...@@ -277,6 +277,15 @@ describe Project do ...@@ -277,6 +277,15 @@ describe Project do
update_remote_mirrors update_remote_mirrors
end end
it 'does nothing when remote mirror is disabled globally and not overridden' do
stub_application_setting(remote_mirror_available: false)
project.remote_mirror_available_overridden = false
expect_any_instance_of(RemoteMirror).not_to receive(:sync)
update_remote_mirrors
end
it 'does nothing when unlicensed' do it 'does nothing when unlicensed' do
stub_licensed_features(repository_mirrors: false) stub_licensed_features(repository_mirrors: false)
...@@ -824,6 +833,32 @@ describe Project do ...@@ -824,6 +833,32 @@ describe Project do
end end
end end
describe '#remote_mirror_available?' do
let(:project) { create(:project) }
context 'when remote mirror global setting is enabled' do
it 'returns true' do
expect(project.remote_mirror_available?).to be(true)
end
end
context 'when remote mirror global setting is disabled' do
before do
stub_application_setting(remote_mirror_available: false)
end
it 'returns true when overridden' do
project.remote_mirror_available_overridden = true
expect(project.remote_mirror_available?).to be(true)
end
it 'returns false when not overridden' do
expect(project.remote_mirror_available?).to be(false)
end
end
end
describe '#username_only_import_url' do describe '#username_only_import_url' do
where(:import_url, :username, :expected_import_url) do where(:import_url, :username, :expected_import_url) do
'' | 'foo' | '' '' | 'foo' | ''
......
This diff is collapsed.
...@@ -6,7 +6,7 @@ describe Projects::DestroyService do ...@@ -6,7 +6,7 @@ describe Projects::DestroyService do
let!(:project_id) { project.id } let!(:project_id) { project.id }
let!(:project_name) { project.name } let!(:project_name) { project.name }
let!(:project_path) { project.disk_path } let!(:project_path) { project.disk_path }
let!(:wiki_path) { project.disk_path + '.wiki' } let!(:wiki_path) { project.wiki.disk_path }
let!(:storage_name) { project.repository_storage } let!(:storage_name) { project.repository_storage }
let!(:storage_path) { project.repository_storage_path } let!(:storage_path) { project.repository_storage_path }
......
...@@ -25,8 +25,8 @@ describe WikiPages::CreateService do ...@@ -25,8 +25,8 @@ describe WikiPages::CreateService do
end end
it 'triggers Geo::RepositoryUpdatedEventStore when Geo is enabled' do it 'triggers Geo::RepositoryUpdatedEventStore when Geo is enabled' do
expect(Geo::RepositoryUpdatedEventStore).to receive(:new).with(instance_of(Project), source: Geo::RepositoryUpdatedEvent::WIKI).and_call_original expect(::Geo::RepositoryUpdatedEventStore).to receive(:new).with(instance_of(Project), source: Geo::RepositoryUpdatedEvent::WIKI).and_call_original
expect_any_instance_of(Geo::RepositoryUpdatedEventStore).to receive(:create) expect_any_instance_of(::Geo::RepositoryUpdatedEventStore).to receive(:create)
service.execute service.execute
end end
......
...@@ -18,8 +18,8 @@ describe WikiPages::DestroyService do ...@@ -18,8 +18,8 @@ describe WikiPages::DestroyService do
end end
it 'triggers Geo::RepositoryUpdatedEventStore when Geo is enabled' do it 'triggers Geo::RepositoryUpdatedEventStore when Geo is enabled' do
expect(Geo::RepositoryUpdatedEventStore).to receive(:new).with(instance_of(Project), source: Geo::RepositoryUpdatedEvent::WIKI).and_call_original expect(::Geo::RepositoryUpdatedEventStore).to receive(:new).with(instance_of(Project), source: ::Geo::RepositoryUpdatedEvent::WIKI).and_call_original
expect_any_instance_of(Geo::RepositoryUpdatedEventStore).to receive(:create) expect_any_instance_of(::Geo::RepositoryUpdatedEventStore).to receive(:create)
service.execute(page) service.execute(page)
end end
......
...@@ -26,8 +26,8 @@ describe WikiPages::UpdateService do ...@@ -26,8 +26,8 @@ describe WikiPages::UpdateService do
end end
it 'triggers Geo::RepositoryUpdatedEventStore when Geo is enabled' do it 'triggers Geo::RepositoryUpdatedEventStore when Geo is enabled' do
expect(Geo::RepositoryUpdatedEventStore).to receive(:new).with(instance_of(Project), source: Geo::RepositoryUpdatedEvent::WIKI).and_call_original expect(::Geo::RepositoryUpdatedEventStore).to receive(:new).with(instance_of(Project), source: Geo::RepositoryUpdatedEvent::WIKI).and_call_original
expect_any_instance_of(Geo::RepositoryUpdatedEventStore).to receive(:create) expect_any_instance_of(::Geo::RepositoryUpdatedEventStore).to receive(:create)
service.execute(page) service.execute(page)
end end
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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