Commit 15f10ec0 authored by Clement Ho's avatar Clement Ho

Merge branch 'master' into dispatcher-branches-create

parents acefbad2 b5cde6b6
...@@ -8,7 +8,8 @@ ...@@ -8,7 +8,8 @@
"plugins": [ "plugins": [
["istanbul", { ["istanbul", {
"exclude": [ "exclude": [
"spec/javascripts/**/*" "spec/javascripts/**/*",
"app/assets/javascripts/locale/**/app.js"
] ]
}], }],
["transform-define", { ["transform-define", {
......
...@@ -10,7 +10,6 @@ ...@@ -10,7 +10,6 @@
], ],
"globals": { "globals": {
"__webpack_public_path__": true, "__webpack_public_path__": true,
"_": false,
"gl": false, "gl": false,
"gon": false, "gon": false,
"localStorage": false "localStorage": false
......
...@@ -604,6 +604,7 @@ codequality: ...@@ -604,6 +604,7 @@ codequality:
paths: [codeclimate.json] paths: [codeclimate.json]
sast: sast:
<<: *except-docs
image: registry.gitlab.com/gitlab-org/gl-sast:latest image: registry.gitlab.com/gitlab-org/gl-sast:latest
before_script: [] before_script: []
script: script:
...@@ -623,6 +624,18 @@ qa:internal: ...@@ -623,6 +624,18 @@ qa:internal:
- bundle install - bundle install
- bundle exec rspec - bundle exec rspec
qa:selectors:
<<: *dedicated-runner
<<: *except-docs
stage: test
variables:
SETUP_DB: "false"
services: []
script:
- cd qa/
- bundle install
- bundle exec bin/qa Test::Sanity::Selectors
coverage: coverage:
<<: *dedicated-runner <<: *dedicated-runner
<<: *except-docs-and-qa <<: *except-docs-and-qa
......
...@@ -2,6 +2,19 @@ ...@@ -2,6 +2,19 @@
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.3.4 (2018-01-10)
### Security (7 changes, 1 of them is from the community)
- Prevent a SQL injection in the MilestonesFinder.
- Fix RCE via project import mechanism.
- Prevent OAuth login POST requests when a provider has been disabled.
- Filter out sensitive fields from the project services API. (Robert Schilling)
- Check user authorization for source and target projects when creating a merge request.
- Fix path traversal in gitlab-ci.yml cache:key.
- Fix writable shared deploy keys.
## 10.3.3 (2018-01-02) ## 10.3.3 (2018-01-02)
### Fixed (3 changes) ### Fixed (3 changes)
...@@ -180,6 +193,21 @@ entry. ...@@ -180,6 +193,21 @@ entry.
- Clean up schema of the "merge_requests" table. - Clean up schema of the "merge_requests" table.
## 10.2.6 (2018-01-11)
### Security (9 changes, 1 of them is from the community)
- Fix writable shared deploy keys.
- Filter out sensitive fields from the project services API. (Robert Schilling)
- Fix RCE via project import mechanism.
- Fixed IPython notebook output not being sanitized.
- Prevent OAuth login POST requests when a provider has been disabled.
- Prevent a SQL injection in the MilestonesFinder.
- Check user authorization for source and target projects when creating a merge request.
- Fix path traversal in gitlab-ci.yml cache:key.
- Fix XSS vulnerability in pipeline job trace.
## 10.2.5 (2017-12-15) ## 10.2.5 (2017-12-15)
### Fixed (8 changes) ### Fixed (8 changes)
...@@ -446,6 +474,20 @@ entry. ...@@ -446,6 +474,20 @@ entry.
- Add Gitaly metrics to the performance bar. - Add Gitaly metrics to the performance bar.
## 10.1.6 (2018-01-11)
### Security (8 changes, 1 of them is from the community)
- Fix writable shared deploy keys.
- Filter out sensitive fields from the project services API. (Robert Schilling)
- Fix RCE via project import mechanism.
- Prevent OAuth login POST requests when a provider has been disabled.
- Prevent a SQL injection in the MilestonesFinder.
- Check user authorization for source and target projects when creating a merge request.
- Fix path traversal in gitlab-ci.yml cache:key.
- Fix XSS vulnerability in pipeline job trace.
## 10.1.5 (2017-12-07) ## 10.1.5 (2017-12-07)
### Security (5 changes) ### Security (5 changes)
......
...@@ -385,9 +385,6 @@ gem 'ruby-prof', '~> 0.16.2' ...@@ -385,9 +385,6 @@ gem 'ruby-prof', '~> 0.16.2'
# OAuth # OAuth
gem 'oauth2', '~> 1.4' gem 'oauth2', '~> 1.4'
# Soft deletion
gem 'paranoia', '~> 2.3.1'
# Health check # Health check
gem 'health_check', '~> 2.6.0' gem 'health_check', '~> 2.6.0'
......
...@@ -580,8 +580,6 @@ GEM ...@@ -580,8 +580,6 @@ GEM
orm_adapter (0.5.0) orm_adapter (0.5.0)
os (0.9.6) os (0.9.6)
parallel (1.12.0) parallel (1.12.0)
paranoia (2.3.1)
activerecord (>= 4.0, < 5.2)
parser (2.4.0.2) parser (2.4.0.2)
ast (~> 2.3) ast (~> 2.3)
parslet (1.5.0) parslet (1.5.0)
...@@ -1117,7 +1115,6 @@ DEPENDENCIES ...@@ -1117,7 +1115,6 @@ DEPENDENCIES
omniauth-twitter (~> 1.2.0) omniauth-twitter (~> 1.2.0)
omniauth_crowd (~> 2.2.0) omniauth_crowd (~> 2.2.0)
org-ruby (~> 0.9.12) org-ruby (~> 0.9.12)
paranoia (~> 2.3.1)
peek (~> 1.0.1) peek (~> 1.0.1)
peek-gc (~> 0.0.2) peek-gc (~> 0.0.2)
peek-host (~> 1.0.0) peek-host (~> 1.0.0)
......
app/assets/images/multi-editor-on.png

5.34 KB | W: | H:

app/assets/images/multi-editor-on.png

3.88 KB | W: | H:

app/assets/images/multi-editor-on.png
app/assets/images/multi-editor-on.png
app/assets/images/multi-editor-on.png
app/assets/images/multi-editor-on.png
  • 2-up
  • Swipe
  • Onion skin
...@@ -4,6 +4,8 @@ import { visitUrl } from '../lib/utils/url_utility'; ...@@ -4,6 +4,8 @@ import { visitUrl } from '../lib/utils/url_utility';
import { HIDDEN_CLASS } from '../lib/utils/constants'; import { HIDDEN_CLASS } from '../lib/utils/constants';
import csrf from '../lib/utils/csrf'; import csrf from '../lib/utils/csrf';
Dropzone.autoDiscover = false;
function toggleLoading($el, $icon, loading) { function toggleLoading($el, $icon, loading) {
if (loading) { if (loading) {
$el.disable(); $el.disable();
......
/* eslint-disable comma-dangle, space-before-function-paren, one-var */ /* eslint-disable comma-dangle, space-before-function-paren, one-var */
/* global Sortable */ import Sortable from 'vendor/Sortable';
import Vue from 'vue'; import Vue from 'vue';
import AccessorUtilities from '../../lib/utils/accessor'; import AccessorUtilities from '../../lib/utils/accessor';
import boardList from './board_list'; import boardList from './board_list';
......
/* global Sortable */ import Sortable from 'vendor/Sortable';
import boardNewIssue from './board_new_issue'; import boardNewIssue from './board_new_issue';
import boardCard from './board_card.vue'; import boardCard from './board_card.vue';
import eventHub from '../eventhub'; import eventHub from '../eventhub';
......
...@@ -46,14 +46,15 @@ ...@@ -46,14 +46,15 @@
)); ));
const extraCostParagraph = sprintf( const extraCostParagraph = sprintf(
_.escape(s__(`ClusterIntegration|%{boldNotice} This will add some _.escape(s__(
extra resources like a load balancer, `ClusterIntegration|%{boldNotice} This will add some extra resources
which incur additional costs. See %{pricingLink}`)), like a load balancer, which may incur additional costs depending on
{ the hosting provider Kubernetes is installed on. If you are using GKE,
you can %{pricingLink}.`,
)), {
boldNotice: `<strong>${_.escape(s__('ClusterIntegration|Note:'))}</strong>`, boldNotice: `<strong>${_.escape(s__('ClusterIntegration|Note:'))}</strong>`,
pricingLink: `<a href="https://cloud.google.com/compute/pricing#lb" target="_blank" rel="noopener noreferrer"> pricingLink: `<a href="https://cloud.google.com/compute/pricing#lb" target="_blank" rel="noopener noreferrer">
${_.escape(s__('ClusterIntegration|GKE pricing'))} ${_.escape(s__('ClusterIntegration|check the pricing here'))}</a>`,
</a>`,
}, },
false, false,
); );
......
/* eslint-disable no-new */ /* eslint-disable no-new */
import _ from 'underscore';
import Flash from './flash'; import Flash from './flash';
import DropLab from './droplab/drop_lab'; import DropLab from './droplab/drop_lab';
import ISetter from './droplab/plugins/input_setter'; import ISetter from './droplab/plugins/input_setter';
......
This diff is collapsed.
...@@ -3,6 +3,8 @@ import _ from 'underscore'; ...@@ -3,6 +3,8 @@ import _ from 'underscore';
import './preview_markdown'; import './preview_markdown';
import csrf from './lib/utils/csrf'; import csrf from './lib/utils/csrf';
Dropzone.autoDiscover = false;
export default function dropzoneInput(form) { export default function dropzoneInput(form) {
const divHover = '<div class="div-dropzone-hover"></div>'; const divHover = '<div class="div-dropzone-hover"></div>';
const iconPaperclip = '<i class="fa fa-paperclip div-dropzone-icon"></i>'; const iconPaperclip = '<i class="fa fa-paperclip div-dropzone-icon"></i>';
......
/** /**
* Common code between environmets app and folder view * Common code between environmets app and folder view
*/ */
import _ from 'underscore';
import Visibility from 'visibilityjs'; import Visibility from 'visibilityjs';
import Poll from '../../lib/utils/poll'; import Poll from '../../lib/utils/poll';
import { import {
......
import _ from 'underscore';
import DropLab from '~/droplab/drop_lab'; import DropLab from '~/droplab/drop_lab';
import FilteredSearchContainer from './container'; import FilteredSearchContainer from './container';
......
import _ from 'underscore';
import { visitUrl } from '../lib/utils/url_utility'; import { visitUrl } from '../lib/utils/url_utility';
import Flash from '../flash'; import Flash from '../flash';
import FilteredSearchContainer from './container'; import FilteredSearchContainer from './container';
......
import _ from 'underscore';
import AjaxCache from '../lib/utils/ajax_cache'; import AjaxCache from '../lib/utils/ajax_cache';
import Flash from '../flash'; import Flash from '../flash';
import FilteredSearchContainer from './container'; import FilteredSearchContainer from './container';
......
<script> <script>
/* global Flash */ /* global Flash */
import { s__ } from '~/locale';
import loadingIcon from '~/vue_shared/components/loading_icon.vue';
import modal from '~/vue_shared/components/modal.vue';
import { getParameterByName } from '~/lib/utils/common_utils';
import { mergeUrlParams } from '~/lib/utils/url_utility';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
import { getParameterByName } from '../../lib/utils/common_utils';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import { COMMON_STR } from '../constants'; import { COMMON_STR } from '../constants';
import { mergeUrlParams } from '../../lib/utils/url_utility';
import groupsComponent from './groups.vue'; import groupsComponent from './groups.vue';
export default { export default {
components: { components: {
loadingIcon, loadingIcon,
modal,
groupsComponent, groupsComponent,
}, },
props: { props: {
...@@ -32,6 +36,10 @@ export default { ...@@ -32,6 +36,10 @@ export default {
isLoading: true, isLoading: true,
isSearchEmpty: false, isSearchEmpty: false,
searchEmptyMessage: '', searchEmptyMessage: '',
showModal: false,
groupLeaveConfirmationMessage: '',
targetGroup: null,
targetParentGroup: null,
}; };
}, },
computed: { computed: {
...@@ -48,7 +56,7 @@ export default { ...@@ -48,7 +56,7 @@ export default {
eventHub.$on('fetchPage', this.fetchPage); eventHub.$on('fetchPage', this.fetchPage);
eventHub.$on('toggleChildren', this.toggleChildren); eventHub.$on('toggleChildren', this.toggleChildren);
eventHub.$on('leaveGroup', this.leaveGroup); eventHub.$on('showLeaveGroupModal', this.showLeaveGroupModal);
eventHub.$on('updatePagination', this.updatePagination); eventHub.$on('updatePagination', this.updatePagination);
eventHub.$on('updateGroups', this.updateGroups); eventHub.$on('updateGroups', this.updateGroups);
}, },
...@@ -58,7 +66,7 @@ export default { ...@@ -58,7 +66,7 @@ export default {
beforeDestroy() { beforeDestroy() {
eventHub.$off('fetchPage', this.fetchPage); eventHub.$off('fetchPage', this.fetchPage);
eventHub.$off('toggleChildren', this.toggleChildren); eventHub.$off('toggleChildren', this.toggleChildren);
eventHub.$off('leaveGroup', this.leaveGroup); eventHub.$off('showLeaveGroupModal', this.showLeaveGroupModal);
eventHub.$off('updatePagination', this.updatePagination); eventHub.$off('updatePagination', this.updatePagination);
eventHub.$off('updateGroups', this.updateGroups); eventHub.$off('updateGroups', this.updateGroups);
}, },
...@@ -141,14 +149,23 @@ export default { ...@@ -141,14 +149,23 @@ export default {
parentGroup.isOpen = false; parentGroup.isOpen = false;
} }
}, },
leaveGroup(group, parentGroup) { showLeaveGroupModal(group, parentGroup) {
const targetGroup = group; this.targetGroup = group;
targetGroup.isBeingRemoved = true; this.targetParentGroup = parentGroup;
this.service.leaveGroup(targetGroup.leavePath) this.showModal = true;
this.groupLeaveConfirmationMessage = s__(`GroupsTree|Are you sure you want to leave the "${group.fullName}" group?`);
},
hideLeaveGroupModal() {
this.showModal = false;
},
leaveGroup() {
this.showModal = false;
this.targetGroup.isBeingRemoved = true;
this.service.leaveGroup(this.targetGroup.leavePath)
.then(res => res.json()) .then(res => res.json())
.then((res) => { .then((res) => {
$.scrollTo(0); $.scrollTo(0);
this.store.removeGroup(targetGroup, parentGroup); this.store.removeGroup(this.targetGroup, this.targetParentGroup);
Flash(res.notice, 'notice'); Flash(res.notice, 'notice');
}) })
.catch((err) => { .catch((err) => {
...@@ -157,7 +174,7 @@ export default { ...@@ -157,7 +174,7 @@ export default {
message = COMMON_STR.LEAVE_FORBIDDEN; message = COMMON_STR.LEAVE_FORBIDDEN;
} }
Flash(message); Flash(message);
targetGroup.isBeingRemoved = false; this.targetGroup.isBeingRemoved = false;
}); });
}, },
updatePagination(headers) { updatePagination(headers) {
...@@ -190,5 +207,14 @@ export default { ...@@ -190,5 +207,14 @@ export default {
:search-empty-message="searchEmptyMessage" :search-empty-message="searchEmptyMessage"
:page-info="pageInfo" :page-info="pageInfo"
/> />
<modal
v-show="showModal"
:primary-button-label="__('Leave')"
kind="warning"
:title="__('Are you sure?')"
:text="groupLeaveConfirmationMessage"
@cancel="hideLeaveGroupModal"
@submit="leaveGroup"
/>
</div> </div>
</template> </template>
<script> <script>
import { s__ } from '~/locale'; import tooltip from '~/vue_shared/directives/tooltip';
import tooltip from '~/vue_shared/directives/tooltip'; import icon from '~/vue_shared/components/icon.vue';
import icon from '~/vue_shared/components/icon.vue'; import eventHub from '../event_hub';
import modal from '~/vue_shared/components/modal.vue'; import { COMMON_STR } from '../constants';
import eventHub from '../event_hub';
import { COMMON_STR } from '../constants';
export default { export default {
components: { components: {
icon, icon,
modal,
}, },
directives: { directives: {
tooltip, tooltip,
...@@ -25,11 +22,6 @@ ...@@ -25,11 +22,6 @@
required: true, required: true,
}, },
}, },
data() {
return {
modalStatus: false,
};
},
computed: { computed: {
leaveBtnTitle() { leaveBtnTitle() {
return COMMON_STR.LEAVE_BTN_TITLE; return COMMON_STR.LEAVE_BTN_TITLE;
...@@ -37,20 +29,13 @@ ...@@ -37,20 +29,13 @@
editBtnTitle() { editBtnTitle() {
return COMMON_STR.EDIT_BTN_TITLE; return COMMON_STR.EDIT_BTN_TITLE;
}, },
leaveConfirmationMessage() {
return s__(`GroupsTree|Are you sure you want to leave the "${this.group.fullName}" group?`);
},
}, },
methods: { methods: {
onLeaveGroup() { onLeaveGroup() {
this.modalStatus = true; eventHub.$emit('showLeaveGroupModal', this.group, this.parentGroup);
},
leaveGroup() {
this.modalStatus = false;
eventHub.$emit('leaveGroup', this.group, this.parentGroup);
}, },
}, },
}; };
</script> </script>
<template> <template>
...@@ -78,14 +63,5 @@ ...@@ -78,14 +63,5 @@
class="leave-group btn no-expand"> class="leave-group btn no-expand">
<icon name="leave"/> <icon name="leave"/>
</a> </a>
<modal
v-show="modalStatus"
:primary-button-label="__('Leave')"
kind="warning"
:title="__('Are you sure?')"
:text="__('Are you sure you want to leave this group?')"
:body="leaveConfirmationMessage"
@submit="leaveGroup"
/>
</div> </div>
</template> </template>
import _ from 'underscore';
import DecorationsController from './decorations/controller'; import DecorationsController from './decorations/controller';
import DirtyDiffController from './diff/controller'; import DirtyDiffController from './diff/controller';
import Disposable from './common/disposable'; import Disposable from './common/disposable';
......
import _ from 'underscore';
export const dataStructure = () => ({ export const dataStructure = () => ({
id: '', id: '',
key: '', key: '',
......
/* eslint-disable comma-dangle, class-methods-use-this, no-underscore-dangle, no-param-reassign, no-unused-vars, consistent-return, func-names, space-before-function-paren, max-len */ /* eslint-disable comma-dangle, class-methods-use-this, no-underscore-dangle, no-param-reassign, no-unused-vars, consistent-return, func-names, space-before-function-paren, max-len */
/* global Sortable */ import Sortable from 'vendor/Sortable';
import Flash from './flash'; import Flash from './flash';
......
This diff is collapsed.
/* global Sortable */
import Flash from './flash'; import Flash from './flash';
export default class Milestone { export default class Milestone {
......
import { truncate } from './lib/utils/text_utility'; import { truncate } from '../../../lib/utils/text_utility';
const MAX_MESSAGE_LENGTH = 500; const MAX_MESSAGE_LENGTH = 500;
const MESSAGE_CELL_SELECTOR = '.abuse-reports .message'; const MESSAGE_CELL_SELECTOR = '.abuse-reports .message';
......
import AbuseReports from './abuse_reports';
export default () => new AbuseReports();
import { refreshCurrentPage } from './lib/utils/url_utility'; import { refreshCurrentPage } from '../../lib/utils/url_utility';
function showBlacklistType() { function showBlacklistType() {
if ($('input[name="blacklist_type"]:checked').val() === 'file') { if ($('input[name="blacklist_type"]:checked').val() === 'file') {
......
import _ from 'underscore';
export default function initBroadcastMessagesForm() { export default function initBroadcastMessagesForm() {
$('input#broadcast_message_color').on('input', function onMessageColorInput() { $('input#broadcast_message_color').on('input', function onMessageColorInput() {
const previewColor = $(this).val(); const previewColor = $(this).val();
......
import initBroadcastMessagesForm from './broadcast_message';
export default () => initBroadcastMessagesForm();
import initUsagePing from './usage_ping';
export default () => initUsagePing();
import groupAvatar from '../../../../group_avatar';
export default () => groupAvatar();
import BindInOut from '../../../../behaviors/bind_in_out';
import Group from '../../../../group';
import groupAvatar from '../../../../group_avatar';
export default () => {
BindInOut.initAll();
new Group(); // eslint-disable-line no-new
groupAvatar();
};
import UsersSelect from '../../../../users_select';
export default () => new UsersSelect();
import DueDateSelectors from '../../../due_date_select';
export default () => new DueDateSelectors();
import initAdmin from './admin';
export default () => initAdmin();
import Labels from '../../../../labels';
export default () => new Labels();
import Labels from '../../../../labels';
export default () => new Labels();
import ProjectsList from '../../../projects_list';
import NamespaceSelect from '../../../namespace_select';
export default () => {
new ProjectsList(); // eslint-disable-line no-new
document.querySelectorAll('.js-namespace-select')
.forEach(dropdown => new NamespaceSelect({ dropdown }));
};
import Activities from '~/activities';
export default () => new Activities();
import projectSelect from '~/project_select';
import initLegacyFilters from '~/init_legacy_filters';
export default () => {
projectSelect();
initLegacyFilters();
};
import GroupsList from '~/groups_list';
import Landing from '~/landing';
export default function () {
new GroupsList(); // eslint-disable-line no-new
const landingElement = document.querySelector('.js-explore-groups-landing');
if (!landingElement) return;
const exploreGroupsLanding = new Landing(
landingElement,
landingElement.querySelector('.dismiss-button'),
'explore_groups_landing_dismissed',
);
exploreGroupsLanding.toggle();
}
import ProjectsList from '~/projects_list';
export default () => new ProjectsList();
import VersionCheckImage from '../../version_check_image';
export default () => VersionCheckImage.bindErrorEvent($('img.js-version-status-badge'));
import NotificationsForm from '../../../notifications_form';
import notificationsDropdown from '../../../notifications_dropdown';
export default () => {
new NotificationsForm(); // eslint-disable-line no-new
notificationsDropdown();
};
import DueDateSelectors from '../../../due_date_select';
export default () => new DueDateSelectors();
import Activities from '~/activities';
import ShortcutsNavigation from '~/shortcuts_navigation';
export default function () {
new Activities(); // eslint-disable-line no-new
new ShortcutsNavigation(); // eslint-disable-line no-new
}
import BuildArtifacts from '~/build_artifacts';
import ShortcutsNavigation from '~/shortcuts_navigation';
export default function () {
new ShortcutsNavigation(); // eslint-disable-line no-new
new BuildArtifacts(); // eslint-disable-line no-new
}
import BlobViewer from '~/blob/viewer/index';
import ShortcutsNavigation from '~/shortcuts_navigation';
export default function () {
new ShortcutsNavigation(); // eslint-disable-line no-new
new BlobViewer(); // eslint-disable-line no-new
}
import UsersSelect from '~/users_select';
import ShortcutsNavigation from '~/shortcuts_navigation';
export default () => {
new UsersSelect(); // eslint-disable-line no-new
new ShortcutsNavigation(); // eslint-disable-line no-new
};
import Pipelines from '../../../../pipelines';
export default () => {
const { controllerAction } = document.querySelector('.js-pipeline-container').dataset;
const pipelineStatusUrl = `${document.querySelector('.js-pipeline-tab-link a').getAttribute('href')}/status.json`;
new Pipelines({ // eslint-disable-line no-new
initTabs: true,
pipelineStatusUrl,
tabsOptions: {
action: controllerAction,
defaultAction: 'pipelines',
parentEl: '.pipelines-tabs',
},
});
};
import NewBranchForm from '../../../../new_branch_form';
export default () => {
new NewBranchForm($('.js-new-pipeline-form')); // eslint-disable-line no-new
};
import memberExpirationDate from '../../../member_expiration_date';
import UsersSelect from '../../../users_select';
import groupsSelect from '../../../groups_select';
import Members from '../../../members';
export default () => {
memberExpirationDate('.js-access-expiration-date-groups');
groupsSelect();
memberExpirationDate();
new Members(); // eslint-disable-line no-new
new UsersSelect(); // eslint-disable-line no-new
};
import Search from './search';
export default () => new Search();
import Flash from './flash'; import Flash from '~/flash';
import Api from './api'; import Api from '~/api';
export default class Search { export default class Search {
constructor() { constructor() {
......
import UsernameValidator from './username_validator';
import SigninTabsMemoizer from './signin_tabs_memoizer';
import OAuthRememberMe from './oauth_remember_me';
export default () => {
new UsernameValidator(); // eslint-disable-line no-new
new SigninTabsMemoizer(); // eslint-disable-line no-new
new OAuthRememberMe({ // eslint-disable-line no-new
container: $('.omniauth-container'),
}).bindEvents();
};
/* eslint no-param-reassign: ["error", { "props": false }]*/ /* eslint no-param-reassign: ["error", { "props": false }]*/
/* eslint no-new: "off" */ /* eslint no-new: "off" */
import AccessorUtilities from './lib/utils/accessor'; import AccessorUtilities from '~/lib/utils/accessor';
/** /**
* Memorize the last selected tab after reloading a page. * Memorize the last selected tab after reloading a page.
......
import _ from 'underscore';
import Vue from 'vue'; import Vue from 'vue';
import VueResource from 'vue-resource'; import VueResource from 'vue-resource';
......
/* global Mousetrap */ import Mousetrap from 'mousetrap';
import { getLocationHash, visitUrl } from './lib/utils/url_utility'; import { getLocationHash, visitUrl } from './lib/utils/url_utility';
import Shortcuts from './shortcuts'; import Shortcuts from './shortcuts';
......
/* global Mousetrap */ import Mousetrap from 'mousetrap';
import ShortcutsNavigation from './shortcuts_navigation'; import ShortcutsNavigation from './shortcuts_navigation';
export default class ShortcutsFindFile extends ShortcutsNavigation { export default class ShortcutsFindFile extends ShortcutsNavigation {
......
/* global Mousetrap */ import Mousetrap from 'mousetrap';
import _ from 'underscore'; import _ from 'underscore';
import 'mousetrap';
import Sidebar from './right_sidebar'; import Sidebar from './right_sidebar';
import ShortcutsNavigation from './shortcuts_navigation'; import ShortcutsNavigation from './shortcuts_navigation';
import { CopyAsGFM } from './behaviors/copy_as_gfm'; import { CopyAsGFM } from './behaviors/copy_as_gfm';
......
/* global Mousetrap */ import Mousetrap from 'mousetrap';
import findAndFollowLink from './shortcuts_dashboard_navigation'; import findAndFollowLink from './shortcuts_dashboard_navigation';
import Shortcuts from './shortcuts'; import Shortcuts from './shortcuts';
......
/* global Mousetrap */ import Mousetrap from 'mousetrap';
import ShortcutsNavigation from './shortcuts_navigation'; import ShortcutsNavigation from './shortcuts_navigation';
export default class ShortcutsNetwork extends ShortcutsNavigation { export default class ShortcutsNetwork extends ShortcutsNavigation {
......
/* eslint-disable class-methods-use-this */ import Mousetrap from 'mousetrap';
/* global Mousetrap */
import ShortcutsNavigation from './shortcuts_navigation'; import ShortcutsNavigation from './shortcuts_navigation';
import findAndFollowLink from './shortcuts_dashboard_navigation'; import findAndFollowLink from './shortcuts_dashboard_navigation';
export default class ShortcutsWiki extends ShortcutsNavigation { export default class ShortcutsWiki extends ShortcutsNavigation {
constructor() { constructor() {
super(); super();
Mousetrap.bind('e', this.editWiki); Mousetrap.bind('e', ShortcutsWiki.editWiki);
} }
editWiki() { static editWiki() {
findAndFollowLink('.js-wiki-edit'); findAndFollowLink('.js-wiki-edit');
} }
} }
<script> <script>
import tooltip from '../directives/tooltip';
/** /**
* Falls back to the code used in `copy_to_clipboard.js` * Falls back to the code used in `copy_to_clipboard.js`
*/ */
export default { export default {
name: 'ClipboardButton', name: 'ClipboardButton',
directives: {
tooltip,
},
props: { props: {
text: { text: {
type: String, type: String,
...@@ -14,6 +18,16 @@ ...@@ -14,6 +18,16 @@
type: String, type: String,
required: true, required: true,
}, },
tooltipPlacement: {
type: String,
required: false,
default: 'top',
},
tooltipContainer: {
type: [String, Boolean],
required: false,
default: false,
},
}, },
}; };
</script> </script>
...@@ -22,8 +36,11 @@ ...@@ -22,8 +36,11 @@
<button <button
type="button" type="button"
class="btn btn-transparent btn-clipboard" class="btn btn-transparent btn-clipboard"
:data-title="title" :title="title"
:data-clipboard-text="text" :data-clipboard-text="text"
v-tooltip
:data-container="tooltipContainer"
:data-placement="tooltipPlacement"
> >
<i <i
aria-hidden="true" aria-hidden="true"
......
/* eslint-disable func-names, space-before-function-paren, wrap-iife, prefer-arrow-callback, no-unused-vars, consistent-return, camelcase, comma-dangle, max-len, class-methods-use-this */ /* eslint-disable func-names, space-before-function-paren, wrap-iife, prefer-arrow-callback, no-unused-vars, consistent-return, camelcase, comma-dangle, max-len, class-methods-use-this */
/* global Mousetrap */
// Zen Mode (full screen) textarea // Zen Mode (full screen) textarea
// //
...@@ -8,9 +7,11 @@ ...@@ -8,9 +7,11 @@
import 'vendor/jquery.scrollTo'; import 'vendor/jquery.scrollTo';
import Dropzone from 'dropzone'; import Dropzone from 'dropzone';
import 'mousetrap'; import Mousetrap from 'mousetrap';
import 'mousetrap/plugins/pause/mousetrap-pause'; import 'mousetrap/plugins/pause/mousetrap-pause';
Dropzone.autoDiscover = false;
// //
// ### Events // ### Events
// //
......
...@@ -18,14 +18,9 @@ ...@@ -18,14 +18,9 @@
margin: $gl-padding 0; margin: $gl-padding 0;
&.limited-width-container .file-content { &.limited-width-container .file-content {
max-width: $limited-layout-width-sm; max-width: $limited-layout-width;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
@media (min-width: $screen-md-min) {
padding-top: 64px;
padding-bottom: 64px;
}
} }
} }
...@@ -128,7 +123,7 @@ ...@@ -128,7 +123,7 @@
} }
&.wiki { &.wiki {
padding: 30px $gl-padding; padding: $gl-padding;
} }
&.blob-no-preview { &.blob-no-preview {
......
...@@ -651,12 +651,18 @@ ...@@ -651,12 +651,18 @@
min-width: 0; min-width: 0;
} }
.diff-changed-file-name { .diff-changed-file-name,
.diff-changed-blank-file-name {
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
.diff-changed-blank-file-name {
color: $gl-text-color-tertiary;
font-style: italic;
}
.diff-changed-file-path { .diff-changed-file-path {
color: $gl-text-color-tertiary; color: $gl-text-color-tertiary;
} }
......
...@@ -798,7 +798,6 @@ button.mini-pipeline-graph-dropdown-toggle { ...@@ -798,7 +798,6 @@ button.mini-pipeline-graph-dropdown-toggle {
// link to the build // link to the build
.mini-pipeline-graph-dropdown-item { .mini-pipeline-graph-dropdown-item {
padding: 3px 7px 4px;
align-items: center; align-items: center;
clear: both; clear: both;
display: flex; display: flex;
......
...@@ -16,12 +16,6 @@ ...@@ -16,12 +16,6 @@
display: inline-block; display: inline-block;
} }
@media (min-width: $screen-md-min) {
.blob-viewer[data-type="rich"] {
margin: 20px;
}
}
.ide-view { .ide-view {
display: flex; display: flex;
height: calc(100vh - #{$header-height}); height: calc(100vh - #{$header-height});
......
...@@ -65,6 +65,7 @@ class Admin::RunnersController < Admin::ApplicationController ...@@ -65,6 +65,7 @@ class Admin::RunnersController < Admin::ApplicationController
else else
Project.all Project.all
end end
@projects = @projects.where.not(id: runner.projects.select(:id)) if runner.projects.any? @projects = @projects.where.not(id: runner.projects.select(:id)) if runner.projects.any?
@projects = @projects.page(params[:page]).per(30) @projects = @projects.page(params[:page]).per(30)
end end
......
...@@ -8,6 +8,7 @@ module GroupTree ...@@ -8,6 +8,7 @@ module GroupTree
# Only show root groups if no parent-id is given # Only show root groups if no parent-id is given
groups.where(parent_id: params[:parent_id]) groups.where(parent_id: params[:parent_id])
end end
@groups = @groups.with_selects_for_list(archived: params[:archived]) @groups = @groups.with_selects_for_list(archived: params[:archived])
.sort(@sort = params[:sort]) .sort(@sort = params[:sort])
.page(params[:page]) .page(params[:page])
......
...@@ -32,6 +32,7 @@ module RoutableActions ...@@ -32,6 +32,7 @@ module RoutableActions
if canonical_path.casecmp(requested_full_path) != 0 if canonical_path.casecmp(requested_full_path) != 0
flash[:notice] = "#{routable.class.to_s.titleize} '#{requested_full_path}' was moved to '#{canonical_path}'. Please update any links and bookmarks that may still have the old path." flash[:notice] = "#{routable.class.to_s.titleize} '#{requested_full_path}' was moved to '#{canonical_path}'. Please update any links and bookmarks that may still have the old path."
end end
redirect_to build_canonical_path(routable) redirect_to build_canonical_path(routable)
end end
end end
......
...@@ -12,6 +12,7 @@ class MetricsController < ActionController::Base ...@@ -12,6 +12,7 @@ class MetricsController < ActionController::Base
) )
"# Metrics are disabled, see: #{help_page}\n" "# Metrics are disabled, see: #{help_page}\n"
end end
render text: response, content_type: 'text/plain; version=0.0.4' render text: response, content_type: 'text/plain; version=0.0.4'
end end
......
...@@ -83,6 +83,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController ...@@ -83,6 +83,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
if ticket if ticket
handle_service_ticket oauth['provider'], ticket handle_service_ticket oauth['provider'], ticket
end end
handle_omniauth handle_omniauth
end end
...@@ -90,6 +91,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController ...@@ -90,6 +91,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
if params['sid'] if params['sid']
handle_service_ticket oauth['provider'], params['sid'] handle_service_ticket oauth['provider'], params['sid']
end end
handle_omniauth handle_omniauth
end end
...@@ -124,6 +126,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController ...@@ -124,6 +126,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
# Only allow properly saved users to login. # Only allow properly saved users to login.
if @user.persisted? && @user.valid? if @user.persisted? && @user.valid?
log_audit_event(@user, with: oauth['provider']) log_audit_event(@user, with: oauth['provider'])
if @user.two_factor_enabled? if @user.two_factor_enabled?
params[:remember_me] = '1' if remember_me? params[:remember_me] = '1' if remember_me?
prompt_for_two_factor(@user) prompt_for_two_factor(@user)
......
...@@ -150,6 +150,7 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -150,6 +150,7 @@ class Projects::BlobController < Projects::ApplicationController
if params[:file].present? if params[:file].present?
params[:file_name] = params[:file].original_filename params[:file_name] = params[:file].original_filename
end end
File.join(@path, params[:file_name]) File.join(@path, params[:file_name])
elsif params[:file_path].present? elsif params[:file_path].present?
params[:file_path] params[:file_path]
......
class Projects::Clusters::GcpController < Projects::ApplicationController class Projects::Clusters::GcpController < Projects::ApplicationController
before_action :authorize_read_cluster! before_action :authorize_read_cluster!
before_action :authorize_google_api, except: [:login] before_action :authorize_google_api, except: [:login]
before_action :authorize_google_project_billing, only: [:new] before_action :authorize_google_project_billing, only: [:new, :create]
before_action :authorize_create_cluster!, only: [:new, :create] before_action :authorize_create_cluster!, only: [:new, :create]
before_action :verify_billing, only: [:create]
def login def login
begin begin
...@@ -23,24 +24,34 @@ class Projects::Clusters::GcpController < Projects::ApplicationController ...@@ -23,24 +24,34 @@ class Projects::Clusters::GcpController < Projects::ApplicationController
end end
def create def create
case google_project_billing_status
when 'true'
@cluster = ::Clusters::CreateService @cluster = ::Clusters::CreateService
.new(project, current_user, create_params) .new(project, current_user, create_params)
.execute(token_in_session) .execute(token_in_session)
return redirect_to project_cluster_path(project, @cluster) if @cluster.persisted? if @cluster.persisted?
redirect_to project_cluster_path(project, @cluster)
else
render :new
end
end
private
def verify_billing
case google_project_billing_status
when 'true'
return
when 'false' when 'false'
flash[:error] = _('Please enable billing for one of your projects to be able to create a cluster.') flash[:alert] = _('Please <a href=%{link_to_billing} target="_blank" rel="noopener noreferrer">enable billing for one of your projects to be able to create a cluster</a>, then try again.').html_safe % { link_to_billing: "https://console.cloud.google.com/freetrial?utm_campaign=2018_cpanel&utm_source=gitlab&utm_medium=referral" }
else else
flash[:error] = _('We could not verify that one of your projects on GCP has billing enabled. Please try again.') flash[:alert] = _('We could not verify that one of your projects on GCP has billing enabled. Please try again.')
end end
@cluster = ::Clusters::Cluster.new(create_params)
render :new render :new
end end
private
def create_params def create_params
params.require(:cluster).permit( params.require(:cluster).permit(
:enabled, :enabled,
......
...@@ -27,6 +27,7 @@ class Projects::DeployKeysController < Projects::ApplicationController ...@@ -27,6 +27,7 @@ class Projects::DeployKeysController < Projects::ApplicationController
unless @key.valid? && @project.deploy_keys << @key unless @key.valid? && @project.deploy_keys << @key
flash[:alert] = @key.errors.full_messages.join(', ').html_safe flash[:alert] = @key.errors.full_messages.join(', ').html_safe
end end
redirect_to_repository_settings(@project) redirect_to_repository_settings(@project)
end end
......
...@@ -21,6 +21,7 @@ class Projects::HooksController < Projects::ApplicationController ...@@ -21,6 +21,7 @@ class Projects::HooksController < Projects::ApplicationController
@hooks = @project.hooks.select(&:persisted?) @hooks = @project.hooks.select(&:persisted?)
flash[:alert] = @hook.errors.full_messages.join.html_safe flash[:alert] = @hook.errors.full_messages.join.html_safe
end end
redirect_to project_settings_integrations_path(@project) redirect_to project_settings_integrations_path(@project)
end end
......
...@@ -48,6 +48,7 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap ...@@ -48,6 +48,7 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
else else
[] []
end end
@diff_notes_disabled = true @diff_notes_disabled = true
@environment = @merge_request.environments_for(current_user).last @environment = @merge_request.environments_for(current_user).last
......
...@@ -203,6 +203,7 @@ class ProjectsController < Projects::ApplicationController ...@@ -203,6 +203,7 @@ class ProjectsController < Projects::ApplicationController
else else
flash[:alert] = _("Project export could not be deleted.") flash[:alert] = _("Project export could not be deleted.")
end end
redirect_to(edit_project_path(@project)) redirect_to(edit_project_path(@project))
end end
......
...@@ -28,6 +28,7 @@ class SessionsController < Devise::SessionsController ...@@ -28,6 +28,7 @@ class SessionsController < Devise::SessionsController
resource.update_attributes(reset_password_token: nil, resource.update_attributes(reset_password_token: nil,
reset_password_sent_at: nil) reset_password_sent_at: nil)
end end
# hide the signed-in notification # hide the signed-in notification
flash[:notice] = nil flash[:notice] = nil
log_audit_event(current_user, resource, with: authentication_method) log_audit_event(current_user, resource, with: authentication_method)
......
...@@ -63,6 +63,7 @@ class GroupDescendantsFinder ...@@ -63,6 +63,7 @@ class GroupDescendantsFinder
groups_table = Group.arel_table groups_table = Group.arel_table
visible_to_user = groups_table[:visibility_level] visible_to_user = groups_table[:visibility_level]
.in(Gitlab::VisibilityLevel.levels_for_user(current_user)) .in(Gitlab::VisibilityLevel.levels_for_user(current_user))
if current_user if current_user
authorized_groups = GroupsFinder.new(current_user, authorized_groups = GroupsFinder.new(current_user,
all_available: false) all_available: false)
...@@ -115,6 +116,7 @@ class GroupDescendantsFinder ...@@ -115,6 +116,7 @@ class GroupDescendantsFinder
else else
direct_child_groups direct_child_groups
end end
groups.with_selects_for_list(archived: params[:archived]).order_by(sort) groups.with_selects_for_list(archived: params[:archived]).order_by(sort)
end end
...@@ -140,6 +142,7 @@ class GroupDescendantsFinder ...@@ -140,6 +142,7 @@ class GroupDescendantsFinder
else else
direct_child_projects direct_child_projects
end end
projects.with_route.order_by(sort) projects.with_route.order_by(sort)
end end
......
...@@ -34,6 +34,7 @@ class GroupProjectsFinder < ProjectsFinder ...@@ -34,6 +34,7 @@ class GroupProjectsFinder < ProjectsFinder
else else
collection_without_user collection_without_user
end end
union(projects) union(projects)
end end
......
...@@ -46,7 +46,7 @@ module BlobHelper ...@@ -46,7 +46,7 @@ module BlobHelper
end end
def ide_edit_text def ide_edit_text
"#{_('Multi Edit')} <span class='label label-primary'>#{_('Beta')}</span>".html_safe "#{_('Web IDE')}"
end end
def ide_blob_link(project = @project, ref = @ref, path = @path, options = {}) def ide_blob_link(project = @project, ref = @ref, path = @path, options = {})
......
...@@ -203,6 +203,7 @@ module MarkupHelper ...@@ -203,6 +203,7 @@ module MarkupHelper
node.content = node.content.truncate(num_remaining) node.content = node.content.truncate(num_remaining)
truncated = true truncated = true
end end
content_length += node.content.length content_length += node.content.length
end end
......
...@@ -12,6 +12,7 @@ module NavHelper ...@@ -12,6 +12,7 @@ module NavHelper
current_path?('projects/merge_requests/conflicts#show') || current_path?('projects/merge_requests/conflicts#show') ||
current_path?('issues#show') || current_path?('issues#show') ||
current_path?('milestones#show') current_path?('milestones#show')
if cookies[:collapsed_gutter] == 'true' if cookies[:collapsed_gutter] == 'true'
%w[page-gutter right-sidebar-collapsed] %w[page-gutter right-sidebar-collapsed]
else else
......
...@@ -89,6 +89,7 @@ module SnippetsHelper ...@@ -89,6 +89,7 @@ module SnippetsHelper
snippet_chunk = [lined_content[line_number]] snippet_chunk = [lined_content[line_number]]
snippet_start_line = line_number snippet_start_line = line_number
end end
last_line = line_number last_line = line_number
end end
# Add final chunk to chunk array # Add final chunk to chunk array
......
...@@ -58,6 +58,7 @@ module SubmoduleHelper ...@@ -58,6 +58,7 @@ module SubmoduleHelper
url_no_dotgit = url.chomp('.git') url_no_dotgit = url.chomp('.git')
return true if url_no_dotgit == [Gitlab.config.gitlab.url, '/', namespace, '/', return true if url_no_dotgit == [Gitlab.config.gitlab.url, '/', namespace, '/',
project].join('') project].join('')
url_with_dotgit = url_no_dotgit + '.git' url_with_dotgit = url_no_dotgit + '.git'
url_with_dotgit == Gitlab::Shell.new.url_to_repo([namespace, '/', project].join('')) url_with_dotgit == Gitlab::Shell.new.url_to_repo([namespace, '/', project].join(''))
end end
......
...@@ -30,6 +30,7 @@ module TodosHelper ...@@ -30,6 +30,7 @@ module TodosHelper
else else
todo.target_reference todo.target_reference
end end
link_to text, todo_target_path(todo), class: 'has-tooltip', title: todo.target.title link_to text, todo_target_path(todo), class: 'has-tooltip', title: todo.target.title
end end
......
...@@ -418,6 +418,7 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -418,6 +418,7 @@ class ApplicationSetting < ActiveRecord::Base
super(group_full_path) super(group_full_path)
Gitlab::PerformanceBar.expire_allowed_user_ids_cache Gitlab::PerformanceBar.expire_allowed_user_ids_cache
end end
return return
end end
......
...@@ -2,8 +2,9 @@ module Ci ...@@ -2,8 +2,9 @@ module Ci
class PipelineSchedule < ActiveRecord::Base class PipelineSchedule < ActiveRecord::Base
extend Gitlab::Ci::Model extend Gitlab::Ci::Model
include Importable include Importable
include IgnorableColumn
acts_as_paranoid ignore_column :deleted_at
belongs_to :project belongs_to :project
belongs_to :owner, class_name: 'User' belongs_to :owner, class_name: 'User'
......
module Ci module Ci
class Trigger < ActiveRecord::Base class Trigger < ActiveRecord::Base
extend Gitlab::Ci::Model extend Gitlab::Ci::Model
include IgnorableColumn
acts_as_paranoid ignore_column :deleted_at
belongs_to :project belongs_to :project
belongs_to :owner, class_name: "User" belongs_to :owner, class_name: "User"
......
...@@ -10,7 +10,6 @@ module InternalId ...@@ -10,7 +10,6 @@ module InternalId
if iid.blank? if iid.blank?
parent = project || group parent = project || group
records = parent.public_send(self.class.name.tableize) # rubocop:disable GitlabSecurity/PublicSend records = parent.public_send(self.class.name.tableize) # rubocop:disable GitlabSecurity/PublicSend
records = records.with_deleted if self.paranoid?
max_iid = records.maximum(:iid) max_iid = records.maximum(:iid)
self.iid = max_iid.to_i + 1 self.iid = max_iid.to_i + 1
......
...@@ -314,6 +314,7 @@ module Issuable ...@@ -314,6 +314,7 @@ module Issuable
includes = [] includes = []
includes << :author unless notes.authors_loaded? includes << :author unless notes.authors_loaded?
includes << :award_emoji unless notes.award_emojis_loaded? includes << :award_emoji unless notes.award_emojis_loaded?
if includes.any? if includes.any?
notes.includes(includes) notes.includes(includes)
else else
......
...@@ -25,6 +25,7 @@ module LoadedInGroupList ...@@ -25,6 +25,7 @@ module LoadedInGroupList
base_count = projects.project(Arel.star.count.as('preloaded_project_count')) base_count = projects.project(Arel.star.count.as('preloaded_project_count'))
.where(projects[:namespace_id].eq(namespaces[:id])) .where(projects[:namespace_id].eq(namespaces[:id]))
if archived == 'only' if archived == 'only'
base_count.where(projects[:archived].eq(true)) base_count.where(projects[:archived].eq(true))
elsif Gitlab::Utils.to_boolean(archived) elsif Gitlab::Utils.to_boolean(archived)
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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