Commit e039bbbd authored by Filipa Lacerda's avatar Filipa Lacerda

Merge branch 'master' into ce-to-ee-2018-01-25

* master: (34 commits)
  Add a lint check to restrict use of Rugged, EE version
  Fix EE offenses to the RSpec/SingleLineHook cop
  Update qa.rb
  Use flash message instead of regular text block
  Update common.rb
  Update gitlab-styles and re-enable the RSpec/SingleLineHook cop again
  Move EE-specific migrations under ee/db/
  Make Gitlab::Git::Repository#run_git private
  Update secret_values to support dynamic elements within parent
  Improve PostgreSQL warning message in Rake task
  Improve warning message about the replication status
  Add a warning for Postgresql < 9.6 when show replication status
  Refactored expand_section to use name instead of selector
  Refactored view/element definition to dry up
  add view/element, some small fixes and code refactor
  Small fixes and codestyle changes
  Reduced rename replication spec steps to the bare minimum.
  Dont send enter key
  Refactor to address codestyle feedback
  Make AdvancedSettings a Page and move `rename_to to it.
  ...
parents 8d99d9ee 595a616c
...@@ -13,11 +13,24 @@ AllCops: ...@@ -13,11 +13,24 @@ AllCops:
- 'db/*' - 'db/*'
- 'db/fixtures/**/*' - 'db/fixtures/**/*'
- 'db/geo/*' - 'db/geo/*'
- 'ee/db/geo/*'
- 'tmp/**/*' - 'tmp/**/*'
- 'bin/**/*' - 'bin/**/*'
- 'generator_templates/**/*' - 'generator_templates/**/*'
- 'builds/**/*' - 'builds/**/*'
# This cop checks whether some constant value isn't a
# mutable literal (e.g. array or hash).
Style/MutableConstant:
Enabled: true
Exclude:
- 'db/migrate/**/*'
- 'db/post_migrate/**/*'
- 'db/geo/migrate/**/*'
- 'ee/db/migrate/**/*'
- 'ee/db/post_migrate/**/*'
- 'ee/db/geo/migrate/**/*'
# Gitlab ################################################################### # Gitlab ###################################################################
Gitlab/ModuleWithInstanceVariables: Gitlab/ModuleWithInstanceVariables:
......
...@@ -342,10 +342,6 @@ RSpec/SharedContext: ...@@ -342,10 +342,6 @@ RSpec/SharedContext:
Exclude: Exclude:
- 'spec/features/admin/admin_groups_spec.rb' - 'spec/features/admin/admin_groups_spec.rb'
# Offense count: 90
RSpec/SingleLineHook:
Enabled: false
# Offense count: 5 # Offense count: 5
RSpec/VoidExpect: RSpec/VoidExpect:
Exclude: Exclude:
......
...@@ -329,7 +329,7 @@ GEM ...@@ -329,7 +329,7 @@ GEM
posix-spawn (~> 0.3) posix-spawn (~> 0.3)
gitlab-license (1.0.0) gitlab-license (1.0.0)
gitlab-markup (1.6.3) gitlab-markup (1.6.3)
gitlab-styles (2.3.0) gitlab-styles (2.3.1)
rubocop (~> 0.51) rubocop (~> 0.51)
rubocop-gitlab-security (~> 0.1.0) rubocop-gitlab-security (~> 0.1.0)
rubocop-rspec (~> 1.19) rubocop-rspec (~> 1.19)
......
...@@ -2,18 +2,19 @@ import { n__ } from '../locale'; ...@@ -2,18 +2,19 @@ import { n__ } from '../locale';
import { convertPermissionToBoolean } from '../lib/utils/common_utils'; import { convertPermissionToBoolean } from '../lib/utils/common_utils';
export default class SecretValues { export default class SecretValues {
constructor(container) { constructor({
container,
valueSelector = '.js-secret-value',
placeholderSelector = '.js-secret-value-placeholder',
}) {
this.container = container; this.container = container;
this.valueSelector = valueSelector;
this.placeholderSelector = placeholderSelector;
} }
init() { init() {
this.values = this.container.querySelectorAll('.js-secret-value');
this.placeholders = this.container.querySelectorAll('.js-secret-value-placeholder');
this.revealButton = this.container.querySelector('.js-secret-value-reveal-button'); this.revealButton = this.container.querySelector('.js-secret-value-reveal-button');
this.revealText = n__('Reveal value', 'Reveal values', this.values.length);
this.hideText = n__('Hide value', 'Hide values', this.values.length);
const isRevealed = convertPermissionToBoolean(this.revealButton.dataset.secretRevealStatus); const isRevealed = convertPermissionToBoolean(this.revealButton.dataset.secretRevealStatus);
this.updateDom(isRevealed); this.updateDom(isRevealed);
...@@ -28,15 +29,17 @@ export default class SecretValues { ...@@ -28,15 +29,17 @@ export default class SecretValues {
} }
updateDom(isRevealed) { updateDom(isRevealed) {
this.values.forEach((value) => { const values = this.container.querySelectorAll(this.valueSelector);
values.forEach((value) => {
value.classList.toggle('hide', !isRevealed); value.classList.toggle('hide', !isRevealed);
}); });
this.placeholders.forEach((placeholder) => { const placeholders = this.container.querySelectorAll(this.placeholderSelector);
placeholders.forEach((placeholder) => {
placeholder.classList.toggle('hide', isRevealed); placeholder.classList.toggle('hide', isRevealed);
}); });
this.revealButton.textContent = isRevealed ? this.hideText : this.revealText; this.revealButton.textContent = isRevealed ? n__('Hide value', 'Hide values', values.length) : n__('Reveal value', 'Reveal values', values.length);
this.revealButton.dataset.secretRevealStatus = isRevealed; this.revealButton.dataset.secretRevealStatus = isRevealed;
} }
} }
...@@ -132,14 +132,17 @@ $(() => { ...@@ -132,14 +132,17 @@ $(() => {
if (sidebarInfoEndpoint && newIssue.subscribed === undefined) { if (sidebarInfoEndpoint && newIssue.subscribed === undefined) {
newIssue.setFetchingState('subscriptions', true); newIssue.setFetchingState('subscriptions', true);
newIssue.setFetchingState('weight', true); newIssue.setFetchingState('weight', true);
newIssue.setFetchingState('epic', true);
BoardService.getIssueInfo(sidebarInfoEndpoint) BoardService.getIssueInfo(sidebarInfoEndpoint)
.then(res => res.data) .then(res => res.data)
.then((data) => { .then((data) => {
newIssue.setFetchingState('subscriptions', false); newIssue.setFetchingState('subscriptions', false);
newIssue.setFetchingState('weight', false); newIssue.setFetchingState('weight', false);
newIssue.setFetchingState('epic', false);
newIssue.updateData({ newIssue.updateData({
subscribed: data.subscribed, subscribed: data.subscribed,
weight: data.weight, weight: data.weight,
epic: data.epic,
}); });
}) })
.catch(() => { .catch(() => {
......
...@@ -3,7 +3,9 @@ import SecretValues from '~/behaviors/secret_values'; ...@@ -3,7 +3,9 @@ import SecretValues from '~/behaviors/secret_values';
export default () => { export default () => {
const secretVariableTable = document.querySelector('.js-secret-variable-table'); const secretVariableTable = document.querySelector('.js-secret-variable-table');
if (secretVariableTable) { if (secretVariableTable) {
const secretVariableTableValues = new SecretValues(secretVariableTable); const secretVariableTableValues = new SecretValues({
container: secretVariableTable,
});
secretVariableTableValues.init(); secretVariableTableValues.init();
} }
}; };
...@@ -6,13 +6,17 @@ export default function () { ...@@ -6,13 +6,17 @@ export default function () {
initSettingsPanels(); initSettingsPanels();
const runnerToken = document.querySelector('.js-secret-runner-token'); const runnerToken = document.querySelector('.js-secret-runner-token');
if (runnerToken) { if (runnerToken) {
const runnerTokenSecretValue = new SecretValues(runnerToken); const runnerTokenSecretValue = new SecretValues({
container: runnerToken,
});
runnerTokenSecretValue.init(); runnerTokenSecretValue.init();
} }
const secretVariableTable = document.querySelector('.js-secret-variable-table'); const secretVariableTable = document.querySelector('.js-secret-variable-table');
if (secretVariableTable) { if (secretVariableTable) {
const secretVariableTableValues = new SecretValues(secretVariableTable); const secretVariableTableValues = new SecretValues({
container: secretVariableTable,
});
secretVariableTableValues.init(); secretVariableTableValues.init();
} }
} }
...@@ -537,7 +537,7 @@ module Ci ...@@ -537,7 +537,7 @@ module Ci
return unless sha return unless sha
project.repository.gitlab_ci_yml_for(sha, ci_yaml_file_path) project.repository.gitlab_ci_yml_for(sha, ci_yaml_file_path)
rescue GRPC::NotFound, Rugged::ReferenceError, GRPC::Internal rescue GRPC::NotFound, GRPC::Internal
nil nil
end end
......
...@@ -45,14 +45,7 @@ class Deployment < ActiveRecord::Base ...@@ -45,14 +45,7 @@ class Deployment < ActiveRecord::Base
def includes_commit?(commit) def includes_commit?(commit)
return false unless commit return false unless commit
# Before 8.10, deployments didn't have keep-around refs. Any deployment
# created before then could have a `sha` referring to a commit that no
# longer exists in the repository, so just ignore those.
begin
project.repository.ancestor?(commit.id, sha) project.repository.ancestor?(commit.id, sha)
rescue Rugged::OdbError
false
end
end end
def update_merge_request_metrics! def update_merge_request_metrics!
......
...@@ -173,17 +173,11 @@ class Repository ...@@ -173,17 +173,11 @@ class Repository
return [] return []
end end
raw_repository.gitaly_migrate(:commits_by_message) do |is_enabled| commits = raw_repository.find_commits_by_message(query, ref, path, limit, offset).map do |c|
commits = commit(c)
if is_enabled
find_commits_by_message_by_gitaly(query, ref, path, limit, offset)
else
find_commits_by_message_by_shelling_out(query, ref, path, limit, offset)
end end
CommitCollection.new(project, commits, ref) CommitCollection.new(project, commits, ref)
end end
end
def find_branch(name, fresh_repo: true) def find_branch(name, fresh_repo: true)
# Since the Repository object may have in-memory index changes, invalidating the memoized Repository object may # Since the Repository object may have in-memory index changes, invalidating the memoized Repository object may
...@@ -747,23 +741,6 @@ class Repository ...@@ -747,23 +741,6 @@ class Repository
Commit.order_by(collection: commits, order_by: order_by, sort: sort) Commit.order_by(collection: commits, order_by: order_by, sort: sort)
end end
def refs_contains_sha(ref_type, sha)
args = %W(#{ref_type} --contains #{sha})
names = run_git(args).first
if names.respond_to?(:split)
names = names.split("\n").map(&:strip)
names.each do |name|
name.slice! '* '
end
names
else
[]
end
end
def branch_names_contains(sha) def branch_names_contains(sha)
refs_contains_sha('branch', sha) refs_contains_sha('branch', sha)
end end
...@@ -970,25 +947,6 @@ class Repository ...@@ -970,25 +947,6 @@ class Repository
end end
end end
def search_files_by_content(query, ref)
return [] if empty? || query.blank?
offset = 2
args = %W(grep -i -I -n -z --before-context #{offset} --after-context #{offset} -E -e #{Regexp.escape(query)} #{ref || root_ref})
run_git(args).first.scrub.split(/^--$/)
end
def search_files_by_name(query, ref)
safe_query = Regexp.escape(query.sub(/^\/*/, ""))
return [] if empty? || safe_query.blank?
args = %W(ls-tree --full-tree -r #{ref || root_ref} --name-status | #{safe_query})
run_git(args).first.lines.map(&:strip)
end
def fetch_as_mirror(url, forced: false, refmap: :all_refs, remote_name: nil) def fetch_as_mirror(url, forced: false, refmap: :all_refs, remote_name: nil)
unless remote_name unless remote_name
remote_name = "tmp-#{SecureRandom.hex}" remote_name = "tmp-#{SecureRandom.hex}"
...@@ -1036,6 +994,18 @@ class Repository ...@@ -1036,6 +994,18 @@ class Repository
raw_repository.ls_files(actual_ref) raw_repository.ls_files(actual_ref)
end end
def search_files_by_content(query, ref)
return [] if empty? || query.blank?
raw_repository.search_files_by_content(query, ref)
end
def search_files_by_name(query, ref)
return [] if empty?
raw_repository.search_files_by_name(query, ref)
end
def copy_gitattributes(ref) def copy_gitattributes(ref)
actual_ref = ref || root_ref actual_ref = ref || root_ref
begin begin
...@@ -1202,25 +1172,4 @@ class Repository ...@@ -1202,25 +1172,4 @@ class Repository
def rugged_can_be_merged?(their_commit, our_commit) def rugged_can_be_merged?(their_commit, our_commit)
!rugged.merge_commits(our_commit, their_commit).conflicts? !rugged.merge_commits(our_commit, their_commit).conflicts?
end end
def find_commits_by_message_by_shelling_out(query, ref, path, limit, offset)
ref ||= root_ref
args = %W(
log #{ref} --pretty=%H --skip #{offset}
--max-count #{limit} --grep=#{query} --regexp-ignore-case
)
args = args.concat(%W(-- #{path})) if path.present?
git_log_results = run_git(args).first.lines
git_log_results.map { |c| commit(c.chomp) }.compact
end
def find_commits_by_message_by_gitaly(query, ref, path, limit, offset)
raw_repository
.gitaly_commit_client
.commits_by_message(query, revision: ref, path: path, limit: limit, offset: offset)
.map { |c| commit(c) }
end
end end
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
.settings-header .settings-header
%h4 %h4
Deploy Keys Deploy Keys
%button.btn.js-settings-toggle.qa-expand-deploy-keys %button.btn.js-settings-toggle
= expanded ? 'Collapse' : 'Expand' = expanded ? 'Collapse' : 'Expand'
%p %p
Deploy keys allow read-only or read-write (if enabled) access to your repository. Deploy keys can be used for CI, staging or production servers. You can create a deploy key or add an existing one. Deploy keys allow read-only or read-write (if enabled) access to your repository. Deploy keys can be used for CI, staging or production servers. You can create a deploy key or add an existing one.
......
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
= custom_icon("icon_close", size: 15) = custom_icon("icon_close", size: 15)
.js-issuable-update .js-issuable-update
= render "shared/boards/components/sidebar/assignee" = render "shared/boards/components/sidebar/assignee"
= render "shared/boards/components/sidebar/epic"
= render "shared/boards/components/sidebar/milestone" = render "shared/boards/components/sidebar/milestone"
= render "shared/boards/components/sidebar/due_date" = render "shared/boards/components/sidebar/due_date"
= render "shared/boards/components/sidebar/labels" = render "shared/boards/components/sidebar/labels"
......
...@@ -20,10 +20,7 @@ module RepositoryCheck ...@@ -20,10 +20,7 @@ module RepositoryCheck
# Historically some projects never had their wiki repos initialized; # Historically some projects never had their wiki repos initialized;
# this happens on project creation now. Let's initialize an empty repo # this happens on project creation now. Let's initialize an empty repo
# if it is not already there. # if it is not already there.
begin
project.create_wiki project.create_wiki
rescue Rugged::RepositoryError
end
git_fsck(project.wiki.repository) git_fsck(project.wiki.repository)
else else
......
---
title: 'Geo: Improve replication status. Using pg_stat_wal_receiver'
merge_request:
author:
type: other
---
title: Add Epic information for selected issue in Issue boards sidebar
merge_request: 4104
author:
type: added
...@@ -12,3 +12,12 @@ unless ENV['SKIP_POST_DEPLOYMENT_MIGRATIONS'] ...@@ -12,3 +12,12 @@ unless ENV['SKIP_POST_DEPLOYMENT_MIGRATIONS']
ActiveRecord::Migrator.migrations_paths << path ActiveRecord::Migrator.migrations_paths << path
end end
end end
migrate_paths = Rails.application.config.paths['db/migrate'].to_a
migrate_paths.each do |migrate_path|
absolute_migrate_path = Pathname.new(migrate_path).realpath(Rails.root)
ee_migrate_path = Rails.root.join('ee/', absolute_migrate_path.relative_path_from(Rails.root))
Rails.application.config.paths['db/migrate'] << ee_migrate_path.to_s
ActiveRecord::Migrator.migrations_paths << ee_migrate_path.to_s
end
# We don't want to ever call Rugged::Repository#fetch_attributes, because it has
# a lot of I/O overhead:
# <https://gitlab.com/gitlab-org/gitlab_git/commit/340e111e040ae847b614d35b4d3173ec48329015>
#
# While we don't do this from within the GitLab source itself, the Linguist gem
# has a dependency on Rugged and uses the gitattributes file when calculating
# repository-wide language statistics:
# <https://github.com/github/linguist/blob/v4.7.0/lib/linguist/lazy_blob.rb#L33-L36>
#
# The options passed by Linguist are those assumed by Gitlab::Git::InfoAttributes
# anyway, and there is no great efficiency gain from just fetching the listed
# attributes with our implementation, so we ignore the additional arguments.
#
module Rugged
class Repository
module UseGitlabGitAttributes
def fetch_attributes(name, *)
attributes.attributes(name)
end
def attributes
@attributes ||= Gitlab::Git::InfoAttributes.new(path)
end
end
prepend UseGitlabGitAttributes
end
end
...@@ -167,7 +167,7 @@ otherwise it may fail with an encryption error. ...@@ -167,7 +167,7 @@ otherwise it may fail with an encryption error.
Secondary Geo nodes track data about what has been downloaded in a second Secondary Geo nodes track data about what has been downloaded in a second
PostgreSQL database that is distinct from the production GitLab database. PostgreSQL database that is distinct from the production GitLab database.
The database configuration is set in `config/database_geo.yml`. The database configuration is set in `config/database_geo.yml`.
`db/geo` contains the schema and migrations for this database. `ee/db/geo` contains the schema and migrations for this database.
To write a migration for the database, use the `GeoMigrationGenerator`: To write a migration for the database, use the `GeoMigrationGenerator`:
......
...@@ -18,6 +18,10 @@ ...@@ -18,6 +18,10 @@
%strong exact order %strong exact order
they appear. they appear.
- unless Gitlab::Database.pg_stat_wal_receiver_supported?
= content_for :flash_message do
.alert.alert-warning WARNING: Please upgrade PostgreSQL to version 9.6 or greater. The status of the replication cannot be determined reliably with the current version.
- if @nodes.any? - if @nodes.any?
#js-geo-nodes{ data: { primary_version: "#{Gitlab::VERSION}", primary_revision: "#{Gitlab::REVISION}", node_details_path: "#{admin_geo_nodes_path}", node_actions_allowed: "#{Gitlab::Database.read_write?}", node_edit_allowed: "#{Gitlab::Geo.license_allows?}" } } #js-geo-nodes{ data: { primary_version: "#{Gitlab::VERSION}", primary_revision: "#{Gitlab::REVISION}", node_details_path: "#{admin_geo_nodes_path}", node_actions_allowed: "#{Gitlab::Database.read_write?}", node_edit_allowed: "#{Gitlab::Geo.license_allows?}" } }
- else - else
......
- return unless @group&.feature_available?(:epics) || @project&.group&.feature_available?(:epics)
.block.epic
.title
Epic
%span.js-epic-label-loading{ "v-if" => "issue.isFetching && issue.isFetching.epic" }
= icon('spinner spin', class: 'loading-icon')
.value.js-epic-label{ "v-if" => "issue.isFetching && !issue.isFetching.epic" }
%a.bold{ "v-if" => "issue.epic", ":href" => "issue.epic.url" }
{{ issue.epic.title }}
.no-value{ "v-if" => "!issue.epic" }
None
...@@ -12,6 +12,6 @@ class GeoMigrationGenerator < ActiveRecord::Generators::MigrationGenerator ...@@ -12,6 +12,6 @@ class GeoMigrationGenerator < ActiveRecord::Generators::MigrationGenerator
def create_migration_file def create_migration_file
set_local_assigns! set_local_assigns!
validate_file_name! validate_file_name!
migration_template @migration_template, "db/geo/migrate/#{file_name}.rb" migration_template @migration_template, "ee/db/geo/migrate/#{file_name}.rb"
end end
end end
...@@ -5,7 +5,7 @@ module Gitlab ...@@ -5,7 +5,7 @@ module Gitlab
DATABASE_CONFIG = 'config/database.yml'.freeze DATABASE_CONFIG = 'config/database.yml'.freeze
GEO_DATABASE_CONFIG = 'config/database_geo.yml'.freeze GEO_DATABASE_CONFIG = 'config/database_geo.yml'.freeze
GEO_DB_DIR = 'db/geo'.freeze GEO_DB_DIR = 'ee/db/geo'.freeze
def method_missing(method_name, *args, &block) def method_missing(method_name, *args, &block)
with_geo_db do with_geo_db do
......
...@@ -7,7 +7,7 @@ module Gitlab ...@@ -7,7 +7,7 @@ module Gitlab
return '' unless Gitlab::Geo.secondary? return '' unless Gitlab::Geo.secondary?
return 'The Geo database configuration file is missing.' unless Gitlab::Geo.geo_database_configured? return 'The Geo database configuration file is missing.' unless Gitlab::Geo.geo_database_configured?
return 'The Geo node has a database that is not configured for streaming replication with the primary node.' unless self.database_secondary? return 'The Geo node has a database that is not configured for streaming replication with the primary node.' unless self.database_secondary?
return 'The Geo node does not appear to be replicating data from the primary node.' unless self.db_replication_lag_seconds.present? return 'The Geo node does not appear to be replicating data from the primary node.' if Gitlab::Database.pg_stat_wal_receiver_supported? && !self.streaming_active?
database_version = self.get_database_version.to_i database_version = self.get_database_version.to_i
migration_version = self.get_migration_version.to_i migration_version = self.get_migration_version.to_i
...@@ -76,6 +76,18 @@ module Gitlab ...@@ -76,6 +76,18 @@ module Gitlab
lag.present? ? lag.to_i : lag lag.present? ? lag.to_i : lag
end end
def self.streaming_active?
# Get a streaming status
# This only works for Postgresql 9.6 and greater
status =
ActiveRecord::Base.connection.execute('
SELECT * FROM pg_stat_wal_receiver;')
.first
.fetch('status')
status == 'streaming'
end
end end
end end
end end
...@@ -41,14 +41,14 @@ namespace :geo do ...@@ -41,14 +41,14 @@ namespace :geo do
puts "Current version: #{Gitlab::Geo::DatabaseTasks.version}" puts "Current version: #{Gitlab::Geo::DatabaseTasks.version}"
end end
desc 'Drops and recreates the database from db/geo/schema.rb for the current environment and loads the seeds.' desc 'Drops and recreates the database from ee/db/geo/schema.rb for the current environment and loads the seeds.'
task reset: [:environment] do task reset: [:environment] do
ns['drop'].invoke ns['drop'].invoke
ns['create'].invoke ns['create'].invoke
ns['setup'].invoke ns['setup'].invoke
end end
desc 'Load the seed data from db/geo/seeds.rb' desc 'Load the seed data from ee/db/geo/seeds.rb'
task seed: [:environment] do task seed: [:environment] do
ns['abort_if_pending_migrations'].invoke ns['abort_if_pending_migrations'].invoke
...@@ -97,7 +97,7 @@ namespace :geo do ...@@ -97,7 +97,7 @@ namespace :geo do
Gitlab::Geo::DatabaseTasks.load_schema_current(:ruby, ENV['SCHEMA']) Gitlab::Geo::DatabaseTasks.load_schema_current(:ruby, ENV['SCHEMA'])
end end
desc 'Create a db/geo/schema.rb file that is portable against any DB supported by AR' desc 'Create a ee/db/geo/schema.rb file that is portable against any DB supported by AR'
task dump: [:environment] do task dump: [:environment] do
Gitlab::Geo::DatabaseTasks::Schema.dump Gitlab::Geo::DatabaseTasks::Schema.dump
...@@ -219,6 +219,12 @@ namespace :geo do ...@@ -219,6 +219,12 @@ namespace :geo do
puts GeoNode.current_node_url puts GeoNode.current_node_url
puts '-----------------------------------------------------'.color(:yellow) puts '-----------------------------------------------------'.color(:yellow)
unless Gitlab::Database.pg_stat_wal_receiver_supported?
puts
puts 'WARNING: Please upgrade PostgreSQL to version 9.6 or greater. The status of the replication cannot be determined reliably with the current version.'.color(:red)
puts
end
print 'GitLab version: '.rjust(COLUMN_WIDTH) print 'GitLab version: '.rjust(COLUMN_WIDTH)
puts Gitlab::VERSION puts Gitlab::VERSION
......
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/953
#
module Gitlab module Gitlab
module BareRepositoryImport module BareRepositoryImport
class Repository class Repository
......
...@@ -54,6 +54,10 @@ module Gitlab ...@@ -54,6 +54,10 @@ module Gitlab
postgresql? && version.to_f >= 9.4 postgresql? && version.to_f >= 9.4
end end
def self.pg_stat_wal_receiver_supported?
postgresql? && version.to_f >= 9.6
end
def self.nulls_last_order(field, direction = 'ASC') def self.nulls_last_order(field, direction = 'ASC')
order = "#{field} #{direction}" order = "#{field} #{direction}"
......
...@@ -563,6 +563,8 @@ module Gitlab ...@@ -563,6 +563,8 @@ module Gitlab
return false if ancestor_id.nil? || descendant_id.nil? return false if ancestor_id.nil? || descendant_id.nil?
merge_base_commit(ancestor_id, descendant_id) == ancestor_id merge_base_commit(ancestor_id, descendant_id) == ancestor_id
rescue Rugged::OdbError
false
end end
# Returns true is +from+ is direct ancestor to +to+, otherwise false # Returns true is +from+ is direct ancestor to +to+, otherwise false
...@@ -1125,23 +1127,6 @@ module Gitlab ...@@ -1125,23 +1127,6 @@ module Gitlab
target_ref target_ref
end end
# Refactoring aid; allows us to copy code from app/models/repository.rb
def run_git(args, chdir: path, env: {}, nice: false, &block)
cmd = [Gitlab.config.git.bin_path, *args]
cmd.unshift("nice") if nice
circuit_breaker.perform do
popen(cmd, chdir, env, &block)
end
end
def run_git!(args, chdir: path, env: {}, nice: false, &block)
output, status = run_git(args, chdir: chdir, env: env, nice: nice, &block)
raise GitError, output unless status.zero?
output
end
# Refactoring aid; allows us to copy code from app/models/repository.rb # Refactoring aid; allows us to copy code from app/models/repository.rb
def run_git_with_timeout(args, timeout, env: {}) def run_git_with_timeout(args, timeout, env: {})
circuit_breaker.perform do circuit_breaker.perform do
...@@ -1365,6 +1350,52 @@ module Gitlab ...@@ -1365,6 +1350,52 @@ module Gitlab
raise CommandError.new(e) raise CommandError.new(e)
end end
def refs_contains_sha(ref_type, sha)
args = %W(#{ref_type} --contains #{sha})
names = run_git(args).first
if names.respond_to?(:split)
names = names.split("\n").map(&:strip)
names.each do |name|
name.slice! '* '
end
names
else
[]
end
end
def search_files_by_content(query, ref)
return [] if empty? || query.blank?
offset = 2
args = %W(grep -i -I -n -z --before-context #{offset} --after-context #{offset} -E -e #{Regexp.escape(query)} #{ref || root_ref})
run_git(args).first.scrub.split(/^--$/)
end
def search_files_by_name(query, ref)
safe_query = Regexp.escape(query.sub(/^\/*/, ""))
return [] if empty? || safe_query.blank?
args = %W(ls-tree --full-tree -r #{ref || root_ref} --name-status | #{safe_query})
run_git(args).first.lines.map(&:strip)
end
def find_commits_by_message(query, ref, path, limit, offset)
gitaly_migrate(:commits_by_message) do |is_enabled|
if is_enabled
find_commits_by_message_by_gitaly(query, ref, path, limit, offset)
else
find_commits_by_message_by_shelling_out(query, ref, path, limit, offset)
end
end
end
private private
def shell_write_ref(ref_path, ref, old_ref) def shell_write_ref(ref_path, ref, old_ref)
...@@ -1386,6 +1417,22 @@ module Gitlab ...@@ -1386,6 +1417,22 @@ module Gitlab
Rails.logger.error "Unable to create #{ref_path} reference for repository #{path}: #{ex}" Rails.logger.error "Unable to create #{ref_path} reference for repository #{path}: #{ex}"
end end
def run_git(args, chdir: path, env: {}, nice: false, &block)
cmd = [Gitlab.config.git.bin_path, *args]
cmd.unshift("nice") if nice
circuit_breaker.perform do
popen(cmd, chdir, env, &block)
end
end
def run_git!(args, chdir: path, env: {}, nice: false, &block)
output, status = run_git(args, chdir: chdir, env: env, nice: nice, &block)
raise GitError, output unless status.zero?
output
end
def fresh_worktree?(path) def fresh_worktree?(path)
File.exist?(path) && !clean_stuck_worktree(path) File.exist?(path) && !clean_stuck_worktree(path)
end end
...@@ -2161,6 +2208,26 @@ module Gitlab ...@@ -2161,6 +2208,26 @@ module Gitlab
def gitlab_projects_error def gitlab_projects_error
raise CommandError, @gitlab_projects.output raise CommandError, @gitlab_projects.output
end end
def find_commits_by_message_by_shelling_out(query, ref, path, limit, offset)
ref ||= root_ref
args = %W(
log #{ref} --pretty=%H --skip #{offset}
--max-count #{limit} --grep=#{query} --regexp-ignore-case
)
args = args.concat(%W(-- #{path})) if path.present?
git_log_results = run_git(args).first.lines
git_log_results.map { |c| commit(c.chomp) }.compact
end
def find_commits_by_message_by_gitaly(query, ref, path, limit, offset)
gitaly_commit_client
.commits_by_message(query, revision: ref, path: path, limit: limit, offset: offset)
.map { |c| commit(c) }
end
end end
end end
end end
...@@ -38,20 +38,28 @@ module Gitlab ...@@ -38,20 +38,28 @@ module Gitlab
from_id = case from from_id = case from
when NilClass when NilClass
EMPTY_TREE_ID EMPTY_TREE_ID
when Rugged::Commit else
if from.respond_to?(:oid)
# This is meant to match a Rugged::Commit. This should be impossible in
# the future.
from.oid from.oid
else else
from from
end end
end
to_id = case to to_id = case to
when NilClass when NilClass
EMPTY_TREE_ID EMPTY_TREE_ID
when Rugged::Commit else
if to.respond_to?(:oid)
# This is meant to match a Rugged::Commit. This should be impossible in
# the future.
to.oid to.oid
else else
to to
end end
end
request_params = diff_between_commits_request_params(from_id, to_id, options) request_params = diff_between_commits_request_params(from_id, to_id, options)
......
...@@ -57,10 +57,7 @@ module Gitlab ...@@ -57,10 +57,7 @@ module Gitlab
end end
def commit_exists?(sha) def commit_exists?(sha)
project.repository.lookup(sha) project.repository.commit(sha).present?
true
rescue Rugged::Error
false
end end
def collection_method def collection_method
......
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/954
#
namespace :gitlab do namespace :gitlab do
namespace :cleanup do namespace :cleanup do
HASHED_REPOSITORY_NAME = '@hashed'.freeze HASHED_REPOSITORY_NAME = '@hashed'.freeze
......
...@@ -107,18 +107,20 @@ module QA ...@@ -107,18 +107,20 @@ module QA
autoload :New, 'qa/page/project/new' autoload :New, 'qa/page/project/new'
autoload :Show, 'qa/page/project/show' autoload :Show, 'qa/page/project/show'
module Pipeline
autoload :Index, 'qa/page/project/pipeline/index'
autoload :Show, 'qa/page/project/pipeline/show'
end
module Settings module Settings
autoload :Common, 'qa/page/project/settings/common' autoload :Common, 'qa/page/project/settings/common'
autoload :Advanced, 'qa/page/project/settings/advanced'
autoload :Main, 'qa/page/project/settings/main'
autoload :Repository, 'qa/page/project/settings/repository' autoload :Repository, 'qa/page/project/settings/repository'
autoload :CICD, 'qa/page/project/settings/ci_cd' autoload :CICD, 'qa/page/project/settings/ci_cd'
autoload :DeployKeys, 'qa/page/project/settings/deploy_keys' autoload :DeployKeys, 'qa/page/project/settings/deploy_keys'
autoload :Runners, 'qa/page/project/settings/runners' autoload :Runners, 'qa/page/project/settings/runners'
end end
module Pipeline
autoload :Index, 'qa/page/project/pipeline/index'
autoload :Show, 'qa/page/project/pipeline/show'
end
end end
module Profile module Profile
......
...@@ -3,10 +3,21 @@ module QA ...@@ -3,10 +3,21 @@ module QA
module Dashboard module Dashboard
class Projects < Page::Base class Projects < Page::Base
view 'app/views/dashboard/projects/index.html.haml' view 'app/views/dashboard/projects/index.html.haml'
view 'app/views/shared/projects/_search_form.html.haml' do
element :form_filter_by_name, /form_tag.+id: 'project-filter-form'/
end
def go_to_project(name) def go_to_project(name)
filter_by_name(name)
find_link(text: name).click find_link(text: name).click
end end
def filter_by_name(name)
page.within('form#project-filter-form') do
fill_in :name, with: name
end
end
end end
end end
end end
......
...@@ -4,6 +4,7 @@ module QA ...@@ -4,6 +4,7 @@ module QA
class Side < Page::Base class Side < Page::Base
view 'app/views/layouts/nav/sidebar/_project.html.haml' do view 'app/views/layouts/nav/sidebar/_project.html.haml' do
element :settings_item element :settings_item
element :settings_link, 'link_to edit_project_path'
element :repository_link, "title: 'Repository'" element :repository_link, "title: 'Repository'"
element :pipelines_settings_link, "title: 'CI / CD'" element :pipelines_settings_link, "title: 'CI / CD'"
element :top_level_items, '.sidebar-top-level-items' element :top_level_items, '.sidebar-top-level-items'
...@@ -31,6 +32,12 @@ module QA ...@@ -31,6 +32,12 @@ module QA
end end
end end
def go_to_settings
within_sidebar do
click_on 'Settings'
end
end
private private
def hover_settings def hover_settings
......
module QA
module Page
module Project
module Settings
class Advanced < Page::Base
view 'app/views/projects/edit.html.haml' do
element :project_path_field, 'f.text_field :path'
element :project_name_field, 'f.text_field :name'
element :rename_project_button, "f.submit 'Rename project'"
end
def rename_to(path)
fill_project_name(path)
fill_project_path(path)
rename_project!
end
def fill_project_path(path)
fill_in :project_path, with: path
end
def fill_project_name(name)
fill_in :project_name, with: name
end
def rename_project!
click_on 'Rename project'
end
end
end
end
end
end
...@@ -3,20 +3,23 @@ module QA ...@@ -3,20 +3,23 @@ module QA
module Project module Project
module Settings module Settings
module Common module Common
def expand(element_name) def self.included(base)
page.within('#content-body') do base.class_eval do
click_element(element_name) view 'app/views/projects/edit.html.haml' do
element :advanced_settings_expand, "= expanded ? 'Collapse' : 'Expand'"
yield end
end end
end end
# Click the Expand button present in the specified section
#
# @param [String] name present in the container in the DOM
def expand_section(name) def expand_section(name)
page.within('#content-body') do page.within('#content-body') do
page.within('section', text: name) do page.within('section', text: name) do
click_button 'Expand' click_button('Expand')
yield yield if block_given?
end end
end end
end end
......
module QA
module Page
module Project
module Settings
class Main < Page::Base
include Common
view 'app/views/projects/edit.html.haml' do
element :advanced_settings_section, 'Advanced settings'
end
def expand_advanced_settings(&block)
expand_section('Advanced settings') do
Advanced.perform(&block)
end
end
end
end
end
end
end
...@@ -6,11 +6,11 @@ module QA ...@@ -6,11 +6,11 @@ module QA
include Common include Common
view 'app/views/projects/deploy_keys/_index.html.haml' do view 'app/views/projects/deploy_keys/_index.html.haml' do
element :expand_deploy_keys element :deploy_keys_section, 'Deploy Keys'
end end
def expand_deploy_keys(&block) def expand_deploy_keys(&block)
expand(:expand_deploy_keys) do expand_section('Deploy Keys') do
DeployKeys.perform(&block) DeployKeys.perform(&block)
end end
end end
......
...@@ -23,11 +23,11 @@ module QA ...@@ -23,11 +23,11 @@ module QA
# In case of an address that is a symbol we will try to guess address # In case of an address that is a symbol we will try to guess address
# based on `Runtime::Scenario#something_address`. # based on `Runtime::Scenario#something_address`.
# #
def visit(address, page, &block) def visit(address, page = nil, &block)
Browser::Session.new(address, page).perform(&block) Browser::Session.new(address, page).perform(&block)
end end
def self.visit(address, page, &block) def self.visit(address, page = nil, &block)
new.visit(address, page, &block) new.visit(address, page, &block)
end end
......
module QA
feature 'GitLab Geo project rename replication', :geo do
scenario 'user renames project' do
# create the project and push code
Runtime::Browser.visit(:geo_primary, QA::Page::Main::Login) do
Page::Main::Login.act { sign_in_using_credentials }
project = Factory::Resource::Project.fabricate! do |project|
project.name = 'geo-before-rename'
project.description = 'Geo project to be renamed'
end
geo_project_name = project.name
expect(project.name).to include 'geo-before-rename'
Factory::Repository::Push.fabricate! do |push|
push.project = project
push.file_name = 'README.md'
push.file_content = '# This is Geo project!'
push.commit_message = 'Add README.md'
end
# rename the project
Page::Menu::Main.act { go_to_projects }
Page::Dashboard::Projects.perform do |dashboard|
dashboard.go_to_project(geo_project_name)
end
Page::Menu::Side.act { go_to_settings }
geo_project_renamed = "geo-after-rename-#{SecureRandom.hex(8)}"
Page::Project::Settings::Main.perform do |settings|
settings.expand_advanced_settings do |page|
page.rename_to(geo_project_renamed)
end
end
sleep 2 # wait for replication
# check renamed project exist on secondary node
Runtime::Browser.visit(:geo_secondary, QA::Page::Main::Login) do
Page::Main::OAuth.act do
authorize! if needs_authorization?
end
expect(page).to have_content 'You are on a secondary (read-only) Geo node'
Page::Menu::Main.perform do |menu|
menu.go_to_projects
expect(page).to have_content(geo_project_renamed)
end
Page::Dashboard::Projects.perform do |dashboard|
dashboard.go_to_project(geo_project_renamed)
end
Page::Project::Show.perform do
expect(page).to have_content 'README.md'
expect(page).to have_content 'This is Geo project!'
end
end
end
end
end
end
...@@ -4,7 +4,7 @@ module QA ...@@ -4,7 +4,7 @@ module QA
Runtime::Browser.visit(:geo_primary, QA::Page::Main::Login) do Runtime::Browser.visit(:geo_primary, QA::Page::Main::Login) do
Page::Main::Login.act { sign_in_using_credentials } Page::Main::Login.act { sign_in_using_credentials }
Factory::Resource::Project.fabricate! do |project| project = Factory::Resource::Project.fabricate! do |project|
project.name = 'geo-project' project.name = 'geo-project'
project.description = 'Geo test project' project.description = 'Geo test project'
end end
...@@ -12,21 +12,11 @@ module QA ...@@ -12,21 +12,11 @@ module QA
geo_project_name = Page::Project::Show.act { project_name } geo_project_name = Page::Project::Show.act { project_name }
expect(geo_project_name).to include 'geo-project' expect(geo_project_name).to include 'geo-project'
Git::Repository.perform do |repository| Factory::Repository::Push.fabricate! do |push|
repository.location = Page::Project::Show.act do push.file_name = 'README.md'
choose_repository_clone_http push.file_content = '# This is Geo project!'
repository_location push.commit_message = 'Add README.md'
end push.project = project
repository.use_default_credentials
repository.act do
clone
configure_identity('GitLab QA', 'root@gitlab.com')
add_file('README.md', '# This is Geo project!')
commit('Add README.md')
push_changes
end
end end
Runtime::Browser.visit(:geo_secondary, QA::Page::Main::Login) do Runtime::Browser.visit(:geo_secondary, QA::Page::Main::Login) do
......
#!/usr/bin/env ruby
ALLOWED = [
# https://gitlab.com/gitlab-org/gitaly/issues/760
'lib/elasticsearch/git/repository.rb',
# Can be deleted (?) once rugged is no longer used in production. Doesn't make Rugged calls.
'config/initializers/8_metrics.rb',
# Can be deleted once wiki's are fully (mandatory) migrated
'config/initializers/gollum.rb',
# Needs to be migrated, https://gitlab.com/gitlab-org/gitaly/issues/953
'lib/gitlab/bare_repository_import/repository.rb',
# Needs to be migrated, https://gitlab.com/gitlab-org/gitaly/issues/954
'lib/tasks/gitlab/cleanup.rake',
# https://gitlab.com/gitlab-org/gitaly/issues/961
'app/models/repository.rb',
# The only place where Rugged code is still allowed in production
'lib/gitlab/git/'
].freeze
rugged_lines = IO.popen(%w[git grep -i -n rugged -- app config lib], &:read).lines
rugged_lines = rugged_lines.reject { |l| l.start_with?(*ALLOWED) }
rugged_lines = rugged_lines.reject do |line|
code, _comment = line.split('# ', 2)
code !~ /rugged/i
end
exit if rugged_lines.empty?
puts "Using Rugged is only allowed in test and #{ALLOWED}\n\n"
puts rugged_lines
exit(false)
...@@ -13,7 +13,8 @@ tasks = [ ...@@ -13,7 +13,8 @@ tasks = [
%w[bundle exec rake gettext:lint], %w[bundle exec rake gettext:lint],
%w[bundle exec rake lint:static_verification], %w[bundle exec rake lint:static_verification],
%w[scripts/lint-changelog-yaml], %w[scripts/lint-changelog-yaml],
%w[scripts/lint-conflicts.sh] %w[scripts/lint-conflicts.sh],
%w[scripts/lint-rugged]
] ]
failed_tasks = tasks.reduce({}) do |failures, task| failed_tasks = tasks.reduce({}) do |failures, task|
......
...@@ -5,7 +5,8 @@ describe 'Issue Boards', :js do ...@@ -5,7 +5,8 @@ describe 'Issue Boards', :js do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:user2) { create(:user) } let(:user2) { create(:user) }
let(:project) { create(:project, :public) } let(:group) { create(:group) }
let(:project) { create(:project, :public, group: group) }
let!(:milestone) { create(:milestone, project: project) } let!(:milestone) { create(:milestone, project: project) }
let!(:development) { create(:label, project: project, name: 'Development') } let!(:development) { create(:label, project: project, name: 'Development') }
let!(:stretch) { create(:label, project: project, name: 'Stretch') } let!(:stretch) { create(:label, project: project, name: 'Stretch') }
...@@ -141,6 +142,36 @@ describe 'Issue Boards', :js do ...@@ -141,6 +142,36 @@ describe 'Issue Boards', :js do
end end
end end
context 'epic' do
before do
stub_licensed_features(epics: true)
visit project_board_path(project, board)
wait_for_requests
end
context 'when the issue is not associated with an epic' do
it 'displays `None` for value of epic' do
click_card(card1)
wait_for_requests
expect(find('.js-epic-label').text).to have_content('None')
end
end
context 'when the issue is associated with an epic' do
let(:epic) { create(:epic, group: group) }
let!(:epic_issue) { create(:epic_issue, issue: issue1, epic: epic) }
it 'displays name of epic and links to it' do
click_card(card1)
wait_for_requests
expect(find('.js-epic-label')).to have_link(epic.title, href: epic_path(epic))
end
end
end
context 'weight' do context 'weight' do
it 'displays weight async' do it 'displays weight async' do
click_card(card1) click_card(card1)
......
...@@ -15,7 +15,7 @@ describe Gitlab::Geo::HealthCheck, :postgresql do ...@@ -15,7 +15,7 @@ describe Gitlab::Geo::HealthCheck, :postgresql do
allow(described_class).to receive(:database_secondary?).and_return(true) allow(described_class).to receive(:database_secondary?).and_return(true)
allow(described_class).to receive(:get_database_version).and_return('20170101') allow(described_class).to receive(:get_database_version).and_return('20170101')
allow(described_class).to receive(:get_migration_version).and_return('20170201') allow(described_class).to receive(:get_migration_version).and_return('20170201')
allow(described_class).to receive(:db_replication_lag_seconds).and_return(0) allow(described_class).to receive(:streaming_active?).and_return(true)
message = subject.perform_checks message = subject.perform_checks
...@@ -54,7 +54,7 @@ describe Gitlab::Geo::HealthCheck, :postgresql do ...@@ -54,7 +54,7 @@ describe Gitlab::Geo::HealthCheck, :postgresql do
it 'returns an error when Geo database version does not match the latest migration version' do it 'returns an error when Geo database version does not match the latest migration version' do
allow(described_class).to receive(:database_secondary?).and_return(true) allow(described_class).to receive(:database_secondary?).and_return(true)
allow(subject).to receive(:get_database_version) { 1 } allow(subject).to receive(:get_database_version) { 1 }
allow(described_class).to receive(:db_replication_lag_seconds).and_return(0) allow(described_class).to receive(:streaming_active?).and_return(true)
expect(subject.perform_checks).to match(/Current Geo database version \([0-9]+\) does not match latest migration \([0-9]+\)/) expect(subject.perform_checks).to match(/Current Geo database version \([0-9]+\) does not match latest migration \([0-9]+\)/)
end end
...@@ -62,17 +62,26 @@ describe Gitlab::Geo::HealthCheck, :postgresql do ...@@ -62,17 +62,26 @@ describe Gitlab::Geo::HealthCheck, :postgresql do
it 'returns an error when latest migration version does not match the Geo database version' do it 'returns an error when latest migration version does not match the Geo database version' do
allow(described_class).to receive(:database_secondary?).and_return(true) allow(described_class).to receive(:database_secondary?).and_return(true)
allow(subject).to receive(:get_migration_version) { 1 } allow(subject).to receive(:get_migration_version) { 1 }
allow(described_class).to receive(:db_replication_lag_seconds).and_return(0) allow(described_class).to receive(:streaming_active?).and_return(true)
expect(subject.perform_checks).to match(/Current Geo database version \([0-9]+\) does not match latest migration \([0-9]+\)/) expect(subject.perform_checks).to match(/Current Geo database version \([0-9]+\) does not match latest migration \([0-9]+\)/)
end end
it 'returns an error when replication lag is not present' do it 'returns an error when streaming is not active and Postgresql supports pg_stat_wal_receiver' do
allow(Gitlab::Database).to receive(:pg_stat_wal_receiver_supported?).and_return(true)
allow(described_class).to receive(:database_secondary?).and_return(true) allow(described_class).to receive(:database_secondary?).and_return(true)
allow(described_class).to receive(:db_replication_lag_seconds).and_return(nil) allow(described_class).to receive(:streaming_active?).and_return(false)
expect(subject.perform_checks).to match(/The Geo node does not appear to be replicating data from the primary node/) expect(subject.perform_checks).to match(/The Geo node does not appear to be replicating data from the primary node/)
end end
it 'returns an error when streaming is not active and Postgresql does not support pg_stat_wal_receiver' do
allow(Gitlab::Database).to receive(:pg_stat_wal_receiver_supported?).and_return(false)
allow(described_class).to receive(:database_secondary?).and_return(true)
allow(described_class).to receive(:streaming_active?).and_return(false)
expect(subject.perform_checks).to be_empty
end
end end
describe 'MySQL checks' do describe 'MySQL checks' do
......
require 'spec_helper' require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20171103152048_geo_drain_redis_queues') require Rails.root.join('ee', 'db', 'post_migrate', '20171103152048_geo_drain_redis_queues')
describe GeoDrainRedisQueues, :clean_gitlab_redis_shared_state do describe GeoDrainRedisQueues, :clean_gitlab_redis_shared_state do
subject(:migration) { described_class.new } subject(:migration) { described_class.new }
......
require "spec_helper" require 'spec_helper'
require Rails.root.join("db", "post_migrate", "20170811082658_remove_system_hook_from_geo_nodes.rb") require Rails.root.join('ee', 'db', 'post_migrate', '20170811082658_remove_system_hook_from_geo_nodes.rb')
describe RemoveSystemHookFromGeoNodes, :migration do describe RemoveSystemHookFromGeoNodes, :migration do
let(:geo_nodes) { table(:geo_nodes) } let(:geo_nodes) { table(:geo_nodes) }
......
require 'spec_helper' require 'spec_helper'
require Rails.root.join('db', 'migrate', '20170626202753_update_authorized_keys_file.rb') require Rails.root.join('ee', 'db', 'migrate', '20170626202753_update_authorized_keys_file.rb')
describe UpdateAuthorizedKeysFile, :migration do describe UpdateAuthorizedKeysFile, :migration do
let(:migration) { described_class.new } let(:migration) { described_class.new }
......
...@@ -474,7 +474,9 @@ describe GeoNodeStatus, :geo do ...@@ -474,7 +474,9 @@ describe GeoNodeStatus, :geo do
end end
describe '#storage_shards_match?' do describe '#storage_shards_match?' do
before { stub_primary_node } before do
stub_primary_node
end
set(:status) { create(:geo_node_status) } set(:status) { create(:geo_node_status) }
let(:data) { GeoNodeStatusSerializer.new.represent(status).as_json } let(:data) { GeoNodeStatusSerializer.new.represent(status).as_json }
......
import SecretValues from '~/behaviors/secret_values'; import SecretValues from '~/behaviors/secret_values';
function generateFixtureMarkup(secrets, isRevealed) { function generateValueMarkup(
secret,
valueClass = 'js-secret-value',
placeholderClass = 'js-secret-value-placeholder',
) {
return ` return `
<div class="js-secret-container"> <div class="${placeholderClass}">
${secrets.map(secret => `
<div class="js-secret-value-placeholder">
*** ***
</div> </div>
<div class="hide js-secret-value"> <div class="hide ${valueClass}">
${secret} ${secret}
</div> </div>
`).join('')} `;
}
function generateFixtureMarkup(secrets, isRevealed, valueClass, placeholderClass) {
return `
<div class="js-secret-container">
${secrets.map(secret => generateValueMarkup(secret, valueClass, placeholderClass)).join('')}
<button <button
class="js-secret-value-reveal-button" class="js-secret-value-reveal-button"
data-secret-reveal-status="${isRevealed}" data-secret-reveal-status="${isRevealed}"
...@@ -21,11 +29,25 @@ function generateFixtureMarkup(secrets, isRevealed) { ...@@ -21,11 +29,25 @@ function generateFixtureMarkup(secrets, isRevealed) {
`; `;
} }
function setupSecretFixture(secrets, isRevealed) { function setupSecretFixture(
secrets,
isRevealed,
valueClass = 'js-secret-value',
placeholderClass = 'js-secret-value-placeholder',
) {
const wrapper = document.createElement('div'); const wrapper = document.createElement('div');
wrapper.innerHTML = generateFixtureMarkup(secrets, isRevealed); wrapper.innerHTML = generateFixtureMarkup(
secrets,
const secretValues = new SecretValues(wrapper.querySelector('.js-secret-container')); isRevealed,
valueClass,
placeholderClass,
);
const secretValues = new SecretValues({
container: wrapper.querySelector('.js-secret-container'),
valueSelector: `.${valueClass}`,
placeholderSelector: `.${placeholderClass}`,
});
secretValues.init(); secretValues.init();
return wrapper; return wrapper;
...@@ -49,7 +71,7 @@ describe('setupSecretValues', () => { ...@@ -49,7 +71,7 @@ describe('setupSecretValues', () => {
expect(revealButton.textContent).toEqual('Hide value'); expect(revealButton.textContent).toEqual('Hide value');
}); });
it('should value hidden initially', () => { it('should have value hidden initially', () => {
const wrapper = setupSecretFixture(secrets, false); const wrapper = setupSecretFixture(secrets, false);
const values = wrapper.querySelectorAll('.js-secret-value'); const values = wrapper.querySelectorAll('.js-secret-value');
const placeholders = wrapper.querySelectorAll('.js-secret-value-placeholder'); const placeholders = wrapper.querySelectorAll('.js-secret-value-placeholder');
...@@ -143,4 +165,64 @@ describe('setupSecretValues', () => { ...@@ -143,4 +165,64 @@ describe('setupSecretValues', () => {
}); });
}); });
}); });
describe('with dynamic secrets', () => {
const secrets = ['mysecret123', 'happygoat456', 'tanuki789'];
it('should toggle values and placeholders', () => {
const wrapper = setupSecretFixture(secrets, false);
// Insert the new dynamic row
wrapper.querySelector('.js-secret-container').insertAdjacentHTML('afterbegin', generateValueMarkup('foobarbazdynamic'));
const revealButton = wrapper.querySelector('.js-secret-value-reveal-button');
const values = wrapper.querySelectorAll('.js-secret-value');
const placeholders = wrapper.querySelectorAll('.js-secret-value-placeholder');
revealButton.click();
expect(values.length).toEqual(4);
values.forEach((value) => {
expect(value.classList.contains('hide')).toEqual(false);
});
expect(placeholders.length).toEqual(4);
placeholders.forEach((placeholder) => {
expect(placeholder.classList.contains('hide')).toEqual(true);
});
revealButton.click();
expect(values.length).toEqual(4);
values.forEach((value) => {
expect(value.classList.contains('hide')).toEqual(true);
});
expect(placeholders.length).toEqual(4);
placeholders.forEach((placeholder) => {
expect(placeholder.classList.contains('hide')).toEqual(false);
});
});
});
describe('selector options', () => {
const secrets = ['mysecret123'];
it('should respect `valueSelector` and `placeholderSelector` options', () => {
const valueClass = 'js-some-custom-placeholder-selector';
const placeholderClass = 'js-some-custom-value-selector';
const wrapper = setupSecretFixture(secrets, false, valueClass, placeholderClass);
const values = wrapper.querySelectorAll(`.${valueClass}`);
const placeholders = wrapper.querySelectorAll(`.${placeholderClass}`);
const revealButton = wrapper.querySelector('.js-secret-value-reveal-button');
expect(values.length).toEqual(1);
expect(placeholders.length).toEqual(1);
revealButton.click();
expect(values.length).toEqual(1);
expect(values[0].classList.contains('hide')).toEqual(false);
expect(placeholders.length).toEqual(1);
expect(placeholders[0].classList.contains('hide')).toEqual(true);
});
});
}); });
...@@ -9,7 +9,9 @@ describe API::Entities::GeoNodeStatus, :postgresql do ...@@ -9,7 +9,9 @@ describe API::Entities::GeoNodeStatus, :postgresql do
subject { entity.as_json } subject { entity.as_json }
before { stub_primary_node } before do
stub_primary_node
end
describe '#healthy' do describe '#healthy' do
context 'when node is healthy' do context 'when node is healthy' do
...@@ -127,7 +129,9 @@ describe API::Entities::GeoNodeStatus, :postgresql do ...@@ -127,7 +129,9 @@ describe API::Entities::GeoNodeStatus, :postgresql do
end end
context 'secondary Geo node' do context 'secondary Geo node' do
before { stub_secondary_node } before do
stub_secondary_node
end
it { is_expected.to have_key(:storage_shards) } it { is_expected.to have_key(:storage_shards) }
it { is_expected.not_to have_key(:storage_shards_match) } it { is_expected.not_to have_key(:storage_shards_match) }
......
...@@ -244,7 +244,7 @@ describe Gitlab::GithubImport::Importer::PullRequestsImporter do ...@@ -244,7 +244,7 @@ describe Gitlab::GithubImport::Importer::PullRequestsImporter do
it 'returns true when a commit exists' do it 'returns true when a commit exists' do
expect(project.repository) expect(project.repository)
.to receive(:lookup) .to receive(:commit)
.with('123') .with('123')
.and_return(double(:commit)) .and_return(double(:commit))
...@@ -253,9 +253,9 @@ describe Gitlab::GithubImport::Importer::PullRequestsImporter do ...@@ -253,9 +253,9 @@ describe Gitlab::GithubImport::Importer::PullRequestsImporter do
it 'returns false when a commit does not exist' do it 'returns false when a commit does not exist' do
expect(project.repository) expect(project.repository)
.to receive(:lookup) .to receive(:commit)
.with('123') .with('123')
.and_raise(Rugged::OdbError) .and_return(nil)
expect(importer.commit_exists?('123')).to eq(false) expect(importer.commit_exists?('123')).to eq(false)
end end
......
...@@ -5,7 +5,11 @@ require 'spec_helper' ...@@ -5,7 +5,11 @@ require 'spec_helper'
describe ActiveRecord::Schema do describe ActiveRecord::Schema do
let(:latest_migration_timestamp) do let(:latest_migration_timestamp) do
migrations = Dir[Rails.root.join('db', 'migrate', '*'), Rails.root.join('db', 'post_migrate', '*')] migrations_paths =
%w[db ee/db].product(%w[migrate post_migrate]).each_with_object([]) do |migration_dir, memo|
memo << Rails.root.join(*migration_dir, '*')
end
migrations = Dir[*migrations_paths]
migrations.map { |migration| File.basename(migration).split('_').first.to_i }.max migrations.map { |migration| File.basename(migration).split('_').first.to_i }.max
end end
......
...@@ -2478,7 +2478,7 @@ describe Repository do ...@@ -2478,7 +2478,7 @@ describe Repository do
let(:commit) { repository.commit } let(:commit) { repository.commit }
let(:ancestor) { commit.parents.first } let(:ancestor) { commit.parents.first }
context 'with Gitaly enabled' do shared_examples '#ancestor?' do
it 'it is an ancestor' do it 'it is an ancestor' do
expect(repository.ancestor?(ancestor.id, commit.id)).to eq(true) expect(repository.ancestor?(ancestor.id, commit.id)).to eq(true)
end end
...@@ -2492,27 +2492,19 @@ describe Repository do ...@@ -2492,27 +2492,19 @@ describe Repository do
expect(repository.ancestor?(ancestor.id, nil)).to eq(false) expect(repository.ancestor?(ancestor.id, nil)).to eq(false)
expect(repository.ancestor?(nil, nil)).to eq(false) expect(repository.ancestor?(nil, nil)).to eq(false)
end end
end
context 'with Gitaly disabled' do it 'returns false for invalid commit IDs' do
before do expect(repository.ancestor?(commit.id, Gitlab::Git::BLANK_SHA)).to eq(false)
allow(Gitlab::GitalyClient).to receive(:enabled?).and_return(false) expect(repository.ancestor?( Gitlab::Git::BLANK_SHA, commit.id)).to eq(false)
allow(Gitlab::GitalyClient).to receive(:feature_enabled?).with(:is_ancestor).and_return(false)
end end
it 'it is an ancestor' do
expect(repository.ancestor?(ancestor.id, commit.id)).to eq(true)
end end
it 'it is not an ancestor' do context 'with Gitaly enabled' do
expect(repository.ancestor?(commit.id, ancestor.id)).to eq(false) it_behaves_like('#ancestor?')
end end
it 'returns false on nil-values' do context 'with Gitaly disabled', :skip_gitaly_mock do
expect(repository.ancestor?(nil, commit.id)).to eq(false) it_behaves_like('#ancestor?')
expect(repository.ancestor?(ancestor.id, nil)).to eq(false)
expect(repository.ancestor?(nil, nil)).to eq(false)
end
end end
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment