Commit 3c633844 authored by Stan Hu's avatar Stan Hu

Merge branch 'master' into sh-add-object-storage-qa

parents b041dc20 ba99dfcd
...@@ -4,7 +4,9 @@ entry. ...@@ -4,7 +4,9 @@ entry.
## 11.2.3 (2018-08-28) ## 11.2.3 (2018-08-28)
- No changes. ### Fixed (1 change)
- Fixed cache invalidation issue with diff lines from 11.2.2.
## 11.2.2 (2018-08-27) ## 11.2.2 (2018-08-27)
...@@ -269,7 +271,9 @@ entry. ...@@ -269,7 +271,9 @@ entry.
## 11.1.6 (2018-08-28) ## 11.1.6 (2018-08-28)
- No changes. ### Fixed (1 change)
- Fixed cache invalidation issue with diff lines from 11.2.2.
## 11.1.5 (2018-08-27) ## 11.1.5 (2018-08-27)
......
...@@ -195,6 +195,9 @@ gem 're2', '~> 1.1.1' ...@@ -195,6 +195,9 @@ gem 're2', '~> 1.1.1'
gem 'version_sorter', '~> 2.1.0' gem 'version_sorter', '~> 2.1.0'
# Export Ruby Regex to Javascript
gem 'js_regex', '~> 2.2.1'
# User agent parsing # User agent parsing
gem 'device_detector' gem 'device_detector'
...@@ -365,7 +368,7 @@ group :development, :test do ...@@ -365,7 +368,7 @@ group :development, :test do
gem 'benchmark-ips', '~> 2.3.0', require: false gem 'benchmark-ips', '~> 2.3.0', require: false
gem 'license_finder', '~> 3.1', require: false gem 'license_finder', '~> 5.4', require: false
gem 'knapsack', '~> 1.16' gem 'knapsack', '~> 1.16'
gem 'activerecord_sane_schema_dumper', gem_versions['activerecord_sane_schema_dumper'] gem 'activerecord_sane_schema_dumper', gem_versions['activerecord_sane_schema_dumper']
......
...@@ -86,7 +86,6 @@ GEM ...@@ -86,7 +86,6 @@ GEM
bindata (2.4.3) bindata (2.4.3)
binding_of_caller (0.7.2) binding_of_caller (0.7.2)
debug_inspector (>= 0.0.1) debug_inspector (>= 0.0.1)
blankslate (2.1.2.4)
bootsnap (1.3.1) bootsnap (1.3.1)
msgpack (~> 1.0) msgpack (~> 1.0)
bootstrap_form (2.7.0) bootstrap_form (2.7.0)
...@@ -428,6 +427,8 @@ GEM ...@@ -428,6 +427,8 @@ GEM
multipart-post multipart-post
oauth (~> 0.5, >= 0.5.0) oauth (~> 0.5, >= 0.5.0)
jquery-atwho-rails (1.3.2) jquery-atwho-rails (1.3.2)
js_regex (2.2.1)
regexp_parser (>= 0.4.11, <= 0.5.0)
json (1.8.6) json (1.8.6)
json-jwt (1.9.4) json-jwt (1.9.4)
activesupport activesupport
...@@ -463,13 +464,12 @@ GEM ...@@ -463,13 +464,12 @@ GEM
actionmailer (>= 3.2) actionmailer (>= 3.2)
letter_opener (~> 1.0) letter_opener (~> 1.0)
railties (>= 3.2) railties (>= 3.2)
license_finder (3.1.1) license_finder (5.4.0)
bundler bundler
httparty
rubyzip rubyzip
thor thor
toml (= 0.1.2) toml (= 0.2.0)
with_env (> 1.0) with_env (= 1.1.0)
xml-simple xml-simple
licensee (8.9.2) licensee (8.9.2)
rugged (~> 0.24) rugged (~> 0.24)
...@@ -587,8 +587,7 @@ GEM ...@@ -587,8 +587,7 @@ GEM
parallel (1.12.1) parallel (1.12.1)
parser (2.5.1.0) parser (2.5.1.0)
ast (~> 2.4.0) ast (~> 2.4.0)
parslet (1.5.0) parslet (1.8.2)
blankslate (~> 2.0)
path_expander (1.0.2) path_expander (1.0.2)
peek (1.0.1) peek (1.0.1)
concurrent-ruby (>= 0.9.0) concurrent-ruby (>= 0.9.0)
...@@ -726,6 +725,7 @@ GEM ...@@ -726,6 +725,7 @@ GEM
redis-store (>= 1.2, < 2) redis-store (>= 1.2, < 2)
redis-store (1.4.1) redis-store (1.4.1)
redis (>= 2.2, < 5) redis (>= 2.2, < 5)
regexp_parser (0.5.0)
representable (3.0.4) representable (3.0.4)
declarative (< 0.1.0) declarative (< 0.1.0)
declarative-option (< 0.2.0) declarative-option (< 0.2.0)
...@@ -907,8 +907,8 @@ GEM ...@@ -907,8 +907,8 @@ GEM
tilt (2.0.8) tilt (2.0.8)
timecop (0.8.1) timecop (0.8.1)
timfel-krb5-auth (0.8.3) timfel-krb5-auth (0.8.3)
toml (0.1.2) toml (0.2.0)
parslet (~> 1.5.0) parslet (~> 1.8.0)
toml-rb (1.0.0) toml-rb (1.0.0)
citrus (~> 3.0, > 3.0) citrus (~> 3.0, > 3.0)
trollop (2.1.3) trollop (2.1.3)
...@@ -1074,13 +1074,14 @@ DEPENDENCIES ...@@ -1074,13 +1074,14 @@ DEPENDENCIES
influxdb (~> 0.2) influxdb (~> 0.2)
jira-ruby (~> 1.4) jira-ruby (~> 1.4)
jquery-atwho-rails (~> 1.3.2) jquery-atwho-rails (~> 1.3.2)
js_regex (~> 2.2.1)
json-schema (~> 2.8.0) json-schema (~> 2.8.0)
jwt (~> 1.5.6) jwt (~> 1.5.6)
kaminari (~> 1.0) kaminari (~> 1.0)
knapsack (~> 1.16) knapsack (~> 1.16)
kubeclient (~> 3.1.0) kubeclient (~> 3.1.0)
letter_opener_web (~> 1.3.0) letter_opener_web (~> 1.3.0)
license_finder (~> 3.1) license_finder (~> 5.4)
licensee (~> 8.9) licensee (~> 8.9)
lograge (~> 0.5) lograge (~> 0.5)
loofah (~> 2.2) loofah (~> 2.2)
...@@ -1201,4 +1202,4 @@ DEPENDENCIES ...@@ -1201,4 +1202,4 @@ DEPENDENCIES
wikicloth (= 0.8.1) wikicloth (= 0.8.1)
BUNDLED WITH BUNDLED WITH
1.16.3 1.16.4
...@@ -89,7 +89,6 @@ GEM ...@@ -89,7 +89,6 @@ GEM
bindata (2.4.3) bindata (2.4.3)
binding_of_caller (0.7.2) binding_of_caller (0.7.2)
debug_inspector (>= 0.0.1) debug_inspector (>= 0.0.1)
blankslate (2.1.2.4)
bootsnap (1.3.1) bootsnap (1.3.1)
msgpack (~> 1.0) msgpack (~> 1.0)
bootstrap_form (2.7.0) bootstrap_form (2.7.0)
...@@ -431,6 +430,8 @@ GEM ...@@ -431,6 +430,8 @@ GEM
multipart-post multipart-post
oauth (~> 0.5, >= 0.5.0) oauth (~> 0.5, >= 0.5.0)
jquery-atwho-rails (1.3.2) jquery-atwho-rails (1.3.2)
js_regex (2.2.1)
regexp_parser (>= 0.4.11, <= 0.5.0)
json (1.8.6) json (1.8.6)
json-jwt (1.9.4) json-jwt (1.9.4)
activesupport activesupport
...@@ -466,13 +467,12 @@ GEM ...@@ -466,13 +467,12 @@ GEM
actionmailer (>= 3.2) actionmailer (>= 3.2)
letter_opener (~> 1.0) letter_opener (~> 1.0)
railties (>= 3.2) railties (>= 3.2)
license_finder (3.1.1) license_finder (5.4.0)
bundler bundler
httparty
rubyzip rubyzip
thor thor
toml (= 0.1.2) toml (= 0.2.0)
with_env (> 1.0) with_env (= 1.1.0)
xml-simple xml-simple
licensee (8.9.2) licensee (8.9.2)
rugged (~> 0.24) rugged (~> 0.24)
...@@ -591,8 +591,7 @@ GEM ...@@ -591,8 +591,7 @@ GEM
parallel (1.12.1) parallel (1.12.1)
parser (2.5.1.0) parser (2.5.1.0)
ast (~> 2.4.0) ast (~> 2.4.0)
parslet (1.5.0) parslet (1.8.2)
blankslate (~> 2.0)
path_expander (1.0.2) path_expander (1.0.2)
peek (1.0.1) peek (1.0.1)
concurrent-ruby (>= 0.9.0) concurrent-ruby (>= 0.9.0)
...@@ -735,6 +734,7 @@ GEM ...@@ -735,6 +734,7 @@ GEM
redis-store (>= 1.2, < 2) redis-store (>= 1.2, < 2)
redis-store (1.4.1) redis-store (1.4.1)
redis (>= 2.2, < 5) redis (>= 2.2, < 5)
regexp_parser (0.5.0)
representable (3.0.4) representable (3.0.4)
declarative (< 0.1.0) declarative (< 0.1.0)
declarative-option (< 0.2.0) declarative-option (< 0.2.0)
...@@ -914,8 +914,8 @@ GEM ...@@ -914,8 +914,8 @@ GEM
tilt (2.0.8) tilt (2.0.8)
timecop (0.8.1) timecop (0.8.1)
timfel-krb5-auth (0.8.3) timfel-krb5-auth (0.8.3)
toml (0.1.2) toml (0.2.0)
parslet (~> 1.5.0) parslet (~> 1.8.0)
toml-rb (1.0.0) toml-rb (1.0.0)
citrus (~> 3.0, > 3.0) citrus (~> 3.0, > 3.0)
trollop (2.1.3) trollop (2.1.3)
...@@ -1084,13 +1084,14 @@ DEPENDENCIES ...@@ -1084,13 +1084,14 @@ DEPENDENCIES
influxdb (~> 0.2) influxdb (~> 0.2)
jira-ruby (~> 1.4) jira-ruby (~> 1.4)
jquery-atwho-rails (~> 1.3.2) jquery-atwho-rails (~> 1.3.2)
js_regex (~> 2.2.1)
json-schema (~> 2.8.0) json-schema (~> 2.8.0)
jwt (~> 1.5.6) jwt (~> 1.5.6)
kaminari (~> 1.0) kaminari (~> 1.0)
knapsack (~> 1.16) knapsack (~> 1.16)
kubeclient (~> 3.1.0) kubeclient (~> 3.1.0)
letter_opener_web (~> 1.3.0) letter_opener_web (~> 1.3.0)
license_finder (~> 3.1) license_finder (~> 5.4)
licensee (~> 8.9) licensee (~> 8.9)
lograge (~> 0.5) lograge (~> 0.5)
loofah (~> 2.2) loofah (~> 2.2)
...@@ -1211,4 +1212,4 @@ DEPENDENCIES ...@@ -1211,4 +1212,4 @@ DEPENDENCIES
wikicloth (= 0.8.1) wikicloth (= 0.8.1)
BUNDLED WITH BUNDLED WITH
1.16.3 1.16.4
...@@ -10,6 +10,7 @@ const hideFlash = (flashEl, fadeTransition = true) => { ...@@ -10,6 +10,7 @@ const hideFlash = (flashEl, fadeTransition = true) => {
flashEl.addEventListener('transitionend', () => { flashEl.addEventListener('transitionend', () => {
flashEl.remove(); flashEl.remove();
window.dispatchEvent(new Event('resize'));
if (document.body.classList.contains('flash-shown')) document.body.classList.remove('flash-shown'); if (document.body.classList.contains('flash-shown')) document.body.classList.remove('flash-shown');
}, { }, {
once: true, once: true,
......
...@@ -78,13 +78,13 @@ export default { ...@@ -78,13 +78,13 @@ export default {
</script> </script>
<template> <template>
<article class="ide"> <article class="ide position-relative d-flex flex-column align-items-stretch">
<error-message <error-message
v-if="errorMessage" v-if="errorMessage"
:message="errorMessage" :message="errorMessage"
/> />
<div <div
class="ide-view" class="ide-view flex-grow d-flex"
> >
<find-file <find-file
v-show="fileFindVisible" v-show="fileFindVisible"
......
...@@ -24,12 +24,6 @@ export default { ...@@ -24,12 +24,6 @@ export default {
default: null, default: null,
}, },
}, },
mounted() {
this.$refs.fileUpload.addEventListener('change', this.openFile);
},
beforeDestroy() {
this.$refs.fileUpload.removeEventListener('change', this.openFile);
},
methods: { methods: {
createFile(target, file, isText) { createFile(target, file, isText) {
const { name } = file; const { name } = file;
...@@ -85,6 +79,8 @@ export default { ...@@ -85,6 +79,8 @@ export default {
ref="fileUpload" ref="fileUpload"
type="file" type="file"
class="hidden" class="hidden"
multiple
@change="openFile"
/> />
</div> </div>
</template> </template>
...@@ -95,16 +95,18 @@ export default { ...@@ -95,16 +95,18 @@ export default {
return this.file.changed || this.file.tempFile || this.file.staged; return this.file.changed || this.file.tempFile || this.file.staged;
}, },
}, },
watch: {
'file.active': function fileActiveWatch(active) {
if (this.file.type === 'blob' && active) {
this.scrollIntoView();
}
},
},
mounted() { mounted() {
if (this.hasPathAtCurrentRoute()) { if (this.hasPathAtCurrentRoute()) {
this.scrollIntoView(true); this.scrollIntoView(true);
} }
}, },
updated() {
if (this.file.type === 'blob' && this.file.active) {
this.scrollIntoView();
}
},
methods: { methods: {
...mapActions(['toggleTreeOpen']), ...mapActions(['toggleTreeOpen']),
clickFile() { clickFile() {
......
import { __ } from '~/locale';
export const PLACEHOLDER_USER_EXTERNAL_DEFAULT_TRUE = __('Regex pattern');
export const PLACEHOLDER_USER_EXTERNAL_DEFAULT_FALSE = __('To define internal users, first enable new users set to external');
function setUserInternalRegexPlaceholder(checkbox) {
const userInternalRegex = document.getElementById('application_setting_user_default_internal_regex');
if (checkbox && userInternalRegex) {
if (checkbox.checked) {
userInternalRegex.readOnly = false;
userInternalRegex.placeholder = PLACEHOLDER_USER_EXTERNAL_DEFAULT_TRUE;
} else {
userInternalRegex.readOnly = true;
userInternalRegex.placeholder = PLACEHOLDER_USER_EXTERNAL_DEFAULT_FALSE;
}
}
}
export default function initUserInternalRegexPlaceholder() {
const checkbox = document.getElementById('application_setting_user_default_external');
setUserInternalRegexPlaceholder(checkbox);
checkbox.addEventListener('change', () => {
setUserInternalRegexPlaceholder(checkbox);
});
}
import initAdmin from './admin'; import initAdmin from './admin';
import initUserInternalRegexPlaceholder from './application_settings/account_and_limits';
document.addEventListener('DOMContentLoaded', initAdmin); document.addEventListener('DOMContentLoaded', () => {
initAdmin();
initUserInternalRegexPlaceholder();
});
import $ from 'jquery';
export default class UserInternalRegexHandler {
constructor() {
this.regexPattern = $('[data-user-internal-regex-pattern]').data('user-internal-regex-pattern');
if (this.regexPattern && this.regexPattern !== '') {
this.regexOptions = $('[data-user-internal-regex-options]').data('user-internal-regex-options');
this.external = $('#user_external');
this.warningMessage = $('#warning_external_automatically_set');
this.addListenerToEmailField();
this.addListenerToUserExternalCheckbox();
}
}
addListenerToEmailField() {
$('#user_email').on('input', (event) => {
this.setExternalCheckbox(event.currentTarget.value);
});
}
addListenerToUserExternalCheckbox() {
this.external.on('click', () => {
this.warningMessage.addClass('hidden');
});
}
isEmailInternal(email) {
const regex = new RegExp(this.regexPattern, this.regexOptions);
return regex.test(email);
}
setExternalCheckbox(email) {
const isChecked = this.external.prop('checked');
if (this.isEmailInternal(email)) {
if (isChecked) {
this.external.prop('checked', false);
this.warningMessage.removeClass('hidden');
}
} else if (!isChecked) {
this.external.prop('checked', true);
this.warningMessage.addClass('hidden');
}
}
}
document.addEventListener('DOMContentLoaded', () => {
// eslint-disable-next-line
new UserInternalRegexHandler();
});
...@@ -3,6 +3,6 @@ gl-emoji { ...@@ -3,6 +3,6 @@ gl-emoji {
display: inline-flex; display: inline-flex;
vertical-align: middle; vertical-align: middle;
font-family: "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; font-family: "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
font-size: 1.5em; font-size: 1.4em;
line-height: 0.9; line-height: 1em;
} }
...@@ -111,3 +111,42 @@ body { ...@@ -111,3 +111,42 @@ body {
.with-performance-bar .layout-page { .with-performance-bar .layout-page {
margin-top: $header-height + $performance-bar-height; margin-top: $header-height + $performance-bar-height;
} }
.fullscreen-layout {
padding-top: 0;
height: 100vh;
width: 100%;
display: flex;
flex-direction: column;
align-items: stretch;
overflow: hidden;
> #js-peek,
> .navbar-gitlab {
position: static;
top: auto;
}
.flash-container {
margin-top: 0;
margin-bottom: 0;
}
.alert-wrapper .flash-container .flash-alert:last-child,
.alert-wrapper .flash-container .flash-notice:last-child {
margin-bottom: 0;
}
.content-wrapper {
margin-top: 0;
padding-bottom: 0;
flex: 1;
min-height: 0;
}
&.flash-shown {
.content-wrapper {
margin-top: 0;
}
}
}
...@@ -327,7 +327,7 @@ h6 { ...@@ -327,7 +327,7 @@ h6 {
pre { pre {
font-family: $monospace-font; font-family: $monospace-font;
display: block; display: block;
padding: $gl-padding-8; padding: $gl-padding-8 $input-horizontal-padding;
margin: 0 0 $gl-padding-8; margin: 0 0 $gl-padding-8;
font-size: 13px; font-size: 13px;
word-break: break-all; word-break: break-all;
......
...@@ -236,6 +236,7 @@ $gl-vert-padding: 6px; ...@@ -236,6 +236,7 @@ $gl-vert-padding: 6px;
$gl-padding-top: 10px; $gl-padding-top: 10px;
$gl-sidebar-padding: 22px; $gl-sidebar-padding: 22px;
$gl-bar-padding: 3px; $gl-bar-padding: 3px;
$input-horizontal-padding: 12px;
/* /*
* Misc * Misc
......
...@@ -28,11 +28,10 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding; ...@@ -28,11 +28,10 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
.ide-view { .ide-view {
position: relative; position: relative;
display: flex;
height: calc(100vh - #{$header-height});
margin-top: 0; margin-top: 0;
padding-bottom: $ide-statusbar-height; padding-bottom: $ide-statusbar-height;
color: $gl-text-color; color: $gl-text-color;
min-height: 0; // firefox fix
&.is-collapsed { &.is-collapsed {
.ide-file-list { .ide-file-list {
...@@ -50,7 +49,7 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding; ...@@ -50,7 +49,7 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
flex: 1; flex: 1;
min-height: 0; min-height: 0; // firefox fix
.file { .file {
height: 32px; height: 32px;
...@@ -357,7 +356,7 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding; ...@@ -357,7 +356,7 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
.multi-file-editor-holder { .multi-file-editor-holder {
height: 100%; height: 100%;
min-height: 0; min-height: 0; // firefox fix
&.is-readonly, &.is-readonly,
.editor.original { .editor.original {
...@@ -546,7 +545,7 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding; ...@@ -546,7 +545,7 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
border-left: 1px solid $white-dark; border-left: 1px solid $white-dark;
border-top: 1px solid $white-dark; border-top: 1px solid $white-dark;
border-top-left-radius: $border-radius-small; border-top-left-radius: $border-radius-small;
min-height: 0; min-height: 0; // firefox fix
} }
} }
...@@ -758,7 +757,7 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding; ...@@ -758,7 +757,7 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
.ide-loading { .ide-loading {
display: flex; display: flex;
height: 100vh; height: 100%;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
...@@ -772,60 +771,7 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding; ...@@ -772,60 +771,7 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
.ide { .ide {
overflow: hidden; overflow: hidden;
flex: 1;
&.nav-only {
padding-top: $header-height;
.with-performance-bar & {
padding-top: $header-height + $performance-bar-height;
}
.flash-container {
margin-top: 0;
margin-bottom: 0;
}
.alert-wrapper .flash-container .flash-alert:last-child,
.alert-wrapper .flash-container .flash-notice:last-child {
margin-bottom: 0;
}
.content-wrapper {
margin-top: 0;
padding-bottom: 0;
}
&.flash-shown {
.content-wrapper {
margin-top: 0;
}
.ide-view {
height: calc(100vh - #{$header-height + $flash-height});
}
}
}
}
.with-performance-bar .ide.nav-only {
.flash-container {
margin-top: 0;
}
.content-wrapper {
margin-top: 0;
padding-bottom: 0;
}
.ide-view {
height: calc(100vh - #{$header-height + $performance-bar-height});
}
&.flash-shown {
.ide-view {
height: calc(100vh - #{$header-height + $performance-bar-height + $flash-height});
}
}
} }
.drag-handle { .drag-handle {
...@@ -1199,7 +1145,7 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding; ...@@ -1199,7 +1145,7 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
} }
.avatar-container { .avatar-container {
flex: initial; flex: 0 0 auto;
margin-right: 0; margin-right: 0;
} }
...@@ -1209,7 +1155,7 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding; ...@@ -1209,7 +1155,7 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
} }
.ide-context-body { .ide-context-body {
min-height: 0; min-height: 0; // firefox fix
} }
.ide-sidebar-project-title { .ide-sidebar-project-title {
......
...@@ -25,10 +25,6 @@ ...@@ -25,10 +25,6 @@
color: $gl-text-color; color: $gl-text-color;
border-radius: 0 0 3px 3px; border-radius: 0 0 3px 3px;
.code {
padding: 0;
}
.unfold { .unfold {
cursor: pointer; cursor: pointer;
} }
......
...@@ -141,6 +141,9 @@ ul.notes { ...@@ -141,6 +141,9 @@ ul.notes {
} }
.note-body { .note-body {
overflow-x: auto;
overflow-y: hidden;
.note-text { .note-text {
@include md-typography; @include md-typography;
// Reset ul style types since we're nested inside a ul already // Reset ul style types since we're nested inside a ul already
......
# frozen_string_literal: true
module SendsBlob
extend ActiveSupport::Concern
included do
include BlobHelper
include SendFileUpload
end
def send_blob(blob, params = {})
if blob
headers['X-Content-Type-Options'] = 'nosniff'
return if cached_blob?(blob)
if blob.stored_externally?
send_lfs_object(blob)
else
send_git_blob(repository, blob, params)
end
else
render_404
end
end
private
def cached_blob?(blob)
stale = stale?(etag: blob.id) # The #stale? method sets cache headers.
# Because we are opinionated we set the cache headers ourselves.
response.cache_control[:public] = project.public?
response.cache_control[:max_age] =
if @ref && @commit && @ref == @commit.id # rubocop:disable Gitlab/ModuleWithInstanceVariables
# This is a link to a commit by its commit SHA. That means that the blob
# is immutable. The only reason to invalidate the cache is if the commit
# was deleted or if the user lost access to the repository.
Blob::CACHE_TIME_IMMUTABLE
else
# A branch or tag points at this blob. That means that the expected blob
# value may change over time.
Blob::CACHE_TIME
end
response.etag = blob.id
!stale
end
def send_lfs_object(blob)
lfs_object = find_lfs_object(blob)
if lfs_object && lfs_object.project_allowed_access?(project)
send_upload(lfs_object.file, attachment: blob.name)
else
render_404
end
end
def find_lfs_object(blob)
lfs_object = LfsObject.find_by_oid(blob.lfs_oid)
if lfs_object && lfs_object.file.exists?
lfs_object
else
nil
end
end
end
class IdeController < ApplicationController class IdeController < ApplicationController
layout 'nav_only' layout 'fullscreen'
def index def index
end end
......
class Projects::AvatarsController < Projects::ApplicationController class Projects::AvatarsController < Projects::ApplicationController
include BlobHelper include SendsBlob
before_action :authorize_admin_project!, only: [:destroy] before_action :authorize_admin_project!, only: [:destroy]
def show def show
@blob = @repository.blob_at_branch(@repository.root_ref, @project.avatar_in_git) @blob = @repository.blob_at_branch(@repository.root_ref, @project.avatar_in_git)
if @blob
headers['X-Content-Type-Options'] = 'nosniff'
return if cached_blob? send_blob(@blob)
send_git_blob @repository, @blob
else
render_404
end
end end
def destroy def destroy
@project.remove_avatar! @project.remove_avatar!
@project.save @project.save
redirect_to edit_project_path(@project, anchor: 'js-general-project-settings'), status: :found redirect_to edit_project_path(@project, anchor: 'js-general-project-settings'), status: :found
......
# Controller for viewing a file's raw # Controller for viewing a file's raw
class Projects::RawController < Projects::ApplicationController class Projects::RawController < Projects::ApplicationController
include ExtractsPath include ExtractsPath
include BlobHelper include SendsBlob
include SendFileUpload
before_action :require_non_empty_project before_action :require_non_empty_project
before_action :assign_ref_vars before_action :assign_ref_vars
...@@ -10,39 +9,7 @@ class Projects::RawController < Projects::ApplicationController ...@@ -10,39 +9,7 @@ class Projects::RawController < Projects::ApplicationController
def show def show
@blob = @repository.blob_at(@commit.id, @path) @blob = @repository.blob_at(@commit.id, @path)
if @blob
headers['X-Content-Type-Options'] = 'nosniff'
return if cached_blob? send_blob(@blob, inline: (params[:inline] != 'false'))
if @blob.stored_externally?
send_lfs_object
else
send_git_blob @repository, @blob, inline: (params[:inline] != 'false')
end
else
render_404
end
end
private
def send_lfs_object
lfs_object = find_lfs_object
if lfs_object && lfs_object.project_allowed_access?(@project)
send_upload(lfs_object.file, attachment: @blob.name)
else
render_404
end
end
def find_lfs_object
lfs_object = LfsObject.find_by_oid(@blob.lfs_oid)
if lfs_object && lfs_object.file.exists?
lfs_object
else
nil
end
end end
end end
...@@ -255,6 +255,7 @@ module ApplicationSettingsHelper ...@@ -255,6 +255,7 @@ module ApplicationSettingsHelper
:instance_statistics_visibility_private, :instance_statistics_visibility_private,
:user_default_external, :user_default_external,
:user_show_add_ssh_key_message, :user_show_add_ssh_key_message,
:user_default_internal_regex,
:user_oauth_applications, :user_oauth_applications,
:version_check_enabled, :version_check_enabled,
:web_ide_clientside_preview_enabled :web_ide_clientside_preview_enabled
......
...@@ -157,28 +157,6 @@ module BlobHelper ...@@ -157,28 +157,6 @@ module BlobHelper
end end
end end
def cached_blob?
stale = stale?(etag: @blob.id) # The #stale? method sets cache headers.
# Because we are opionated we set the cache headers ourselves.
response.cache_control[:public] = @project.public?
response.cache_control[:max_age] =
if @ref && @commit && @ref == @commit.id
# This is a link to a commit by its commit SHA. That means that the blob
# is immutable. The only reason to invalidate the cache is if the commit
# was deleted or if the user lost access to the repository.
Blob::CACHE_TIME_IMMUTABLE
else
# A branch or tag points at this blob. That means that the expected blob
# value may change over time.
Blob::CACHE_TIME
end
response.etag = @blob.id
!stale
end
def licenses_for_select def licenses_for_select
return @licenses_for_select if defined?(@licenses_for_select) return @licenses_for_select if defined?(@licenses_for_select)
......
...@@ -64,8 +64,7 @@ module SubmoduleHelper ...@@ -64,8 +64,7 @@ module SubmoduleHelper
end end
def relative_self_url?(url) def relative_self_url?(url)
# (./)?(../repo.git) || (./)?(../../project/repo.git) ) url.start_with?('../', './')
url =~ %r{\A((\./)?(\.\./))(?!(\.\.)|(.*/)).*(\.git)?\z} || url =~ %r{\A((\./)?(\.\./){2})(?!(\.\.))([^/]*)/(?!(\.\.)|(.*/)).*(\.git)?\z}
end end
def standard_links(host, namespace, project, commit) def standard_links(host, namespace, project, commit)
...@@ -73,25 +72,29 @@ module SubmoduleHelper ...@@ -73,25 +72,29 @@ module SubmoduleHelper
[base, [base, '/tree/', commit].join('')] [base, [base, '/tree/', commit].join('')]
end end
def relative_self_links(url, commit, project) def relative_self_links(relative_path, commit, project)
url.rstrip! relative_path.rstrip!
# Map relative links to a namespace and project absolute_project_path = "/" + project.full_path
# For example:
# ../bar.git -> same namespace, repo bar # Resolve `relative_path` to target path
# ../foo/bar.git -> namespace foo, repo bar # Assuming `absolute_project_path` is `/g1/p1`:
# ../../foo/bar/baz.git -> namespace bar, repo baz # ../p2.git -> /g1/p2
components = url.split('/') # ../g2/p3.git -> /g1/g2/p3
base = components.pop.gsub(/.git$/, '') # ../../g3/g4/p4.git -> /g3/g4/p4
namespace = components.pop.gsub(/^\.\.$/, '') submodule_project_path = File.absolute_path(relative_path, absolute_project_path)
target_namespace_path = File.dirname(submodule_project_path)
if namespace.empty?
namespace = project.namespace.full_path if target_namespace_path == '/' || target_namespace_path.start_with?(absolute_project_path)
return [nil, nil]
end end
target_namespace_path.sub!(%r{^/}, '')
submodule_base = File.basename(submodule_project_path, '.git')
begin begin
[ [
namespace_project_path(namespace, base), namespace_project_path(target_namespace_path, submodule_base),
namespace_project_tree_path(namespace, base, commit) namespace_project_tree_path(target_namespace_path, submodule_base, commit)
] ]
rescue ActionController::UrlGenerationError rescue ActionController::UrlGenerationError
[nil, nil] [nil, nil]
......
...@@ -23,6 +23,17 @@ module UsersHelper ...@@ -23,6 +23,17 @@ module UsersHelper
profile_tabs.include?(tab) profile_tabs.include?(tab)
end end
def user_internal_regex_data
settings = Gitlab::CurrentSettings.current_application_settings
pattern, options = if settings.user_default_internal_regex_enabled?
regex = settings.user_default_internal_regex_instance
JsRegex.new(regex).to_h.slice(:source, :options).values
end
{ user_internal_regex_pattern: pattern, user_internal_regex_options: options }
end
def current_user_menu_items def current_user_menu_items
@current_user_menu_items ||= get_current_user_menu_items @current_user_menu_items ||= get_current_user_menu_items
end end
......
...@@ -192,6 +192,8 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -192,6 +192,8 @@ class ApplicationSetting < ActiveRecord::Base
numericality: { less_than_or_equal_to: :gitaly_timeout_default }, numericality: { less_than_or_equal_to: :gitaly_timeout_default },
if: :gitaly_timeout_default if: :gitaly_timeout_default
validates :user_default_internal_regex, js_regex: true, allow_nil: true
SUPPORTED_KEY_TYPES.each do |type| SUPPORTED_KEY_TYPES.each do |type|
validates :"#{type}_key_restriction", presence: true, key_restriction: { type: type } validates :"#{type}_key_restriction", presence: true, key_restriction: { type: type }
end end
...@@ -299,6 +301,7 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -299,6 +301,7 @@ class ApplicationSetting < ActiveRecord::Base
usage_ping_enabled: Settings.gitlab['usage_ping_enabled'], usage_ping_enabled: Settings.gitlab['usage_ping_enabled'],
instance_statistics_visibility_private: false, instance_statistics_visibility_private: false,
user_default_external: false, user_default_external: false,
user_default_internal_regex: nil,
user_show_add_ssh_key_message: true user_show_add_ssh_key_message: true
} }
end end
...@@ -435,6 +438,14 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -435,6 +438,14 @@ class ApplicationSetting < ActiveRecord::Base
password_authentication_enabled_for_web? || password_authentication_enabled_for_git? password_authentication_enabled_for_web? || password_authentication_enabled_for_git?
end end
def user_default_internal_regex_enabled?
user_default_external? && user_default_internal_regex.present?
end
def user_default_internal_regex_instance
Regexp.new(user_default_internal_regex, Regexp::IGNORECASE)
end
delegate :terms, to: :latest_terms, allow_nil: true delegate :terms, to: :latest_terms, allow_nil: true
def latest_terms def latest_terms
@latest_terms ||= Term.latest @latest_terms ||= Term.latest
......
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
module AtomicInternalId module AtomicInternalId
extend ActiveSupport::Concern extend ActiveSupport::Concern
module ClassMethods class_methods do
def has_internal_id(column, scope:, init:, presence: true) # rubocop:disable Naming/PredicateName def has_internal_id(column, scope:, init:, presence: true) # rubocop:disable Naming/PredicateName
# We require init here to retain the ability to recalculate in the absence of a # We require init here to retain the ability to recalculate in the absence of a
# InternaLId record (we may delete records in `internal_ids` for example). # InternaLId record (we may delete records in `internal_ids` for example).
......
...@@ -12,7 +12,7 @@ module Awardable ...@@ -12,7 +12,7 @@ module Awardable
end end
end end
module ClassMethods class_methods do
def awarded(user, name) def awarded(user, name)
sql = <<~EOL sql = <<~EOL
EXISTS ( EXISTS (
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
module CaseSensitivity module CaseSensitivity
extend ActiveSupport::Concern extend ActiveSupport::Concern
module ClassMethods class_methods do
# Queries the given columns regardless of the casing used. # Queries the given columns regardless of the casing used.
# #
# Unlike other ActiveRecord methods this method only operates on a Hash. # Unlike other ActiveRecord methods this method only operates on a Hash.
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
module EachBatch module EachBatch
extend ActiveSupport::Concern extend ActiveSupport::Concern
module ClassMethods class_methods do
# Iterates over the rows in a relation in batches, similar to Rails' # Iterates over the rows in a relation in batches, similar to Rails'
# `in_batches` but in a more efficient way. # `in_batches` but in a more efficient way.
# #
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
module IgnorableColumn module IgnorableColumn
extend ActiveSupport::Concern extend ActiveSupport::Concern
module ClassMethods class_methods do
def columns def columns
super.reject { |column| ignored_columns.include?(column.name) } super.reject { |column| ignored_columns.include?(column.name) }
end end
......
...@@ -118,7 +118,7 @@ module Issuable ...@@ -118,7 +118,7 @@ module Issuable
end end
end end
module ClassMethods class_methods do
# Searches for records with a matching title. # Searches for records with a matching title.
# #
# This method uses ILIKE on PostgreSQL and LIKE on MySQL. # This method uses ILIKE on PostgreSQL and LIKE on MySQL.
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
module LoadedInGroupList module LoadedInGroupList
extend ActiveSupport::Concern extend ActiveSupport::Concern
module ClassMethods class_methods do
def with_counts(archived:) def with_counts(archived:)
selects_including_counts = [ selects_including_counts = [
'namespaces.*', 'namespaces.*',
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
module ManualInverseAssociation module ManualInverseAssociation
extend ActiveSupport::Concern extend ActiveSupport::Concern
module ClassMethods class_methods do
def manual_inverse_association(association, inverse) def manual_inverse_association(association, inverse)
define_method(association) do |*args| define_method(association) do |*args|
super(*args).tap do |value| super(*args).tap do |value|
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
module Mentionable module Mentionable
extend ActiveSupport::Concern extend ActiveSupport::Concern
module ClassMethods class_methods do
# Indicate which attributes of the Mentionable to search for GFM references. # Indicate which attributes of the Mentionable to search for GFM references.
def attr_mentionable(attr, options = {}) def attr_mentionable(attr, options = {})
attr = attr.to_s attr = attr.to_s
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
module OptionallySearch module OptionallySearch
extend ActiveSupport::Concern extend ActiveSupport::Concern
module ClassMethods class_methods do
def search(*) def search(*)
raise( raise(
NotImplementedError, NotImplementedError,
......
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
module Participable module Participable
extend ActiveSupport::Concern extend ActiveSupport::Concern
module ClassMethods class_methods do
# Adds a list of participant attributes. Attributes can either be symbols or # Adds a list of participant attributes. Attributes can either be symbols or
# Procs. # Procs.
# #
......
...@@ -40,7 +40,7 @@ module Referable ...@@ -40,7 +40,7 @@ module Referable
end end
end end
module ClassMethods class_methods do
# The character that prefixes the actual reference identifier # The character that prefixes the actual reference identifier
# #
# This should be overridden by the including class. # This should be overridden by the including class.
......
...@@ -20,7 +20,7 @@ module ResolvableNote ...@@ -20,7 +20,7 @@ module ResolvableNote
scope :unresolved, -> { resolvable.where(resolved_at: nil) } scope :unresolved, -> { resolvable.where(resolved_at: nil) }
end end
module ClassMethods class_methods do
# This method must be kept in sync with `#resolve!` # This method must be kept in sync with `#resolve!`
def resolve!(current_user) def resolve!(current_user)
unresolved.update_all(resolved_at: Time.now, resolved_by_id: current_user.id) unresolved.update_all(resolved_at: Time.now, resolved_by_id: current_user.id)
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
module SelectForProjectAuthorization module SelectForProjectAuthorization
extend ActiveSupport::Concern extend ActiveSupport::Concern
module ClassMethods class_methods do
def select_for_project_authorization def select_for_project_authorization
select("projects.id AS project_id, members.access_level") select("projects.id AS project_id, members.access_level")
end end
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
module ShaAttribute module ShaAttribute
extend ActiveSupport::Concern extend ActiveSupport::Concern
module ClassMethods class_methods do
def sha_attribute(name) def sha_attribute(name)
return if ENV['STATIC_VERIFICATION'] return if ENV['STATIC_VERIFICATION']
......
...@@ -19,7 +19,7 @@ module Sortable ...@@ -19,7 +19,7 @@ module Sortable
scope :order_name_desc, -> { reorder(Arel::Nodes::Descending.new(arel_table[:name].lower)) } scope :order_name_desc, -> { reorder(Arel::Nodes::Descending.new(arel_table[:name].lower)) }
end end
module ClassMethods class_methods do
def order_by(method) def order_by(method)
case method.to_s case method.to_s
when 'created_asc' then order_created_asc when 'created_asc' then order_created_asc
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
module Spammable module Spammable
extend ActiveSupport::Concern extend ActiveSupport::Concern
module ClassMethods class_methods do
def attr_spammable(attr, options = {}) def attr_spammable(attr, options = {})
spammable_attrs << [attr.to_s, options] spammable_attrs << [attr.to_s, options]
end end
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
module StripAttribute module StripAttribute
extend ActiveSupport::Concern extend ActiveSupport::Concern
module ClassMethods class_methods do
def strip_attributes(*attrs) def strip_attributes(*attrs)
strip_attrs.concat(attrs) strip_attrs.concat(attrs)
end end
......
...@@ -6,6 +6,7 @@ module TriggerableHooks ...@@ -6,6 +6,7 @@ module TriggerableHooks
push_hooks: :push_events, push_hooks: :push_events,
tag_push_hooks: :tag_push_events, tag_push_hooks: :tag_push_events,
issue_hooks: :issues_events, issue_hooks: :issues_events,
confidential_note_hooks: :confidential_note_events,
confidential_issue_hooks: :confidential_issues_events, confidential_issue_hooks: :confidential_issues_events,
note_hooks: :note_events, note_hooks: :note_events,
merge_request_hooks: :merge_requests_events, merge_request_hooks: :merge_requests_events,
......
...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
class DiffFileEntity < Grape::Entity class DiffFileEntity < Grape::Entity
include RequestAwareEntity include RequestAwareEntity
include BlobHelper
include CommitsHelper include CommitsHelper
include DiffHelper include DiffHelper
include SubmoduleHelper include SubmoduleHelper
......
...@@ -43,8 +43,8 @@ module Projects ...@@ -43,8 +43,8 @@ module Projects
@new_path = File.join(@new_namespace.try(:full_path) || '', project.path) @new_path = File.join(@new_namespace.try(:full_path) || '', project.path)
@old_namespace = project.namespace @old_namespace = project.namespace
if Project.where(path: project.path, namespace_id: @new_namespace.try(:id)).exists? if Project.where(namespace_id: @new_namespace.try(:id)).where('path = ? or name = ?', project.path, project.name).exists?
raise TransferError.new("Project with same path in target namespace already exists") raise TransferError.new("Project with same name or path in target namespace already exists")
end end
if project.has_container_registry_tags? if project.has_container_registry_tags?
...@@ -118,6 +118,7 @@ module Projects ...@@ -118,6 +118,7 @@ module Projects
def rollback_side_effects def rollback_side_effects
rollback_folder_move rollback_folder_move
project.reload
update_namespace_and_visibility(@old_namespace) update_namespace_and_visibility(@old_namespace)
write_repository_config(@old_path) write_repository_config(@old_path)
end end
......
...@@ -2,6 +2,10 @@ ...@@ -2,6 +2,10 @@
module Users module Users
class BuildService < BaseService class BuildService < BaseService
delegate :user_default_internal_regex_enabled?,
:user_default_internal_regex_instance,
to: :'Gitlab::CurrentSettings.current_application_settings'
def initialize(current_user, params = {}) def initialize(current_user, params = {})
@current_user = current_user @current_user = current_user
@params = params.dup @params = params.dup
...@@ -89,6 +93,10 @@ module Users ...@@ -89,6 +93,10 @@ module Users
if params[:reset_password] if params[:reset_password]
user_params.merge!(force_random_password: true, password_expires_at: nil) user_params.merge!(force_random_password: true, password_expires_at: nil)
end end
if user_default_internal_regex_enabled? && !user_params.key?(:external)
user_params[:external] = user_external?
end
else else
allowed_signup_params = signup_params allowed_signup_params = signup_params
allowed_signup_params << :skip_confirmation if skip_authorization allowed_signup_params << :skip_confirmation if skip_authorization
...@@ -105,5 +113,9 @@ module Users ...@@ -105,5 +113,9 @@ module Users
def skip_user_confirmation_email_from_setting def skip_user_confirmation_email_from_setting
!Gitlab::CurrentSettings.send_user_confirmation_email !Gitlab::CurrentSettings.send_user_confirmation_email
end end
def user_external?
user_default_internal_regex_instance.match(params[:email]).nil?
end
end end
end end
class JsRegexValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
return true if value.blank?
parsed_regex = JsRegex.new(Regexp.new(value, Regexp::IGNORECASE))
if parsed_regex.source.empty?
record.errors.add(attribute, "Regex Pattern #{value} can not be expressed in Javascript")
else
parsed_regex.warnings.each { |warning| record.errors.add(attribute, warning) }
end
rescue RegexpError => regex_error
record.errors.add(attribute, regex_error.to_s)
end
end
...@@ -29,6 +29,13 @@ ...@@ -29,6 +29,13 @@
= f.check_box :user_default_external, class: 'form-check-input' = f.check_box :user_default_external, class: 'form-check-input'
= f.label :user_default_external, class: 'form-check-label' do = f.label :user_default_external, class: 'form-check-label' do
Newly registered users will by default be external Newly registered users will by default be external
.prepend-top-10
= _('Internal users')
= f.text_field :user_default_internal_regex, placeholder: _('Regex pattern'), class: 'form-control prepend-top-5'
.help-block
= _('Specify an e-mail address regex pattern to identify default internal users.')
= link_to _('More information'), help_page_path('user/permissions', anchor: 'external-users-permissions'),
target: '_blank'
.form-group .form-group
= f.label :user_show_add_ssh_key_message, 'Prompt users to upload SSH keys', class: 'label-bold' = f.label :user_show_add_ssh_key_message, 'Prompt users to upload SSH keys', class: 'label-bold'
.form-check .form-check
......
...@@ -34,8 +34,12 @@ ...@@ -34,8 +34,12 @@
.form-group.row .form-group.row
.col-sm-2.text-right .col-sm-2.text-right
= f.label :external, class: 'col-form-label' = f.label :external, class: 'col-form-label'
.hidden{ data: user_internal_regex_data }
.col-sm-10 .col-sm-10
= f.check_box :external do = f.check_box :external do
External External
%p.light %p.light
External users cannot see internal or private projects unless access is explicitly granted. Also, external users cannot create projects or groups. External users cannot see internal or private projects unless access is explicitly granted. Also, external users cannot create projects or groups.
%row.hidden#warning_external_automatically_set.hidden
.badge.badge-warning.text-white
= _('Automatically marked as default internal user')
- @body_class = 'ide' - @body_class = 'ide-layout'
- page_title 'IDE' - page_title 'IDE'
- content_for :page_specific_javascripts do - content_for :page_specific_javascripts do
......
!!! 5 !!! 5
%html{ lang: I18n.locale, class: page_class } %html{ lang: I18n.locale, class: page_class }
= render "layouts/head" = render "layouts/head"
%body{ class: "#{user_application_theme} #{@body_class} nav-only", data: { page: body_data_page } } %body{ class: "#{user_application_theme} #{@body_class} fullscreen-layout", data: { page: body_data_page } }
= render 'peek/bar' = render 'peek/bar'
= render "layouts/header/default" = render "layouts/header/default"
= render 'shared/outdated_browser' = render 'shared/outdated_browser'
...@@ -10,5 +10,5 @@ ...@@ -10,5 +10,5 @@
= render "layouts/broadcast" = render "layouts/broadcast"
= yield :flash_message = yield :flash_message
= render "layouts/flash" = render "layouts/flash"
.content{ id: "content-body" } .content-wrapper{ id: "content-body", class: "d-flex flex-column align-items-stretch" }
= yield = yield
...@@ -30,11 +30,13 @@ ...@@ -30,11 +30,13 @@
%pre.dark#merge-info-3 %pre.dark#merge-info-3
- if @merge_request.for_fork? - if @merge_request.for_fork?
:preserve :preserve
git checkout #{h @merge_request.target_branch} git fetch origin
git checkout origin/#{h @merge_request.target_branch}
git merge --no-ff #{h @merge_request.source_project_path}-#{h @merge_request.source_branch} git merge --no-ff #{h @merge_request.source_project_path}-#{h @merge_request.source_branch}
- else - else
:preserve :preserve
git checkout #{h @merge_request.target_branch} git fetch origin
git checkout origin/#{h @merge_request.target_branch}
git merge --no-ff #{h @merge_request.source_branch} git merge --no-ff #{h @merge_request.source_branch}
%p %p
%strong Step 4. %strong Step 4.
......
.banner-callout.compact.milestone-deprecation-message.prepend-top-20
.banner-graphic= image_tag 'illustrations/milestone_removing-page.svg'
.banner-body.prepend-left-10.append-right-10
%h5.banner-title.prepend-top-0
= _('The tabs below will be removed in a future version')
%p.milestone-banner-text
= _('Learn more about %{issue_boards_url}, to keep track of issues in multiple lists, using labels, assignees, and milestones. If you’re missing something from issue boards, please create an issue on %{gitlab_issues_url}.').html_safe % { issue_boards_url: link_to(_('issue boards'), help_page_url('user/project/issue_board'), target: '_blank', rel: 'noopener noreferrer'), gitlab_issues_url: link_to(_('GitLab’s issue tracker'), 'https://gitlab.com/gitlab-org/gitlab-ce/issues', target: '_blank', rel: 'noopener noreferrer') }
...@@ -67,5 +67,6 @@ ...@@ -67,5 +67,6 @@
.alert.alert-success.prepend-top-default .alert.alert-success.prepend-top-default
%span All issues for this milestone are closed. You may close this milestone now. %span All issues for this milestone are closed. You may close this milestone now.
= render 'deprecation_message'
= render 'shared/milestones/tabs', milestone: @milestone = render 'shared/milestones/tabs', milestone: @milestone
= render 'shared/milestones/sidebar', milestone: @milestone, project: @project, affix_offset: 153 = render 'shared/milestones/sidebar', milestone: @milestone, project: @project, affix_offset: 153
...@@ -10,17 +10,7 @@ class BackgroundMigrationWorker ...@@ -10,17 +10,7 @@ class BackgroundMigrationWorker
# maintenance related tasks have plenty of time to clean up after a migration # maintenance related tasks have plenty of time to clean up after a migration
# has been performed. # has been performed.
def self.minimum_interval def self.minimum_interval
if enable_health_check? 2.minutes.to_i
2.minutes.to_i
else
5.minutes.to_i
end
end
def self.enable_health_check?
Rails.env.development? ||
Rails.env.test? ||
Feature.enabled?('background_migration_health_check')
end end
# Performs the background migration. # Performs the background migration.
...@@ -86,8 +76,6 @@ class BackgroundMigrationWorker ...@@ -86,8 +76,6 @@ class BackgroundMigrationWorker
# class_name - The name of the background migration that we might want to # class_name - The name of the background migration that we might want to
# run. # run.
def healthy_database? def healthy_database?
return true unless self.class.enable_health_check?
return true unless Gitlab::Database.postgresql? return true unless Gitlab::Database.postgresql?
!Postgresql::ReplicationSlot.lag_too_great? !Postgresql::ReplicationSlot.lag_too_great?
......
...@@ -11,7 +11,7 @@ module ApplicationWorker ...@@ -11,7 +11,7 @@ module ApplicationWorker
set_queue set_queue
end end
module ClassMethods class_methods do
def inherited(subclass) def inherited(subclass)
subclass.set_queue subclass.set_queue
end end
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
module WaitableWorker module WaitableWorker
extend ActiveSupport::Concern extend ActiveSupport::Concern
module ClassMethods class_methods do
# Schedules multiple jobs and waits for them to be completed. # Schedules multiple jobs and waits for them to be completed.
def bulk_perform_and_wait(args_list, timeout: 10) def bulk_perform_and_wait(args_list, timeout: 10)
# Short-circuit: it's more efficient to do small numbers of jobs inline # Short-circuit: it's more efficient to do small numbers of jobs inline
......
---
title: Fix git submodule link for subgroup projects with relative path
merge_request: 21154
author:
type: fixed
---
title: Fix project transfer name validation issues causing a redirect loop
merge_request: 21408
author:
type: fixed
---
title: Fix IDE issues with persistent banners
merge_request: 21283
author:
type: fixed
---
title: Importing a project no longer fails when visibility level holds a string value
type
merge_request: 21242
author:
type: fixed
---
title: Show deprecation message on project milestone page for category tabs
merge_request: 21236
author:
type: changed
---
title: Adds Rubocop rule to enforce class_methods over module ClassMethods
merge_request: 21379
author: Jacopo Beschi @jacopo-beschi
type: added
---
title: Add default parameter to branches API
merge_request: 21294
author: Riccardo Padovani
type: changed
---
title: Add an option to whitelist users based on email address as internal when the "New user set to external" setting is enabled.
merge_request: 17711
author: Roger Rüttimann
type: added
---
title: Fix Emojis cutting in the right way
merge_request:
author: Alexander Popov
type: fixed
---
title: Fixed bug when the project logo file is stored in LFS
merge_request: 20948
author:
type: fixed
---
title: Enabled multiple file uploads in the Web IDE
merge_request:
author:
type: added
---
title: Fixed IDE file row scrolling into view when hovering
merge_request:
author:
type: fixed
---
title: Remove health check feature flag in BackgroundMigrationWorker
merge_request:
author:
type: changed
---
title: Backport schema_changed.sh from EE which prints the diff if the schema is different
merge_request: 21422
author: Jasper Maes
type: other
---
title: Fix "Confidential comments" button not saving in project hooks
merge_request: 21289
author:
type: fixed
---
title: Fix Error 500s due to encoding issues when Wiki hooks fire
merge_request: 21414
author:
type: fixed
---
title: Increase padding in code blocks
merge_request:
author:
type: fixed
class AddUserInternalRegexToApplicationSetting < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def up
add_column :application_settings, :user_default_internal_regex, :string, null: true
end
def down
remove_column :application_settings, :user_default_internal_regex
end
end
...@@ -164,6 +164,7 @@ ActiveRecord::Schema.define(version: 20180826111825) do ...@@ -164,6 +164,7 @@ ActiveRecord::Schema.define(version: 20180826111825) do
t.boolean "authorized_keys_enabled", default: true, null: false t.boolean "authorized_keys_enabled", default: true, null: false
t.string "auto_devops_domain" t.string "auto_devops_domain"
t.boolean "pages_domain_verification_enabled", default: true, null: false t.boolean "pages_domain_verification_enabled", default: true, null: false
t.string "user_default_internal_regex"
t.boolean "allow_local_requests_from_hooks_and_services", default: false, null: false t.boolean "allow_local_requests_from_hooks_and_services", default: false, null: false
t.boolean "enforce_terms", default: false t.boolean "enforce_terms", default: false
t.boolean "mirror_available", default: true, null: false t.boolean "mirror_available", default: true, null: false
......
# Compliance features
You can configure the following GitLab features to help ensure that your GitLab instance meets common compliance standards. Click a feature name for further documentation.
GitLab’s [security features](../security/README.md) may also help you meet relevant compliance standards.
|Feature |GitLab tier |GitLab.com |
| ---------| :--------: | :-------: |
|**[Restrict SSH Keys](../README.html#administrator-documentation)**<br>Control the technology and key length of SSH keys used to access GitLab|Core+||
|**[Granular user roles and flexible permissions](../user/permissions.html)**<br>Manage access and permissions with five different user roles and settings for external users. Set permissions according to people's role, rather than either read or write access to a repository. Don't share the source code with people that only need access to the issue tracker.|Core+|✓|
|**[Enforce TOS acceptance](../user/admin_area/settings/terms.html)**<br>Enforce your users accepting new terms of service by blocking GitLab traffic.|Core+||
|**[Email all users of a project, group, or entire server](../user/admin_area/settings/terms.html)**<br>An admin can email groups of users based on project or group membership, or email everyone using the GitLab instance. This is great for scheduled maintenance or upgrades.|Starter+||
|**[Omnibus package supports log forwarding](https://docs.gitlab.com/omnibus/settings/logs.html#udp-log-forwarding)**<br>Forward your logs to a central system.|Starter+||
|**[Lock project membership to group](../workflow/groups.html#lock-project-membership-to-members-of-this-group)**<br>Group owners can prevent new members from being added to projects within a group.|Starter+|✓|
|**[LDAP group sync](https://docs.gitlab.com/ee/administration/auth/ldap-ee.html#group-sync)**<br>GitLab Enterprise Edition gives admins the ability to automatically sync groups and manage SSH keys, permissions, and authentication, so you can focus on building your product, not configuring your tools.|Starter+||
|**[LDAP group sync filters](https://docs.gitlab.com/ee/administration/auth/ldap-ee.html#group-sync)**<br>GitLab Enterprise Edition Premium gives more flexibility to synchronize with LDAP based on filters, meaning you can leverage LDAP attributes to map GitLab permissions.|Premium+||
|**[Audit logs](https://docs.gitlab.com/ee/administration/audit_events.html)**<br>To maintain the integrity of your code, GitLab Enterprise Edition Premium gives admins the ability to view any modifications made within the GitLab server in an advanced audit log system, so you can control, analyze and track every change.|Premium+||
|**[Auditor users](https://docs.gitlab.com/ee/administration/auditor_users.html)**<br>Auditor users are users who are given read-only access to all projects, groups, and other resources on the GitLab instance.|Premium+||
\ No newline at end of file
...@@ -46,6 +46,7 @@ Learn how to install, configure, update, and maintain your GitLab instance. ...@@ -46,6 +46,7 @@ Learn how to install, configure, update, and maintain your GitLab instance.
- [Plugins](plugins.md): With custom plugins, GitLab administrators can introduce custom integrations without modifying GitLab's source code. - [Plugins](plugins.md): With custom plugins, GitLab administrators can introduce custom integrations without modifying GitLab's source code.
- [Enforcing Terms of Service](../user/admin_area/settings/terms.md) - [Enforcing Terms of Service](../user/admin_area/settings/terms.md)
- [Third party offers](../user/admin_area/settings/third_party_offers.md) - [Third party offers](../user/admin_area/settings/third_party_offers.md)
- [Compliance](compliance.md): A collection of features from across the application that you may configure to help ensure that your GitLab instance and DevOps workflow meet compliance standards.
#### Customizing GitLab's appearance #### Customizing GitLab's appearance
......
...@@ -27,6 +27,7 @@ Example response: ...@@ -27,6 +27,7 @@ Example response:
"name": "master", "name": "master",
"merged": false, "merged": false,
"protected": true, "protected": true,
"default": true,
"developers_can_push": false, "developers_can_push": false,
"developers_can_merge": false, "developers_can_merge": false,
"can_push": true, "can_push": true,
...@@ -75,6 +76,7 @@ Example response: ...@@ -75,6 +76,7 @@ Example response:
"name": "master", "name": "master",
"merged": false, "merged": false,
"protected": true, "protected": true,
"default": true,
"developers_can_push": false, "developers_can_push": false,
"developers_can_merge": false, "developers_can_merge": false,
"can_push": true, "can_push": true,
...@@ -141,6 +143,7 @@ Example response: ...@@ -141,6 +143,7 @@ Example response:
"name": "master", "name": "master",
"merged": false, "merged": false,
"protected": true, "protected": true,
"default": true,
"developers_can_push": true, "developers_can_push": true,
"developers_can_merge": true, "developers_can_merge": true,
"can_push": true "can_push": true
...@@ -190,6 +193,7 @@ Example response: ...@@ -190,6 +193,7 @@ Example response:
"name": "master", "name": "master",
"merged": false, "merged": false,
"protected": false, "protected": false,
"default": true,
"developers_can_push": false, "developers_can_push": false,
"developers_can_merge": false, "developers_can_merge": false,
"can_push": true "can_push": true
...@@ -234,6 +238,7 @@ Example response: ...@@ -234,6 +238,7 @@ Example response:
"name": "newbranch", "name": "newbranch",
"merged": false, "merged": false,
"protected": false, "protected": false,
"default": false,
"developers_can_push": false, "developers_can_push": false,
"developers_can_merge": false, "developers_can_merge": false,
"can_push": true "can_push": true
......
doc/user/group/img/groups.png

60.6 KB | W: | H:

doc/user/group/img/groups.png

60.1 KB | W: | H:

doc/user/group/img/groups.png
doc/user/group/img/groups.png
doc/user/group/img/groups.png
doc/user/group/img/groups.png
  • 2-up
  • Swipe
  • Onion skin
...@@ -197,7 +197,7 @@ They will, like usual users, receive a role in the project or group with all ...@@ -197,7 +197,7 @@ They will, like usual users, receive a role in the project or group with all
the abilities that are mentioned in the table above. They cannot however create the abilities that are mentioned in the table above. They cannot however create
groups or projects, and they have the same access as logged out users in all groups or projects, and they have the same access as logged out users in all
other cases. other cases.
An administrator can flag a user as external [through the API](../api/users.md) An administrator can flag a user as external [through the API](../api/users.md)
or by checking the checkbox on the admin panel. As an administrator, navigate or by checking the checkbox on the admin panel. As an administrator, navigate
to **Admin > Users** to create a new user or edit an existing one. There, you to **Admin > Users** to create a new user or edit an existing one. There, you
...@@ -206,6 +206,21 @@ will find the option to flag the user as external. ...@@ -206,6 +206,21 @@ will find the option to flag the user as external.
By default new users are not set as external users. This behavior can be changed By default new users are not set as external users. This behavior can be changed
by an administrator under **Admin > Application Settings**. by an administrator under **Admin > Application Settings**.
### Default internal users
The "Internal users" field allows specifying an e-mail address regex pattern to identify default internal users.
New users whose email address matches the regex pattern will be set to internal by default rather than an external collaborator.
The regex pattern format is Ruby, but it needs to be convertible to JavaScript, and the ignore case flag will be set, e.g. "/regex pattern/i".
Here are some examples:
- Use `\.internal@domain\.com` to mark email addresses containing ".internal@domain.com" internal.
- Use `^(?:(?!\.ext@domain\.com).)*$\r?` to mark users with email addresses NOT including .ext@domain.com internal.
Please be aware that this regex could lead to a DOS attack, [see](https://en.wikipedia.org/wiki/ReDoS?) ReDos on Wikipedia.
## Auditor users **[PREMIUM ONLY]** ## Auditor users **[PREMIUM ONLY]**
>[Introduced][ee-998] in [GitLab Premium][eep] 8.17. >[Introduced][ee-998] in [GitLab Premium][eep] 8.17.
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
## On Microsoft Teams ## On Microsoft Teams
To enable Microsoft Teams integration you must create an incoming webhook integration on Microsoft Teams by following the steps described in this [document](https://docs.microsoft.com/en-us/microsoftteams/platform/concepts/connectors#setting-up-a-custom-incoming-webhook). To enable Microsoft Teams integration you must create an incoming webhook integration on Microsoft Teams by following the steps described in this [document](https://docs.microsoft.com/en-us/microsoftteams/platform/concepts/connectors/connectors-using#setting-up-a-custom-incoming-webhook).
## On GitLab ## On GitLab
......
...@@ -84,7 +84,7 @@ module API ...@@ -84,7 +84,7 @@ module API
end end
end end
module ClassMethods class_methods do
private private
def install_error_responders(base) def install_error_responders(base)
......
...@@ -370,6 +370,10 @@ module API ...@@ -370,6 +370,10 @@ module API
expose :can_push do |repo_branch, options| expose :can_push do |repo_branch, options|
Gitlab::UserAccess.new(options[:current_user], project: options[:project]).can_push_to_branch?(repo_branch.name) Gitlab::UserAccess.new(options[:current_user], project: options[:project]).can_push_to_branch?(repo_branch.name)
end end
expose :default do |repo_branch, options|
options[:project].default_branch == repo_branch.name
end
end end
class TreeObject < Grape::Entity class TreeObject < Grape::Entity
......
...@@ -2,7 +2,7 @@ module API ...@@ -2,7 +2,7 @@ module API
module ProjectsRelationBuilder module ProjectsRelationBuilder
extend ActiveSupport::Concern extend ActiveSupport::Concern
module ClassMethods class_methods do
def prepare_relation(projects_relation, options = {}) def prepare_relation(projects_relation, options = {})
projects_relation = preload_relation(projects_relation, options) projects_relation = preload_relation(projects_relation, options)
execute_batch_counting(projects_relation) execute_batch_counting(projects_relation)
......
...@@ -75,7 +75,7 @@ module Gitlab ...@@ -75,7 +75,7 @@ module Gitlab
end end
def binary_stringio(str) def binary_stringio(str)
StringIO.new(str || '').tap { |io| io.set_encoding(Encoding::ASCII_8BIT) } StringIO.new(str.freeze || '').tap { |io| io.set_encoding(Encoding::ASCII_8BIT) }
end end
private private
......
...@@ -233,6 +233,8 @@ module Gitlab ...@@ -233,6 +233,8 @@ module Gitlab
end end
elsif user elsif user
# User access is verified in check_change_access! # User access is verified in check_change_access!
elsif authed_via_jwt?
# Authenticated via JWT
else else
raise UnauthorizedError, ERROR_MESSAGES[:upload] raise UnauthorizedError, ERROR_MESSAGES[:upload]
end end
...@@ -321,6 +323,10 @@ module Gitlab ...@@ -321,6 +323,10 @@ module Gitlab
!Gitlab.config.gitlab_shell.receive_pack !Gitlab.config.gitlab_shell.receive_pack
end end
def authed_via_jwt?
false
end
protected protected
def changes_list def changes_list
......
...@@ -6,7 +6,7 @@ module Gitlab ...@@ -6,7 +6,7 @@ module Gitlab
module ExposeAttribute module ExposeAttribute
extend ActiveSupport::Concern extend ActiveSupport::Concern
module ClassMethods class_methods do
# Defines getter methods for the given attribute names. # Defines getter methods for the given attribute names.
# #
# Example: # Example:
......
...@@ -5,7 +5,7 @@ module Gitlab ...@@ -5,7 +5,7 @@ module Gitlab
module MountMutation module MountMutation
extend ActiveSupport::Concern extend ActiveSupport::Concern
module ClassMethods class_methods do
def mount_mutation(mutation_class) def mount_mutation(mutation_class)
# Using an underscored field name symbol will make `graphql-ruby` # Using an underscored field name symbol will make `graphql-ruby`
# standardize the field name # standardize the field name
......
...@@ -94,7 +94,10 @@ module Gitlab ...@@ -94,7 +94,10 @@ module Gitlab
end end
def restore_project def restore_project
@project.update_columns(project_params) Gitlab::Timeless.timeless(@project) do
@project.update(project_params)
end
@project @project
end end
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
module StaticModel module StaticModel
extend ActiveSupport::Concern extend ActiveSupport::Concern
module ClassMethods class_methods do
# Used by ActiveRecord's polymorphic association to set object_id # Used by ActiveRecord's polymorphic association to set object_id
def primary_key def primary_key
'id' 'id'
......
...@@ -727,6 +727,9 @@ msgstr "" ...@@ -727,6 +727,9 @@ msgstr ""
msgid "AutoDevOps|enable Auto DevOps" msgid "AutoDevOps|enable Auto DevOps"
msgstr "" msgstr ""
msgid "Automatically marked as default internal user"
msgstr ""
msgid "Available" msgid "Available"
msgstr "" msgstr ""
...@@ -2801,6 +2804,9 @@ msgstr "" ...@@ -2801,6 +2804,9 @@ msgstr ""
msgid "GitLab.com import" msgid "GitLab.com import"
msgstr "" msgstr ""
msgid "GitLab’s issue tracker"
msgstr ""
msgid "Gitaly" msgid "Gitaly"
msgstr "" msgstr ""
...@@ -3187,6 +3193,9 @@ msgstr "" ...@@ -3187,6 +3193,9 @@ msgstr ""
msgid "Internal - The project can be accessed by any logged in user." msgid "Internal - The project can be accessed by any logged in user."
msgstr "" msgstr ""
msgid "Internal users"
msgstr ""
msgid "Interval Pattern" msgid "Interval Pattern"
msgstr "" msgstr ""
...@@ -3387,6 +3396,9 @@ msgstr "" ...@@ -3387,6 +3396,9 @@ msgstr ""
msgid "Learn more" msgid "Learn more"
msgstr "" msgstr ""
msgid "Learn more about %{issue_boards_url}, to keep track of issues in multiple lists, using labels, assignees, and milestones. If you’re missing something from issue boards, please create an issue on %{gitlab_issues_url}."
msgstr ""
msgid "Learn more about Kubernetes" msgid "Learn more about Kubernetes"
msgstr "" msgstr ""
...@@ -4670,6 +4682,9 @@ msgid_plural "Refreshing in %d seconds to show the updated status..." ...@@ -4670,6 +4682,9 @@ msgid_plural "Refreshing in %d seconds to show the updated status..."
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
msgid "Regex pattern"
msgstr ""
msgid "Register / Sign In" msgid "Register / Sign In"
msgstr "" msgstr ""
...@@ -5277,6 +5292,9 @@ msgstr "" ...@@ -5277,6 +5292,9 @@ msgstr ""
msgid "Specific Runners" msgid "Specific Runners"
msgstr "" msgstr ""
msgid "Specify an e-mail address regex pattern to identify default internal users."
msgstr ""
msgid "Specify the following URL during the Runner setup:" msgid "Specify the following URL during the Runner setup:"
msgstr "" msgstr ""
...@@ -5555,6 +5573,9 @@ msgstr "" ...@@ -5555,6 +5573,9 @@ msgstr ""
msgid "The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time." msgid "The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time."
msgstr "" msgstr ""
msgid "The tabs below will be removed in a future version"
msgstr ""
msgid "The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running." msgid "The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running."
msgstr "" msgstr ""
...@@ -5934,6 +5955,9 @@ msgstr "" ...@@ -5934,6 +5955,9 @@ msgstr ""
msgid "To add an SSH key you need to %{generate_link_start}generate one%{link_end} or use an %{existing_link_start}existing key%{link_end}." msgid "To add an SSH key you need to %{generate_link_start}generate one%{link_end} or use an %{existing_link_start}existing key%{link_end}."
msgstr "" msgstr ""
msgid "To define internal users, first enable new users set to external"
msgstr ""
msgid "To get started you enter your FogBugz URL and login information below. In the next steps, you'll be able to map users and select the projects you want to import." msgid "To get started you enter your FogBugz URL and login information below. In the next steps, you'll be able to map users and select the projects you want to import."
msgstr "" msgstr ""
...@@ -6590,6 +6614,9 @@ msgstr "" ...@@ -6590,6 +6614,9 @@ msgstr ""
msgid "importing" msgid "importing"
msgstr "" msgstr ""
msgid "issue boards"
msgstr ""
msgid "latest version" msgid "latest version"
msgstr "" msgstr ""
......
require 'pathname'
module QA module QA
module Page module Page
class View class View
...@@ -9,7 +11,7 @@ module QA ...@@ -9,7 +11,7 @@ module QA
end end
def pathname def pathname
@pathname ||= Pathname.new(::File.join(__dir__, '../../../', @path)) @pathname ||= ::Pathname.new(::File.join(__dir__, '../../../', @path))
.cleanpath.expand_path .cleanpath.expand_path
end end
......
# frozen_string_literal: true
module RuboCop
module Cop
# Enforces the use of 'class_methods' instead of 'module ClassMethods' for activesupport concerns.
# For more information see: https://gitlab.com/gitlab-org/gitlab-ce/issues/50414
#
# @example
# # bad
# module Foo
# extend ActiveSupport::Concern
#
# module ClassMethods
# def a_class_method
# end
# end
# end
#
# # good
# module Foo
# extend ActiveSupport::Concern
#
# class_methods do
# def a_class_method
# end
# end
# end
#
class PreferClassMethodsOverModule < RuboCop::Cop::Cop
include RangeHelp
MSG = 'Do not use module ClassMethods, use class_methods block instead.'
def_node_matcher :extend_activesupport_concern?, <<~PATTERN
(:send nil? :extend (:const (:const nil? :ActiveSupport) :Concern))
PATTERN
def on_module(node)
add_offense(node) if node.defined_module_name == 'ClassMethods' && module_extends_activesupport_concern?(node)
end
def autocorrect(node)
lambda do |corrector|
corrector.replace(module_range(node), 'class_methods do')
end
end
private
def module_extends_activesupport_concern?(node)
container_module = container_module_of(node)
return false unless container_module
container_module.descendants.any? do |descendant|
extend_activesupport_concern?(descendant)
end
end
def container_module_of(node)
while node = node.parent
break if node.type == :module
end
node
end
def module_range(node)
module_node, _ = *node
range_between(node.loc.keyword.begin_pos, module_node.source_range.end_pos)
end
end
end
end
...@@ -7,6 +7,7 @@ require_relative 'cop/include_sidekiq_worker' ...@@ -7,6 +7,7 @@ require_relative 'cop/include_sidekiq_worker'
require_relative 'cop/avoid_return_from_blocks' require_relative 'cop/avoid_return_from_blocks'
require_relative 'cop/avoid_break_from_strong_memoize' require_relative 'cop/avoid_break_from_strong_memoize'
require_relative 'cop/line_break_around_conditional_block' require_relative 'cop/line_break_around_conditional_block'
require_relative 'cop/prefer_class_methods_over_module'
require_relative 'cop/migration/add_column' require_relative 'cop/migration/add_column'
require_relative 'cop/migration/add_concurrent_foreign_key' require_relative 'cop/migration/add_concurrent_foreign_key'
require_relative 'cop/migration/add_concurrent_index' require_relative 'cop/migration/add_concurrent_index'
......
function schema_changed() { #!/bin/sh
if [[ ! -z `git diff --name-only -- db/schema.rb` ]]; then
echo "db/schema.rb after rake db:migrate:reset is different from one in the repository" schema_changed() {
if [ ! -z "$(git diff --name-only -- db/schema.rb)" ]; then
printf "db/schema.rb after rake db:migrate:reset is different from one in the repository"
printf "The diff is as follows:\n"
diff=$(git diff -p --binary -- db/schema.rb)
printf "%s" "$diff"
exit 1 exit 1
else else
echo "db/schema.rb after rake db:migrate:reset matches one in the repository" printf "db/schema.rb after rake db:migrate:reset matches one in the repository"
fi fi
} }
......
require 'spec_helper' require 'spec_helper'
describe Projects::AvatarsController do describe Projects::AvatarsController do
let(:project) { create(:project, :repository, avatar: fixture_file_upload("spec/fixtures/dk.png", "image/png")) } let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
before do before do
sign_in(user)
project.add_maintainer(user)
controller.instance_variable_set(:@project, project) controller.instance_variable_set(:@project, project)
end end
it 'GET #show' do describe 'GET #show' do
get :show, namespace_id: project.namespace.id, project_id: project.id subject { get :show, namespace_id: project.namespace, project_id: project }
expect(response).to have_gitlab_http_status(404) context 'when repository has no avatar' do
it 'shows 404' do
subject
expect(response).to have_gitlab_http_status(404)
end
end
context 'when repository has an avatar' do
before do
allow(project).to receive(:avatar_in_git).and_return(filepath)
end
context 'when the avatar is stored in the repository' do
let(:filepath) { 'files/images/logo-white.png' }
it 'sends the avatar' do
subject
expect(response).to have_gitlab_http_status(200)
expect(response.header['Content-Type']).to eq('image/png')
expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with('git-blob:')
end
end
context 'when the avatar is stored in lfs' do
it_behaves_like 'repository lfs file load' do
let(:filename) { 'lfs_object.iso' }
let(:filepath) { "files/lfs/#{filename}" }
end
end
end
end end
it 'removes avatar from DB by calling destroy' do describe 'DELETE #destroy' do
delete :destroy, namespace_id: project.namespace.id, project_id: project.id it 'removes avatar from DB by calling destroy' do
expect(project.avatar.present?).to be_falsey delete :destroy, namespace_id: project.namespace.id, project_id: project.id
expect(project).to be_valid
expect(project.avatar.present?).to be_falsey
expect(project).to be_valid
end
end end
end end
...@@ -30,6 +30,7 @@ describe Projects::HooksController do ...@@ -30,6 +30,7 @@ describe Projects::HooksController do
tag_push_events: true, tag_push_events: true,
merge_requests_events: true, merge_requests_events: true,
issues_events: true, issues_events: true,
confidential_note_events: true,
confidential_issues_events: true, confidential_issues_events: true,
note_events: true, note_events: true,
job_events: true, job_events: true,
......
require 'spec_helper' require 'spec_helper'
describe Projects::RawController do describe Projects::RawController do
let(:public_project) { create(:project, :public, :repository) } let(:project) { create(:project, :public, :repository) }
describe 'GET #show' do
subject do
get(:show,
namespace_id: project.namespace,
project_id: project,
id: filepath)
end
describe '#show' do
context 'regular filename' do context 'regular filename' do
let(:id) { 'master/README.md' } let(:filepath) { 'master/README.md' }
it 'delivers ASCII file' do it 'delivers ASCII file' do
get_show(public_project, id) subject
expect(response).to have_gitlab_http_status(200) expect(response).to have_gitlab_http_status(200)
expect(response.header['Content-Type']).to eq('text/plain; charset=utf-8') expect(response.header['Content-Type']).to eq('text/plain; charset=utf-8')
...@@ -19,10 +26,10 @@ describe Projects::RawController do ...@@ -19,10 +26,10 @@ describe Projects::RawController do
end end
context 'image header' do context 'image header' do
let(:id) { 'master/files/images/6049019_460s.jpg' } let(:filepath) { 'master/files/images/6049019_460s.jpg' }
it 'sets image content type header' do it 'sets image content type header' do
get_show(public_project, id) subject
expect(response).to have_gitlab_http_status(200) expect(response).to have_gitlab_http_status(200)
expect(response.header['Content-Type']).to eq('image/jpeg') expect(response.header['Content-Type']).to eq('image/jpeg')
...@@ -30,85 +37,9 @@ describe Projects::RawController do ...@@ -30,85 +37,9 @@ describe Projects::RawController do
end end
end end
context 'lfs object' do it_behaves_like 'repository lfs file load' do
let(:id) { 'be93687/files/lfs/lfs_object.iso' } let(:filename) { 'lfs_object.iso' }
let!(:lfs_object) { create(:lfs_object, oid: '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', size: '1575078') } let(:filepath) { "be93687/files/lfs/#{filename}" }
context 'when lfs is enabled' do
before do
allow_any_instance_of(Project).to receive(:lfs_enabled?).and_return(true)
end
context 'when project has access' do
before do
public_project.lfs_objects << lfs_object
allow_any_instance_of(LfsObjectUploader).to receive(:exists?).and_return(true)
allow(controller).to receive(:send_file) { controller.head :ok }
end
it 'serves the file' do
expect(controller).to receive(:send_file).with("#{LfsObjectUploader.root}/91/ef/f75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897", filename: 'lfs_object.iso', disposition: 'attachment')
get_show(public_project, id)
expect(response).to have_gitlab_http_status(200)
end
context 'and lfs uses object storage' do
let(:lfs_object) { create(:lfs_object, :with_file, oid: '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', size: '1575078') }
before do
stub_lfs_object_storage
lfs_object.file.migrate!(LfsObjectUploader::Store::REMOTE)
end
it 'responds with redirect to file' do
get_show(public_project, id)
expect(response).to have_gitlab_http_status(302)
expect(response.location).to include(lfs_object.reload.file.path)
end
it 'sets content disposition' do
get_show(public_project, id)
file_uri = URI.parse(response.location)
params = CGI.parse(file_uri.query)
expect(params["response-content-disposition"].first).to eq 'attachment;filename="lfs_object.iso"'
end
end
end
context 'when project does not have access' do
it 'does not serve the file' do
get_show(public_project, id)
expect(response).to have_gitlab_http_status(404)
end
end
end
context 'when lfs is not enabled' do
before do
allow_any_instance_of(Project).to receive(:lfs_enabled?).and_return(false)
end
it 'delivers ASCII file' do
get_show(public_project, id)
expect(response).to have_gitlab_http_status(200)
expect(response.header['Content-Type']).to eq('text/plain; charset=utf-8')
expect(response.header['Content-Disposition'])
.to eq('inline')
expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with('git-blob:')
end
end
end end
end end
def get_show(project, id)
get(:show, namespace_id: project.namespace.to_param,
project_id: project,
id: id)
end
end end
...@@ -78,6 +78,18 @@ describe 'Admin updates settings' do ...@@ -78,6 +78,18 @@ describe 'Admin updates settings' do
expect(page).to have_content "Application settings saved successfully" expect(page).to have_content "Application settings saved successfully"
end end
it 'Change New users set to external', :js do
user_internal_regex = find('#application_setting_user_default_internal_regex', visible: :all)
expect(user_internal_regex).to be_readonly
expect(user_internal_regex['placeholder']).to eq 'To define internal users, first enable new users set to external'
check 'application_setting_user_default_external'
expect(user_internal_regex).not_to be_readonly
expect(user_internal_regex['placeholder']).to eq 'Regex pattern'
end
it 'Change Sign-in restrictions' do it 'Change Sign-in restrictions' do
page.within('.as-signin') do page.within('.as-signin') do
fill_in 'Home page URL', with: 'https://about.gitlab.com/' fill_in 'Home page URL', with: 'https://about.gitlab.com/'
......
...@@ -125,6 +125,52 @@ describe "Admin::Users" do ...@@ -125,6 +125,52 @@ describe "Admin::Users" do
expect(page).to have_content('Username can contain only letters, digits') expect(page).to have_content('Username can contain only letters, digits')
end end
end end
context 'with new users set to external enabled' do
context 'with regex to match internal user email address set', :js do
before do
stub_application_setting(user_default_external: true)
stub_application_setting(user_default_internal_regex: '.internal@')
visit new_admin_user_path
end
def expects_external_to_be_checked
expect(find('#user_external')).to be_checked
end
def expects_external_to_be_unchecked
expect(find('#user_external')).not_to be_checked
end
def expects_warning_to_be_hidden
expect(find('#warning_external_automatically_set', visible: :all)[:class]).to include 'hidden'
end
def expects_warning_to_be_shown
expect(find('#warning_external_automatically_set')[:class]).not_to include 'hidden'
end
it 'automatically unchecks external for matching email' do
expects_external_to_be_checked
expects_warning_to_be_hidden
fill_in 'user_email', with: 'test.internal@domain.ch'
expects_external_to_be_unchecked
expects_warning_to_be_shown
fill_in 'user_email', with: 'test@domain.ch'
expects_external_to_be_checked
expects_warning_to_be_hidden
uncheck 'user_external'
expects_warning_to_be_hidden
end
end
end
end end
describe "GET /admin/users/:id" do describe "GET /admin/users/:id" do
......
...@@ -122,7 +122,7 @@ describe 'Merge request > User sees diff', :js do ...@@ -122,7 +122,7 @@ describe 'Merge request > User sees diff', :js do
} }
CONTENT CONTENT
file_name = 'xss_file.txt' file_name = 'xss_file.rs'
create_file('master', file_name, file_content) create_file('master', file_name, file_content)
merge_request = create(:merge_request, source_project: project) merge_request = create(:merge_request, source_project: project)
...@@ -133,6 +133,7 @@ describe 'Merge request > User sees diff', :js do ...@@ -133,6 +133,7 @@ describe 'Merge request > User sees diff', :js do
visit diffs_project_merge_request_path(project, merge_request) visit diffs_project_merge_request_path(project, merge_request)
expect(page).to have_text("function foo<input> {") expect(page).to have_text("function foo<input> {")
expect(page).to have_css(".line[lang='rust'] .k")
end end
end end
end end
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
"commit", "commit",
"merged", "merged",
"protected", "protected",
"default",
"developers_can_push", "developers_can_push",
"developers_can_merge" "developers_can_merge"
], ],
...@@ -13,6 +14,7 @@ ...@@ -13,6 +14,7 @@
"commit": { "$ref": "commit/basic.json" }, "commit": { "$ref": "commit/basic.json" },
"merged": { "type": "boolean" }, "merged": { "type": "boolean" },
"protected": { "type": "boolean" }, "protected": { "type": "boolean" },
"default": { "type": "boolean" },
"developers_can_push": { "type": "boolean" }, "developers_can_push": { "type": "boolean" },
"developers_can_merge": { "type": "boolean" }, "developers_can_merge": { "type": "boolean" },
"can_push": { "type": "boolean" } "can_push": { "type": "boolean" }
......
...@@ -162,42 +162,77 @@ describe SubmoduleHelper do ...@@ -162,42 +162,77 @@ describe SubmoduleHelper do
end end
context 'submodules with relative links' do context 'submodules with relative links' do
let(:group) { create(:group, name: "Master Project", path: "master-project") } let(:group) { create(:group, name: "top group", path: "top-group") }
let(:project) { create(:project, group: group) } let(:project) { create(:project, group: group) }
let(:commit_id) { sample_commit[:id] } let(:repo) { double(:repo, project: project) }
def expect_relative_link_to_resolve_to(relative_path, expected_path)
allow(repo).to receive(:submodule_url_for).and_return(relative_path)
result = submodule_links(submodule_item)
expect(result).to eq([expected_path, "#{expected_path}/tree/#{submodule_item.id}"])
end
it 'one level down' do it 'handles project under same group' do
result = relative_self_links('../test.git', commit_id, project) expect_relative_link_to_resolve_to('../test.git', "/#{group.path}/test")
expect(result).to eq(["/#{group.path}/test", "/#{group.path}/test/tree/#{commit_id}"])
end end
it 'with trailing whitespace' do it 'handles trailing whitespace' do
result = relative_self_links('../test.git ', commit_id, project) expect_relative_link_to_resolve_to('../test.git ', "/#{group.path}/test")
expect(result).to eq(["/#{group.path}/test", "/#{group.path}/test/tree/#{commit_id}"])
end end
it 'two levels down' do it 'handles project under another top group' do
result = relative_self_links('../../test.git', commit_id, project) expect_relative_link_to_resolve_to('../../baz/test.git ', "/baz/test")
expect(result).to eq(["/#{group.path}/test", "/#{group.path}/test/tree/#{commit_id}"]) end
context 'repo path resolves to be located at root (namespace absent)' do
it 'returns nil' do
allow(repo).to receive(:submodule_url_for).and_return('../../test.git')
result = submodule_links(submodule_item)
expect(result).to eq([nil, nil])
end
end end
it 'one level down with namespace and repo' do context 'repo path resolves to be located underneath current project path' do
result = relative_self_links('../foobar/test.git', commit_id, project) it 'returns nil because it is not possible to have repo nested under another repo' do
expect(result).to eq(["/foobar/test", "/foobar/test/tree/#{commit_id}"]) allow(repo).to receive(:submodule_url_for).and_return('./test.git')
result = submodule_links(submodule_item)
expect(result).to eq([nil, nil])
end
end end
it 'two levels down with namespace and repo' do context 'subgroup' do
result = relative_self_links('../foobar/baz/test.git', commit_id, project) let(:sub_group) { create(:group, parent: group, name: "sub group", path: "sub-group") }
expect(result).to eq(["/baz/test", "/baz/test/tree/#{commit_id}"]) let(:sub_project) { create(:project, group: sub_group) }
context 'project in sub group' do
let(:project) { sub_project }
it "handles referencing ancestor group's project" do
expect_relative_link_to_resolve_to('../../../top-group/test.git', "/#{group.path}/test")
end
end
it "handles referencing descendent group's project" do
expect_relative_link_to_resolve_to('../sub-group/test.git', "/top-group/sub-group/test")
end
it "handles referencing another top group's project" do
expect_relative_link_to_resolve_to('../../frontend/css/test.git', "/frontend/css/test")
end
end end
context 'personal project' do context 'personal project' do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project) { create(:project, namespace: user.namespace) } let(:project) { create(:project, namespace: user.namespace) }
it 'one level down with personal project' do it 'handles referencing another personal project' do
result = relative_self_links('../test.git', commit_id, project) expect_relative_link_to_resolve_to('../test.git', "/#{user.username}/test")
expect(result).to eq(["/#{user.username}/test", "/#{user.username}/test/tree/#{commit_id}"])
end end
end end
end end
......
...@@ -42,6 +42,30 @@ describe UsersHelper do ...@@ -42,6 +42,30 @@ describe UsersHelper do
end end
end end
describe '#user_internal_regex_data' do
using RSpec::Parameterized::TableSyntax
where(:user_default_external, :user_default_internal_regex, :result) do
false | nil | { user_internal_regex_pattern: nil, user_internal_regex_options: nil }
false | '' | { user_internal_regex_pattern: nil, user_internal_regex_options: nil }
false | 'mockRegexPattern' | { user_internal_regex_pattern: nil, user_internal_regex_options: nil }
true | nil | { user_internal_regex_pattern: nil, user_internal_regex_options: nil }
true | '' | { user_internal_regex_pattern: nil, user_internal_regex_options: nil }
true | 'mockRegexPattern' | { user_internal_regex_pattern: 'mockRegexPattern', user_internal_regex_options: 'gi' }
end
with_them do
before do
stub_application_setting(user_default_external: user_default_external)
stub_application_setting(user_default_internal_regex: user_default_internal_regex)
end
subject { helper.user_internal_regex_data }
it { is_expected.to eq(result) }
end
end
describe '#current_user_menu_items' do describe '#current_user_menu_items' do
subject(:items) { helper.current_user_menu_items } subject(:items) { helper.current_user_menu_items }
......
require 'spec_helper'
describe Admin::UsersController, '(JavaScript fixtures)', type: :controller do
include StubENV
include JavaScriptFixturesHelpers
let(:admin) { create(:admin) }
before do
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
sign_in(admin)
end
render_views
before(:all) do
clean_frontend_fixtures('admin/users')
end
it 'admin/users/new_with_internal_user_regex.html.raw' do |example|
stub_application_setting(user_default_external: true)
stub_application_setting(user_default_internal_regex: '^(?:(?!\.ext@).)*$\r?')
get :new
expect(response).to be_success
store_frontend_fixture(response, example.description)
end
end
require 'spec_helper'
describe Admin::ApplicationSettingsController, '(JavaScript fixtures)', type: :controller do
include StubENV
include JavaScriptFixturesHelpers
let(:admin) { create(:admin) }
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
let(:project) { create(:project_empty_repo, namespace: namespace, path: 'application-settings') }
before do
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
sign_in(admin)
end
render_views
before(:all) do
clean_frontend_fixtures('application_settings/')
end
after do
remove_repository(project)
end
it 'application_settings/accounts_and_limit.html.raw' do |example|
stub_application_setting(user_default_external: false)
get :show
expect(response).to be_success
store_frontend_fixture(response, example.description)
end
end
...@@ -21,6 +21,23 @@ describe('new dropdown upload', () => { ...@@ -21,6 +21,23 @@ describe('new dropdown upload', () => {
vm.$destroy(); vm.$destroy();
}); });
describe('openFile', () => {
it('calls for each file', () => {
const files = ['test', 'test2', 'test3'];
spyOn(vm, 'readFile');
spyOnProperty(vm.$refs.fileUpload, 'files').and.returnValue(files);
vm.openFile();
expect(vm.readFile.calls.count()).toBe(3);
files.forEach((file, i) => {
expect(vm.readFile.calls.argsFor(i)).toEqual([file]);
});
});
});
describe('readFile', () => { describe('readFile', () => {
beforeEach(() => { beforeEach(() => {
spyOn(FileReader.prototype, 'readAsText'); spyOn(FileReader.prototype, 'readAsText');
......
...@@ -121,4 +121,25 @@ describe('RepoFile', () => { ...@@ -121,4 +121,25 @@ describe('RepoFile', () => {
).toContain('Locked by testuser'); ).toContain('Locked by testuser');
}); });
}); });
it('calls scrollIntoView if made active', done => {
createComponent({
file: {
...file(),
type: 'blob',
active: false,
},
level: 0,
});
spyOn(vm, 'scrollIntoView');
vm.file.active = true;
vm.$nextTick(() => {
expect(vm.scrollIntoView).toHaveBeenCalled();
done();
});
});
}); });
import $ from 'jquery';
import initUserInternalRegexPlaceholder, { PLACEHOLDER_USER_EXTERNAL_DEFAULT_FALSE,
PLACEHOLDER_USER_EXTERNAL_DEFAULT_TRUE } from '~/pages/admin/application_settings/account_and_limits';
describe('AccountAndLimits', () => {
const FIXTURE = 'application_settings/accounts_and_limit.html.raw';
let $userDefaultExternal;
let $userInternalRegex;
preloadFixtures(FIXTURE);
beforeEach(() => {
loadFixtures(FIXTURE);
initUserInternalRegexPlaceholder();
$userDefaultExternal = $('#application_setting_user_default_external');
$userInternalRegex = document.querySelector('#application_setting_user_default_internal_regex');
});
describe('Changing of userInternalRegex when userDefaultExternal', () => {
it('is unchecked', () => {
expect($userDefaultExternal.prop('checked')).toBeFalsy();
expect($userInternalRegex.placeholder).toEqual(PLACEHOLDER_USER_EXTERNAL_DEFAULT_FALSE);
expect($userInternalRegex.readOnly).toBeTruthy();
});
it('is checked', (done) => {
if (!$userDefaultExternal.prop('checked')) $userDefaultExternal.click();
expect($userDefaultExternal.prop('checked')).toBeTruthy();
expect($userInternalRegex.placeholder).toEqual(PLACEHOLDER_USER_EXTERNAL_DEFAULT_TRUE);
expect($userInternalRegex.readOnly).toBeFalsy();
done();
});
});
});
import $ from 'jquery';
import UserInternalRegexHandler from '~/pages/admin/users/new/index';
describe('UserInternalRegexHandler', () => {
const FIXTURE = 'admin/users/new_with_internal_user_regex.html.raw';
let $userExternal;
let $userEmail;
let $warningMessage;
preloadFixtures(FIXTURE);
beforeEach(() => {
loadFixtures(FIXTURE);
// eslint-disable-next-line no-new
new UserInternalRegexHandler();
$userExternal = $('#user_external');
$userEmail = $('#user_email');
$warningMessage = $('#warning_external_automatically_set');
if (!$userExternal.prop('checked')) $userExternal.prop('checked', 'checked');
});
describe('Behaviour of userExternal checkbox when', () => {
it('matches email as internal', (done) => {
expect($warningMessage.hasClass('hidden')).toBeTruthy();
$userEmail.val('test@').trigger('input');
expect($userExternal.prop('checked')).toBeFalsy();
expect($warningMessage.hasClass('hidden')).toBeFalsy();
done();
});
it('matches email as external', (done) => {
expect($warningMessage.hasClass('hidden')).toBeTruthy();
$userEmail.val('test.ext@').trigger('input');
expect($userExternal.prop('checked')).toBeTruthy();
expect($warningMessage.hasClass('hidden')).toBeTruthy();
done();
});
});
});
...@@ -3,53 +3,45 @@ import pdfjsLib from 'vendor/pdf'; ...@@ -3,53 +3,45 @@ import pdfjsLib from 'vendor/pdf';
import workerSrc from 'vendor/pdf.worker.min'; import workerSrc from 'vendor/pdf.worker.min';
import PageComponent from '~/pdf/page/index.vue'; import PageComponent from '~/pdf/page/index.vue';
import testPDF from '../fixtures/blob/pdf/test.pdf'; import mountComponent from 'spec/helpers/vue_mount_component_helper';
import testPDF from 'spec/fixtures/blob/pdf/test.pdf';
const Component = Vue.extend(PageComponent);
describe('Page component', () => { describe('Page component', () => {
const Component = Vue.extend(PageComponent);
let vm; let vm;
let testPage; let testPage;
pdfjsLib.PDFJS.workerSrc = workerSrc;
const checkRendered = (done) => {
if (vm.rendering) {
setTimeout(() => {
checkRendered(done);
}, 100);
} else {
done();
}
};
beforeEach((done) => { beforeEach(done => {
pdfjsLib.getDocument(testPDF) pdfjsLib.PDFJS.workerSrc = workerSrc;
pdfjsLib
.getDocument(testPDF)
.then(pdf => pdf.getPage(1)) .then(pdf => pdf.getPage(1))
.then((page) => { .then(page => {
testPage = page; testPage = page;
done();
}) })
.catch((error) => { .then(done)
done.fail(error); .catch(done.fail);
});
}); });
describe('render', () => { afterEach(() => {
beforeEach((done) => { vm.$destroy();
vm = new Component({ });
propsData: {
page: testPage,
number: 1,
},
});
vm.$mount();
checkRendered(done); it('renders the page when mounting', done => {
const promise = Promise.resolve();
spyOn(testPage, 'render').and.callFake(() => promise);
vm = mountComponent(Component, {
page: testPage,
number: 1,
}); });
expect(vm.rendering).toBe(true);
it('renders first page', () => { promise
expect(vm.$el.tagName).toBeDefined(); .then(() => {
}); expect(testPage.render).toHaveBeenCalledWith(vm.renderContext);
expect(vm.rendering).toBe(false);
})
.then(done)
.catch(done.fail);
}); });
}); });
# coding: utf-8
require "spec_helper" require "spec_helper"
describe Gitlab::EncodingHelper do describe Gitlab::EncodingHelper do
...@@ -187,4 +188,15 @@ describe Gitlab::EncodingHelper do ...@@ -187,4 +188,15 @@ describe Gitlab::EncodingHelper do
end end
end end
end end
describe '#binary_stringio' do
it 'does not mutate the original string encoding' do
test = 'my-test'
io_stream = ext_class.binary_stringio(test)
expect(io_stream.external_encoding.name).to eq('ASCII-8BIT')
expect(test.encoding.name).to eq('UTF-8')
end
end
end end
...@@ -63,6 +63,16 @@ describe Gitlab::ImportExport::Importer do ...@@ -63,6 +63,16 @@ describe Gitlab::ImportExport::Importer do
importer.execute importer.execute
end end
it 'sets the correct visibility_level when visibility level is a string' do
project.create_or_update_import_data(
data: { override_params: { visibility_level: Gitlab::VisibilityLevel::PRIVATE.to_s } }
)
importer.execute
expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE)
end
end end
context 'when project successfully restored' do context 'when project successfully restored' do
......
...@@ -538,4 +538,28 @@ describe ApplicationSetting do ...@@ -538,4 +538,28 @@ describe ApplicationSetting do
expect(setting.allow_signup?).to be_falsey expect(setting.allow_signup?).to be_falsey
end end
end end
describe '#user_default_internal_regex_enabled?' do
using RSpec::Parameterized::TableSyntax
where(:user_default_external, :user_default_internal_regex, :result) do
false | nil | false
false | '' | false
false | '^(?:(?!\.ext@).)*$\r?\n?' | false
true | '' | false
true | nil | false
true | '^(?:(?!\.ext@).)*$\r?\n?' | true
end
with_them do
before do
setting.update(user_default_external: user_default_external)
setting.update(user_default_internal_regex: user_default_internal_regex)
end
subject { setting.user_default_internal_regex_enabled? }
it { is_expected.to eq(result) }
end
end
end end
# frozen_string_literal: true
require 'spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../rubocop/cop/prefer_class_methods_over_module'
describe RuboCop::Cop::PreferClassMethodsOverModule do
include CopHelper
subject(:cop) { described_class.new }
it 'flags violation when using module ClassMethods' do
expect_offense(<<~RUBY)
module Foo
extend ActiveSupport::Concern
module ClassMethods
^^^^^^^^^^^^^^^^^^^ Do not use module ClassMethods, use class_methods block instead.
def a_class_method
end
end
end
RUBY
end
it "doesn't flag violation when using class_methods" do
expect_no_offenses(<<~RUBY)
module Foo
extend ActiveSupport::Concern
class_methods do
def a_class_method
end
end
end
RUBY
end
it "doesn't flag violation when module is not extending ActiveSupport::Concern" do
expect_no_offenses(<<~RUBY)
module Foo
module ClassMethods
def a_class_method
end
end
end
RUBY
end
it "doesn't flag violation when ClassMethods is used inside a class" do
expect_no_offenses(<<~RUBY)
class Foo
module ClassMethods
def a_class_method
end
end
end
RUBY
end
it "doesn't flag violation when not using either class_methods or ClassMethods" do
expect_no_offenses(<<~RUBY)
module Foo
extend ActiveSupport::Concern
def a_method
end
end
RUBY
end
it 'autocorrects ClassMethods into class_methods' do
source = <<~RUBY
module Foo
extend ActiveSupport::Concern
module ClassMethods
def a_class_method
end
end
end
RUBY
autocorrected = autocorrect_source(source)
expected_source = <<~RUBY
module Foo
extend ActiveSupport::Concern
class_methods do
def a_class_method
end
end
end
RUBY
expect(autocorrected).to eq(expected_source)
end
end
...@@ -169,6 +169,35 @@ describe Projects::TransferService do ...@@ -169,6 +169,35 @@ describe Projects::TransferService do
it { expect(project.errors[:new_namespace]).to include('Cannot move project') } it { expect(project.errors[:new_namespace]).to include('Cannot move project') }
end end
context 'target namespace containing the same project name' do
before do
group.add_owner(user)
project.update(name: 'new_name')
create(:project, name: 'new_name', group: group, path: 'other')
@result = transfer_project(project, user, group)
end
it { expect(@result).to eq false }
it { expect(project.namespace).to eq(user.namespace) }
it { expect(project.errors[:new_namespace]).to include('Project with same name or path in target namespace already exists') }
end
context 'target namespace containing the same project path' do
before do
group.add_owner(user)
create(:project, name: 'other-name', path: project.path, group: group)
@result = transfer_project(project, user, group)
end
it { expect(@result).to eq false }
it { expect(project.namespace).to eq(user.namespace) }
it { expect(project.errors[:new_namespace]).to include('Project with same name or path in target namespace already exists') }
end
def transfer_project(project, user, new_namespace) def transfer_project(project, user, new_namespace)
service = Projects::TransferService.new(project, user) service = Projects::TransferService.new(project, user)
......
...@@ -13,6 +13,59 @@ describe Users::BuildService do ...@@ -13,6 +13,59 @@ describe Users::BuildService do
it 'returns a valid user' do it 'returns a valid user' do
expect(service.execute).to be_valid expect(service.execute).to be_valid
end end
context 'with "user_default_external" application setting' do
using RSpec::Parameterized::TableSyntax
where(:user_default_external, :external, :email, :user_default_internal_regex, :result) do
true | nil | 'fl@example.com' | nil | true
true | true | 'fl@example.com' | nil | true
true | false | 'fl@example.com' | nil | false
true | nil | 'fl@example.com' | '' | true
true | true | 'fl@example.com' | '' | true
true | false | 'fl@example.com' | '' | false
true | nil | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | false
true | true | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | true
true | false | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | false
true | nil | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | true
true | true | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | true
true | false | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | false
false | nil | 'fl@example.com' | nil | false
false | true | 'fl@example.com' | nil | true
false | false | 'fl@example.com' | nil | false
false | nil | 'fl@example.com' | '' | false
false | true | 'fl@example.com' | '' | true
false | false | 'fl@example.com' | '' | false
false | nil | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | false
false | true | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | true
false | false | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | false
false | nil | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | false
false | true | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | true
false | false | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | false
end
with_them do
before do
stub_application_setting(user_default_external: user_default_external)
stub_application_setting(user_default_internal_regex: user_default_internal_regex)
params.merge!({ external: external, email: email }.compact)
end
subject(:user) { service.execute }
it 'correctly sets user.external' do
expect(user.external).to eq(result)
end
end
end
end end
context 'with non admin user' do context 'with non admin user' do
...@@ -50,6 +103,59 @@ describe Users::BuildService do ...@@ -50,6 +103,59 @@ describe Users::BuildService do
expect(service.execute).to be_confirmed expect(service.execute).to be_confirmed
end end
end end
context 'with "user_default_external" application setting' do
using RSpec::Parameterized::TableSyntax
where(:user_default_external, :external, :email, :user_default_internal_regex, :result) do
true | nil | 'fl@example.com' | nil | true
true | true | 'fl@example.com' | nil | true
true | false | 'fl@example.com' | nil | true
true | nil | 'fl@example.com' | '' | true
true | true | 'fl@example.com' | '' | true
true | false | 'fl@example.com' | '' | true
true | nil | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | true
true | true | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | true
true | false | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | true
true | nil | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | true
true | true | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | true
true | false | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | true
false | nil | 'fl@example.com' | nil | false
false | true | 'fl@example.com' | nil | false
false | false | 'fl@example.com' | nil | false
false | nil | 'fl@example.com' | '' | false
false | true | 'fl@example.com' | '' | false
false | false | 'fl@example.com' | '' | false
false | nil | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | false
false | true | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | false
false | false | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | false
false | nil | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | false
false | true | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | false
false | false | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | false
end
with_them do
before do
stub_application_setting(user_default_external: user_default_external)
stub_application_setting(user_default_internal_regex: user_default_internal_regex)
params.merge!({ external: external, email: email }.compact)
end
subject(:user) { service.execute }
it 'sets the value of Gitlab::CurrentSettings.user_default_external' do
expect(user.external).to eq(result)
end
end
end
end end
end end
end end
...@@ -42,6 +42,7 @@ Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f } ...@@ -42,6 +42,7 @@ Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
RSpec.configure do |config| RSpec.configure do |config|
config.use_transactional_fixtures = false config.use_transactional_fixtures = false
config.use_instantiated_fixtures = false config.use_instantiated_fixtures = false
config.fixture_path = Rails.root
config.verbose_retry = true config.verbose_retry = true
config.display_try_failure_messages = true config.display_try_failure_messages = true
......
...@@ -11,6 +11,4 @@ RSpec.configure do |config| ...@@ -11,6 +11,4 @@ RSpec.configure do |config|
config.include StubMetrics config.include StubMetrics
config.include StubObjectStorage config.include StubObjectStorage
config.include StubENV config.include StubENV
config.fixture_path = Rails.root if defined?(Rails)
end end
# frozen_string_literal: true
# Shared examples for controllers that load and send files from the git repository
# (like Projects::RawController or Projects::AvatarsController)
# These examples requires the following variables:
# - `project`
# - `filename`: filename of the file
# - `filepath`: path of the file (contains filename)
# - `subject`: the request to be made to the controller. Example:
# subject { get :show, namespace_id: project.namespace, project_id: project }
shared_examples 'repository lfs file load' do
context 'when file is stored in lfs' do
let(:lfs_oid) { '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897' }
let(:lfs_size) { '1575078' }
let!(:lfs_object) { create(:lfs_object, oid: lfs_oid, size: lfs_size) }
context 'when lfs is enabled' do
before do
allow_any_instance_of(Project).to receive(:lfs_enabled?).and_return(true)
end
context 'when project has access' do
before do
project.lfs_objects << lfs_object
allow_any_instance_of(LfsObjectUploader).to receive(:exists?).and_return(true)
allow(controller).to receive(:send_file) { controller.head :ok }
end
it 'serves the file' do
expect(controller).to receive(:send_file).with("#{LfsObjectUploader.root}/91/ef/f75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897", filename: filename, disposition: 'attachment')
subject
expect(response).to have_gitlab_http_status(200)
end
context 'and lfs uses object storage' do
let(:lfs_object) { create(:lfs_object, :with_file, oid: lfs_oid, size: lfs_size) }
before do
stub_lfs_object_storage
lfs_object.file.migrate!(LfsObjectUploader::Store::REMOTE)
end
it 'responds with redirect to file' do
subject
expect(response).to have_gitlab_http_status(302)
expect(response.location).to include(lfs_object.reload.file.path)
end
it 'sets content disposition' do
subject
file_uri = URI.parse(response.location)
params = CGI.parse(file_uri.query)
expect(params["response-content-disposition"].first).to eq "attachment;filename=\"#{filename}\""
end
end
end
context 'when project does not have access' do
it 'does not serve the file' do
subject
expect(response).to have_gitlab_http_status(404)
end
end
end
context 'when lfs is not enabled' do
before do
allow_any_instance_of(Project).to receive(:lfs_enabled?).and_return(false)
end
it 'delivers ASCII file' do
subject
expect(response).to have_gitlab_http_status(200)
expect(response.header['Content-Type']).to eq('text/plain; charset=utf-8')
expect(response.header['Content-Disposition'])
.to eq('inline')
expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with('git-blob:')
end
end
end
end
require 'spec_helper'
describe JsRegexValidator do
describe '#validates_each' do
using RSpec::Parameterized::TableSyntax
let(:validator) { described_class.new(attributes: [:user_default_internal_regex]) }
let(:application_setting) { build(:application_setting, user_default_external: true) }
where(:user_default_internal_regex, :result) do
nil | []
'' | []
'(?#comment)' | ['Regex Pattern (?#comment) can not be expressed in Javascript']
'(?(a)b|c)' | ['invalid conditional pattern: /(?(a)b|c)/i']
'[a-z&&[^uo]]' | ["Dropped unsupported set intersection '[a-z&&[^uo]]' at index 0",
"Dropped unsupported nested negative set data '[^uo]' at index 6"]
end
with_them do
it 'generates correct errors' do
validator.validate_each(application_setting, :user_default_internal_regex, user_default_internal_regex)
expect(application_setting.errors[:user_default_internal_regex]).to eq result
end
end
end
end
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