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.
## 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)
......@@ -269,7 +271,9 @@ entry.
## 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)
......
......@@ -195,6 +195,9 @@ gem 're2', '~> 1.1.1'
gem 'version_sorter', '~> 2.1.0'
# Export Ruby Regex to Javascript
gem 'js_regex', '~> 2.2.1'
# User agent parsing
gem 'device_detector'
......@@ -365,7 +368,7 @@ group :development, :test do
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 'activerecord_sane_schema_dumper', gem_versions['activerecord_sane_schema_dumper']
......
......@@ -86,7 +86,6 @@ GEM
bindata (2.4.3)
binding_of_caller (0.7.2)
debug_inspector (>= 0.0.1)
blankslate (2.1.2.4)
bootsnap (1.3.1)
msgpack (~> 1.0)
bootstrap_form (2.7.0)
......@@ -428,6 +427,8 @@ GEM
multipart-post
oauth (~> 0.5, >= 0.5.0)
jquery-atwho-rails (1.3.2)
js_regex (2.2.1)
regexp_parser (>= 0.4.11, <= 0.5.0)
json (1.8.6)
json-jwt (1.9.4)
activesupport
......@@ -463,13 +464,12 @@ GEM
actionmailer (>= 3.2)
letter_opener (~> 1.0)
railties (>= 3.2)
license_finder (3.1.1)
license_finder (5.4.0)
bundler
httparty
rubyzip
thor
toml (= 0.1.2)
with_env (> 1.0)
toml (= 0.2.0)
with_env (= 1.1.0)
xml-simple
licensee (8.9.2)
rugged (~> 0.24)
......@@ -587,8 +587,7 @@ GEM
parallel (1.12.1)
parser (2.5.1.0)
ast (~> 2.4.0)
parslet (1.5.0)
blankslate (~> 2.0)
parslet (1.8.2)
path_expander (1.0.2)
peek (1.0.1)
concurrent-ruby (>= 0.9.0)
......@@ -726,6 +725,7 @@ GEM
redis-store (>= 1.2, < 2)
redis-store (1.4.1)
redis (>= 2.2, < 5)
regexp_parser (0.5.0)
representable (3.0.4)
declarative (< 0.1.0)
declarative-option (< 0.2.0)
......@@ -907,8 +907,8 @@ GEM
tilt (2.0.8)
timecop (0.8.1)
timfel-krb5-auth (0.8.3)
toml (0.1.2)
parslet (~> 1.5.0)
toml (0.2.0)
parslet (~> 1.8.0)
toml-rb (1.0.0)
citrus (~> 3.0, > 3.0)
trollop (2.1.3)
......@@ -1074,13 +1074,14 @@ DEPENDENCIES
influxdb (~> 0.2)
jira-ruby (~> 1.4)
jquery-atwho-rails (~> 1.3.2)
js_regex (~> 2.2.1)
json-schema (~> 2.8.0)
jwt (~> 1.5.6)
kaminari (~> 1.0)
knapsack (~> 1.16)
kubeclient (~> 3.1.0)
letter_opener_web (~> 1.3.0)
license_finder (~> 3.1)
license_finder (~> 5.4)
licensee (~> 8.9)
lograge (~> 0.5)
loofah (~> 2.2)
......@@ -1201,4 +1202,4 @@ DEPENDENCIES
wikicloth (= 0.8.1)
BUNDLED WITH
1.16.3
1.16.4
......@@ -89,7 +89,6 @@ GEM
bindata (2.4.3)
binding_of_caller (0.7.2)
debug_inspector (>= 0.0.1)
blankslate (2.1.2.4)
bootsnap (1.3.1)
msgpack (~> 1.0)
bootstrap_form (2.7.0)
......@@ -431,6 +430,8 @@ GEM
multipart-post
oauth (~> 0.5, >= 0.5.0)
jquery-atwho-rails (1.3.2)
js_regex (2.2.1)
regexp_parser (>= 0.4.11, <= 0.5.0)
json (1.8.6)
json-jwt (1.9.4)
activesupport
......@@ -466,13 +467,12 @@ GEM
actionmailer (>= 3.2)
letter_opener (~> 1.0)
railties (>= 3.2)
license_finder (3.1.1)
license_finder (5.4.0)
bundler
httparty
rubyzip
thor
toml (= 0.1.2)
with_env (> 1.0)
toml (= 0.2.0)
with_env (= 1.1.0)
xml-simple
licensee (8.9.2)
rugged (~> 0.24)
......@@ -591,8 +591,7 @@ GEM
parallel (1.12.1)
parser (2.5.1.0)
ast (~> 2.4.0)
parslet (1.5.0)
blankslate (~> 2.0)
parslet (1.8.2)
path_expander (1.0.2)
peek (1.0.1)
concurrent-ruby (>= 0.9.0)
......@@ -735,6 +734,7 @@ GEM
redis-store (>= 1.2, < 2)
redis-store (1.4.1)
redis (>= 2.2, < 5)
regexp_parser (0.5.0)
representable (3.0.4)
declarative (< 0.1.0)
declarative-option (< 0.2.0)
......@@ -914,8 +914,8 @@ GEM
tilt (2.0.8)
timecop (0.8.1)
timfel-krb5-auth (0.8.3)
toml (0.1.2)
parslet (~> 1.5.0)
toml (0.2.0)
parslet (~> 1.8.0)
toml-rb (1.0.0)
citrus (~> 3.0, > 3.0)
trollop (2.1.3)
......@@ -1084,13 +1084,14 @@ DEPENDENCIES
influxdb (~> 0.2)
jira-ruby (~> 1.4)
jquery-atwho-rails (~> 1.3.2)
js_regex (~> 2.2.1)
json-schema (~> 2.8.0)
jwt (~> 1.5.6)
kaminari (~> 1.0)
knapsack (~> 1.16)
kubeclient (~> 3.1.0)
letter_opener_web (~> 1.3.0)
license_finder (~> 3.1)
license_finder (~> 5.4)
licensee (~> 8.9)
lograge (~> 0.5)
loofah (~> 2.2)
......@@ -1211,4 +1212,4 @@ DEPENDENCIES
wikicloth (= 0.8.1)
BUNDLED WITH
1.16.3
1.16.4
......@@ -10,6 +10,7 @@ const hideFlash = (flashEl, fadeTransition = true) => {
flashEl.addEventListener('transitionend', () => {
flashEl.remove();
window.dispatchEvent(new Event('resize'));
if (document.body.classList.contains('flash-shown')) document.body.classList.remove('flash-shown');
}, {
once: true,
......
......@@ -78,13 +78,13 @@ export default {
</script>
<template>
<article class="ide">
<article class="ide position-relative d-flex flex-column align-items-stretch">
<error-message
v-if="errorMessage"
:message="errorMessage"
/>
<div
class="ide-view"
class="ide-view flex-grow d-flex"
>
<find-file
v-show="fileFindVisible"
......
......@@ -24,12 +24,6 @@ export default {
default: null,
},
},
mounted() {
this.$refs.fileUpload.addEventListener('change', this.openFile);
},
beforeDestroy() {
this.$refs.fileUpload.removeEventListener('change', this.openFile);
},
methods: {
createFile(target, file, isText) {
const { name } = file;
......@@ -85,6 +79,8 @@ export default {
ref="fileUpload"
type="file"
class="hidden"
multiple
@change="openFile"
/>
</div>
</template>
......@@ -95,16 +95,18 @@ export default {
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() {
if (this.hasPathAtCurrentRoute()) {
this.scrollIntoView(true);
}
},
updated() {
if (this.file.type === 'blob' && this.file.active) {
this.scrollIntoView();
}
},
methods: {
...mapActions(['toggleTreeOpen']),
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 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 {
display: inline-flex;
vertical-align: middle;
font-family: "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
font-size: 1.5em;
line-height: 0.9;
font-size: 1.4em;
line-height: 1em;
}
......@@ -111,3 +111,42 @@ body {
.with-performance-bar .layout-page {
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 {
pre {
font-family: $monospace-font;
display: block;
padding: $gl-padding-8;
padding: $gl-padding-8 $input-horizontal-padding;
margin: 0 0 $gl-padding-8;
font-size: 13px;
word-break: break-all;
......
......@@ -236,6 +236,7 @@ $gl-vert-padding: 6px;
$gl-padding-top: 10px;
$gl-sidebar-padding: 22px;
$gl-bar-padding: 3px;
$input-horizontal-padding: 12px;
/*
* Misc
......
......@@ -28,11 +28,10 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
.ide-view {
position: relative;
display: flex;
height: calc(100vh - #{$header-height});
margin-top: 0;
padding-bottom: $ide-statusbar-height;
color: $gl-text-color;
min-height: 0; // firefox fix
&.is-collapsed {
.ide-file-list {
......@@ -50,7 +49,7 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
display: flex;
flex-direction: column;
flex: 1;
min-height: 0;
min-height: 0; // firefox fix
.file {
height: 32px;
......@@ -357,7 +356,7 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
.multi-file-editor-holder {
height: 100%;
min-height: 0;
min-height: 0; // firefox fix
&.is-readonly,
.editor.original {
......@@ -546,7 +545,7 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
border-left: 1px solid $white-dark;
border-top: 1px solid $white-dark;
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;
.ide-loading {
display: flex;
height: 100vh;
height: 100%;
align-items: center;
justify-content: center;
}
......@@ -772,60 +771,7 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
.ide {
overflow: hidden;
&.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});
}
}
flex: 1;
}
.drag-handle {
......@@ -1199,7 +1145,7 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
}
.avatar-container {
flex: initial;
flex: 0 0 auto;
margin-right: 0;
}
......@@ -1209,7 +1155,7 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
}
.ide-context-body {
min-height: 0;
min-height: 0; // firefox fix
}
.ide-sidebar-project-title {
......
......@@ -25,10 +25,6 @@
color: $gl-text-color;
border-radius: 0 0 3px 3px;
.code {
padding: 0;
}
.unfold {
cursor: pointer;
}
......
......@@ -141,6 +141,9 @@ ul.notes {
}
.note-body {
overflow-x: auto;
overflow-y: hidden;
.note-text {
@include md-typography;
// 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
layout 'nav_only'
layout 'fullscreen'
def index
end
......
class Projects::AvatarsController < Projects::ApplicationController
include BlobHelper
include SendsBlob
before_action :authorize_admin_project!, only: [:destroy]
def show
@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_git_blob @repository, @blob
else
render_404
end
send_blob(@blob)
end
def destroy
@project.remove_avatar!
@project.save
redirect_to edit_project_path(@project, anchor: 'js-general-project-settings'), status: :found
......
# Controller for viewing a file's raw
class Projects::RawController < Projects::ApplicationController
include ExtractsPath
include BlobHelper
include SendFileUpload
include SendsBlob
before_action :require_non_empty_project
before_action :assign_ref_vars
......@@ -10,39 +9,7 @@ class Projects::RawController < Projects::ApplicationController
def show
@blob = @repository.blob_at(@commit.id, @path)
if @blob
headers['X-Content-Type-Options'] = 'nosniff'
return if cached_blob?
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
send_blob(@blob, inline: (params[:inline] != 'false'))
end
end
......@@ -255,6 +255,7 @@ module ApplicationSettingsHelper
:instance_statistics_visibility_private,
:user_default_external,
:user_show_add_ssh_key_message,
:user_default_internal_regex,
:user_oauth_applications,
:version_check_enabled,
:web_ide_clientside_preview_enabled
......
......@@ -157,28 +157,6 @@ module BlobHelper
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
return @licenses_for_select if defined?(@licenses_for_select)
......
......@@ -64,8 +64,7 @@ module SubmoduleHelper
end
def relative_self_url?(url)
# (./)?(../repo.git) || (./)?(../../project/repo.git) )
url =~ %r{\A((\./)?(\.\./))(?!(\.\.)|(.*/)).*(\.git)?\z} || url =~ %r{\A((\./)?(\.\./){2})(?!(\.\.))([^/]*)/(?!(\.\.)|(.*/)).*(\.git)?\z}
url.start_with?('../', './')
end
def standard_links(host, namespace, project, commit)
......@@ -73,25 +72,29 @@ module SubmoduleHelper
[base, [base, '/tree/', commit].join('')]
end
def relative_self_links(url, commit, project)
url.rstrip!
# Map relative links to a namespace and project
# For example:
# ../bar.git -> same namespace, repo bar
# ../foo/bar.git -> namespace foo, repo bar
# ../../foo/bar/baz.git -> namespace bar, repo baz
components = url.split('/')
base = components.pop.gsub(/.git$/, '')
namespace = components.pop.gsub(/^\.\.$/, '')
def relative_self_links(relative_path, commit, project)
relative_path.rstrip!
absolute_project_path = "/" + project.full_path
if namespace.empty?
namespace = project.namespace.full_path
# Resolve `relative_path` to target path
# Assuming `absolute_project_path` is `/g1/p1`:
# ../p2.git -> /g1/p2
# ../g2/p3.git -> /g1/g2/p3
# ../../g3/g4/p4.git -> /g3/g4/p4
submodule_project_path = File.absolute_path(relative_path, absolute_project_path)
target_namespace_path = File.dirname(submodule_project_path)
if target_namespace_path == '/' || target_namespace_path.start_with?(absolute_project_path)
return [nil, nil]
end
target_namespace_path.sub!(%r{^/}, '')
submodule_base = File.basename(submodule_project_path, '.git')
begin
[
namespace_project_path(namespace, base),
namespace_project_tree_path(namespace, base, commit)
namespace_project_path(target_namespace_path, submodule_base),
namespace_project_tree_path(target_namespace_path, submodule_base, commit)
]
rescue ActionController::UrlGenerationError
[nil, nil]
......
......@@ -23,6 +23,17 @@ module UsersHelper
profile_tabs.include?(tab)
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
@current_user_menu_items ||= get_current_user_menu_items
end
......
......@@ -192,6 +192,8 @@ class ApplicationSetting < ActiveRecord::Base
numericality: { less_than_or_equal_to: :gitaly_timeout_default },
if: :gitaly_timeout_default
validates :user_default_internal_regex, js_regex: true, allow_nil: true
SUPPORTED_KEY_TYPES.each do |type|
validates :"#{type}_key_restriction", presence: true, key_restriction: { type: type }
end
......@@ -299,6 +301,7 @@ class ApplicationSetting < ActiveRecord::Base
usage_ping_enabled: Settings.gitlab['usage_ping_enabled'],
instance_statistics_visibility_private: false,
user_default_external: false,
user_default_internal_regex: nil,
user_show_add_ssh_key_message: true
}
end
......@@ -435,6 +438,14 @@ class ApplicationSetting < ActiveRecord::Base
password_authentication_enabled_for_web? || password_authentication_enabled_for_git?
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
def latest_terms
@latest_terms ||= Term.latest
......
......@@ -26,7 +26,7 @@
module AtomicInternalId
extend ActiveSupport::Concern
module ClassMethods
class_methods do
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
# InternaLId record (we may delete records in `internal_ids` for example).
......
......@@ -12,7 +12,7 @@ module Awardable
end
end
module ClassMethods
class_methods do
def awarded(user, name)
sql = <<~EOL
EXISTS (
......
......@@ -4,7 +4,7 @@
module CaseSensitivity
extend ActiveSupport::Concern
module ClassMethods
class_methods do
# Queries the given columns regardless of the casing used.
#
# Unlike other ActiveRecord methods this method only operates on a Hash.
......
......@@ -3,7 +3,7 @@
module EachBatch
extend ActiveSupport::Concern
module ClassMethods
class_methods do
# Iterates over the rows in a relation in batches, similar to Rails'
# `in_batches` but in a more efficient way.
#
......
......@@ -14,7 +14,7 @@
module IgnorableColumn
extend ActiveSupport::Concern
module ClassMethods
class_methods do
def columns
super.reject { |column| ignored_columns.include?(column.name) }
end
......
......@@ -118,7 +118,7 @@ module Issuable
end
end
module ClassMethods
class_methods do
# Searches for records with a matching title.
#
# This method uses ILIKE on PostgreSQL and LIKE on MySQL.
......
......@@ -3,7 +3,7 @@
module LoadedInGroupList
extend ActiveSupport::Concern
module ClassMethods
class_methods do
def with_counts(archived:)
selects_including_counts = [
'namespaces.*',
......
......@@ -3,7 +3,7 @@
module ManualInverseAssociation
extend ActiveSupport::Concern
module ClassMethods
class_methods do
def manual_inverse_association(association, inverse)
define_method(association) do |*args|
super(*args).tap do |value|
......
......@@ -10,7 +10,7 @@
module Mentionable
extend ActiveSupport::Concern
module ClassMethods
class_methods do
# Indicate which attributes of the Mentionable to search for GFM references.
def attr_mentionable(attr, options = {})
attr = attr.to_s
......
......@@ -3,7 +3,7 @@
module OptionallySearch
extend ActiveSupport::Concern
module ClassMethods
class_methods do
def search(*)
raise(
NotImplementedError,
......
......@@ -26,7 +26,7 @@
module Participable
extend ActiveSupport::Concern
module ClassMethods
class_methods do
# Adds a list of participant attributes. Attributes can either be symbols or
# Procs.
#
......
......@@ -40,7 +40,7 @@ module Referable
end
end
module ClassMethods
class_methods do
# The character that prefixes the actual reference identifier
#
# This should be overridden by the including class.
......
......@@ -20,7 +20,7 @@ module ResolvableNote
scope :unresolved, -> { resolvable.where(resolved_at: nil) }
end
module ClassMethods
class_methods do
# This method must be kept in sync with `#resolve!`
def resolve!(current_user)
unresolved.update_all(resolved_at: Time.now, resolved_by_id: current_user.id)
......
......@@ -3,7 +3,7 @@
module SelectForProjectAuthorization
extend ActiveSupport::Concern
module ClassMethods
class_methods do
def select_for_project_authorization
select("projects.id AS project_id, members.access_level")
end
......
......@@ -3,7 +3,7 @@
module ShaAttribute
extend ActiveSupport::Concern
module ClassMethods
class_methods do
def sha_attribute(name)
return if ENV['STATIC_VERIFICATION']
......
......@@ -19,7 +19,7 @@ module Sortable
scope :order_name_desc, -> { reorder(Arel::Nodes::Descending.new(arel_table[:name].lower)) }
end
module ClassMethods
class_methods do
def order_by(method)
case method.to_s
when 'created_asc' then order_created_asc
......
......@@ -3,7 +3,7 @@
module Spammable
extend ActiveSupport::Concern
module ClassMethods
class_methods do
def attr_spammable(attr, options = {})
spammable_attrs << [attr.to_s, options]
end
......
......@@ -14,7 +14,7 @@
module StripAttribute
extend ActiveSupport::Concern
module ClassMethods
class_methods do
def strip_attributes(*attrs)
strip_attrs.concat(attrs)
end
......
......@@ -6,6 +6,7 @@ module TriggerableHooks
push_hooks: :push_events,
tag_push_hooks: :tag_push_events,
issue_hooks: :issues_events,
confidential_note_hooks: :confidential_note_events,
confidential_issue_hooks: :confidential_issues_events,
note_hooks: :note_events,
merge_request_hooks: :merge_requests_events,
......
......@@ -2,7 +2,6 @@
class DiffFileEntity < Grape::Entity
include RequestAwareEntity
include BlobHelper
include CommitsHelper
include DiffHelper
include SubmoduleHelper
......
......@@ -43,8 +43,8 @@ module Projects
@new_path = File.join(@new_namespace.try(:full_path) || '', project.path)
@old_namespace = project.namespace
if Project.where(path: project.path, namespace_id: @new_namespace.try(:id)).exists?
raise TransferError.new("Project with same path in target namespace already 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 name or path in target namespace already exists")
end
if project.has_container_registry_tags?
......@@ -118,6 +118,7 @@ module Projects
def rollback_side_effects
rollback_folder_move
project.reload
update_namespace_and_visibility(@old_namespace)
write_repository_config(@old_path)
end
......
......@@ -2,6 +2,10 @@
module Users
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 = {})
@current_user = current_user
@params = params.dup
......@@ -89,6 +93,10 @@ module Users
if params[:reset_password]
user_params.merge!(force_random_password: true, password_expires_at: nil)
end
if user_default_internal_regex_enabled? && !user_params.key?(:external)
user_params[:external] = user_external?
end
else
allowed_signup_params = signup_params
allowed_signup_params << :skip_confirmation if skip_authorization
......@@ -105,5 +113,9 @@ module Users
def skip_user_confirmation_email_from_setting
!Gitlab::CurrentSettings.send_user_confirmation_email
end
def user_external?
user_default_internal_regex_instance.match(params[:email]).nil?
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 @@
= f.check_box :user_default_external, class: 'form-check-input'
= f.label :user_default_external, class: 'form-check-label' do
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
= f.label :user_show_add_ssh_key_message, 'Prompt users to upload SSH keys', class: 'label-bold'
.form-check
......
......@@ -34,8 +34,12 @@
.form-group.row
.col-sm-2.text-right
= f.label :external, class: 'col-form-label'
.hidden{ data: user_internal_regex_data }
.col-sm-10
= f.check_box :external do
External
%p.light
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'
- content_for :page_specific_javascripts do
......
!!! 5
%html{ lang: I18n.locale, class: page_class }
= 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 "layouts/header/default"
= render 'shared/outdated_browser'
......@@ -10,5 +10,5 @@
= render "layouts/broadcast"
= yield :flash_message
= render "layouts/flash"
.content{ id: "content-body" }
.content-wrapper{ id: "content-body", class: "d-flex flex-column align-items-stretch" }
= yield
......@@ -30,11 +30,13 @@
%pre.dark#merge-info-3
- if @merge_request.for_fork?
: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}
- else
: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}
%p
%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 @@
.alert.alert-success.prepend-top-default
%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/sidebar', milestone: @milestone, project: @project, affix_offset: 153
......@@ -10,17 +10,7 @@ class BackgroundMigrationWorker
# maintenance related tasks have plenty of time to clean up after a migration
# has been performed.
def self.minimum_interval
if enable_health_check?
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
# Performs the background migration.
......@@ -86,8 +76,6 @@ class BackgroundMigrationWorker
# class_name - The name of the background migration that we might want to
# run.
def healthy_database?
return true unless self.class.enable_health_check?
return true unless Gitlab::Database.postgresql?
!Postgresql::ReplicationSlot.lag_too_great?
......
......@@ -11,7 +11,7 @@ module ApplicationWorker
set_queue
end
module ClassMethods
class_methods do
def inherited(subclass)
subclass.set_queue
end
......
......@@ -3,7 +3,7 @@
module WaitableWorker
extend ActiveSupport::Concern
module ClassMethods
class_methods do
# Schedules multiple jobs and waits for them to be completed.
def bulk_perform_and_wait(args_list, timeout: 10)
# 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
t.boolean "authorized_keys_enabled", default: true, null: false
t.string "auto_devops_domain"
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 "enforce_terms", default: 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.
- [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)
- [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
......
......@@ -27,6 +27,7 @@ Example response:
"name": "master",
"merged": false,
"protected": true,
"default": true,
"developers_can_push": false,
"developers_can_merge": false,
"can_push": true,
......@@ -75,6 +76,7 @@ Example response:
"name": "master",
"merged": false,
"protected": true,
"default": true,
"developers_can_push": false,
"developers_can_merge": false,
"can_push": true,
......@@ -141,6 +143,7 @@ Example response:
"name": "master",
"merged": false,
"protected": true,
"default": true,
"developers_can_push": true,
"developers_can_merge": true,
"can_push": true
......@@ -190,6 +193,7 @@ Example response:
"name": "master",
"merged": false,
"protected": false,
"default": true,
"developers_can_push": false,
"developers_can_merge": false,
"can_push": true
......@@ -234,6 +238,7 @@ Example response:
"name": "newbranch",
"merged": false,
"protected": false,
"default": false,
"developers_can_push": false,
"developers_can_merge": false,
"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
......@@ -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 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]**
>[Introduced][ee-998] in [GitLab Premium][eep] 8.17.
......
......@@ -2,7 +2,7 @@
## 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
......
......@@ -84,7 +84,7 @@ module API
end
end
module ClassMethods
class_methods do
private
def install_error_responders(base)
......
......@@ -370,6 +370,10 @@ module API
expose :can_push do |repo_branch, options|
Gitlab::UserAccess.new(options[:current_user], project: options[:project]).can_push_to_branch?(repo_branch.name)
end
expose :default do |repo_branch, options|
options[:project].default_branch == repo_branch.name
end
end
class TreeObject < Grape::Entity
......
......@@ -2,7 +2,7 @@ module API
module ProjectsRelationBuilder
extend ActiveSupport::Concern
module ClassMethods
class_methods do
def prepare_relation(projects_relation, options = {})
projects_relation = preload_relation(projects_relation, options)
execute_batch_counting(projects_relation)
......
......@@ -75,7 +75,7 @@ module Gitlab
end
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
private
......
......@@ -233,6 +233,8 @@ module Gitlab
end
elsif user
# User access is verified in check_change_access!
elsif authed_via_jwt?
# Authenticated via JWT
else
raise UnauthorizedError, ERROR_MESSAGES[:upload]
end
......@@ -321,6 +323,10 @@ module Gitlab
!Gitlab.config.gitlab_shell.receive_pack
end
def authed_via_jwt?
false
end
protected
def changes_list
......
......@@ -6,7 +6,7 @@ module Gitlab
module ExposeAttribute
extend ActiveSupport::Concern
module ClassMethods
class_methods do
# Defines getter methods for the given attribute names.
#
# Example:
......
......@@ -5,7 +5,7 @@ module Gitlab
module MountMutation
extend ActiveSupport::Concern
module ClassMethods
class_methods do
def mount_mutation(mutation_class)
# Using an underscored field name symbol will make `graphql-ruby`
# standardize the field name
......
......@@ -94,7 +94,10 @@ module Gitlab
end
def restore_project
@project.update_columns(project_params)
Gitlab::Timeless.timeless(@project) do
@project.update(project_params)
end
@project
end
......
......@@ -2,7 +2,7 @@
module StaticModel
extend ActiveSupport::Concern
module ClassMethods
class_methods do
# Used by ActiveRecord's polymorphic association to set object_id
def primary_key
'id'
......
......@@ -727,6 +727,9 @@ msgstr ""
msgid "AutoDevOps|enable Auto DevOps"
msgstr ""
msgid "Automatically marked as default internal user"
msgstr ""
msgid "Available"
msgstr ""
......@@ -2801,6 +2804,9 @@ msgstr ""
msgid "GitLab.com import"
msgstr ""
msgid "GitLab’s issue tracker"
msgstr ""
msgid "Gitaly"
msgstr ""
......@@ -3187,6 +3193,9 @@ msgstr ""
msgid "Internal - The project can be accessed by any logged in user."
msgstr ""
msgid "Internal users"
msgstr ""
msgid "Interval Pattern"
msgstr ""
......@@ -3387,6 +3396,9 @@ msgstr ""
msgid "Learn more"
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"
msgstr ""
......@@ -4670,6 +4682,9 @@ msgid_plural "Refreshing in %d seconds to show the updated status..."
msgstr[0] ""
msgstr[1] ""
msgid "Regex pattern"
msgstr ""
msgid "Register / Sign In"
msgstr ""
......@@ -5277,6 +5292,9 @@ msgstr ""
msgid "Specific Runners"
msgstr ""
msgid "Specify an e-mail address regex pattern to identify default internal users."
msgstr ""
msgid "Specify the following URL during the Runner setup:"
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."
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."
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}."
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."
msgstr ""
......@@ -6590,6 +6614,9 @@ msgstr ""
msgid "importing"
msgstr ""
msgid "issue boards"
msgstr ""
msgid "latest version"
msgstr ""
......
require 'pathname'
module QA
module Page
class View
......@@ -9,7 +11,7 @@ module QA
end
def pathname
@pathname ||= Pathname.new(::File.join(__dir__, '../../../', @path))
@pathname ||= ::Pathname.new(::File.join(__dir__, '../../../', @path))
.cleanpath.expand_path
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'
require_relative 'cop/avoid_return_from_blocks'
require_relative 'cop/avoid_break_from_strong_memoize'
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_concurrent_foreign_key'
require_relative 'cop/migration/add_concurrent_index'
......
function schema_changed() {
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"
#!/bin/sh
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
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
}
......
require 'spec_helper'
describe Projects::AvatarsController do
let(:project) { create(:project, :repository, avatar: fixture_file_upload("spec/fixtures/dk.png", "image/png")) }
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
before do
sign_in(user)
project.add_maintainer(user)
controller.instance_variable_set(:@project, project)
end
it 'GET #show' do
get :show, namespace_id: project.namespace.id, project_id: project.id
describe 'GET #show' do
subject { get :show, namespace_id: project.namespace, project_id: project }
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
describe 'DELETE #destroy' do
it 'removes avatar from DB by calling destroy' do
delete :destroy, namespace_id: project.namespace.id, project_id: project.id
expect(project.avatar.present?).to be_falsey
expect(project).to be_valid
end
end
end
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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