Commit 7f658220 authored by GitLab Bot's avatar GitLab Bot

Merge remote-tracking branch 'upstream/master' into ce-to-ee-2018-07-06

# Conflicts:
#	spec/lib/gitlab/database_spec.rb

[ci skip]
parents 60904171 fbaaa574
...@@ -433,7 +433,7 @@ group :ed25519 do ...@@ -433,7 +433,7 @@ group :ed25519 do
end end
# Gitaly GRPC client # Gitaly GRPC client
gem 'gitaly-proto', '~> 0.103.0', require: 'gitaly' gem 'gitaly-proto', '~> 0.105.0', require: 'gitaly'
gem 'grpc', '~> 1.11.0' gem 'grpc', '~> 1.11.0'
# Locked until https://github.com/google/protobuf/issues/4210 is closed # Locked until https://github.com/google/protobuf/issues/4210 is closed
......
...@@ -306,7 +306,7 @@ GEM ...@@ -306,7 +306,7 @@ GEM
gettext_i18n_rails (>= 0.7.1) gettext_i18n_rails (>= 0.7.1)
po_to_json (>= 1.0.0) po_to_json (>= 1.0.0)
rails (>= 3.2.0) rails (>= 3.2.0)
gitaly-proto (0.103.0) gitaly-proto (0.105.0)
google-protobuf (~> 3.1) google-protobuf (~> 3.1)
grpc (~> 1.10) grpc (~> 1.10)
github-linguist (5.3.3) github-linguist (5.3.3)
...@@ -1071,7 +1071,7 @@ DEPENDENCIES ...@@ -1071,7 +1071,7 @@ DEPENDENCIES
gettext (~> 3.2.2) gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3) gettext_i18n_rails_js (~> 1.3)
gitaly-proto (~> 0.103.0) gitaly-proto (~> 0.105.0)
github-linguist (~> 5.3.3) github-linguist (~> 5.3.3)
gitlab-flowdock-git-hook (~> 1.0.1) gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-gollum-lib (~> 4.2) gitlab-gollum-lib (~> 4.2)
......
...@@ -309,7 +309,7 @@ GEM ...@@ -309,7 +309,7 @@ GEM
gettext_i18n_rails (>= 0.7.1) gettext_i18n_rails (>= 0.7.1)
po_to_json (>= 1.0.0) po_to_json (>= 1.0.0)
rails (>= 3.2.0) rails (>= 3.2.0)
gitaly-proto (0.103.0) gitaly-proto (0.105.0)
google-protobuf (~> 3.1) google-protobuf (~> 3.1)
grpc (~> 1.10) grpc (~> 1.10)
github-linguist (5.3.3) github-linguist (5.3.3)
...@@ -1081,7 +1081,7 @@ DEPENDENCIES ...@@ -1081,7 +1081,7 @@ DEPENDENCIES
gettext (~> 3.2.2) gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3) gettext_i18n_rails_js (~> 1.3)
gitaly-proto (~> 0.103.0) gitaly-proto (~> 0.105.0)
github-linguist (~> 5.3.3) github-linguist (~> 5.3.3)
gitlab-flowdock-git-hook (~> 1.0.1) gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-gollum-lib (~> 4.2) gitlab-gollum-lib (~> 4.2)
......
...@@ -20,16 +20,13 @@ export default { ...@@ -20,16 +20,13 @@ export default {
}, },
}, },
computed: { computed: {
...mapGetters(['commit']), ...mapGetters(['commitId']),
normalizedDiffLines() { normalizedDiffLines() {
return this.diffLines.map(line => (line.richText ? trimFirstCharOfLineContent(line) : line)); return this.diffLines.map(line => (line.richText ? trimFirstCharOfLineContent(line) : line));
}, },
diffLinesLength() { diffLinesLength() {
return this.normalizedDiffLines.length; return this.normalizedDiffLines.length;
}, },
commitId() {
return this.commit && this.commit.id;
},
userColorScheme() { userColorScheme() {
return window.gon.user_color_scheme; return window.gon.user_color_scheme;
}, },
......
...@@ -21,7 +21,7 @@ export default { ...@@ -21,7 +21,7 @@ export default {
}, },
}, },
computed: { computed: {
...mapGetters(['commit']), ...mapGetters(['commitId']),
parallelDiffLines() { parallelDiffLines() {
return this.diffLines.map(line => { return this.diffLines.map(line => {
const parallelLine = Object.assign({}, line); const parallelLine = Object.assign({}, line);
...@@ -44,9 +44,6 @@ export default { ...@@ -44,9 +44,6 @@ export default {
diffLinesLength() { diffLinesLength() {
return this.parallelDiffLines.length; return this.parallelDiffLines.length;
}, },
commitId() {
return this.commit && this.commit.id;
},
userColorScheme() { userColorScheme() {
return window.gon.user_color_scheme; return window.gon.user_color_scheme;
}, },
......
import { PARALLEL_DIFF_VIEW_TYPE, INLINE_DIFF_VIEW_TYPE } from '../constants'; import { PARALLEL_DIFF_VIEW_TYPE, INLINE_DIFF_VIEW_TYPE } from '../constants';
export default { export const isParallelView = state => state.diffViewType === PARALLEL_DIFF_VIEW_TYPE;
isParallelView(state) {
return state.diffViewType === PARALLEL_DIFF_VIEW_TYPE; export const isInlineView = state => state.diffViewType === INLINE_DIFF_VIEW_TYPE;
},
isInlineView(state) { export const areAllFilesCollapsed = state => state.diffFiles.every(file => file.collapsed);
return state.diffViewType === INLINE_DIFF_VIEW_TYPE;
}, export const commitId = state => (state.commit && state.commit.id ? state.commit.id : null);
areAllFilesCollapsed(state) {
return state.diffFiles.every(file => file.collapsed); // prevent babel-plugin-rewire from generating an invalid default during karma tests
}, export default () => {};
commit(state) {
return state.commit;
},
};
import Cookies from 'js-cookie';
import { getParameterValues } from '~/lib/utils/url_utility';
import { INLINE_DIFF_VIEW_TYPE, DIFF_VIEW_COOKIE_NAME } from '../../constants';
const viewTypeFromQueryString = getParameterValues('view')[0];
const viewTypeFromCookie = Cookies.get(DIFF_VIEW_COOKIE_NAME);
const defaultViewType = INLINE_DIFF_VIEW_TYPE;
export default () => ({
isLoading: true,
endpoint: '',
basePath: '',
commit: null,
diffFiles: [],
mergeRequestDiffs: [],
diffLineCommentForms: {},
diffViewType: viewTypeFromQueryString || viewTypeFromCookie || defaultViewType,
});
import Cookies from 'js-cookie';
import { getParameterValues } from '~/lib/utils/url_utility';
import actions from '../actions'; import actions from '../actions';
import getters from '../getters'; import * as getters from '../getters';
import mutations from '../mutations'; import mutations from '../mutations';
import { INLINE_DIFF_VIEW_TYPE, DIFF_VIEW_COOKIE_NAME } from '../../constants'; import createState from './diff_state';
const viewTypeFromQueryString = getParameterValues('view')[0];
const viewTypeFromCookie = Cookies.get(DIFF_VIEW_COOKIE_NAME);
const defaultViewType = INLINE_DIFF_VIEW_TYPE;
export default { export default {
state: { state: createState(),
isLoading: true,
endpoint: '',
basePath: '',
commit: null,
diffFiles: [],
mergeRequestDiffs: [],
diffLineCommentForms: {},
diffViewType: viewTypeFromQueryString || viewTypeFromCookie || defaultViewType,
},
getters, getters,
actions, actions,
mutations, mutations,
......
...@@ -66,15 +66,10 @@ export default { ...@@ -66,15 +66,10 @@ export default {
}, },
[types.EXPAND_ALL_FILES](state) { [types.EXPAND_ALL_FILES](state) {
const diffFiles = []; // eslint-disable-next-line no-param-reassign
state.diffFiles = state.diffFiles.map(file => ({
state.diffFiles.forEach(file => { ...file,
diffFiles.push({ collapsed: false,
...file, }));
collapsed: false,
});
});
Object.assign(state, { diffFiles });
}, },
}; };
...@@ -117,7 +117,6 @@ ...@@ -117,7 +117,6 @@
overflow-x: scroll; overflow-x: scroll;
white-space: nowrap; white-space: nowrap;
min-height: 200px; min-height: 200px;
display: flex;
@include media-breakpoint-only(sm) { @include media-breakpoint-only(sm) {
height: calc(100vh - #{$issue-board-list-difference-sm}); height: calc(100vh - #{$issue-board-list-difference-sm});
...@@ -148,15 +147,17 @@ ...@@ -148,15 +147,17 @@
.board { .board {
display: inline-block; display: inline-block;
flex: 1; width: calc(85vw - 15px);
min-width: 300px;
max-width: 400px;
height: 100%; height: 100%;
padding-right: ($gl-padding / 2); padding-right: ($gl-padding / 2);
padding-left: ($gl-padding / 2); padding-left: ($gl-padding / 2);
white-space: normal; white-space: normal;
vertical-align: top; vertical-align: top;
@include media-breakpoint-up(sm) {
width: 400px;
}
&.is-expandable { &.is-expandable {
.board-header { .board-header {
cursor: pointer; cursor: pointer;
...@@ -164,8 +165,6 @@ ...@@ -164,8 +165,6 @@
} }
&.is-collapsed { &.is-collapsed {
flex: none;
min-width: 0;
width: 50px; width: 50px;
.board-header { .board-header {
......
module GroupTree module GroupTree
# rubocop:disable Gitlab/ModuleWithInstanceVariables # rubocop:disable Gitlab/ModuleWithInstanceVariables
def render_group_tree(groups) def render_group_tree(groups)
@groups = if params[:filter].present? groups = groups.sort_by_attribute(@sort = params[:sort])
# We find the ancestors by ID of the search results here.
# Otherwise the ancestors would also have filters applied,
# which would cause them not to be preloaded.
group_ids = groups.search(params[:filter]).select(:id)
Gitlab::GroupHierarchy.new(Group.where(id: group_ids))
.base_and_ancestors
else
# Only show root groups if no parent-id is given
groups.where(parent_id: params[:parent_id])
end
@groups = @groups.with_selects_for_list(archived: params[:archived]) groups = if params[:filter].present?
.sort_by_attribute(@sort = params[:sort]) filtered_groups_with_ancestors(groups)
.page(params[:page]) else
# If `params[:parent_id]` is `nil`, we will only show root-groups
groups.where(parent_id: params[:parent_id]).page(params[:page])
end
@groups = groups.with_selects_for_list(archived: params[:archived])
respond_to do |format| respond_to do |format|
format.html format.html
...@@ -28,4 +23,21 @@ module GroupTree ...@@ -28,4 +23,21 @@ module GroupTree
end end
# rubocop:enable Gitlab/ModuleWithInstanceVariables # rubocop:enable Gitlab/ModuleWithInstanceVariables
end end
def filtered_groups_with_ancestors(groups)
filtered_groups = groups.search(params[:filter]).page(params[:page])
if Group.supports_nested_groups?
# We find the ancestors by ID of the search results here.
# Otherwise the ancestors would also have filters applied,
# which would cause them not to be preloaded.
#
# Pagination needs to be applied before loading the ancestors to
# make sure ancestors are not cut off by pagination.
Gitlab::GroupHierarchy.new(Group.where(id: filtered_groups.select(:id)))
.base_and_ancestors
else
filtered_groups
end
end
end end
...@@ -44,8 +44,8 @@ module GroupDescendant ...@@ -44,8 +44,8 @@ module GroupDescendant
This error is not user facing, but causes a +1 query. This error is not user facing, but causes a +1 query.
MSG MSG
extras = { extras = {
parent: parent, parent: parent.inspect,
child: child, child: child.inspect,
preloaded: preloaded.map(&:full_path) preloaded: preloaded.map(&:full_path)
} }
issue_url = 'https://gitlab.com/gitlab-org/gitlab-ce/issues/40785' issue_url = 'https://gitlab.com/gitlab-org/gitlab-ce/issues/40785'
......
...@@ -90,7 +90,7 @@ class Repository ...@@ -90,7 +90,7 @@ class Repository
@raw_repository&.cleanup @raw_repository&.cleanup
end end
# Return absolute path to repository # Don't use this! It's going away. Use Gitaly to read or write from repos.
def path_to_repo def path_to_repo
@path_to_repo ||= @path_to_repo ||=
begin begin
...@@ -257,7 +257,7 @@ class Repository ...@@ -257,7 +257,7 @@ class Repository
# This will still fail if the file is corrupted (e.g. 0 bytes) # This will still fail if the file is corrupted (e.g. 0 bytes)
raw_repository.write_ref(keep_around_ref_name(sha), sha, shell: false) raw_repository.write_ref(keep_around_ref_name(sha), sha, shell: false)
rescue Gitlab::Git::CommandError => ex rescue Gitlab::Git::CommandError => ex
Rails.logger.error "Unable to create keep-around reference for repository #{path}: #{ex}" Rails.logger.error "Unable to create keep-around reference for repository #{disk_path}: #{ex}"
end end
def kept_around?(sha) def kept_around?(sha)
......
...@@ -6,9 +6,11 @@ module RepositoryCheck ...@@ -6,9 +6,11 @@ module RepositoryCheck
include ApplicationWorker include ApplicationWorker
include RepositoryCheckQueue include RepositoryCheckQueue
include ExclusiveLeaseGuard
RUN_TIME = 3600 RUN_TIME = 3600
BATCH_SIZE = 10_000 BATCH_SIZE = 10_000
LEASE_TIMEOUT = 1.hour
attr_reader :shard_name attr_reader :shard_name
...@@ -18,6 +20,20 @@ module RepositoryCheck ...@@ -18,6 +20,20 @@ module RepositoryCheck
return unless Gitlab::CurrentSettings.repository_checks_enabled return unless Gitlab::CurrentSettings.repository_checks_enabled
return unless Gitlab::ShardHealthCache.healthy_shard?(shard_name) return unless Gitlab::ShardHealthCache.healthy_shard?(shard_name)
try_obtain_lease do
perform_repository_checks
end
end
def lease_timeout
LEASE_TIMEOUT
end
def lease_key
"repository_check_batch_worker:#{shard_name}"
end
def perform_repository_checks
start = Time.now start = Time.now
# This loop will break after a little more than one hour ('a little # This loop will break after a little more than one hour ('a little
...@@ -28,7 +44,7 @@ module RepositoryCheck ...@@ -28,7 +44,7 @@ module RepositoryCheck
project_ids.each do |project_id| project_ids.each do |project_id|
break if Time.now - start >= RUN_TIME break if Time.now - start >= RUN_TIME
next unless try_obtain_lease(project_id) next unless try_obtain_lease_for_project(project_id)
SingleRepositoryWorker.new.perform(project_id) SingleRepositoryWorker.new.perform(project_id)
end end
...@@ -62,7 +78,7 @@ module RepositoryCheck ...@@ -62,7 +78,7 @@ module RepositoryCheck
Project.where(repository_storage: shard_name) Project.where(repository_storage: shard_name)
end end
def try_obtain_lease(id) def try_obtain_lease_for_project(id)
# Use a 24-hour timeout because on servers/projects where 'git fsck' is # Use a 24-hour timeout because on servers/projects where 'git fsck' is
# super slow we definitely do not want to run it twice in parallel. # super slow we definitely do not want to run it twice in parallel.
Gitlab::ExclusiveLease.new( Gitlab::ExclusiveLease.new(
......
...@@ -3,13 +3,22 @@ module RepositoryCheck ...@@ -3,13 +3,22 @@ module RepositoryCheck
include ApplicationWorker include ApplicationWorker
include CronjobQueue include CronjobQueue
include ::EachShardWorker include ::EachShardWorker
include ExclusiveLeaseGuard
LEASE_TIMEOUT = 1.hour
def perform def perform
return unless Gitlab::CurrentSettings.repository_checks_enabled return unless Gitlab::CurrentSettings.repository_checks_enabled
each_eligible_shard do |shard_name| try_obtain_lease do
RepositoryCheck::BatchWorker.perform_async(shard_name) each_eligible_shard do |shard_name|
RepositoryCheck::BatchWorker.perform_async(shard_name)
end
end end
end end
def lease_timeout
LEASE_TIMEOUT
end
end end
end end
---
title: Reduce the number of queries when searching for groups
merge_request: 20398
author:
type: performance
---
title: Structure getters for diff Store properly and adds specs
merge_request:
author:
type: fixed
---
title: Flex issue board columns
merge_request: 19250
author: Roman Rosluk
type: changed
# Integrate your GitLab server with Bitbucket # Integrate your GitLab server with Bitbucket
NOTE: **Note:**
You need to [enable OmniAuth](omniauth.md) in order to use this.
Import projects from Bitbucket.org and login to your GitLab instance with your Import projects from Bitbucket.org and login to your GitLab instance with your
Bitbucket.org account. Bitbucket.org account.
...@@ -76,13 +79,13 @@ you to use. ...@@ -76,13 +79,13 @@ you to use.
sudo -u git -H editor /home/git/gitlab/config/gitlab.yml sudo -u git -H editor /home/git/gitlab/config/gitlab.yml
``` ```
1. Follow the [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration)
for initial settings.
1. Add the Bitbucket provider configuration: 1. Add the Bitbucket provider configuration:
For Omnibus packages: For Omnibus packages:
```ruby ```ruby
gitlab_rails['omniauth_enabled'] = true
gitlab_rails['omniauth_providers'] = [ gitlab_rails['omniauth_providers'] = [
{ {
"name" => "bitbucket", "name" => "bitbucket",
...@@ -96,10 +99,13 @@ you to use. ...@@ -96,10 +99,13 @@ you to use.
For installations from source: For installations from source:
```yaml ```yaml
- { name: 'bitbucket', omniauth:
app_id: 'BITBUCKET_APP_KEY', enabled: true
app_secret: 'BITBUCKET_APP_SECRET', providers:
url: 'https://bitbucket.org/' } - { name: 'bitbucket',
app_id: 'BITBUCKET_APP_KEY',
app_secret: 'BITBUCKET_APP_SECRET',
url: 'https://bitbucket.org/' }
``` ```
--- ---
...@@ -121,6 +127,9 @@ well, the user will be returned to GitLab and will be signed in. ...@@ -121,6 +127,9 @@ well, the user will be returned to GitLab and will be signed in.
Once the above configuration is set up, you can use Bitbucket to sign into Once the above configuration is set up, you can use Bitbucket to sign into
GitLab and [start importing your projects][bb-import]. GitLab and [start importing your projects][bb-import].
If you don't want to enable signing in with Bitbucket but just want to import
projects from Bitbucket, you could [disable it in the admin panel](omniauth.md#enable-or-disable-sign-in-with-an-omniauth-provider-without-disabling-import-sources).
[init-oauth]: omniauth.md#initial-omniauth-configuration [init-oauth]: omniauth.md#initial-omniauth-configuration
[bb-import]: ../workflow/importing/import_projects_from_bitbucket.md [bb-import]: ../workflow/importing/import_projects_from_bitbucket.md
[bb-old]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-14-stable/doc/integration/bitbucket.md [bb-old]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-14-stable/doc/integration/bitbucket.md
......
# SAML OmniAuth Provider # SAML OmniAuth Provider
NOTE: **Note:**
You need to [enable OmniAuth](omniauth.md) in order to use this.
GitLab can be configured to act as a SAML 2.0 Service Provider (SP). This allows GitLab can be configured to act as a SAML 2.0 Service Provider (SP). This allows
GitLab to consume assertions from a SAML 2.0 Identity Provider (IdP) such as GitLab to consume assertions from a SAML 2.0 Identity Provider (IdP) such as
Microsoft ADFS to authenticate users. Microsoft ADFS to authenticate users.
...@@ -15,33 +18,33 @@ in your SAML IdP: ...@@ -15,33 +18,33 @@ in your SAML IdP:
For omnibus package: For omnibus package:
```sh ```sh
sudo editor /etc/gitlab/gitlab.rb sudo editor /etc/gitlab/gitlab.rb
``` ```
For installations from source: For installations from source:
```sh ```sh
cd /home/git/gitlab cd /home/git/gitlab
sudo -u git -H editor config/gitlab.yml sudo -u git -H editor config/gitlab.yml
``` ```
1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration)
for initial settings.
1. To allow your users to use SAML to sign up without having to manually create 1. To allow your users to use SAML to sign up without having to manually create
an account first, don't forget to add the following values to your configuration: an account first, don't forget to add the following values to your configuration:
For omnibus package: For omnibus package:
```ruby ```ruby
gitlab_rails['omniauth_allow_single_sign_on'] = ['saml'] gitlab_rails['omniauth_enabled'] = true
gitlab_rails['omniauth_block_auto_created_users'] = false gitlab_rails['omniauth_allow_single_sign_on'] = ['saml']
gitlab_rails['omniauth_block_auto_created_users'] = false
``` ```
For installations from source: For installations from source:
```yaml ```yaml
omniauth:
enabled: true
allow_single_sign_on: ["saml"] allow_single_sign_on: ["saml"]
block_auto_created_users: false block_auto_created_users: false
``` ```
...@@ -52,13 +55,13 @@ in your SAML IdP: ...@@ -52,13 +55,13 @@ in your SAML IdP:
For omnibus package: For omnibus package:
```ruby ```ruby
gitlab_rails['omniauth_auto_link_saml_user'] = true gitlab_rails['omniauth_auto_link_saml_user'] = true
``` ```
For installations from source: For installations from source:
```yaml ```yaml
auto_link_saml_user: true auto_link_saml_user: true
``` ```
1. Add the provider configuration: 1. Add the provider configuration:
...@@ -66,35 +69,37 @@ in your SAML IdP: ...@@ -66,35 +69,37 @@ in your SAML IdP:
For omnibus package: For omnibus package:
```ruby ```ruby
gitlab_rails['omniauth_providers'] = [ gitlab_rails['omniauth_providers'] = [
{ {
name: 'saml', name: 'saml',
args: { args: {
assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback',
idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8',
idp_sso_target_url: 'https://login.example.com/idp',
issuer: 'https://gitlab.example.com',
name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent'
},
label: 'Company Login' # optional label for SAML login button, defaults to "Saml"
}
]
```
For installations from source:
```yaml
- {
name: 'saml',
args: {
assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback', assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback',
idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8', idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8',
idp_sso_target_url: 'https://login.example.com/idp', idp_sso_target_url: 'https://login.example.com/idp',
issuer: 'https://gitlab.example.com', issuer: 'https://gitlab.example.com',
name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent' name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent'
}, },
label: 'Company Login' # optional label for SAML login button, defaults to "Saml" label: 'Company Login' # optional label for SAML login button, defaults to "Saml"
} }
]
```
For installations from source:
```yaml
omniauth:
providers:
- {
name: 'saml',
args: {
assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback',
idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8',
idp_sso_target_url: 'https://login.example.com/idp',
issuer: 'https://gitlab.example.com',
name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent'
},
label: 'Company Login' # optional label for SAML login button, defaults to "Saml"
}
``` ```
1. Change the value for `assertion_consumer_service_url` to match the HTTPS endpoint 1. Change the value for `assertion_consumer_service_url` to match the HTTPS endpoint
...@@ -140,8 +145,8 @@ This setting is only available on GitLab 8.7 and above. ...@@ -140,8 +145,8 @@ This setting is only available on GitLab 8.7 and above.
SAML login includes support for automatically identifying whether a user should SAML login includes support for automatically identifying whether a user should
be considered an [external](../user/permissions.md) user based on the user's group be considered an [external](../user/permissions.md) user based on the user's group
membership in the SAML identity provider. This feature **does not** allow you to membership in the SAML identity provider. This feature **does not** allow you to
automatically add users to GitLab [Groups](../user/group/index.md), it simply automatically add users to GitLab [Groups](../user/group/index.md), it simply
allows you to mark users as External if they are members of certain groups in the allows you to mark users as External if they are members of certain groups in the
Identity Provider. Identity Provider.
### Requirements ### Requirements
...@@ -240,28 +245,28 @@ If you want some SAML authentication methods to count as 2FA on a per session ba ...@@ -240,28 +245,28 @@ If you want some SAML authentication methods to count as 2FA on a per session ba
1. Edit `/etc/gitlab/gitlab.rb`: 1. Edit `/etc/gitlab/gitlab.rb`:
```ruby ```ruby
gitlab_rails['omniauth_providers'] = [ gitlab_rails['omniauth_providers'] = [
{ {
name: 'saml', name: 'saml',
args: { args: {
assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback', assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback',
idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8', idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8',
idp_sso_target_url: 'https://login.example.com/idp', idp_sso_target_url: 'https://login.example.com/idp',
issuer: 'https://gitlab.example.com', issuer: 'https://gitlab.example.com',
name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent', name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent',
upstream_two_factor_authn_contexts: upstream_two_factor_authn_contexts:
%w( %w(
urn:oasis:names:tc:SAML:2.0:ac:classes:CertificateProtectedTransport urn:oasis:names:tc:SAML:2.0:ac:classes:CertificateProtectedTransport
urn:oasis:names:tc:SAML:2.0:ac:classes:SecondFactorOTPSMS urn:oasis:names:tc:SAML:2.0:ac:classes:SecondFactorOTPSMS
urn:oasis:names:tc:SAML:2.0:ac:classes:SecondFactorIGTOKEN urn:oasis:names:tc:SAML:2.0:ac:classes:SecondFactorIGTOKEN
) )
}, },
label: 'Company Login' # optional label for SAML login button, defaults to "Saml" label: 'Company Login' # optional label for SAML login button, defaults to "Saml"
} }
] ]
``` ```
1. Save the file and [reconfigure][] GitLab for the changes to take effect. 1. Save the file and [reconfigure][] GitLab for the changes to take effect.
--- ---
...@@ -269,40 +274,41 @@ If you want some SAML authentication methods to count as 2FA on a per session ba ...@@ -269,40 +274,41 @@ If you want some SAML authentication methods to count as 2FA on a per session ba
**For installations from source:** **For installations from source:**
1. Edit `config/gitlab.yml`: 1. Edit `config/gitlab.yml`:
```yaml ```yaml
- { omniauth:
name: 'saml', providers:
args: { - {
assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback', name: 'saml',
idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8', args: {
idp_sso_target_url: 'https://login.example.com/idp', assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback',
issuer: 'https://gitlab.example.com', idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8',
name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent', idp_sso_target_url: 'https://login.example.com/idp',
upstream_two_factor_authn_contexts: issuer: 'https://gitlab.example.com',
[ name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent',
'urn:oasis:names:tc:SAML:2.0:ac:classes:CertificateProtectedTransport', upstream_two_factor_authn_contexts:
'urn:oasis:names:tc:SAML:2.0:ac:classes:SecondFactorOTPSMS', [
'urn:oasis:names:tc:SAML:2.0:ac:classes:SecondFactorIGTOKEN' 'urn:oasis:names:tc:SAML:2.0:ac:classes:CertificateProtectedTransport',
] 'urn:oasis:names:tc:SAML:2.0:ac:classes:SecondFactorOTPSMS',
'urn:oasis:names:tc:SAML:2.0:ac:classes:SecondFactorIGTOKEN'
}, ]
label: 'Company Login' # optional label for SAML login button, defaults to "Saml" },
} label: 'Company Login' # optional label for SAML login button, defaults to "Saml"
}
``` ```
1. Save the file and [restart GitLab][] for the changes ot take effect 1. Save the file and [restart GitLab][] for the changes ot take effect
In addition to the changes in GitLab, make sure that your Idp is returning the In addition to the changes in GitLab, make sure that your Idp is returning the
`AuthnContext`. For example: `AuthnContext`. For example:
```xml ```xml
<saml:AuthnStatement> <saml:AuthnStatement>
<saml:AuthnContext> <saml:AuthnContext>
<saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:MediumStrongCertificateProtectedTransport</saml:AuthnContextClassRef> <saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:MediumStrongCertificateProtectedTransport</saml:AuthnContextClassRef>
</saml:AuthnContext> </saml:AuthnContext>
</saml:AuthnStatement> </saml:AuthnStatement>
``` ```
## Customization ## Customization
......
...@@ -42,6 +42,21 @@ module Gitlab ...@@ -42,6 +42,21 @@ module Gitlab
!self.read_only? !self.read_only?
end end
# check whether the underlying database is in read-only mode
def self.db_read_only?
if postgresql?
ActiveRecord::Base.connection.execute('SELECT pg_is_in_recovery()')
.first
.fetch('pg_is_in_recovery') == 't'
else
false
end
end
def self.db_read_write?
!self.db_read_only?
end
def self.version def self.version
@version ||= database_version.match(/\A(?:PostgreSQL |)([^\s]+).*\z/)[1] @version ||= database_version.match(/\A(?:PostgreSQL |)([^\s]+).*\z/)[1]
end end
......
...@@ -1115,8 +1115,18 @@ module Gitlab ...@@ -1115,8 +1115,18 @@ module Gitlab
# This guard avoids Gitaly log/error spam # This guard avoids Gitaly log/error spam
raise NoRepository, 'repository does not exist' unless exists? raise NoRepository, 'repository does not exist' unless exists?
set_config('gitlab.fullpath' => full_path)
end
def set_config(entries)
wrapped_gitaly_errors do
gitaly_repository_client.set_config(entries)
end
end
def delete_config(*keys)
wrapped_gitaly_errors do wrapped_gitaly_errors do
gitaly_repository_client.write_config(full_path: full_path) gitaly_repository_client.delete_config(keys)
end end
end end
......
...@@ -265,17 +265,39 @@ module Gitlab ...@@ -265,17 +265,39 @@ module Gitlab
true true
end end
def write_config(full_path:) def set_config(entries)
request = Gitaly::WriteConfigRequest.new(repository: @gitaly_repo, full_path: full_path) return if entries.empty?
response = GitalyClient.call(
request = Gitaly::SetConfigRequest.new(repository: @gitaly_repo)
entries.each do |key, value|
request.entries << build_set_config_entry(key, value)
end
GitalyClient.call(
@storage,
:repository_service,
:set_config,
request,
timeout: GitalyClient.fast_timeout
)
nil
end
def delete_config(keys)
return if keys.empty?
request = Gitaly::DeleteConfigRequest.new(repository: @gitaly_repo, keys: keys)
GitalyClient.call(
@storage, @storage,
:repository_service, :repository_service,
:write_config, :delete_config,
request, request,
timeout: GitalyClient.fast_timeout timeout: GitalyClient.fast_timeout
) )
raise Gitlab::Git::OSError.new(response.error) unless response.error.empty? nil
end end
def license_short_name def license_short_name
...@@ -352,6 +374,23 @@ module Gitlab ...@@ -352,6 +374,23 @@ module Gitlab
timeout: timeout timeout: timeout
) )
end end
def build_set_config_entry(key, value)
entry = Gitaly::SetConfigRequest::Entry.new(key: key)
case value
when String
entry.value_str = value
when Integer
entry.value_int32 = value
when TrueClass, FalseClass
entry.value_bool = value
else
raise InvalidArgument, "invalid git config value: #{value.inspect}"
end
entry
end
end end
end end
end end
...@@ -63,6 +63,17 @@ describe GroupTree do ...@@ -63,6 +63,17 @@ describe GroupTree do
expect(assigns(:groups)).to contain_exactly(parent, subgroup) expect(assigns(:groups)).to contain_exactly(parent, subgroup)
end end
it 'preloads parents regardless of pagination' do
allow(Kaminari.config).to receive(:default_per_page).and_return(1)
group = create(:group, :public)
subgroup = create(:group, :public, parent: group)
search_result = create(:group, :public, name: 'result', parent: subgroup)
get :index, filter: 'resu', format: :json
expect(assigns(:groups)).to contain_exactly(group, subgroup, search_result)
end
end end
context 'json content' do context 'json content' do
......
import getters from '~/diffs/store/getters'; import * as getters from '~/diffs/store/getters';
import state from '~/diffs/store/modules/diff_state';
import { PARALLEL_DIFF_VIEW_TYPE, INLINE_DIFF_VIEW_TYPE } from '~/diffs/constants'; import { PARALLEL_DIFF_VIEW_TYPE, INLINE_DIFF_VIEW_TYPE } from '~/diffs/constants';
describe('DiffsStoreGetters', () => { describe('DiffsStoreGetters', () => {
let localState;
beforeEach(() => {
localState = state();
});
describe('isParallelView', () => { describe('isParallelView', () => {
it('should return true if view set to parallel view', () => { it('should return true if view set to parallel view', () => {
expect(getters.isParallelView({ diffViewType: PARALLEL_DIFF_VIEW_TYPE })).toBeTruthy(); localState.diffViewType = PARALLEL_DIFF_VIEW_TYPE;
expect(getters.isParallelView(localState)).toEqual(true);
}); });
it('should return false if view not to parallel view', () => { it('should return false if view not to parallel view', () => {
expect(getters.isParallelView({ diffViewType: 'foo' })).toBeFalsy(); localState.diffViewType = INLINE_DIFF_VIEW_TYPE;
expect(getters.isParallelView(localState)).toEqual(false);
}); });
}); });
describe('isInlineView', () => { describe('isInlineView', () => {
it('should return true if view set to inline view', () => { it('should return true if view set to inline view', () => {
expect(getters.isInlineView({ diffViewType: INLINE_DIFF_VIEW_TYPE })).toBeTruthy(); localState.diffViewType = INLINE_DIFF_VIEW_TYPE;
expect(getters.isInlineView(localState)).toEqual(true);
}); });
it('should return false if view not to inline view', () => { it('should return false if view not to inline view', () => {
expect(getters.isInlineView({ diffViewType: PARALLEL_DIFF_VIEW_TYPE })).toBeFalsy(); localState.diffViewType = PARALLEL_DIFF_VIEW_TYPE;
expect(getters.isInlineView(localState)).toEqual(false);
});
});
describe('areAllFilesCollapsed', () => {
it('returns true when all files are collapsed', () => {
localState.diffFiles = [{ collapsed: true }, { collapsed: true }];
expect(getters.areAllFilesCollapsed(localState)).toEqual(true);
});
it('returns false when at least one file is not collapsed', () => {
localState.diffFiles = [{ collapsed: false }, { collapsed: true }];
expect(getters.areAllFilesCollapsed(localState)).toEqual(false);
});
});
describe('commitId', () => {
it('returns commit id when is set', () => {
const commitID = '800f7a91';
localState.commit = {
id: commitID,
};
expect(getters.commitId(localState)).toEqual(commitID);
});
it('returns null when no commit is set', () => {
expect(getters.commitId(localState)).toEqual(null);
}); });
}); });
}); });
...@@ -443,6 +443,7 @@ describe Gitlab::Database do ...@@ -443,6 +443,7 @@ describe Gitlab::Database do
end end
end end
<<<<<<< HEAD
describe '#disable_prepared_statements' do describe '#disable_prepared_statements' do
it 'disables prepared statements' do it 'disables prepared statements' do
config = {} config = {}
...@@ -488,6 +489,34 @@ describe Gitlab::Database do ...@@ -488,6 +489,34 @@ describe Gitlab::Database do
it 'returns false' do it 'returns false' do
expect(described_class.read_only?).to be_falsey expect(described_class.read_only?).to be_falsey
end end
=======
describe '.db_read_only?' do
context 'when using PostgreSQL' do
before do
allow(ActiveRecord::Base.connection).to receive(:execute).and_call_original
expect(described_class).to receive(:postgresql?).and_return(true)
end
it 'detects a read only database' do
allow(ActiveRecord::Base.connection).to receive(:execute).with('SELECT pg_is_in_recovery()').and_return([{ "pg_is_in_recovery" => "t" }])
expect(described_class.db_read_only?).to be_truthy
end
it 'detects a read write database' do
allow(ActiveRecord::Base.connection).to receive(:execute).with('SELECT pg_is_in_recovery()').and_return([{ "pg_is_in_recovery" => "f" }])
expect(described_class.db_read_only?).to be_falsey
end
end
context 'when using MySQL' do
before do
expect(described_class).to receive(:postgresql?).and_return(false)
end
it { expect(described_class.db_read_only?).to be_falsey }
>>>>>>> upstream/master
end end
end end
......
...@@ -1856,6 +1856,54 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -1856,6 +1856,54 @@ describe Gitlab::Git::Repository, seed_helper: true do
end end
end end
describe '#set_config' do
let(:repository) { Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') }
let(:rugged) { repository_rugged }
let(:entries) do
{
'test.foo1' => 'bla bla',
'test.foo2' => 1234,
'test.foo3' => true
}
end
it 'can set config settings' do
expect(repository.set_config(entries)).to be_nil
expect(rugged.config['test.foo1']).to eq('bla bla')
expect(rugged.config['test.foo2']).to eq('1234')
expect(rugged.config['test.foo3']).to eq('true')
end
after do
entries.keys.each { |k| rugged.config.delete(k) }
end
end
describe '#delete_config' do
let(:repository) { Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') }
let(:rugged) { repository_rugged }
let(:entries) do
{
'test.foo1' => 'bla bla',
'test.foo2' => 1234,
'test.foo3' => true
}
end
it 'can delete config settings' do
entries.each do |key, value|
rugged.config[key] = value
end
expect(repository.delete_config(*%w[does.not.exist test.foo1 test.foo2])).to be_nil
config_keys = rugged.config.each_key.to_a
expect(config_keys).not_to include('test.foo1')
expect(config_keys).not_to include('test.foo2')
end
end
describe '#merge' do describe '#merge' do
let(:repository) do let(:repository) do
Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '')
......
...@@ -62,4 +62,12 @@ describe RepositoryCheck::BatchWorker do ...@@ -62,4 +62,12 @@ describe RepositoryCheck::BatchWorker do
expect(subject.perform(shard_name)).to eq([]) expect(subject.perform(shard_name)).to eq([])
end end
it 'does not run if the exclusive lease is taken' do
allow(subject).to receive(:try_obtain_lease).and_return(false)
expect(subject).not_to receive(:perform_repository_checks)
subject.perform(shard_name)
end
end end
...@@ -11,6 +11,14 @@ describe RepositoryCheck::DispatchWorker do ...@@ -11,6 +11,14 @@ describe RepositoryCheck::DispatchWorker do
subject.perform subject.perform
end end
it 'does nothing if the exclusive lease is taken' do
allow(subject).to receive(:try_obtain_lease).and_return(false)
expect(RepositoryCheck::BatchWorker).not_to receive(:perform_async)
subject.perform
end
it 'dispatches work to RepositoryCheck::BatchWorker' do it 'dispatches work to RepositoryCheck::BatchWorker' do
expect(RepositoryCheck::BatchWorker).to receive(:perform_async).at_least(:once) expect(RepositoryCheck::BatchWorker).to receive(:perform_async).at_least(:once)
......
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