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
end
# Gitaly GRPC client
gem 'gitaly-proto', '~> 0.103.0', require: 'gitaly'
gem 'gitaly-proto', '~> 0.105.0', require: 'gitaly'
gem 'grpc', '~> 1.11.0'
# Locked until https://github.com/google/protobuf/issues/4210 is closed
......
......@@ -306,7 +306,7 @@ GEM
gettext_i18n_rails (>= 0.7.1)
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
gitaly-proto (0.103.0)
gitaly-proto (0.105.0)
google-protobuf (~> 3.1)
grpc (~> 1.10)
github-linguist (5.3.3)
......@@ -1071,7 +1071,7 @@ DEPENDENCIES
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3)
gitaly-proto (~> 0.103.0)
gitaly-proto (~> 0.105.0)
github-linguist (~> 5.3.3)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-gollum-lib (~> 4.2)
......
......@@ -309,7 +309,7 @@ GEM
gettext_i18n_rails (>= 0.7.1)
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
gitaly-proto (0.103.0)
gitaly-proto (0.105.0)
google-protobuf (~> 3.1)
grpc (~> 1.10)
github-linguist (5.3.3)
......@@ -1081,7 +1081,7 @@ DEPENDENCIES
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3)
gitaly-proto (~> 0.103.0)
gitaly-proto (~> 0.105.0)
github-linguist (~> 5.3.3)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-gollum-lib (~> 4.2)
......
......@@ -20,16 +20,13 @@ export default {
},
},
computed: {
...mapGetters(['commit']),
...mapGetters(['commitId']),
normalizedDiffLines() {
return this.diffLines.map(line => (line.richText ? trimFirstCharOfLineContent(line) : line));
},
diffLinesLength() {
return this.normalizedDiffLines.length;
},
commitId() {
return this.commit && this.commit.id;
},
userColorScheme() {
return window.gon.user_color_scheme;
},
......
......@@ -21,7 +21,7 @@ export default {
},
},
computed: {
...mapGetters(['commit']),
...mapGetters(['commitId']),
parallelDiffLines() {
return this.diffLines.map(line => {
const parallelLine = Object.assign({}, line);
......@@ -44,9 +44,6 @@ export default {
diffLinesLength() {
return this.parallelDiffLines.length;
},
commitId() {
return this.commit && this.commit.id;
},
userColorScheme() {
return window.gon.user_color_scheme;
},
......
import { PARALLEL_DIFF_VIEW_TYPE, INLINE_DIFF_VIEW_TYPE } from '../constants';
export default {
isParallelView(state) {
return state.diffViewType === PARALLEL_DIFF_VIEW_TYPE;
},
isInlineView(state) {
return state.diffViewType === INLINE_DIFF_VIEW_TYPE;
},
areAllFilesCollapsed(state) {
return state.diffFiles.every(file => file.collapsed);
},
commit(state) {
return state.commit;
},
};
export const isParallelView = state => state.diffViewType === PARALLEL_DIFF_VIEW_TYPE;
export const isInlineView = state => state.diffViewType === INLINE_DIFF_VIEW_TYPE;
export const areAllFilesCollapsed = state => state.diffFiles.every(file => file.collapsed);
export const commitId = state => (state.commit && state.commit.id ? state.commit.id : null);
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
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 getters from '../getters';
import * as getters from '../getters';
import mutations from '../mutations';
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;
import createState from './diff_state';
export default {
state: {
isLoading: true,
endpoint: '',
basePath: '',
commit: null,
diffFiles: [],
mergeRequestDiffs: [],
diffLineCommentForms: {},
diffViewType: viewTypeFromQueryString || viewTypeFromCookie || defaultViewType,
},
state: createState(),
getters,
actions,
mutations,
......
......@@ -66,15 +66,10 @@ export default {
},
[types.EXPAND_ALL_FILES](state) {
const diffFiles = [];
state.diffFiles.forEach(file => {
diffFiles.push({
...file,
collapsed: false,
});
});
Object.assign(state, { diffFiles });
// eslint-disable-next-line no-param-reassign
state.diffFiles = state.diffFiles.map(file => ({
...file,
collapsed: false,
}));
},
};
......@@ -117,7 +117,6 @@
overflow-x: scroll;
white-space: nowrap;
min-height: 200px;
display: flex;
@include media-breakpoint-only(sm) {
height: calc(100vh - #{$issue-board-list-difference-sm});
......@@ -148,15 +147,17 @@
.board {
display: inline-block;
flex: 1;
min-width: 300px;
max-width: 400px;
width: calc(85vw - 15px);
height: 100%;
padding-right: ($gl-padding / 2);
padding-left: ($gl-padding / 2);
white-space: normal;
vertical-align: top;
@include media-breakpoint-up(sm) {
width: 400px;
}
&.is-expandable {
.board-header {
cursor: pointer;
......@@ -164,8 +165,6 @@
}
&.is-collapsed {
flex: none;
min-width: 0;
width: 50px;
.board-header {
......
module GroupTree
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def render_group_tree(groups)
@groups = if params[:filter].present?
# 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.sort_by_attribute(@sort = params[:sort])
@groups = @groups.with_selects_for_list(archived: params[:archived])
.sort_by_attribute(@sort = params[:sort])
.page(params[:page])
groups = if params[:filter].present?
filtered_groups_with_ancestors(groups)
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|
format.html
......@@ -28,4 +23,21 @@ module GroupTree
end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
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
......@@ -44,8 +44,8 @@ module GroupDescendant
This error is not user facing, but causes a +1 query.
MSG
extras = {
parent: parent,
child: child,
parent: parent.inspect,
child: child.inspect,
preloaded: preloaded.map(&:full_path)
}
issue_url = 'https://gitlab.com/gitlab-org/gitlab-ce/issues/40785'
......
......@@ -90,7 +90,7 @@ class Repository
@raw_repository&.cleanup
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
@path_to_repo ||=
begin
......@@ -257,7 +257,7 @@ class Repository
# 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)
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
def kept_around?(sha)
......
......@@ -6,9 +6,11 @@ module RepositoryCheck
include ApplicationWorker
include RepositoryCheckQueue
include ExclusiveLeaseGuard
RUN_TIME = 3600
BATCH_SIZE = 10_000
LEASE_TIMEOUT = 1.hour
attr_reader :shard_name
......@@ -18,6 +20,20 @@ module RepositoryCheck
return unless Gitlab::CurrentSettings.repository_checks_enabled
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
# This loop will break after a little more than one hour ('a little
......@@ -28,7 +44,7 @@ module RepositoryCheck
project_ids.each do |project_id|
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)
end
......@@ -62,7 +78,7 @@ module RepositoryCheck
Project.where(repository_storage: shard_name)
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
# super slow we definitely do not want to run it twice in parallel.
Gitlab::ExclusiveLease.new(
......
......@@ -3,13 +3,22 @@ module RepositoryCheck
include ApplicationWorker
include CronjobQueue
include ::EachShardWorker
include ExclusiveLeaseGuard
LEASE_TIMEOUT = 1.hour
def perform
return unless Gitlab::CurrentSettings.repository_checks_enabled
each_eligible_shard do |shard_name|
RepositoryCheck::BatchWorker.perform_async(shard_name)
try_obtain_lease do
each_eligible_shard do |shard_name|
RepositoryCheck::BatchWorker.perform_async(shard_name)
end
end
end
def lease_timeout
LEASE_TIMEOUT
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
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
Bitbucket.org account.
......@@ -76,13 +79,13 @@ you to use.
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:
For Omnibus packages:
```ruby
gitlab_rails['omniauth_enabled'] = true
gitlab_rails['omniauth_providers'] = [
{
"name" => "bitbucket",
......@@ -96,10 +99,13 @@ you to use.
For installations from source:
```yaml
- { name: 'bitbucket',
app_id: 'BITBUCKET_APP_KEY',
app_secret: 'BITBUCKET_APP_SECRET',
url: 'https://bitbucket.org/' }
omniauth:
enabled: true
providers:
- { 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.
Once the above configuration is set up, you can use Bitbucket to sign into
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
[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
......
This diff is collapsed.
......@@ -42,6 +42,21 @@ module Gitlab
!self.read_only?
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
@version ||= database_version.match(/\A(?:PostgreSQL |)([^\s]+).*\z/)[1]
end
......
......@@ -1115,8 +1115,18 @@ module Gitlab
# This guard avoids Gitaly log/error spam
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
gitaly_repository_client.write_config(full_path: full_path)
gitaly_repository_client.delete_config(keys)
end
end
......
......@@ -265,17 +265,39 @@ module Gitlab
true
end
def write_config(full_path:)
request = Gitaly::WriteConfigRequest.new(repository: @gitaly_repo, full_path: full_path)
response = GitalyClient.call(
def set_config(entries)
return if entries.empty?
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,
:repository_service,
:write_config,
:delete_config,
request,
timeout: GitalyClient.fast_timeout
)
raise Gitlab::Git::OSError.new(response.error) unless response.error.empty?
nil
end
def license_short_name
......@@ -352,6 +374,23 @@ module Gitlab
timeout: timeout
)
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
......@@ -63,6 +63,17 @@ describe GroupTree do
expect(assigns(:groups)).to contain_exactly(parent, subgroup)
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
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';
describe('DiffsStoreGetters', () => {
let localState;
beforeEach(() => {
localState = state();
});
describe('isParallelView', () => {
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', () => {
expect(getters.isParallelView({ diffViewType: 'foo' })).toBeFalsy();
localState.diffViewType = INLINE_DIFF_VIEW_TYPE;
expect(getters.isParallelView(localState)).toEqual(false);
});
});
describe('isInlineView', () => {
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', () => {
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
end
end
<<<<<<< HEAD
describe '#disable_prepared_statements' do
it 'disables prepared statements' do
config = {}
......@@ -488,6 +489,34 @@ describe Gitlab::Database do
it 'returns false' do
expect(described_class.read_only?).to be_falsey
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
......
......@@ -1856,6 +1856,54 @@ describe Gitlab::Git::Repository, seed_helper: true do
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
let(:repository) do
Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '')
......
......@@ -62,4 +62,12 @@ describe RepositoryCheck::BatchWorker do
expect(subject.perform(shard_name)).to eq([])
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
......@@ -11,6 +11,14 @@ describe RepositoryCheck::DispatchWorker do
subject.perform
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
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