summaryrefslogtreecommitdiff
path: root/spec/lib
diff options
context:
space:
mode:
Diffstat (limited to 'spec/lib')
-rw-r--r--spec/lib/api/helpers/graphql_helpers_spec.rb44
-rw-r--r--spec/lib/backup/files_spec.rb1
-rw-r--r--spec/lib/backup/manager_spec.rb45
-rw-r--r--spec/lib/backup/repository_spec.rb25
-rw-r--r--spec/lib/banzai/filter/audio_link_filter_spec.rb120
-rw-r--r--spec/lib/banzai/filter/project_reference_filter_spec.rb1
-rw-r--r--spec/lib/banzai/filter/relative_link_filter_spec.rb51
-rw-r--r--spec/lib/banzai/filter/table_of_contents_filter_spec.rb4
-rw-r--r--spec/lib/banzai/filter/user_reference_filter_spec.rb1
-rw-r--r--spec/lib/banzai/filter/video_link_filter_spec.rb95
-rw-r--r--spec/lib/banzai/filter/wiki_link_filter_spec.rb8
-rw-r--r--spec/lib/banzai/pipeline/wiki_pipeline_spec.rb24
-rw-r--r--spec/lib/banzai/reference_parser/commit_parser_spec.rb1
-rw-r--r--spec/lib/banzai/reference_parser/commit_range_parser_spec.rb1
-rw-r--r--spec/lib/banzai/reference_parser/external_issue_parser_spec.rb1
-rw-r--r--spec/lib/banzai/reference_parser/label_parser_spec.rb1
-rw-r--r--spec/lib/banzai/reference_parser/mentioned_user_parser_spec.rb51
-rw-r--r--spec/lib/banzai/reference_parser/mentioned_users_by_group_parser_spec.rb46
-rw-r--r--spec/lib/banzai/reference_parser/mentioned_users_by_project_parser_spec.rb46
-rw-r--r--spec/lib/banzai/reference_parser/merge_request_parser_spec.rb1
-rw-r--r--spec/lib/banzai/reference_parser/milestone_parser_spec.rb1
-rw-r--r--spec/lib/banzai/reference_parser/project_parser_spec.rb1
-rw-r--r--spec/lib/banzai/reference_parser/snippet_parser_spec.rb1
-rw-r--r--spec/lib/banzai/reference_parser/user_parser_spec.rb1
-rw-r--r--spec/lib/container_registry/client_spec.rb65
-rw-r--r--spec/lib/container_registry/tag_spec.rb4
-rw-r--r--spec/lib/event_filter_spec.rb6
-rw-r--r--spec/lib/gitlab/analytics/cycle_analytics/base_query_builder_spec.rb62
-rw-r--r--spec/lib/gitlab/analytics/cycle_analytics/records_fetcher_spec.rb131
-rw-r--r--spec/lib/gitlab/analytics/cycle_analytics/stage_events/code_stage_start_spec.rb22
-rw-r--r--spec/lib/gitlab/analytics/cycle_analytics/stage_events/issue_created_spec.rb7
-rw-r--r--spec/lib/gitlab/analytics/cycle_analytics/stage_events/issue_first_mentioned_in_commit_spec.rb7
-rw-r--r--spec/lib/gitlab/analytics/cycle_analytics/stage_events/issue_stage_end_spec.rb7
-rw-r--r--spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_created_spec.rb7
-rw-r--r--spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_first_deployed_to_production_spec.rb7
-rw-r--r--spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_last_build_finished_spec.rb7
-rw-r--r--spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_last_build_started_spec.rb7
-rw-r--r--spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_merged_spec.rb7
-rw-r--r--spec/lib/gitlab/analytics/cycle_analytics/stage_events/plan_stage_start_spec.rb24
-rw-r--r--spec/lib/gitlab/analytics/cycle_analytics/stage_events/stage_event_spec.rb7
-rw-r--r--spec/lib/gitlab/auth/current_user_mode_spec.rb159
-rw-r--r--spec/lib/gitlab/auth/user_access_denied_reason_spec.rb8
-rw-r--r--spec/lib/gitlab/auth_spec.rb74
-rw-r--r--spec/lib/gitlab/background_migration/legacy_upload_mover_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/legacy_uploads_migrator_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/migrate_pages_metadata_spec.rb44
-rw-r--r--spec/lib/gitlab/badge/pipeline/template_spec.rb2
-rw-r--r--spec/lib/gitlab/bare_repository_import/importer_spec.rb8
-rw-r--r--spec/lib/gitlab/bitbucket_import/importer_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/ansi2json/line_spec.rb168
-rw-r--r--spec/lib/gitlab/ci/ansi2json/parser_spec.rb30
-rw-r--r--spec/lib/gitlab/ci/ansi2json/style_spec.rb166
-rw-r--r--spec/lib/gitlab/ci/ansi2json_spec.rb544
-rw-r--r--spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb15
-rw-r--r--spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/build/rules/rule/clause/changes_spec.rb17
-rw-r--r--spec/lib/gitlab/ci/build/rules/rule/clause/exists_spec.rb27
-rw-r--r--spec/lib/gitlab/ci/config/edge_stages_injector_spec.rb112
-rw-r--r--spec/lib/gitlab/ci/config/entry/cache_spec.rb1
-rw-r--r--spec/lib/gitlab/ci/config/entry/coverage_spec.rb7
-rw-r--r--spec/lib/gitlab/ci/config/entry/root_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/rules/rule_spec.rb52
-rw-r--r--spec/lib/gitlab/ci/config/entry/stages_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/external/context_spec.rb128
-rw-r--r--spec/lib/gitlab/ci/config/external/file/base_spec.rb10
-rw-r--r--spec/lib/gitlab/ci/config/external/file/local_spec.rb11
-rw-r--r--spec/lib/gitlab/ci/config/external/file/project_spec.rb8
-rw-r--r--spec/lib/gitlab/ci/config/external/file/remote_spec.rb12
-rw-r--r--spec/lib/gitlab/ci/config/external/file/template_spec.rb12
-rw-r--r--spec/lib/gitlab/ci/config/external/mapper_spec.rb8
-rw-r--r--spec/lib/gitlab/ci/config/external/processor_spec.rb8
-rw-r--r--spec/lib/gitlab/ci/config_spec.rb96
-rw-r--r--spec/lib/gitlab/ci/parsers/test/junit_spec.rb68
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/build_spec.rb18
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/deployment_spec.rb76
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/environment_spec.rb43
-rw-r--r--spec/lib/gitlab/ci/status/composite_spec.rb61
-rw-r--r--spec/lib/gitlab/ci/status/external/factory_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/factory_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/pipeline/factory_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/preparing_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/status/stage/factory_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/trace/stream_spec.rb54
-rw-r--r--spec/lib/gitlab/ci/yaml_processor_spec.rb46
-rw-r--r--spec/lib/gitlab/cleanup/project_uploads_spec.rb1
-rw-r--r--spec/lib/gitlab/cluster/mixins/puma_cluster_spec.rb96
-rw-r--r--spec/lib/gitlab/cluster/mixins/unicorn_http_server_spec.rb112
-rw-r--r--spec/lib/gitlab/config/entry/simplifiable_spec.rb6
-rw-r--r--spec/lib/gitlab/cycle_analytics/code_stage_spec.rb12
-rw-r--r--spec/lib/gitlab/cycle_analytics/issue_stage_spec.rb12
-rw-r--r--spec/lib/gitlab/cycle_analytics/plan_stage_spec.rb12
-rw-r--r--spec/lib/gitlab/cycle_analytics/shared_stage_spec.rb38
-rw-r--r--spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb84
-rw-r--r--spec/lib/gitlab/cycle_analytics/staging_stage_spec.rb5
-rw-r--r--spec/lib/gitlab/cycle_analytics/test_stage_spec.rb30
-rw-r--r--spec/lib/gitlab/daemon_spec.rb26
-rw-r--r--spec/lib/gitlab/danger/helper_spec.rb24
-rw-r--r--spec/lib/gitlab/danger/roulette_spec.rb16
-rw-r--r--spec/lib/gitlab/danger/teammate_spec.rb75
-rw-r--r--spec/lib/gitlab/data_builder/push_spec.rb8
-rw-r--r--spec/lib/gitlab/database_spec.rb14
-rw-r--r--spec/lib/gitlab/diff/file_collection/merge_request_diff_batch_spec.rb126
-rw-r--r--spec/lib/gitlab/diff/position_collection_spec.rb86
-rw-r--r--spec/lib/gitlab/diff/position_spec.rb20
-rw-r--r--spec/lib/gitlab/discussions_diff/file_collection_spec.rb8
-rw-r--r--spec/lib/gitlab/downtime_check_spec.rb1
-rw-r--r--spec/lib/gitlab/email/message/repository_push_spec.rb18
-rw-r--r--spec/lib/gitlab/email/receiver_spec.rb10
-rw-r--r--spec/lib/gitlab/experimentation_spec.rb157
-rw-r--r--spec/lib/gitlab/favicon_spec.rb1
-rw-r--r--spec/lib/gitlab/file_markdown_link_builder_spec.rb56
-rw-r--r--spec/lib/gitlab/file_type_detection_spec.rb562
-rw-r--r--spec/lib/gitlab/gfm/uploads_rewriter_spec.rb2
-rw-r--r--spec/lib/gitlab/git/branch_spec.rb1
-rw-r--r--spec/lib/gitlab/git/changes_spec.rb81
-rw-r--r--spec/lib/gitlab/git/commit_spec.rb9
-rw-r--r--spec/lib/gitlab/git/diff_collection_spec.rb115
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb44
-rw-r--r--spec/lib/gitlab/git_access_spec.rb13
-rw-r--r--spec/lib/gitlab/gitaly_client/commit_service_spec.rb25
-rw-r--r--spec/lib/gitlab/gitaly_client/conflict_files_stitcher_spec.rb2
-rw-r--r--spec/lib/gitlab/gitaly_client/repository_service_spec.rb22
-rw-r--r--spec/lib/gitlab/gitaly_client/storage_service_spec.rb13
-rw-r--r--spec/lib/gitlab/gitaly_client/storage_settings_spec.rb32
-rw-r--r--spec/lib/gitlab/gitaly_client_spec.rb10
-rw-r--r--spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb3
-rw-r--r--spec/lib/gitlab/github_import/importer/releases_importer_spec.rb56
-rw-r--r--spec/lib/gitlab/gitlab_import/client_spec.rb3
-rw-r--r--spec/lib/gitlab/gl_repository/repo_type_spec.rb30
-rw-r--r--spec/lib/gitlab/gpg/commit_spec.rb28
-rw-r--r--spec/lib/gitlab/graphs/commits_spec.rb3
-rw-r--r--spec/lib/gitlab/health_checks/gitaly_check_spec.rb5
-rw-r--r--spec/lib/gitlab/health_checks/probes/collection_spec.rb62
-rw-r--r--spec/lib/gitlab/health_checks/prometheus_text_format_spec.rb41
-rw-r--r--spec/lib/gitlab/health_checks/puma_check_spec.rb65
-rw-r--r--spec/lib/gitlab/health_checks/simple_check_shared.rb13
-rw-r--r--spec/lib/gitlab/health_checks/unicorn_check_spec.rb63
-rw-r--r--spec/lib/gitlab/hook_data/issue_builder_spec.rb3
-rw-r--r--spec/lib/gitlab/import/merge_request_creator_spec.rb7
-rw-r--r--spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_spec.rb26
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml34
-rw-r--r--spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb7
-rw-r--r--spec/lib/gitlab/import_export/model_configuration_spec.rb4
-rw-r--r--spec/lib/gitlab/import_export/project.group.json186
-rw-r--r--spec/lib/gitlab/import_export/project.json7225
-rw-r--r--spec/lib/gitlab/import_export/project.light.json128
-rw-r--r--spec/lib/gitlab/import_export/project.milestone-iid.json80
-rw-r--r--spec/lib/gitlab/import_export/project_tree_restorer_spec.rb94
-rw-r--r--spec/lib/gitlab/import_export/relation_factory_spec.rb4
-rw-r--r--spec/lib/gitlab/import_export/relation_rename_service_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/repo_saver_spec.rb12
-rw-r--r--spec/lib/gitlab/import_export/safe_model_attributes.yml23
-rw-r--r--spec/lib/gitlab/import_export/shared_spec.rb40
-rw-r--r--spec/lib/gitlab/import_export/uploads_manager_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/uploads_restorer_spec.rb4
-rw-r--r--spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb12
-rw-r--r--spec/lib/gitlab/kubernetes/helm/delete_command_spec.rb4
-rw-r--r--spec/lib/gitlab/kubernetes/helm/install_command_spec.rb14
-rw-r--r--spec/lib/gitlab/kubernetes/helm/reset_command_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/kube_client_spec.rb2
-rw-r--r--spec/lib/gitlab/legacy_github_import/release_formatter_spec.rb8
-rw-r--r--spec/lib/gitlab/lets_encrypt_spec.rb12
-rw-r--r--spec/lib/gitlab/lfs_token_spec.rb40
-rw-r--r--spec/lib/gitlab/metrics/exporter/base_exporter_spec.rb180
-rw-r--r--spec/lib/gitlab/metrics/exporter/sidekiq_exporter_spec.rb65
-rw-r--r--spec/lib/gitlab/metrics/exporter/web_exporter_spec.rb64
-rw-r--r--spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb14
-rw-r--r--spec/lib/gitlab/metrics/samplers/puma_sampler_spec.rb1
-rw-r--r--spec/lib/gitlab/metrics/sidekiq_metrics_exporter_spec.rb105
-rw-r--r--spec/lib/gitlab/metrics/system_spec.rb40
-rw-r--r--spec/lib/gitlab/metrics/transaction_spec.rb8
-rw-r--r--spec/lib/gitlab/middleware/read_only_spec.rb7
-rw-r--r--spec/lib/gitlab/pages_client_spec.rb174
-rw-r--r--spec/lib/gitlab/patch/prependable_spec.rb10
-rw-r--r--spec/lib/gitlab/path_regex_spec.rb2
-rw-r--r--spec/lib/gitlab/phabricator_import/worker_state_spec.rb1
-rw-r--r--spec/lib/gitlab/reference_extractor_spec.rb4
-rw-r--r--spec/lib/gitlab/regex_spec.rb11
-rw-r--r--spec/lib/gitlab/request_context_spec.rb1
-rw-r--r--spec/lib/gitlab/sanitizers/exif_spec.rb2
-rw-r--r--spec/lib/gitlab/search_results_spec.rb4
-rw-r--r--spec/lib/gitlab/shell_spec.rb25
-rw-r--r--spec/lib/gitlab/sidekiq_daemon/memory_killer_spec.rb501
-rw-r--r--spec/lib/gitlab/sidekiq_daemon/monitor_spec.rb50
-rw-r--r--spec/lib/gitlab/sidekiq_logging/exception_handler_spec.rb44
-rw-r--r--spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb52
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb1
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/metrics_spec.rb13
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/monitor_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/presenters/access_spec.rb22
-rw-r--r--spec/lib/gitlab/snippet_search_results_spec.rb15
-rw-r--r--spec/lib/gitlab/submodule_links_spec.rb18
-rw-r--r--spec/lib/gitlab/tracking/incident_management_spec.rb78
-rw-r--r--spec/lib/gitlab/tracking_spec.rb105
-rw-r--r--spec/lib/gitlab/url_blocker_spec.rb8
-rw-r--r--spec/lib/gitlab/usage_data_spec.rb73
-rw-r--r--spec/lib/gitlab/utils/inline_hash_spec.rb70
-rw-r--r--spec/lib/gitlab/utils/override_spec.rb6
-rw-r--r--spec/lib/gitlab/utils/safe_inline_hash_spec.rb35
-rw-r--r--spec/lib/gitlab/utils/sanitize_node_link_spec.rb5
-rw-r--r--spec/lib/gitlab_spec.rb77
-rw-r--r--spec/lib/google_api/cloud_platform/client_spec.rb103
-rw-r--r--spec/lib/grafana/client_spec.rb107
-rw-r--r--spec/lib/json_web_token/token_spec.rb1
-rw-r--r--spec/lib/omni_auth/strategies/jwt_spec.rb1
-rw-r--r--spec/lib/quality/test_level_spec.rb28
-rw-r--r--spec/lib/uploaded_file_spec.rb56
207 files changed, 6911 insertions, 8679 deletions
diff --git a/spec/lib/api/helpers/graphql_helpers_spec.rb b/spec/lib/api/helpers/graphql_helpers_spec.rb
new file mode 100644
index 00000000000..c775ba6d5e8
--- /dev/null
+++ b/spec/lib/api/helpers/graphql_helpers_spec.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe API::Helpers::GraphqlHelpers do
+ describe 'run_graphql!' do
+ let(:query) { '{ metadata { version } }' }
+
+ let(:graphql_helper) do
+ Class.new do
+ include API::Helpers::GraphqlHelpers
+ end.new
+ end
+
+ context 'when transform function is provided' do
+ let(:result) { { 'data' => { 'metadata' => { 'version' => '1.0.0' } } } }
+
+ before do
+ allow(GitlabSchema).to receive(:execute).and_return(result)
+ end
+
+ it 'returns the expected result' do
+ expect(
+ graphql_helper.run_graphql!(
+ query: query,
+ transform: ->(result) { result.dig('data', 'metadata') }
+ )
+ ).to eq({ 'version' => '1.0.0' })
+ end
+ end
+
+ context 'when a transform function is not provided' do
+ let(:result) { double('result') }
+
+ before do
+ allow(GitlabSchema).to receive(:execute).and_return(result)
+ end
+
+ it 'returns the expected result' do
+ expect(graphql_helper.run_graphql!(query: query)).to eq(result)
+ end
+ end
+ end
+end
diff --git a/spec/lib/backup/files_spec.rb b/spec/lib/backup/files_spec.rb
index e903eada62d..b75f3bafeef 100644
--- a/spec/lib/backup/files_spec.rb
+++ b/spec/lib/backup/files_spec.rb
@@ -24,6 +24,7 @@ describe Backup::Files do
describe '#restore' do
subject { described_class.new('registry', '/var/gitlab-registry') }
+
let(:timestamp) { Time.utc(2017, 3, 22) }
around do |example|
diff --git a/spec/lib/backup/manager_spec.rb b/spec/lib/backup/manager_spec.rb
index fee7ffc60ee..35594cd2fb8 100644
--- a/spec/lib/backup/manager_spec.rb
+++ b/spec/lib/backup/manager_spec.rb
@@ -21,6 +21,49 @@ describe Backup::Manager do
$progress = @old_progress # rubocop:disable Style/GlobalVars
end
+ describe '#pack' do
+ let(:backup_contents) { ['backup_contents'] }
+ let(:tar_system_options) { { out: [tar_file, 'w', Gitlab.config.backup.archive_permissions] } }
+ let(:tar_cmdline) { ['tar', '-cf', '-', *backup_contents, tar_system_options] }
+
+ let(:backup_information) do
+ {
+ backup_created_at: Time.zone.parse('2019-01-01'),
+ gitlab_version: '12.3'
+ }
+ end
+
+ before do
+ allow(ActiveRecord::Base.connection).to receive(:reconnect!)
+ allow(Kernel).to receive(:system).and_return(true)
+
+ allow(subject).to receive(:backup_contents).and_return(backup_contents)
+ allow(subject).to receive(:backup_information).and_return(backup_information)
+ allow(subject).to receive(:upload)
+ end
+
+ context 'when BACKUP is not set' do
+ let(:tar_file) { '1546300800_2019_01_01_12.3_gitlab_backup.tar' }
+
+ it 'uses the default tar file name' do
+ subject.pack
+
+ expect(Kernel).to have_received(:system).with(*tar_cmdline)
+ end
+ end
+
+ context 'when BACKUP is set' do
+ let(:tar_file) { 'custom_gitlab_backup.tar' }
+
+ it 'uses the given value as tar file name' do
+ stub_env('BACKUP', '/ignored/path/custom')
+ subject.pack
+
+ expect(Kernel).to have_received(:system).with(*tar_cmdline)
+ end
+ end
+ end
+
describe '#remove_old' do
let(:files) do
[
@@ -238,7 +281,7 @@ describe Backup::Manager do
allow(Kernel).to receive(:system).and_return(true)
allow(YAML).to receive(:load_file).and_return(gitlab_version: Gitlab::VERSION)
- stub_env('BACKUP', '1451606400_2016_01_01_1.2.3')
+ stub_env('BACKUP', '/ignored/path/1451606400_2016_01_01_1.2.3')
end
it 'unpacks the file' do
diff --git a/spec/lib/backup/repository_spec.rb b/spec/lib/backup/repository_spec.rb
index e1d46c25338..bf827fb3914 100644
--- a/spec/lib/backup/repository_spec.rb
+++ b/spec/lib/backup/repository_spec.rb
@@ -10,7 +10,6 @@ describe Backup::Repository do
before do
allow(progress).to receive(:puts)
allow(progress).to receive(:print)
- allow(FileUtils).to receive(:mkdir_p).and_return(true)
allow(FileUtils).to receive(:mv).and_return(true)
allow_any_instance_of(described_class).to receive(:progress).and_return(progress)
@@ -84,30 +83,6 @@ describe Backup::Repository do
end
end
- describe '#prepare_directories', :seed_helper do
- before do
- allow(FileUtils).to receive(:mkdir_p).and_call_original
- allow(FileUtils).to receive(:mv).and_call_original
- end
-
- after(:all) do
- ensure_seeds
- end
-
- it' removes all repositories' do
- # Sanity check: there should be something for us to delete
- expect(list_repositories).to include(File.join(SEED_STORAGE_PATH, TEST_REPO_PATH))
-
- subject.prepare_directories
-
- expect(list_repositories).to be_empty
- end
-
- def list_repositories
- Dir[File.join(SEED_STORAGE_PATH, '*.git')]
- end
- end
-
describe '#empty_repo?' do
context 'for a wiki' do
let(:wiki) { create(:project_wiki) }
diff --git a/spec/lib/banzai/filter/audio_link_filter_spec.rb b/spec/lib/banzai/filter/audio_link_filter_spec.rb
new file mode 100644
index 00000000000..a8459137169
--- /dev/null
+++ b/spec/lib/banzai/filter/audio_link_filter_spec.rb
@@ -0,0 +1,120 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Banzai::Filter::AudioLinkFilter do
+ def filter(doc, contexts = {})
+ contexts.reverse_merge!({
+ project: project
+ })
+
+ described_class.call(doc, contexts)
+ end
+
+ def link_to_image(path)
+ return '<img/>' if path.nil?
+
+ %(<img src="#{path}"/>)
+ end
+
+ let(:project) { create(:project, :repository) }
+
+ shared_examples 'an audio element' do
+ let(:image) { link_to_image(src) }
+
+ it 'replaces the image tag with an audio tag' do
+ container = filter(image).children.first
+
+ expect(container.name).to eq 'div'
+ expect(container['class']).to eq 'audio-container'
+
+ audio, paragraph = container.children
+
+ expect(audio.name).to eq 'audio'
+ expect(audio['src']).to eq src
+
+ expect(paragraph.name).to eq 'p'
+
+ link = paragraph.children.first
+
+ expect(link.name).to eq 'a'
+ expect(link['href']).to eq src
+ expect(link['target']).to eq '_blank'
+ end
+ end
+
+ shared_examples 'an unchanged element' do |ext|
+ it 'leaves the document unchanged' do
+ element = filter(link_to_image(src)).children.first
+
+ expect(element.name).to eq 'img'
+ expect(element['src']).to eq src
+ end
+ end
+
+ context 'when the element src has an audio extension' do
+ Gitlab::FileTypeDetection::SAFE_AUDIO_EXT.each do |ext|
+ it_behaves_like 'an audio element' do
+ let(:src) { "/path/audio.#{ext}" }
+ end
+
+ it_behaves_like 'an audio element' do
+ let(:src) { "/path/audio.#{ext.upcase}" }
+ end
+ end
+ end
+
+ context 'when the element has no src attribute' do
+ let(:src) { nil }
+
+ it_behaves_like 'an unchanged element'
+ end
+
+ context 'when the element src is an image' do
+ let(:src) { '/path/my_image.jpg' }
+
+ it_behaves_like 'an unchanged element'
+ end
+
+ context 'when the element src has an invalid file extension' do
+ let(:src) { '/path/my_audio.somewav' }
+
+ it_behaves_like 'an unchanged element'
+ end
+
+ context 'when data-canonical-src is empty' do
+ let(:image) { %(<img src="#{src}" data-canonical-src=""/>) }
+
+ context 'and src is audio' do
+ let(:src) { '/path/audio.wav' }
+
+ it_behaves_like 'an audio element'
+ end
+
+ context 'and src is an image' do
+ let(:src) { '/path/my_image.jpg' }
+
+ it_behaves_like 'an unchanged element'
+ end
+ end
+
+ context 'when data-canonical-src is set' do
+ it 'uses the correct src' do
+ proxy_src = 'https://assets.example.com/6d8b63'
+ canonical_src = 'http://example.com/test.wav'
+ image = %(<img src="#{proxy_src}" data-canonical-src="#{canonical_src}"/>)
+ container = filter(image).children.first
+
+ expect(container['class']).to eq 'audio-container'
+
+ audio, paragraph = container.children
+
+ expect(audio['src']).to eq proxy_src
+ expect(audio['data-canonical-src']).to eq canonical_src
+
+ link = paragraph.children.first
+
+ expect(link['href']).to eq proxy_src
+ end
+ end
+end
diff --git a/spec/lib/banzai/filter/project_reference_filter_spec.rb b/spec/lib/banzai/filter/project_reference_filter_spec.rb
index 927d226c400..d0b4542d503 100644
--- a/spec/lib/banzai/filter/project_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/project_reference_filter_spec.rb
@@ -15,6 +15,7 @@ describe Banzai::Filter::ProjectReferenceFilter do
let(:project) { create(:project, :public) }
subject { project }
+
let(:subject_name) { "project" }
let(:reference) { get_reference(project) }
diff --git a/spec/lib/banzai/filter/relative_link_filter_spec.rb b/spec/lib/banzai/filter/relative_link_filter_spec.rb
index f8b3748c663..046c346a7ac 100644
--- a/spec/lib/banzai/filter/relative_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/relative_link_filter_spec.rb
@@ -3,6 +3,9 @@
require 'spec_helper'
describe Banzai::Filter::RelativeLinkFilter do
+ include GitHelpers
+ include RepoHelpers
+
def filter(doc, contexts = {})
contexts.reverse_merge!({
commit: commit,
@@ -26,6 +29,10 @@ describe Banzai::Filter::RelativeLinkFilter do
%(<video src="#{path}"></video>)
end
+ def audio(path)
+ %(<audio src="#{path}"></audio>)
+ end
+
def link(path)
%(<a href="#{path}">#{path}</a>)
end
@@ -34,6 +41,12 @@ describe Banzai::Filter::RelativeLinkFilter do
%(<div>#{element}</div>)
end
+ def allow_gitaly_n_plus_1
+ Gitlab::GitalyClient.allow_n_plus_1_calls do
+ yield
+ end
+ end
+
let(:project) { create(:project, :repository, :public) }
let(:user) { create(:user) }
let(:group) { nil }
@@ -44,6 +57,19 @@ describe Banzai::Filter::RelativeLinkFilter do
let(:requested_path) { '/' }
let(:only_path) { true }
+ it 'does not trigger a gitaly n+1', :request_store do
+ raw_doc = ""
+
+ allow_gitaly_n_plus_1 do
+ 30.times do |i|
+ create_file_in_repo(project, ref, ref, "new_file_#{i}", "x" )
+ raw_doc += link("new_file_#{i}")
+ end
+ end
+
+ expect { filter(raw_doc) }.to change { Gitlab::GitalyClient.get_request_count }.by(2)
+ end
+
shared_examples :preserve_unchanged do
it 'does not modify any relative URL in anchor' do
doc = filter(link('README.md'))
@@ -60,6 +86,12 @@ describe Banzai::Filter::RelativeLinkFilter do
expect(doc.at_css('video')['src']).to eq 'files/videos/intro.mp4'
end
+
+ it 'does not modify any relative URL in audio' do
+ doc = filter(audio('files/audio/sample.wav'), commit: project.commit('audio'), ref: 'audio')
+
+ expect(doc.at_css('audio')['src']).to eq 'files/audio/sample.wav'
+ end
end
context 'with a project_wiki' do
@@ -196,6 +228,13 @@ describe Banzai::Filter::RelativeLinkFilter do
.to eq "/#{project_path}/raw/video/files/videos/intro.mp4"
end
+ it 'rebuilds relative URL for audio in the repo' do
+ doc = filter(audio('files/audio/sample.wav'), commit: project.commit('audio'), ref: 'audio')
+
+ expect(doc.at_css('audio')['src'])
+ .to eq "/#{project_path}/raw/audio/files/audio/sample.wav"
+ end
+
it 'does not modify relative URL with an anchor only' do
doc = filter(link('#section-1'))
expect(doc.at_css('a')['href']).to eq '#section-1'
@@ -206,6 +245,12 @@ describe Banzai::Filter::RelativeLinkFilter do
expect(doc.at_css('a')['href']).to eq 'http://example.com'
end
+ it 'does not call gitaly' do
+ filter(link('http://example.com'))
+
+ expect(described_class).not_to receive(:get_blob_types)
+ end
+
it 'supports Unicode filenames' do
path = 'files/images/한글.png'
escaped = Addressable::URI.escape(path)
@@ -244,7 +289,8 @@ describe Banzai::Filter::RelativeLinkFilter do
end
context 'when ref name contains special chars' do
- let(:ref) {'mark#\'@],+;-._/#@!$&()+down'}
+ let(:ref) { 'mark#\'@],+;-._/#@!$&()+down' }
+ let(:path) { 'files/images/logo-black.png' }
it 'correctly escapes the ref' do
# Addressable won't escape the '#', so we do this manually
@@ -252,8 +298,9 @@ describe Banzai::Filter::RelativeLinkFilter do
# Stub this method so the branch doesn't actually need to be in the repo
allow_any_instance_of(described_class).to receive(:uri_type).and_return(:raw)
+ allow_any_instance_of(described_class).to receive(:get_uri_types).and_return({ path: :tree })
- doc = filter(link('files/images/logo-black.png'))
+ doc = filter(link(path))
expect(doc.at_css('a')['href'])
.to eq "/#{project_path}/raw/#{ref_escaped}/files/images/logo-black.png"
diff --git a/spec/lib/banzai/filter/table_of_contents_filter_spec.rb b/spec/lib/banzai/filter/table_of_contents_filter_spec.rb
index 5ca3c722e3e..05ef77c811a 100644
--- a/spec/lib/banzai/filter/table_of_contents_filter_spec.rb
+++ b/spec/lib/banzai/filter/table_of_contents_filter_spec.rb
@@ -82,7 +82,9 @@ describe Banzai::Filter::TableOfContentsFilter do
it 'supports Unicode' do
doc = filter(header(1, '한글'))
expect(doc.css('h1 a').first.attr('id')).to eq 'user-content-한글'
- expect(doc.css('h1 a').first.attr('href')).to eq '#한글'
+ # check that we encode the href to avoid issues with the
+ # ExternalLinkFilter (see https://gitlab.com/gitlab-org/gitlab/issues/26210)
+ expect(doc.css('h1 a').first.attr('href')).to eq "##{CGI.escape('한글')}"
end
end
end
diff --git a/spec/lib/banzai/filter/user_reference_filter_spec.rb b/spec/lib/banzai/filter/user_reference_filter_spec.rb
index 6bc87d245f5..a09aeb7d7f6 100644
--- a/spec/lib/banzai/filter/user_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/user_reference_filter_spec.rb
@@ -12,6 +12,7 @@ describe Banzai::Filter::UserReferenceFilter do
let(:project) { create(:project, :public) }
let(:user) { create(:user) }
subject { user }
+
let(:subject_name) { "user" }
let(:reference) { get_reference(user) }
diff --git a/spec/lib/banzai/filter/video_link_filter_spec.rb b/spec/lib/banzai/filter/video_link_filter_spec.rb
index cd932f502f3..a395b021f32 100644
--- a/spec/lib/banzai/filter/video_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/video_link_filter_spec.rb
@@ -12,52 +12,99 @@ describe Banzai::Filter::VideoLinkFilter do
end
def link_to_image(path)
- %(<img src="#{path}" />)
+ return '<img/>' if path.nil?
+
+ %(<img src="#{path}"/>)
end
let(:project) { create(:project, :repository) }
- context 'when the element src has a video extension' do
- UploaderHelper::VIDEO_EXT.each do |ext|
- it "replaces the image tag 'path/video.#{ext}' with a video tag" do
- container = filter(link_to_image("/path/video.#{ext}")).children.first
+ shared_examples 'a video element' do
+ let(:image) { link_to_image(src) }
- expect(container.name).to eq 'div'
- expect(container['class']).to eq 'video-container'
+ it 'replaces the image tag with a video tag' do
+ container = filter(image).children.first
- video, paragraph = container.children
+ expect(container.name).to eq 'div'
+ expect(container['class']).to eq 'video-container'
- expect(video.name).to eq 'video'
- expect(video['src']).to eq "/path/video.#{ext}"
+ video, paragraph = container.children
- expect(paragraph.name).to eq 'p'
+ expect(video.name).to eq 'video'
+ expect(video['src']).to eq src
+ expect(video['width']).to eq "100%"
- link = paragraph.children.first
+ expect(paragraph.name).to eq 'p'
- expect(link.name).to eq 'a'
- expect(link['href']).to eq "/path/video.#{ext}"
- expect(link['target']).to eq '_blank'
- end
+ link = paragraph.children.first
+
+ expect(link.name).to eq 'a'
+ expect(link['href']).to eq src
+ expect(link['target']).to eq '_blank'
end
end
- context 'when the element src is an image' do
+ shared_examples 'an unchanged element' do |ext|
it 'leaves the document unchanged' do
- element = filter(link_to_image('/path/my_image.jpg')).children.first
+ element = filter(link_to_image(src)).children.first
expect(element.name).to eq 'img'
- expect(element['src']).to eq '/path/my_image.jpg'
+ expect(element['src']).to eq src
end
end
- context 'when asset proxy is enabled' do
- it 'uses the correct src' do
- stub_asset_proxy_setting(enabled: true)
+ context 'when the element src has a video extension' do
+ Gitlab::FileTypeDetection::SAFE_VIDEO_EXT.each do |ext|
+ it_behaves_like 'a video element' do
+ let(:src) { "/path/video.#{ext}" }
+ end
+
+ it_behaves_like 'a video element' do
+ let(:src) { "/path/video.#{ext.upcase}" }
+ end
+ end
+ end
+
+ context 'when the element has no src attribute' do
+ let(:src) { nil }
+
+ it_behaves_like 'an unchanged element'
+ end
+
+ context 'when the element src is an image' do
+ let(:src) { '/path/my_image.jpg' }
+
+ it_behaves_like 'an unchanged element'
+ end
+
+ context 'when the element src has an invalid file extension' do
+ let(:src) { '/path/my_video.somemp4' }
+
+ it_behaves_like 'an unchanged element'
+ end
+
+ context 'when data-canonical-src is empty' do
+ let(:image) { %(<img src="#{src}" data-canonical-src=""/>) }
+ context 'and src is a video' do
+ let(:src) { '/path/video.mp4' }
+
+ it_behaves_like 'a video element'
+ end
+
+ context 'and src is an image' do
+ let(:src) { '/path/my_image.jpg' }
+
+ it_behaves_like 'an unchanged element'
+ end
+ end
+
+ context 'when data-canonical-src is set' do
+ it 'uses the correct src' do
proxy_src = 'https://assets.example.com/6d8b63'
canonical_src = 'http://example.com/test.mp4'
- image = %(<img src="#{proxy_src}" data-canonical-src="#{canonical_src}" />)
- container = filter(image, asset_proxy_enabled: true).children.first
+ image = %(<img src="#{proxy_src}" data-canonical-src="#{canonical_src}"/>)
+ container = filter(image).children.first
expect(container['class']).to eq 'video-container'
diff --git a/spec/lib/banzai/filter/wiki_link_filter_spec.rb b/spec/lib/banzai/filter/wiki_link_filter_spec.rb
index d2d539a62fc..4587bd85939 100644
--- a/spec/lib/banzai/filter/wiki_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/wiki_link_filter_spec.rb
@@ -60,6 +60,14 @@ describe Banzai::Filter::WikiLinkFilter do
expect(filtered_link.attribute('src').value).to eq("#{wiki.wiki_base_path}/#{repository_upload_folder}/a.mp4")
end
end
+
+ context 'with "audio" html tag' do
+ it 'rewrites links' do
+ filtered_link = filter("<audio src='#{repository_upload_folder}/a.wav'></audio>", project_wiki: wiki).children[0]
+
+ expect(filtered_link.attribute('src').value).to eq("#{wiki.wiki_base_path}/#{repository_upload_folder}/a.wav")
+ end
+ end
end
describe "invalid links" do
diff --git a/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb b/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb
index 4a485fbc2bd..26f2b0b0acf 100644
--- a/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb
@@ -260,11 +260,11 @@ describe Banzai::Pipeline::WikiPipeline do
end
end
- describe 'videos' do
- let(:namespace) { create(:namespace, name: "wiki_link_ns") }
- let(:project) { create(:project, :public, name: "wiki_link_project", namespace: namespace) }
- let(:project_wiki) { ProjectWiki.new(project, double(:user)) }
- let(:page) { build(:wiki_page, wiki: project_wiki, page: OpenStruct.new(url_path: 'nested/twice/start-page')) }
+ describe 'videos and audio' do
+ let_it_be(:namespace) { create(:namespace, name: "wiki_link_ns") }
+ let_it_be(:project) { create(:project, :public, name: "wiki_link_project", namespace: namespace) }
+ let_it_be(:project_wiki) { ProjectWiki.new(project, double(:user)) }
+ let_it_be(:page) { build(:wiki_page, wiki: project_wiki, page: OpenStruct.new(url_path: 'nested/twice/start-page')) }
it 'generates video html structure' do
markdown = "![video_file](video_file_name.mp4)"
@@ -279,5 +279,19 @@ describe Banzai::Pipeline::WikiPipeline do
expect(output).to include('<video src="/wiki_link_ns/wiki_link_project/wikis/nested/twice/video%20file%20name.mp4"')
end
+
+ it 'generates audio html structure' do
+ markdown = "![audio_file](audio_file_name.wav)"
+ output = described_class.to_html(markdown, project: project, project_wiki: project_wiki, page_slug: page.slug)
+
+ expect(output).to include('<audio src="/wiki_link_ns/wiki_link_project/wikis/nested/twice/audio_file_name.wav"')
+ end
+
+ it 'rewrites and replaces audio links names with white spaces to %20' do
+ markdown = "![audio file](audio file name.wav)"
+ output = described_class.to_html(markdown, project: project, project_wiki: project_wiki, page_slug: page.slug)
+
+ expect(output).to include('<audio src="/wiki_link_ns/wiki_link_project/wikis/nested/twice/audio%20file%20name.wav"')
+ end
end
end
diff --git a/spec/lib/banzai/reference_parser/commit_parser_spec.rb b/spec/lib/banzai/reference_parser/commit_parser_spec.rb
index b44ae67e430..eac1cf16a8f 100644
--- a/spec/lib/banzai/reference_parser/commit_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/commit_parser_spec.rb
@@ -8,6 +8,7 @@ describe Banzai::ReferenceParser::CommitParser do
let(:project) { create(:project, :public) }
let(:user) { create(:user) }
subject { described_class.new(Banzai::RenderContext.new(project, user)) }
+
let(:link) { empty_html_link }
describe '#nodes_visible_to_user' do
diff --git a/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb b/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb
index da853233018..78b337466aa 100644
--- a/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb
@@ -8,6 +8,7 @@ describe Banzai::ReferenceParser::CommitRangeParser do
let(:project) { create(:project, :public) }
let(:user) { create(:user) }
subject { described_class.new(Banzai::RenderContext.new(project, user)) }
+
let(:link) { empty_html_link }
describe '#nodes_visible_to_user' do
diff --git a/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb b/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb
index 0f29a95bdcc..9343d52e44b 100644
--- a/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb
@@ -8,6 +8,7 @@ describe Banzai::ReferenceParser::ExternalIssueParser do
let(:project) { create(:project, :public) }
let(:user) { create(:user) }
subject { described_class.new(Banzai::RenderContext.new(project, user)) }
+
let(:link) { empty_html_link }
describe '#nodes_visible_to_user' do
diff --git a/spec/lib/banzai/reference_parser/label_parser_spec.rb b/spec/lib/banzai/reference_parser/label_parser_spec.rb
index cf8adb57ffc..8b66a891e69 100644
--- a/spec/lib/banzai/reference_parser/label_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/label_parser_spec.rb
@@ -9,6 +9,7 @@ describe Banzai::ReferenceParser::LabelParser do
let(:user) { create(:user) }
let(:label) { create(:label, project: project) }
subject { described_class.new(Banzai::RenderContext.new(project, user)) }
+
let(:link) { empty_html_link }
describe '#nodes_visible_to_user' do
diff --git a/spec/lib/banzai/reference_parser/mentioned_user_parser_spec.rb b/spec/lib/banzai/reference_parser/mentioned_user_parser_spec.rb
new file mode 100644
index 00000000000..1be279375bd
--- /dev/null
+++ b/spec/lib/banzai/reference_parser/mentioned_user_parser_spec.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Banzai::ReferenceParser::MentionedUserParser do
+ include ReferenceParserHelpers
+
+ let(:group) { create(:group, :private) }
+ let(:user) { create(:user) }
+ let(:new_user) { create(:user) }
+ let(:project) { create(:project, group: group, creator: user) }
+ let(:link) { empty_html_link }
+
+ subject { described_class.new(Banzai::RenderContext.new(project, new_user)) }
+
+ describe '#gather_references' do
+ context 'when the link has a data-group attribute' do
+ context 'using an existing group ID' do
+ before do
+ link['data-group'] = project.group.id.to_s
+ group.add_developer(new_user)
+ end
+
+ it 'returns empty list of users' do
+ expect(subject.gather_references([link])).to eq([])
+ end
+ end
+ end
+
+ context 'when the link has a data-project attribute' do
+ context 'using an existing project ID' do
+ before do
+ link['data-project'] = project.id.to_s
+ project.add_developer(new_user)
+ end
+
+ it 'returns empty list of users' do
+ expect(subject.gather_references([link])).to eq([])
+ end
+ end
+ end
+
+ context 'when the link has a data-user attribute' do
+ it 'returns an Array of users' do
+ link['data-user'] = user.id.to_s
+
+ expect(subject.referenced_by([link])).to eq([user])
+ end
+ end
+ end
+end
diff --git a/spec/lib/banzai/reference_parser/mentioned_users_by_group_parser_spec.rb b/spec/lib/banzai/reference_parser/mentioned_users_by_group_parser_spec.rb
new file mode 100644
index 00000000000..99d607629eb
--- /dev/null
+++ b/spec/lib/banzai/reference_parser/mentioned_users_by_group_parser_spec.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Banzai::ReferenceParser::MentionedUsersByGroupParser do
+ include ReferenceParserHelpers
+
+ let(:group) { create(:group, :private) }
+ let(:user) { create(:user) }
+ let(:new_user) { create(:user) }
+ let(:project) { create(:project, group: group, creator: user) }
+ let(:link) { empty_html_link }
+
+ subject { described_class.new(Banzai::RenderContext.new(project, new_user)) }
+
+ describe '#gather_references' do
+ context 'when the link has a data-group attribute' do
+ context 'using an existing group ID where user does not have access' do
+ it 'returns empty array' do
+ link['data-group'] = project.group.id.to_s
+
+ expect(subject.gather_references([link])).to eq([])
+ end
+ end
+
+ context 'using an existing group ID' do
+ before do
+ link['data-group'] = project.group.id.to_s
+ group.add_developer(new_user)
+ end
+
+ it 'returns groups' do
+ expect(subject.gather_references([link])).to eq([group])
+ end
+ end
+
+ context 'using a non-existing group ID' do
+ it 'returns an empty Array' do
+ link['data-group'] = 'test-non-existing'
+
+ expect(subject.gather_references([link])).to eq([])
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/banzai/reference_parser/mentioned_users_by_project_parser_spec.rb b/spec/lib/banzai/reference_parser/mentioned_users_by_project_parser_spec.rb
new file mode 100644
index 00000000000..155f2189d9e
--- /dev/null
+++ b/spec/lib/banzai/reference_parser/mentioned_users_by_project_parser_spec.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Banzai::ReferenceParser::MentionedUsersByProjectParser do
+ include ReferenceParserHelpers
+
+ let(:group) { create(:group, :private) }
+ let(:user) { create(:user) }
+ let(:new_user) { create(:user) }
+ let(:project) { create(:project, group: group, creator: user) }
+ let(:link) { empty_html_link }
+
+ subject { described_class.new(Banzai::RenderContext.new(project, new_user)) }
+
+ describe '#gather_references' do
+ context 'when the link has a data-project attribute' do
+ context 'using an existing project ID where user does not have access' do
+ it 'returns empty Array' do
+ link['data-project'] = project.id.to_s
+
+ expect(subject.gather_references([link])).to eq([])
+ end
+ end
+
+ context 'using an existing project ID' do
+ before do
+ link['data-project'] = project.id.to_s
+ project.add_developer(new_user)
+ end
+
+ it 'returns an Array of referenced projects' do
+ expect(subject.gather_references([link])).to eq([project])
+ end
+ end
+
+ context 'using a non-existing project ID' do
+ it 'returns an empty Array' do
+ link['data-project'] = 'inexisting-project-id'
+
+ expect(subject.gather_references([link])).to eq([])
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/banzai/reference_parser/merge_request_parser_spec.rb b/spec/lib/banzai/reference_parser/merge_request_parser_spec.rb
index 1561dabcdbf..cb65893aea0 100644
--- a/spec/lib/banzai/reference_parser/merge_request_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/merge_request_parser_spec.rb
@@ -9,6 +9,7 @@ describe Banzai::ReferenceParser::MergeRequestParser do
let(:project) { create(:project, :public) }
let(:merge_request) { create(:merge_request, source_project: project) }
subject { described_class.new(Banzai::RenderContext.new(merge_request.target_project, user)) }
+
let(:link) { empty_html_link }
describe '#nodes_visible_to_user' do
diff --git a/spec/lib/banzai/reference_parser/milestone_parser_spec.rb b/spec/lib/banzai/reference_parser/milestone_parser_spec.rb
index 006f8e37690..25ba41dd8a0 100644
--- a/spec/lib/banzai/reference_parser/milestone_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/milestone_parser_spec.rb
@@ -9,6 +9,7 @@ describe Banzai::ReferenceParser::MilestoneParser do
let(:user) { create(:user) }
let(:milestone) { create(:milestone, project: project) }
subject { described_class.new(Banzai::RenderContext.new(project, user)) }
+
let(:link) { empty_html_link }
describe '#nodes_visible_to_user' do
diff --git a/spec/lib/banzai/reference_parser/project_parser_spec.rb b/spec/lib/banzai/reference_parser/project_parser_spec.rb
index e4936aa9e57..356dde1e9c2 100644
--- a/spec/lib/banzai/reference_parser/project_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/project_parser_spec.rb
@@ -8,6 +8,7 @@ describe Banzai::ReferenceParser::ProjectParser do
let(:project) { create(:project, :public) }
let(:user) { create(:user) }
subject { described_class.new(Banzai::RenderContext.new(project, user)) }
+
let(:link) { empty_html_link }
describe '#referenced_by' do
diff --git a/spec/lib/banzai/reference_parser/snippet_parser_spec.rb b/spec/lib/banzai/reference_parser/snippet_parser_spec.rb
index 528f79ed020..05dc1cb4d2d 100644
--- a/spec/lib/banzai/reference_parser/snippet_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/snippet_parser_spec.rb
@@ -12,6 +12,7 @@ describe Banzai::ReferenceParser::SnippetParser do
let(:project_member) { create(:user) }
subject { described_class.new(Banzai::RenderContext.new(project, user)) }
+
let(:link) { empty_html_link }
def visible_references(snippet_visibility, user = nil)
diff --git a/spec/lib/banzai/reference_parser/user_parser_spec.rb b/spec/lib/banzai/reference_parser/user_parser_spec.rb
index a5b4e59a3a1..931fb1e3953 100644
--- a/spec/lib/banzai/reference_parser/user_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/user_parser_spec.rb
@@ -9,6 +9,7 @@ describe Banzai::ReferenceParser::UserParser do
let(:user) { create(:user) }
let(:project) { create(:project, :public, group: group, creator: user) }
subject { described_class.new(Banzai::RenderContext.new(project, user)) }
+
let(:link) { empty_html_link }
describe '#referenced_by' do
diff --git a/spec/lib/container_registry/client_spec.rb b/spec/lib/container_registry/client_spec.rb
index 6c2b338bfcd..3782c30e88a 100644
--- a/spec/lib/container_registry/client_spec.rb
+++ b/spec/lib/container_registry/client_spec.rb
@@ -73,4 +73,69 @@ describe ContainerRegistry::Client do
expect(response).to eq('Successfully redirected')
end
end
+
+ def stub_upload(path, content, digest, status = 200)
+ stub_request(:post, "http://container-registry/v2/#{path}/blobs/uploads/")
+ .to_return(status: status, body: "", headers: { 'location' => 'http://container-registry/next_upload?id=someid' })
+
+ stub_request(:put, "http://container-registry/next_upload?digest=#{digest}&id=someid")
+ .with(body: content)
+ .to_return(status: status, body: "", headers: {})
+ end
+
+ describe '#upload_blob' do
+ subject { client.upload_blob('path', 'content', 'sha256:123') }
+
+ context 'with successful uploads' do
+ it 'starts the upload and posts the blob' do
+ stub_upload('path', 'content', 'sha256:123')
+
+ expect(subject).to be_success
+ end
+ end
+
+ context 'with a failed upload' do
+ before do
+ stub_upload('path', 'content', 'sha256:123', 400)
+ end
+
+ it 'returns nil' do
+ expect(subject).to be nil
+ end
+ end
+ end
+
+ describe '#generate_empty_manifest' do
+ subject { client.generate_empty_manifest('path') }
+
+ let(:result_manifest) do
+ {
+ schemaVersion: 2,
+ mediaType: 'application/vnd.docker.distribution.manifest.v2+json',
+ config: {
+ mediaType: 'application/vnd.docker.container.image.v1+json',
+ size: 21,
+ digest: 'sha256:4435000728ee66e6a80e55637fc22725c256b61de344a2ecdeaac6bdb36e8bc3'
+ }
+ }
+ end
+
+ it 'uploads a random image and returns the manifest' do
+ stub_upload('path', "{\n \"config\": {\n }\n}", 'sha256:4435000728ee66e6a80e55637fc22725c256b61de344a2ecdeaac6bdb36e8bc3')
+
+ expect(subject).to eq(result_manifest)
+ end
+ end
+
+ describe '#put_tag' do
+ subject { client.put_tag('path', 'tagA', { foo: :bar }) }
+
+ it 'uploads the manifest and returns the digest' do
+ stub_request(:put, "http://container-registry/v2/path/manifests/tagA")
+ .with(body: "{\n \"foo\": \"bar\"\n}")
+ .to_return(status: 200, body: "", headers: { 'docker-content-digest' => 'sha256:123' })
+
+ expect(subject).to eq 'sha256:123'
+ end
+ end
end
diff --git a/spec/lib/container_registry/tag_spec.rb b/spec/lib/container_registry/tag_spec.rb
index 110f006536b..3115dfe852f 100644
--- a/spec/lib/container_registry/tag_spec.rb
+++ b/spec/lib/container_registry/tag_spec.rb
@@ -179,7 +179,7 @@ describe ContainerRegistry::Tag do
end
end
- describe '#delete' do
+ describe '#unsafe_delete' do
before do
stub_request(:delete, 'http://registry.gitlab/v2/group/test/manifests/sha256:digest')
.with(headers: headers)
@@ -187,7 +187,7 @@ describe ContainerRegistry::Tag do
end
it 'correctly deletes the tag' do
- expect(tag.delete).to be_truthy
+ expect(tag.unsafe_delete).to be_truthy
end
end
end
diff --git a/spec/lib/event_filter_spec.rb b/spec/lib/event_filter_spec.rb
index d6eca8d85ff..21b8f726425 100644
--- a/spec/lib/event_filter_spec.rb
+++ b/spec/lib/event_filter_spec.rb
@@ -3,12 +3,6 @@
require 'spec_helper'
describe EventFilter do
- describe 'FILTERS' do
- it 'returns a definite list of filters' do
- expect(described_class::FILTERS).to eq(%w[all push merged issue comments team])
- end
- end
-
describe '#filter' do
it 'returns "all" if given filter is nil' do
expect(described_class.new(nil).filter).to eq(described_class::ALL)
diff --git a/spec/lib/gitlab/analytics/cycle_analytics/base_query_builder_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/base_query_builder_spec.rb
new file mode 100644
index 00000000000..0fc9d3c1e9e
--- /dev/null
+++ b/spec/lib/gitlab/analytics/cycle_analytics/base_query_builder_spec.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Analytics::CycleAnalytics::BaseQueryBuilder do
+ let_it_be(:project) { create(:project, :empty_repo) }
+ let_it_be(:mr1) { create(:merge_request, target_project: project, source_project: project, allow_broken: true, created_at: 3.months.ago) }
+ let_it_be(:mr2) { create(:merge_request, target_project: project, source_project: project, allow_broken: true, created_at: 1.month.ago) }
+ let(:params) { {} }
+ let(:records) do
+ stage = build(:cycle_analytics_project_stage, {
+ start_event_identifier: :merge_request_created,
+ end_event_identifier: :merge_request_merged,
+ project: project
+ })
+ described_class.new(stage: stage, params: params).build.to_a
+ end
+
+ before do
+ mr1.metrics.update!(merged_at: 1.month.ago)
+ mr2.metrics.update!(merged_at: Time.now)
+ end
+
+ around do |example|
+ Timecop.freeze { example.run }
+ end
+
+ describe 'date range parameters' do
+ context 'when filters by only the `from` parameter' do
+ before do
+ params[:from] = 4.months.ago
+ end
+
+ it { expect(records.size).to eq(2) }
+ end
+
+ context 'when filters by both `from` and `to` parameters' do
+ before do
+ params.merge!(from: 4.months.ago, to: 2.months.ago)
+ end
+
+ it { expect(records.size).to eq(1) }
+ end
+
+ context 'invalid date range is provided' do
+ before do
+ params.merge!(from: 1.month.ago, to: 10.months.ago)
+ end
+
+ it { expect(records.size).to eq(0) }
+ end
+ end
+
+ it 'scopes query within the target project' do
+ other_mr = create(:merge_request, source_project: create(:project), allow_broken: true, created_at: 2.months.ago)
+ other_mr.metrics.update!(merged_at: 1.month.ago)
+
+ params[:from] = 1.year.ago
+
+ expect(records.size).to eq(2)
+ end
+end
diff --git a/spec/lib/gitlab/analytics/cycle_analytics/records_fetcher_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/records_fetcher_spec.rb
new file mode 100644
index 00000000000..334cab0b799
--- /dev/null
+++ b/spec/lib/gitlab/analytics/cycle_analytics/records_fetcher_spec.rb
@@ -0,0 +1,131 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Analytics::CycleAnalytics::RecordsFetcher do
+ around do |example|
+ Timecop.freeze { example.run }
+ end
+
+ let_it_be(:project) { create(:project, :empty_repo) }
+ let_it_be(:user) { create(:user) }
+
+ subject do
+ Gitlab::Analytics::CycleAnalytics::DataCollector.new(
+ stage: stage,
+ params: {
+ from: 1.year.ago,
+ current_user: user
+ }
+ ).records_fetcher.serialized_records
+ end
+
+ describe '#serialized_records' do
+ shared_context 'when records are loaded by maintainer' do
+ before do
+ project.add_user(user, Gitlab::Access::MAINTAINER)
+ end
+
+ it 'returns all records' do
+ expect(subject.size).to eq(2)
+ end
+ end
+
+ describe 'for issue based stage' do
+ let_it_be(:issue1) { create(:issue, project: project) }
+ let_it_be(:issue2) { create(:issue, project: project, confidential: true) }
+ let(:stage) do
+ build(:cycle_analytics_project_stage, {
+ start_event_identifier: :plan_stage_start,
+ end_event_identifier: :issue_first_mentioned_in_commit,
+ project: project
+ })
+ end
+
+ before do
+ issue1.metrics.update(first_added_to_board_at: 3.days.ago, first_mentioned_in_commit_at: 2.days.ago)
+ issue2.metrics.update(first_added_to_board_at: 3.days.ago, first_mentioned_in_commit_at: 2.days.ago)
+ end
+
+ context 'when records are loaded by guest' do
+ before do
+ project.add_user(user, Gitlab::Access::GUEST)
+ end
+
+ it 'filters out confidential issues' do
+ expect(subject.size).to eq(1)
+ expect(subject.first[:iid].to_s).to eq(issue1.iid.to_s)
+ end
+ end
+
+ include_context 'when records are loaded by maintainer'
+ end
+
+ describe 'for merge request based stage' do
+ let(:mr1) { create(:merge_request, created_at: 5.days.ago, source_project: project, allow_broken: true) }
+ let(:mr2) { create(:merge_request, created_at: 4.days.ago, source_project: project, allow_broken: true) }
+ let(:stage) do
+ build(:cycle_analytics_project_stage, {
+ start_event_identifier: :merge_request_created,
+ end_event_identifier: :merge_request_merged,
+ project: project
+ })
+ end
+
+ before do
+ mr1.metrics.update(merged_at: 3.days.ago)
+ mr2.metrics.update(merged_at: 3.days.ago)
+ end
+
+ include_context 'when records are loaded by maintainer'
+ end
+
+ describe 'special case' do
+ let(:mr1) { create(:merge_request, source_project: project, allow_broken: true, created_at: 20.days.ago) }
+ let(:mr2) { create(:merge_request, source_project: project, allow_broken: true, created_at: 19.days.ago) }
+ let(:ci_build1) { create(:ci_build) }
+ let(:ci_build2) { create(:ci_build) }
+ let(:default_stages) { Gitlab::Analytics::CycleAnalytics::DefaultStages }
+ let(:stage) { build(:cycle_analytics_project_stage, default_stages.params_for_test_stage.merge(project: project)) }
+
+ before do
+ mr1.metrics.update!({
+ merged_at: 5.days.ago,
+ first_deployed_to_production_at: 1.day.ago,
+ latest_build_started_at: 5.days.ago,
+ latest_build_finished_at: 1.day.ago,
+ pipeline: ci_build1.pipeline
+ })
+ mr2.metrics.update!({
+ merged_at: 10.days.ago,
+ first_deployed_to_production_at: 5.days.ago,
+ latest_build_started_at: 9.days.ago,
+ latest_build_finished_at: 7.days.ago,
+ pipeline: ci_build2.pipeline
+ })
+ end
+
+ context 'returns build records' do
+ shared_examples 'orders build records by `latest_build_finished_at`' do
+ it 'orders by `latest_build_finished_at`' do
+ build_ids = subject.map { |item| item[:id] }
+
+ expect(build_ids).to eq([ci_build1.id, ci_build2.id])
+ end
+ end
+
+ context 'when requesting records for default test stage' do
+ include_examples 'orders build records by `latest_build_finished_at`'
+ end
+
+ context 'when requesting records for default staging stage' do
+ before do
+ stage.assign_attributes(default_stages.params_for_staging_stage)
+ end
+
+ include_examples 'orders build records by `latest_build_finished_at`'
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/analytics/cycle_analytics/stage_events/code_stage_start_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/code_stage_start_spec.rb
new file mode 100644
index 00000000000..29c8d548754
--- /dev/null
+++ b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/code_stage_start_spec.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Analytics::CycleAnalytics::StageEvents::CodeStageStart do
+ let(:subject) { described_class.new({}) }
+ let(:project) { create(:project) }
+
+ it_behaves_like 'cycle analytics event'
+
+ it 'needs connection with an issue via merge_requests_closing_issues table' do
+ issue = create(:issue, project: project)
+ merge_request = create(:merge_request, source_project: project)
+ create(:merge_requests_closing_issues, issue: issue, merge_request: merge_request)
+
+ other_merge_request = create(:merge_request, source_project: project, source_branch: 'a', target_branch: 'master')
+
+ records = subject.apply_query_customization(MergeRequest.all)
+ expect(records).to eq([merge_request])
+ expect(records).not_to include(other_merge_request)
+ end
+end
diff --git a/spec/lib/gitlab/analytics/cycle_analytics/stage_events/issue_created_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/issue_created_spec.rb
new file mode 100644
index 00000000000..efdef91c5a2
--- /dev/null
+++ b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/issue_created_spec.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Analytics::CycleAnalytics::StageEvents::IssueCreated do
+ it_behaves_like 'cycle analytics event'
+end
diff --git a/spec/lib/gitlab/analytics/cycle_analytics/stage_events/issue_first_mentioned_in_commit_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/issue_first_mentioned_in_commit_spec.rb
new file mode 100644
index 00000000000..50883e1c1e2
--- /dev/null
+++ b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/issue_first_mentioned_in_commit_spec.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Analytics::CycleAnalytics::StageEvents::IssueFirstMentionedInCommit do
+ it_behaves_like 'cycle analytics event'
+end
diff --git a/spec/lib/gitlab/analytics/cycle_analytics/stage_events/issue_stage_end_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/issue_stage_end_spec.rb
new file mode 100644
index 00000000000..85062db370a
--- /dev/null
+++ b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/issue_stage_end_spec.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Analytics::CycleAnalytics::StageEvents::IssueStageEnd do
+ it_behaves_like 'cycle analytics event'
+end
diff --git a/spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_created_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_created_spec.rb
new file mode 100644
index 00000000000..7858b810661
--- /dev/null
+++ b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_created_spec.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestCreated do
+ it_behaves_like 'cycle analytics event'
+end
diff --git a/spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_first_deployed_to_production_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_first_deployed_to_production_spec.rb
new file mode 100644
index 00000000000..ba9d8be5a2c
--- /dev/null
+++ b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_first_deployed_to_production_spec.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestFirstDeployedToProduction do
+ it_behaves_like 'cycle analytics event'
+end
diff --git a/spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_last_build_finished_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_last_build_finished_spec.rb
new file mode 100644
index 00000000000..8e83e10ef96
--- /dev/null
+++ b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_last_build_finished_spec.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestLastBuildFinished do
+ it_behaves_like 'cycle analytics event'
+end
diff --git a/spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_last_build_started_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_last_build_started_spec.rb
new file mode 100644
index 00000000000..9f6b430a320
--- /dev/null
+++ b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_last_build_started_spec.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestLastBuildStarted do
+ it_behaves_like 'cycle analytics event'
+end
diff --git a/spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_merged_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_merged_spec.rb
new file mode 100644
index 00000000000..ce2aa0a60db
--- /dev/null
+++ b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_merged_spec.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestMerged do
+ it_behaves_like 'cycle analytics event'
+end
diff --git a/spec/lib/gitlab/analytics/cycle_analytics/stage_events/plan_stage_start_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/plan_stage_start_spec.rb
new file mode 100644
index 00000000000..cb63139f0a8
--- /dev/null
+++ b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/plan_stage_start_spec.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Analytics::CycleAnalytics::StageEvents::PlanStageStart do
+ let(:subject) { described_class.new({}) }
+ let(:project) { create(:project) }
+
+ it_behaves_like 'cycle analytics event'
+
+ it 'filters issues where first_associated_with_milestone_at or first_added_to_board_at is filled' do
+ issue1 = create(:issue, project: project)
+ issue1.metrics.update!(first_added_to_board_at: 1.month.ago, first_mentioned_in_commit_at: 2.months.ago)
+
+ issue2 = create(:issue, project: project)
+ issue2.metrics.update!(first_associated_with_milestone_at: 1.month.ago, first_mentioned_in_commit_at: 2.months.ago)
+
+ issue_without_metrics = create(:issue, project: project)
+
+ records = subject.apply_query_customization(Issue.all)
+ expect(records).to match_array([issue1, issue2])
+ expect(records).not_to include(issue_without_metrics)
+ end
+end
diff --git a/spec/lib/gitlab/analytics/cycle_analytics/stage_events/stage_event_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/stage_event_spec.rb
index 29f4be76a65..b05faf5d813 100644
--- a/spec/lib/gitlab/analytics/cycle_analytics/stage_events/stage_event_spec.rb
+++ b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/stage_event_spec.rb
@@ -3,8 +3,11 @@
require 'spec_helper'
describe Gitlab::Analytics::CycleAnalytics::StageEvents::StageEvent do
+ let(:instance) { described_class.new({}) }
+
it { expect(described_class).to respond_to(:name) }
it { expect(described_class).to respond_to(:identifier) }
-
- it { expect(described_class.new({})).to respond_to(:object_type) }
+ it { expect(instance).to respond_to(:object_type) }
+ it { expect(instance).to respond_to(:timestamp_projection) }
+ it { expect(instance).to respond_to(:apply_query_customization) }
end
diff --git a/spec/lib/gitlab/auth/current_user_mode_spec.rb b/spec/lib/gitlab/auth/current_user_mode_spec.rb
new file mode 100644
index 00000000000..b93d460cf48
--- /dev/null
+++ b/spec/lib/gitlab/auth/current_user_mode_spec.rb
@@ -0,0 +1,159 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Auth::CurrentUserMode, :do_not_mock_admin_mode do
+ include_context 'custom session'
+
+ let(:user) { build(:user) }
+
+ subject { described_class.new(user) }
+
+ before do
+ allow(ActiveSession).to receive(:list_sessions).with(user).and_return([session])
+ end
+
+ describe '#admin_mode?', :request_store do
+ context 'when the user is a regular user' do
+ it 'is false by default' do
+ expect(subject.admin_mode?).to be(false)
+ end
+
+ it 'cannot be enabled with a valid password' do
+ subject.enable_admin_mode!(password: user.password)
+
+ expect(subject.admin_mode?).to be(false)
+ end
+
+ it 'cannot be enabled with an invalid password' do
+ subject.enable_admin_mode!(password: nil)
+
+ expect(subject.admin_mode?).to be(false)
+ end
+
+ it 'cannot be enabled with empty params' do
+ subject.enable_admin_mode!
+
+ expect(subject.admin_mode?).to be(false)
+ end
+
+ it 'disable has no effect' do
+ subject.enable_admin_mode!
+ subject.disable_admin_mode!
+
+ expect(subject.admin_mode?).to be(false)
+ end
+
+ context 'skipping password validation' do
+ it 'cannot be enabled with a valid password' do
+ subject.enable_admin_mode!(password: user.password, skip_password_validation: true)
+
+ expect(subject.admin_mode?).to be(false)
+ end
+
+ it 'cannot be enabled with an invalid password' do
+ subject.enable_admin_mode!(skip_password_validation: true)
+
+ expect(subject.admin_mode?).to be(false)
+ end
+ end
+ end
+
+ context 'when the user is an admin' do
+ let(:user) { build(:user, :admin) }
+
+ it 'is false by default' do
+ expect(subject.admin_mode?).to be(false)
+ end
+
+ it 'cannot be enabled with an invalid password' do
+ subject.enable_admin_mode!(password: nil)
+
+ expect(subject.admin_mode?).to be(false)
+ end
+
+ it 'can be enabled with a valid password' do
+ subject.enable_admin_mode!(password: user.password)
+
+ expect(subject.admin_mode?).to be(true)
+ end
+
+ it 'can be disabled' do
+ subject.enable_admin_mode!(password: user.password)
+ subject.disable_admin_mode!
+
+ expect(subject.admin_mode?).to be(false)
+ end
+
+ it 'will expire in the future' do
+ subject.enable_admin_mode!(password: user.password)
+ expect(subject.admin_mode?).to be(true), 'admin mode is not active in the present'
+
+ Timecop.freeze(Gitlab::Auth::CurrentUserMode::MAX_ADMIN_MODE_TIME.from_now) do
+ # in the future this will be a new request, simulate by clearing the RequestStore
+ Gitlab::SafeRequestStore.clear!
+
+ expect(subject.admin_mode?).to be(false), 'admin mode did not expire in the future'
+ end
+ end
+
+ context 'skipping password validation' do
+ it 'can be enabled with a valid password' do
+ subject.enable_admin_mode!(password: user.password, skip_password_validation: true)
+
+ expect(subject.admin_mode?).to be(true)
+ end
+
+ it 'can be enabled with an invalid password' do
+ subject.enable_admin_mode!(skip_password_validation: true)
+
+ expect(subject.admin_mode?).to be(true)
+ end
+ end
+
+ context 'with two independent sessions' do
+ let(:another_session) { {} }
+ let(:another_subject) { described_class.new(user) }
+
+ before do
+ allow(ActiveSession).to receive(:list_sessions).with(user).and_return([session, another_session])
+ end
+
+ it 'can be enabled in one and seen in the other' do
+ Gitlab::Session.with_session(another_session) do
+ another_subject.enable_admin_mode!(password: user.password)
+ end
+
+ expect(subject.admin_mode?).to be(true)
+ end
+ end
+ end
+ end
+
+ describe '#enable_admin_mode!' do
+ let(:user) { build(:user, :admin) }
+
+ it 'creates a timestamp in the session' do
+ subject.enable_admin_mode!(password: user.password)
+
+ expect(session).to include(expected_session_entry(be_within(1.second).of Time.now))
+ end
+ end
+
+ describe '#disable_admin_mode!' do
+ let(:user) { build(:user, :admin) }
+
+ it 'sets the session timestamp to nil' do
+ subject.disable_admin_mode!
+
+ expect(session).to include(expected_session_entry(be_nil))
+ end
+ end
+
+ def expected_session_entry(value_matcher)
+ {
+ Gitlab::Auth::CurrentUserMode::SESSION_STORE_KEY => a_hash_including(
+ Gitlab::Auth::CurrentUserMode::ADMIN_MODE_START_TIME_KEY => value_matcher)
+ }
+ end
+end
diff --git a/spec/lib/gitlab/auth/user_access_denied_reason_spec.rb b/spec/lib/gitlab/auth/user_access_denied_reason_spec.rb
index 8ec19c454d8..7045105a2c7 100644
--- a/spec/lib/gitlab/auth/user_access_denied_reason_spec.rb
+++ b/spec/lib/gitlab/auth/user_access_denied_reason_spec.rb
@@ -33,5 +33,13 @@ describe Gitlab::Auth::UserAccessDeniedReason do
it { is_expected.to match /This action cannot be performed by internal users/ }
end
+
+ context 'when the user is deactivated' do
+ before do
+ user.deactivate!
+ end
+
+ it { is_expected.to eq "Your account has been deactivated by your administrator. Please log back in from a web browser to reactivate your account at #{Gitlab.config.gitlab.url}" }
+ end
end
end
diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb
index 0365d63ea9c..dc4b0b5b1b6 100644
--- a/spec/lib/gitlab/auth_spec.rb
+++ b/spec/lib/gitlab/auth_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
describe Gitlab::Auth do
let(:gl_auth) { described_class }
+ set(:project) { create(:project) }
describe 'constants' do
it 'API_SCOPES contains all scopes for API access' do
@@ -90,13 +91,13 @@ describe Gitlab::Auth do
end
it 'recognises user-less build' do
- expect(subject).to eq(Gitlab::Auth::Result.new(nil, build.project, :ci, build_authentication_abilities))
+ expect(subject).to eq(Gitlab::Auth::Result.new(nil, build.project, :ci, described_class.build_authentication_abilities))
end
it 'recognises user token' do
build.update(user: create(:user))
- expect(subject).to eq(Gitlab::Auth::Result.new(build.user, build.project, :build, build_authentication_abilities))
+ expect(subject).to eq(Gitlab::Auth::Result.new(build.user, build.project, :build, described_class.build_authentication_abilities))
end
end
@@ -117,26 +118,25 @@ describe Gitlab::Auth do
end
it 'recognizes other ci services' do
- project = create(:project)
project.create_drone_ci_service(active: true)
project.drone_ci_service.update(token: 'token')
expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: 'drone-ci-token')
- expect(gl_auth.find_for_git_client('drone-ci-token', 'token', project: project, ip: 'ip')).to eq(Gitlab::Auth::Result.new(nil, project, :ci, build_authentication_abilities))
+ expect(gl_auth.find_for_git_client('drone-ci-token', 'token', project: project, ip: 'ip')).to eq(Gitlab::Auth::Result.new(nil, project, :ci, described_class.build_authentication_abilities))
end
it 'recognizes master passwords' do
user = create(:user, password: 'password')
expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: user.username)
- expect(gl_auth.find_for_git_client(user.username, 'password', project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_authentication_abilities))
+ expect(gl_auth.find_for_git_client(user.username, 'password', project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, described_class.full_authentication_abilities))
end
include_examples 'user login operation with unique ip limit' do
let(:user) { create(:user, password: 'password') }
def operation
- expect(gl_auth.find_for_git_client(user.username, 'password', project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_authentication_abilities))
+ expect(gl_auth.find_for_git_client(user.username, 'password', project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, described_class.full_authentication_abilities))
end
end
@@ -146,7 +146,7 @@ describe Gitlab::Auth do
token = Gitlab::LfsToken.new(user).token
expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: user.username)
- expect(gl_auth.find_for_git_client(user.username, token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :lfs_token, full_authentication_abilities))
+ expect(gl_auth.find_for_git_client(user.username, token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :lfs_token, described_class.read_write_project_authentication_abilities))
end
it 'recognizes deploy key lfs tokens' do
@@ -154,7 +154,7 @@ describe Gitlab::Auth do
token = Gitlab::LfsToken.new(key).token
expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: "lfs+deploy-key-#{key.id}")
- expect(gl_auth.find_for_git_client("lfs+deploy-key-#{key.id}", token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(key, nil, :lfs_deploy_token, read_only_authentication_abilities))
+ expect(gl_auth.find_for_git_client("lfs+deploy-key-#{key.id}", token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(key, nil, :lfs_deploy_token, described_class.read_only_authentication_abilities))
end
it 'does not try password auth before oauth' do
@@ -167,22 +167,20 @@ describe Gitlab::Auth do
end
it 'grants deploy key write permissions' do
- project = create(:project)
key = create(:deploy_key)
create(:deploy_keys_project, :write_access, deploy_key: key, project: project)
token = Gitlab::LfsToken.new(key).token
expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: "lfs+deploy-key-#{key.id}")
- expect(gl_auth.find_for_git_client("lfs+deploy-key-#{key.id}", token, project: project, ip: 'ip')).to eq(Gitlab::Auth::Result.new(key, nil, :lfs_deploy_token, read_write_authentication_abilities))
+ expect(gl_auth.find_for_git_client("lfs+deploy-key-#{key.id}", token, project: project, ip: 'ip')).to eq(Gitlab::Auth::Result.new(key, nil, :lfs_deploy_token, described_class.read_write_authentication_abilities))
end
it 'does not grant deploy key write permissions' do
- project = create(:project)
key = create(:deploy_key)
token = Gitlab::LfsToken.new(key).token
expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: "lfs+deploy-key-#{key.id}")
- expect(gl_auth.find_for_git_client("lfs+deploy-key-#{key.id}", token, project: project, ip: 'ip')).to eq(Gitlab::Auth::Result.new(key, nil, :lfs_deploy_token, read_only_authentication_abilities))
+ expect(gl_auth.find_for_git_client("lfs+deploy-key-#{key.id}", token, project: project, ip: 'ip')).to eq(Gitlab::Auth::Result.new(key, nil, :lfs_deploy_token, described_class.read_only_authentication_abilities))
end
end
@@ -193,7 +191,7 @@ describe Gitlab::Auth do
it 'succeeds for OAuth tokens with the `api` scope' do
expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: 'oauth2')
- expect(gl_auth.find_for_git_client("oauth2", token_w_api_scope.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :oauth, full_authentication_abilities))
+ expect(gl_auth.find_for_git_client("oauth2", token_w_api_scope.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :oauth, described_class.full_authentication_abilities))
end
it 'fails for OAuth tokens with other scopes' do
@@ -214,7 +212,7 @@ describe Gitlab::Auth do
it 'succeeds for personal access tokens with the `api` scope' do
personal_access_token = create(:personal_access_token, scopes: ['api'])
- expect_results_with_abilities(personal_access_token, full_authentication_abilities)
+ expect_results_with_abilities(personal_access_token, described_class.full_authentication_abilities)
end
it 'succeeds for personal access tokens with the `read_repository` scope' do
@@ -244,7 +242,7 @@ describe Gitlab::Auth do
it 'succeeds if it is an impersonation token' do
impersonation_token = create(:personal_access_token, :impersonation, scopes: ['api'])
- expect_results_with_abilities(impersonation_token, full_authentication_abilities)
+ expect_results_with_abilities(impersonation_token, described_class.full_authentication_abilities)
end
it 'limits abilities based on scope' do
@@ -267,7 +265,7 @@ describe Gitlab::Auth do
)
expect(gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip'))
- .to eq(Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_authentication_abilities))
+ .to eq(Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, described_class.full_authentication_abilities))
end
it 'fails through oauth authentication when the username is oauth2' do
@@ -278,7 +276,7 @@ describe Gitlab::Auth do
)
expect(gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip'))
- .to eq(Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_authentication_abilities))
+ .to eq(Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, described_class.full_authentication_abilities))
end
end
@@ -296,7 +294,6 @@ describe Gitlab::Auth do
end
context 'while using deploy tokens' do
- let(:project) { create(:project) }
let(:auth_failure) { Gitlab::Auth::Result.new(nil, nil) }
context 'when deploy token and user have the same username' do
@@ -316,7 +313,7 @@ describe Gitlab::Auth do
end
it 'succeeds for the user' do
- auth_success = Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_authentication_abilities)
+ auth_success = Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, described_class.full_authentication_abilities)
expect(gl_auth.find_for_git_client(username, 'my-secret', project: project, ip: 'ip'))
.to eq(auth_success)
@@ -344,7 +341,7 @@ describe Gitlab::Auth do
end
context 'and belong to different projects' do
- let!(:read_registry) { create(:deploy_token, username: 'deployer', read_repository: false, projects: [create(:project)]) }
+ let!(:read_registry) { create(:deploy_token, username: 'deployer', read_repository: false, projects: [project]) }
let!(:read_repository) { create(:deploy_token, username: read_registry.username, read_registry: false, projects: [project]) }
it 'succeeds for the right token' do
@@ -523,6 +520,12 @@ describe Gitlab::Auth do
end
end
+ it 'finds the user in deactivated state' do
+ user.deactivate!
+
+ expect( gl_auth.find_with_user_password(username, password) ).to eql user
+ end
+
it "does not find user in blocked state" do
user.block
@@ -582,37 +585,6 @@ describe Gitlab::Auth do
private
- def build_authentication_abilities
- [
- :read_project,
- :build_download_code,
- :build_read_container_image,
- :build_create_container_image,
- :build_destroy_container_image
- ]
- end
-
- def read_only_authentication_abilities
- [
- :read_project,
- :download_code,
- :read_container_image
- ]
- end
-
- def read_write_authentication_abilities
- read_only_authentication_abilities + [
- :push_code,
- :create_container_image
- ]
- end
-
- def full_authentication_abilities
- read_write_authentication_abilities + [
- :admin_container_image
- ]
- end
-
def expect_results_with_abilities(personal_access_token, abilities, success = true)
expect(gl_auth).to receive(:rate_limit!).with('ip', success: success, login: '')
expect(gl_auth.find_for_git_client('', personal_access_token&.token, project: nil, ip: 'ip'))
diff --git a/spec/lib/gitlab/background_migration/legacy_upload_mover_spec.rb b/spec/lib/gitlab/background_migration/legacy_upload_mover_spec.rb
index 7d67dc0251d..c1eaf1d3433 100644
--- a/spec/lib/gitlab/background_migration/legacy_upload_mover_spec.rb
+++ b/spec/lib/gitlab/background_migration/legacy_upload_mover_spec.rb
@@ -32,7 +32,7 @@ describe Gitlab::BackgroundMigration::LegacyUploadMover do
if with_file
upload = create(:upload, :with_file, :attachment_upload, params)
- model.update(attachment: upload.build_uploader)
+ model.update(attachment: upload.retrieve_uploader)
model.attachment.upload
else
create(:upload, :attachment_upload, params)
diff --git a/spec/lib/gitlab/background_migration/legacy_uploads_migrator_spec.rb b/spec/lib/gitlab/background_migration/legacy_uploads_migrator_spec.rb
index ed8cbfeb11f..cabca3dbef9 100644
--- a/spec/lib/gitlab/background_migration/legacy_uploads_migrator_spec.rb
+++ b/spec/lib/gitlab/background_migration/legacy_uploads_migrator_spec.rb
@@ -24,7 +24,7 @@ describe Gitlab::BackgroundMigration::LegacyUploadsMigrator do
if with_file
upload = create(:upload, :with_file, :attachment_upload, params)
- model.update(attachment: upload.build_uploader)
+ model.update(attachment: upload.retrieve_uploader)
model.attachment.upload
else
create(:upload, :attachment_upload, params)
diff --git a/spec/lib/gitlab/background_migration/migrate_pages_metadata_spec.rb b/spec/lib/gitlab/background_migration/migrate_pages_metadata_spec.rb
new file mode 100644
index 00000000000..d94a312f605
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/migrate_pages_metadata_spec.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::BackgroundMigration::MigratePagesMetadata, :migration, schema: 20190919040324 do
+ let(:projects) { table(:projects) }
+
+ subject(:migrate_pages_metadata) { described_class.new }
+
+ describe '#perform_on_relation' do
+ let(:namespaces) { table(:namespaces) }
+ let(:builds) { table(:ci_builds) }
+ let(:pages_metadata) { table(:project_pages_metadata) }
+
+ it 'marks specified projects with successful pages deployment' do
+ namespace = namespaces.create!(name: 'gitlab', path: 'gitlab-org')
+ not_migrated_with_pages = projects.create!(namespace_id: namespace.id, name: 'Not Migrated With Pages')
+ builds.create!(project_id: not_migrated_with_pages.id, type: 'GenericCommitStatus', status: 'success', stage: 'deploy', name: 'pages:deploy')
+
+ migrated = projects.create!(namespace_id: namespace.id, name: 'Migrated')
+ pages_metadata.create!(project_id: migrated.id, deployed: true)
+
+ not_migrated_no_pages = projects.create!(namespace_id: namespace.id, name: 'Not Migrated No Pages')
+ project_not_in_relation_scope = projects.create!(namespace_id: namespace.id, name: 'Other')
+
+ projects_relation = projects.where(id: [not_migrated_with_pages, not_migrated_no_pages, migrated])
+
+ migrate_pages_metadata.perform_on_relation(projects_relation)
+
+ expect(pages_metadata.find_by_project_id(not_migrated_with_pages.id).deployed).to eq(true)
+ expect(pages_metadata.find_by_project_id(not_migrated_no_pages.id).deployed).to eq(false)
+ expect(pages_metadata.find_by_project_id(migrated.id).deployed).to eq(true)
+ expect(pages_metadata.find_by_project_id(project_not_in_relation_scope.id)).to be_nil
+ end
+ end
+
+ describe '#perform' do
+ it 'creates relation and delegates to #perform_on_relation' do
+ expect(migrate_pages_metadata).to receive(:perform_on_relation).with(projects.where(id: 3..5))
+
+ migrate_pages_metadata.perform(3, 5)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/badge/pipeline/template_spec.rb b/spec/lib/gitlab/badge/pipeline/template_spec.rb
index c0aaa3d73e1..da95c7219a4 100644
--- a/spec/lib/gitlab/badge/pipeline/template_spec.rb
+++ b/spec/lib/gitlab/badge/pipeline/template_spec.rb
@@ -67,7 +67,7 @@ describe Gitlab::Badge::Pipeline::Template do
end
it 'has expected color' do
- expect(template.value_color).to eq '#dfb317'
+ expect(template.value_color).to eq '#a7a7a7'
end
end
diff --git a/spec/lib/gitlab/bare_repository_import/importer_spec.rb b/spec/lib/gitlab/bare_repository_import/importer_spec.rb
index 0c1eedad7f4..2fb9f1a0a08 100644
--- a/spec/lib/gitlab/bare_repository_import/importer_spec.rb
+++ b/spec/lib/gitlab/bare_repository_import/importer_spec.rb
@@ -89,7 +89,7 @@ describe Gitlab::BareRepositoryImport::Importer, :seed_helper do
repo_path = "#{project.disk_path}.git"
hook_path = File.join(repo_path, 'hooks')
- expect(gitlab_shell.exists?(project.repository_storage, repo_path)).to be(true)
+ expect(gitlab_shell.repository_exists?(project.repository_storage, repo_path)).to be(true)
expect(gitlab_shell.exists?(project.repository_storage, hook_path)).to be(true)
end
@@ -145,8 +145,8 @@ describe Gitlab::BareRepositoryImport::Importer, :seed_helper do
project = Project.find_by_full_path("#{admin.full_path}/#{project_path}")
- expect(gitlab_shell.exists?(project.repository_storage, project.disk_path + '.git')).to be(true)
- expect(gitlab_shell.exists?(project.repository_storage, project.disk_path + '.wiki.git')).to be(true)
+ expect(gitlab_shell.repository_exists?(project.repository_storage, project.disk_path + '.git')).to be(true)
+ expect(gitlab_shell.repository_exists?(project.repository_storage, project.disk_path + '.wiki.git')).to be(true)
end
context 'with a repository already on disk' do
@@ -186,7 +186,7 @@ describe Gitlab::BareRepositoryImport::Importer, :seed_helper do
project = Project.find_by_full_path(project_path)
- expect(gitlab_shell.exists?(project.repository_storage, project.disk_path + '.wiki.git')).to be(true)
+ expect(gitlab_shell.repository_exists?(project.repository_storage, project.disk_path + '.wiki.git')).to be(true)
end
end
diff --git a/spec/lib/gitlab/bitbucket_import/importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importer_spec.rb
index 3d0d3f91859..7f7a285c453 100644
--- a/spec/lib/gitlab/bitbucket_import/importer_spec.rb
+++ b/spec/lib/gitlab/bitbucket_import/importer_spec.rb
@@ -308,8 +308,8 @@ describe Gitlab::BitbucketImport::Importer do
importer.execute
- expect(project.issues.where(state: "closed").size).to eq(5)
- expect(project.issues.where(state: "opened").size).to eq(2)
+ expect(project.issues.where(state_id: Issue.available_states[:closed]).size).to eq(5)
+ expect(project.issues.where(state_id: Issue.available_states[:opened]).size).to eq(2)
end
describe 'wiki import' do
diff --git a/spec/lib/gitlab/ci/ansi2json/line_spec.rb b/spec/lib/gitlab/ci/ansi2json/line_spec.rb
new file mode 100644
index 00000000000..4b5c3f9489e
--- /dev/null
+++ b/spec/lib/gitlab/ci/ansi2json/line_spec.rb
@@ -0,0 +1,168 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Ci::Ansi2json::Line do
+ let(:offset) { 0 }
+ let(:style) { Gitlab::Ci::Ansi2json::Style.new }
+
+ subject { described_class.new(offset: offset, style: style) }
+
+ describe '#<<' do
+ it 'appends new data to the current segment' do
+ expect { subject << 'test 1' }.to change { subject.current_segment.text }
+ expect(subject.current_segment.text).to eq('test 1')
+
+ expect { subject << ', test 2' }.to change { subject.current_segment.text }
+ expect(subject.current_segment.text).to eq('test 1, test 2')
+ end
+ end
+
+ describe '#style' do
+ context 'when style is passed to the initializer' do
+ let(:style) { double }
+
+ it 'returns the same style' do
+ expect(subject.style).to eq(style)
+ end
+ end
+
+ context 'when style is not passed to the initializer' do
+ it 'returns the default style' do
+ expect(subject.style.set?).to be_falsey
+ end
+ end
+ end
+
+ describe '#update_style' do
+ let(:expected_style) do
+ Gitlab::Ci::Ansi2json::Style.new(
+ fg: 'term-fg-l-yellow',
+ bg: 'term-bg-blue',
+ mask: 1)
+ end
+
+ it 'sets the style' do
+ subject.update_style(%w[1 33 44])
+
+ expect(subject.style).to eq(expected_style)
+ end
+ end
+
+ describe '#add_section' do
+ it 'appends a new section to the list' do
+ subject.add_section('section_1')
+ subject.add_section('section_2')
+
+ expect(subject.sections).to eq(%w[section_1 section_2])
+ end
+ end
+
+ describe '#set_as_section_header' do
+ it 'change the section_header to true' do
+ expect { subject.set_as_section_header }
+ .to change { subject.section_header }
+ .to be_truthy
+ end
+ end
+
+ describe '#set_section_duration' do
+ it 'sets and formats the section_duration' do
+ subject.set_section_duration(75)
+
+ expect(subject.section_duration).to eq('01:15')
+ end
+ end
+
+ describe '#flush_current_segment!' do
+ context 'when current segment is not empty' do
+ before do
+ subject << 'some data'
+ end
+
+ it 'adds the segment to the list' do
+ expect { subject.flush_current_segment! }.to change { subject.segments.count }.by(1)
+
+ expect(subject.segments.map { |s| s[:text] }).to eq(['some data'])
+ end
+
+ it 'updates the current segment pointer propagating the style' do
+ previous_segment = subject.current_segment
+
+ subject.flush_current_segment!
+
+ expect(subject.current_segment).not_to eq(previous_segment)
+ expect(subject.current_segment.style).to eq(previous_segment.style)
+ end
+ end
+
+ context 'when current segment is empty' do
+ it 'does not add any segments to the list' do
+ expect { subject.flush_current_segment! }.not_to change { subject.segments.count }
+ end
+
+ it 'does not change the current segment' do
+ expect { subject.flush_current_segment! }.not_to change { subject.current_segment }
+ end
+ end
+ end
+
+ describe '#to_h' do
+ before do
+ subject << 'some data'
+ subject.update_style(['1'])
+ end
+
+ context 'when sections are present' do
+ before do
+ subject.add_section('section_1')
+ subject.add_section('section_2')
+ end
+
+ context 'when section header is set' do
+ before do
+ subject.set_as_section_header
+ end
+
+ it 'serializes the attributes set' do
+ result = {
+ offset: 0,
+ content: [{ text: 'some data', style: 'term-bold' }],
+ section: 'section_2',
+ section_header: true
+ }
+
+ expect(subject.to_h).to eq(result)
+ end
+ end
+
+ context 'when section duration is set' do
+ before do
+ subject.set_section_duration(75)
+ end
+
+ it 'serializes the attributes set' do
+ result = {
+ offset: 0,
+ content: [{ text: 'some data', style: 'term-bold' }],
+ section: 'section_2',
+ section_duration: '01:15'
+ }
+
+ expect(subject.to_h).to eq(result)
+ end
+ end
+ end
+
+ context 'when there are no sections' do
+ it 'serializes the attributes set' do
+ result = {
+ offset: 0,
+ content: [{ text: 'some data', style: 'term-bold' }]
+ }
+
+ expect(subject.to_h).to eq(result)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/ansi2json/parser_spec.rb b/spec/lib/gitlab/ci/ansi2json/parser_spec.rb
new file mode 100644
index 00000000000..e161e74c1ff
--- /dev/null
+++ b/spec/lib/gitlab/ci/ansi2json/parser_spec.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+# The rest of the specs for this class are covered in style_spec.rb
+describe Gitlab::Ci::Ansi2json::Parser do
+ subject { described_class }
+
+ describe 'bold?' do
+ it 'returns true if style mask matches bold format' do
+ expect(subject.bold?(0x01)).to be_truthy
+ end
+
+ it 'returns false if style mask does not match bold format' do
+ expect(subject.bold?(0x02)).to be_falsey
+ end
+ end
+
+ describe 'matching_formats' do
+ it 'returns matching formats given a style mask' do
+ expect(subject.matching_formats(0x01)).to eq(%w[term-bold])
+ expect(subject.matching_formats(0x03)).to eq(%w[term-bold term-italic])
+ expect(subject.matching_formats(0x07)).to eq(%w[term-bold term-italic term-underline])
+ end
+
+ it 'returns an empty array if no formats match the style mask' do
+ expect(subject.matching_formats(0)).to eq([])
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/ansi2json/style_spec.rb b/spec/lib/gitlab/ci/ansi2json/style_spec.rb
new file mode 100644
index 00000000000..88a0ca35859
--- /dev/null
+++ b/spec/lib/gitlab/ci/ansi2json/style_spec.rb
@@ -0,0 +1,166 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Ci::Ansi2json::Style do
+ describe '#set?' do
+ subject { described_class.new(params).set? }
+
+ context 'when fg color is set' do
+ let(:params) { { fg: 'term-fg-black' } }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when bg color is set' do
+ let(:params) { { bg: 'term-bg-black' } }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when mask is set' do
+ let(:params) { { mask: 0x01 } }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'nothing is set' do
+ let(:params) { {} }
+
+ it { is_expected.to be_falsey }
+ end
+ end
+
+ describe '#reset!' do
+ let(:style) { described_class.new(fg: 'term-fg-black', bg: 'term-bg-yellow', mask: 0x01) }
+
+ it 'set the style params to default' do
+ style.reset!
+
+ expect(style.fg).to be_nil
+ expect(style.bg).to be_nil
+ expect(style.mask).to be_zero
+ end
+ end
+
+ describe 'update formats to mimic terminals' do
+ subject { described_class.new(params) }
+
+ context 'when fg color present' do
+ let(:params) { { fg: 'term-fg-black', mask: mask } }
+
+ context 'when mask is set to bold' do
+ let(:mask) { 0x01 }
+
+ it 'changes the fg color to a lighter version' do
+ expect(subject.fg).to eq('term-fg-l-black')
+ end
+ end
+
+ context 'when mask set to another format' do
+ let(:mask) { 0x02 }
+
+ it 'does not change the fg color' do
+ expect(subject.fg).to eq('term-fg-black')
+ end
+ end
+
+ context 'when mask is not set' do
+ let(:mask) { 0 }
+
+ it 'does not change the fg color' do
+ expect(subject.fg).to eq('term-fg-black')
+ end
+ end
+ end
+ end
+
+ describe '#update' do
+ where(:initial_state, :ansi_commands, :result, :description) do
+ [
+ # add format
+ [[], %w[0], '', 'does not set any style'],
+ [[], %w[1], 'term-bold', 'enables format bold'],
+ [[], %w[3], 'term-italic', 'enables format italic'],
+ [[], %w[4], 'term-underline', 'enables format underline'],
+ [[], %w[8], 'term-conceal', 'enables format conceal'],
+ [[], %w[9], 'term-cross', 'enables format cross'],
+ # remove format
+ [%w[1], %w[21], '', 'disables format bold'],
+ [%w[1 3], %w[21], 'term-italic', 'disables format bold and leaves italic'],
+ [%w[1], %w[22], '', 'disables format bold using command 22'],
+ [%w[1 3], %w[22], 'term-italic', 'disables format bold and leaves italic using command 22'],
+ [%w[3], %w[23], '', 'disables format italic'],
+ [%w[1 3], %w[23], 'term-bold', 'disables format italic and leaves bold'],
+ [%w[4], %w[24], '', 'disables format underline'],
+ [%w[1 4], %w[24], 'term-bold', 'disables format underline and leaves bold'],
+ [%w[8], %w[28], '', 'disables format conceal'],
+ [%w[1 8], %w[28], 'term-bold', 'disables format conceal and leaves bold'],
+ [%w[9], %w[29], '', 'disables format cross'],
+ [%w[1 9], %w[29], 'term-bold', 'disables format cross and leaves bold'],
+ # set fg color
+ [[], %w[30], 'term-fg-black', 'sets fg color black'],
+ [[], %w[31], 'term-fg-red', 'sets fg color red'],
+ [[], %w[32], 'term-fg-green', 'sets fg color green'],
+ [[], %w[33], 'term-fg-yellow', 'sets fg color yellow'],
+ [[], %w[34], 'term-fg-blue', 'sets fg color blue'],
+ [[], %w[35], 'term-fg-magenta', 'sets fg color magenta'],
+ [[], %w[36], 'term-fg-cyan', 'sets fg color cyan'],
+ [[], %w[37], 'term-fg-white', 'sets fg color white'],
+ # sets xterm fg color
+ [[], %w[38 5 1], 'xterm-fg-1', 'sets xterm fg color 1'],
+ [[], %w[38 5 2], 'xterm-fg-2', 'sets xterm fg color 2'],
+ [[], %w[38 1], 'term-bold', 'ignores 38 command if not followed by 5 and sets format bold'],
+ # set bg color
+ [[], %w[40], 'term-bg-black', 'sets bg color black'],
+ [[], %w[41], 'term-bg-red', 'sets bg color red'],
+ [[], %w[42], 'term-bg-green', 'sets bg color green'],
+ [[], %w[43], 'term-bg-yellow', 'sets bg color yellow'],
+ [[], %w[44], 'term-bg-blue', 'sets bg color blue'],
+ [[], %w[45], 'term-bg-magenta', 'sets bg color magenta'],
+ [[], %w[46], 'term-bg-cyan', 'sets bg color cyan'],
+ [[], %w[47], 'term-bg-white', 'sets bg color white'],
+ # set xterm bg color
+ [[], %w[48 5 1], 'xterm-bg-1', 'sets xterm bg color 1'],
+ [[], %w[48 5 2], 'xterm-bg-2', 'sets xterm bg color 2'],
+ [[], %w[48 1], 'term-bold', 'ignores 48 command if not followed by 5 and sets format bold'],
+ # set light fg color
+ [[], %w[90], 'term-fg-l-black', 'sets fg color light black'],
+ [[], %w[91], 'term-fg-l-red', 'sets fg color light red'],
+ [[], %w[92], 'term-fg-l-green', 'sets fg color light green'],
+ [[], %w[93], 'term-fg-l-yellow', 'sets fg color light yellow'],
+ [[], %w[94], 'term-fg-l-blue', 'sets fg color light blue'],
+ [[], %w[95], 'term-fg-l-magenta', 'sets fg color light magenta'],
+ [[], %w[96], 'term-fg-l-cyan', 'sets fg color light cyan'],
+ [[], %w[97], 'term-fg-l-white', 'sets fg color light white'],
+ # set light bg color
+ [[], %w[100], 'term-bg-l-black', 'sets bg color light black'],
+ [[], %w[101], 'term-bg-l-red', 'sets bg color light red'],
+ [[], %w[102], 'term-bg-l-green', 'sets bg color light green'],
+ [[], %w[103], 'term-bg-l-yellow', 'sets bg color light yellow'],
+ [[], %w[104], 'term-bg-l-blue', 'sets bg color light blue'],
+ [[], %w[105], 'term-bg-l-magenta', 'sets bg color light magenta'],
+ [[], %w[106], 'term-bg-l-cyan', 'sets bg color light cyan'],
+ [[], %w[107], 'term-bg-l-white', 'sets bg color light white'],
+ # reset
+ [%w[1], %w[0], '', 'resets style from format bold'],
+ [%w[1 3], %w[0], '', 'resets style from format bold and italic'],
+ [%w[1 3 term-fg-l-red term-bg-yellow], %w[0], '', 'resets all formats and colors'],
+ # misc
+ [[], %w[1 30 42 3], 'term-fg-l-black term-bg-green term-bold term-italic', 'adds fg color, bg color and formats from no style'],
+ [%w[3 31], %w[23 1 43], 'term-fg-l-red term-bg-yellow term-bold', 'replaces format italic with bold and adds a yellow background']
+ ]
+ end
+
+ with_them do
+ it 'change the style' do
+ style = described_class.new
+ style.update(initial_state)
+
+ style.update(ansi_commands)
+
+ expect(style.to_s).to eq(result)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/ansi2json_spec.rb b/spec/lib/gitlab/ci/ansi2json_spec.rb
new file mode 100644
index 00000000000..3c6bc46436b
--- /dev/null
+++ b/spec/lib/gitlab/ci/ansi2json_spec.rb
@@ -0,0 +1,544 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Ci::Ansi2json do
+ subject { described_class }
+
+ describe 'lines' do
+ it 'prints non-ansi as-is' do
+ expect(convert_json('Hello')).to eq([
+ { offset: 0, content: [{ text: 'Hello' }] }
+ ])
+ end
+
+ it 'adds new line in a separate element' do
+ expect(convert_json("Hello\nworld")).to eq([
+ { offset: 0, content: [{ text: 'Hello' }] },
+ { offset: 6, content: [{ text: 'world' }] }
+ ])
+ end
+
+ it 'recognizes color changing ANSI sequences' do
+ expect(convert_json("\e[31mHello\e[0m")).to eq([
+ { offset: 0, content: [{ text: 'Hello', style: 'term-fg-red' }] }
+ ])
+ end
+
+ it 'recognizes color changing ANSI sequences across multiple lines' do
+ expect(convert_json("\e[31mHello\nWorld\e[0m")).to eq([
+ { offset: 0, content: [{ text: 'Hello', style: 'term-fg-red' }] },
+ { offset: 11, content: [{ text: 'World', style: 'term-fg-red' }] }
+ ])
+ end
+
+ it 'recognizes background and foreground colors' do
+ expect(convert_json("\e[31;44mHello")).to eq([
+ { offset: 0, content: [{ text: 'Hello', style: 'term-fg-red term-bg-blue' }] }
+ ])
+ end
+
+ it 'recognizes style changes within the same line' do
+ expect(convert_json("\e[31;44mHello\e[0m world")).to eq([
+ { offset: 0, content: [
+ { text: 'Hello', style: 'term-fg-red term-bg-blue' },
+ { text: ' world' }
+ ] }
+ ])
+ end
+
+ context 'with section markers' do
+ let(:section_name) { 'prepare-script' }
+ let(:section_duration) { 63.seconds }
+ let(:section_start_time) { Time.new(2019, 9, 17).utc }
+ let(:section_end_time) { section_start_time + section_duration }
+ let(:section_start) { "section_start:#{section_start_time.to_i}:#{section_name}\r\033[0K"}
+ let(:section_end) { "section_end:#{section_end_time.to_i}:#{section_name}\r\033[0K"}
+
+ it 'marks the first line of the section as header' do
+ expect(convert_json("Hello#{section_start}world!")).to eq([
+ {
+ offset: 0,
+ content: [{ text: 'Hello' }]
+ },
+ {
+ offset: 5,
+ content: [{ text: 'world!' }],
+ section: 'prepare-script',
+ section_header: true
+ }
+ ])
+ end
+
+ it 'does not marks the other lines of the section as header' do
+ expect(convert_json("outside section#{section_start}Hello\nworld!")).to eq([
+ {
+ offset: 0,
+ content: [{ text: 'outside section' }]
+ },
+ {
+ offset: 15,
+ content: [{ text: 'Hello' }],
+ section: 'prepare-script',
+ section_header: true
+ },
+ {
+ offset: 65,
+ content: [{ text: 'world!' }],
+ section: 'prepare-script'
+ }
+ ])
+ end
+
+ it 'marks the last line of the section as footer' do
+ expect(convert_json("#{section_start}Good\nmorning\nworld!#{section_end}")).to eq([
+ {
+ offset: 0,
+ content: [{ text: 'Good' }],
+ section: 'prepare-script',
+ section_header: true
+ },
+ {
+ offset: 49,
+ content: [{ text: 'morning' }],
+ section: 'prepare-script'
+ },
+ {
+ offset: 57,
+ content: [{ text: 'world!' }],
+ section: 'prepare-script'
+ },
+ {
+ offset: 63,
+ content: [],
+ section_duration: '01:03',
+ section: 'prepare-script'
+ },
+ {
+ offset: 63,
+ content: []
+ }
+ ])
+ end
+
+ it 'marks the first line as header and footer if is the only line in the section' do
+ expect(convert_json("#{section_start}Hello world!#{section_end}")).to eq([
+ {
+ offset: 0,
+ content: [{ text: 'Hello world!' }],
+ section: 'prepare-script',
+ section_header: true
+ },
+ {
+ offset: 56,
+ content: [],
+ section: 'prepare-script',
+ section_duration: '01:03'
+ },
+ {
+ offset: 56,
+ content: []
+ }
+ ])
+ end
+
+ it 'does not add sections attribute to lines after the section is closed' do
+ expect(convert_json("#{section_start}Hello#{section_end}world")).to eq([
+ {
+ offset: 0,
+ content: [{ text: 'Hello' }],
+ section: 'prepare-script',
+ section_header: true
+ },
+ {
+ offset: 49,
+ content: [],
+ section: 'prepare-script',
+ section_duration: '01:03'
+ },
+ {
+ offset: 49,
+ content: [{ text: 'world' }]
+ }
+ ])
+ end
+
+ it 'ignores section_end marker if no section_start exists' do
+ expect(convert_json("Hello #{section_end}world")).to eq([
+ {
+ offset: 0,
+ content: [{ text: 'Hello world' }]
+ }
+ ])
+ end
+
+ context 'when section name contains .-_ and capital letters' do
+ let(:section_name) { 'a.Legit-SeCtIoN_namE' }
+
+ it 'sanitizes the section name' do
+ expect(convert_json("Hello#{section_start}world!")).to eq([
+ {
+ offset: 0,
+ content: [{ text: 'Hello' }]
+ },
+ {
+ offset: 5,
+ content: [{ text: 'world!' }],
+ section: 'a-legit-section-name',
+ section_header: true
+ }
+ ])
+ end
+ end
+
+ context 'when section name includes $' do
+ let(:section_name) { 'my_$ection' }
+
+ it 'ignores the section' do
+ expect(convert_json("#{section_start}hello")).to eq([
+ {
+ offset: 0,
+ content: [{ text: "#{section_start.gsub("\033[0K", '')}hello" }]
+ }
+ ])
+ end
+ end
+
+ context 'when section name includes <' do
+ let(:section_name) { '<a_tag>' }
+
+ it 'ignores the section' do
+ expect(convert_json("#{section_start}hello")).to eq([
+ {
+ offset: 0,
+ content: [{ text: "#{section_start.gsub("\033[0K", '').gsub('<', '&lt;')}hello" }]
+ }
+ ])
+ end
+ end
+
+ it 'prevents XSS injection' do
+ trace = "#{section_start}section_end:1:2<script>alert('XSS Hack!');</script>#{section_end}"
+ expect(convert_json(trace)).to eq([
+ {
+ offset: 0,
+ content: [{ text: "section_end:1:2&lt;script>alert('XSS Hack!');&lt;/script>" }],
+ section: 'prepare-script',
+ section_header: true
+ },
+ {
+ offset: 95,
+ content: [],
+ section: 'prepare-script',
+ section_duration: '01:03'
+ },
+ {
+ offset: 95,
+ content: []
+ }
+ ])
+ end
+
+ context 'with nested section' do
+ let(:nested_section_name) { 'prepare-script-nested' }
+ let(:nested_section_duration) { 2.seconds }
+ let(:nested_section_start_time) { Time.new(2019, 9, 17).utc }
+ let(:nested_section_end_time) { nested_section_start_time + nested_section_duration }
+ let(:nested_section_start) { "section_start:#{nested_section_start_time.to_i}:#{nested_section_name}\r\033[0K"}
+ let(:nested_section_end) { "section_end:#{nested_section_end_time.to_i}:#{nested_section_name}\r\033[0K"}
+
+ it 'adds multiple sections to the lines inside the nested section' do
+ trace = "Hello#{section_start}foo#{nested_section_start}bar#{nested_section_end}baz#{section_end}world"
+
+ expect(convert_json(trace)).to eq([
+ {
+ offset: 0,
+ content: [{ text: 'Hello' }]
+ },
+ {
+ offset: 5,
+ content: [{ text: 'foo' }],
+ section: 'prepare-script',
+ section_header: true
+ },
+ {
+ offset: 52,
+ content: [{ text: 'bar' }],
+ section: 'prepare-script-nested',
+ section_header: true
+ },
+ {
+ offset: 106,
+ content: [],
+ section: 'prepare-script-nested',
+ section_duration: '00:02'
+ },
+ {
+ offset: 106,
+ content: [{ text: 'baz' }],
+ section: 'prepare-script'
+ },
+ {
+ offset: 158,
+ content: [],
+ section: 'prepare-script',
+ section_duration: '01:03'
+ },
+ {
+ offset: 158,
+ content: [{ text: 'world' }]
+ }
+ ])
+ end
+
+ it 'adds multiple sections to the lines inside the nested section and closes all sections together' do
+ trace = "Hello#{section_start}\e[91mfoo\e[0m#{nested_section_start}bar#{nested_section_end}#{section_end}"
+
+ expect(convert_json(trace)).to eq([
+ {
+ offset: 0,
+ content: [{ text: 'Hello' }]
+ },
+ {
+ offset: 5,
+ content: [{ text: 'foo', style: 'term-fg-l-red' }],
+ section: 'prepare-script',
+ section_header: true
+ },
+ {
+ offset: 61,
+ content: [{ text: 'bar' }],
+ section: 'prepare-script-nested',
+ section_header: true
+ },
+ {
+ offset: 115,
+ content: [],
+ section: 'prepare-script-nested',
+ section_duration: '00:02'
+ },
+ {
+ offset: 115,
+ content: [],
+ section: 'prepare-script',
+ section_duration: '01:03'
+ },
+ {
+ offset: 164,
+ content: []
+ }
+ ])
+ end
+ end
+ end
+
+ describe 'incremental updates' do
+ let(:pass1_stream) { StringIO.new(pre_text) }
+ let(:pass2_stream) { StringIO.new(pre_text + text) }
+ let(:pass1) { subject.convert(pass1_stream) }
+ let(:pass2) { subject.convert(pass2_stream, pass1.state) }
+
+ context 'with split word' do
+ let(:pre_text) { "\e[1mHello " }
+ let(:text) { "World" }
+
+ let(:lines) do
+ [
+ { offset: 0, content: [{ text: 'Hello World', style: 'term-bold' }] }
+ ]
+ end
+
+ it 'returns the full line' do
+ expect(pass2.lines).to eq(lines)
+ expect(pass2.append).to be_falsey
+ end
+ end
+
+ context 'with split word on second line' do
+ let(:pre_text) { "Good\nmorning " }
+ let(:text) { "World" }
+
+ let(:lines) do
+ [
+ { offset: 5, content: [{ text: 'morning World' }] }
+ ]
+ end
+
+ it 'returns all lines since last partially processed line' do
+ expect(pass2.lines).to eq(lines)
+ expect(pass2.append).to be_truthy
+ end
+ end
+
+ context 'with split sequence across multiple lines' do
+ let(:pre_text) { "\e[1mgood\nmorning\n" }
+ let(:text) { "\e[3mworld" }
+
+ let(:lines) do
+ [
+ { offset: 17, content: [{ text: 'world', style: 'term-bold term-italic' }] }
+ ]
+ end
+
+ it 'returns the full line' do
+ expect(pass2.lines).to eq(lines)
+ expect(pass2.append).to be_truthy
+ end
+ end
+
+ context 'with split partial sequence' do
+ let(:pre_text) { "hello\e" }
+ let(:text) { "[1m world" }
+
+ let(:lines) do
+ [
+ { offset: 0, content: [
+ { text: 'hello' },
+ { text: ' world', style: 'term-bold' }
+ ] }
+ ]
+ end
+
+ it 'returns the full line' do
+ expect(pass2.lines).to eq(lines)
+ expect(pass2.append).to be_falsey
+ end
+ end
+
+ context 'with split new line' do
+ let(:pre_text) { "hello\r" }
+ let(:text) { "\nworld" }
+
+ let(:lines) do
+ [
+ { offset: 0, content: [{ text: 'hello' }] },
+ { offset: 7, content: [{ text: 'world' }] }
+ ]
+ end
+
+ it 'returns the full line' do
+ expect(pass2.lines).to eq(lines)
+ expect(pass2.append).to be_falsey
+ end
+ end
+
+ context 'with split section' do
+ let(:section_name) { 'prepare-script' }
+ let(:section_duration) { 63.seconds }
+ let(:section_start_time) { Time.new(2019, 9, 17).utc }
+ let(:section_end_time) { section_start_time + section_duration }
+ let(:section_start) { "section_start:#{section_start_time.to_i}:#{section_name}\r\033[0K"}
+ let(:section_end) { "section_end:#{section_end_time.to_i}:#{section_name}\r\033[0K"}
+
+ context 'with split section body' do
+ let(:pre_text) { "#{section_start}this is a header\nand " }
+ let(:text) { "this\n is a body" }
+
+ let(:lines) do
+ [
+ {
+ offset: 61,
+ content: [{ text: 'and this' }],
+ section: 'prepare-script'
+ },
+ {
+ offset: 70,
+ content: [{ text: ' is a body' }],
+ section: 'prepare-script'
+ }
+ ]
+ end
+
+ it 'returns the full line' do
+ expect(pass2.lines).to eq(lines)
+ expect(pass2.append).to be_truthy
+ end
+ end
+
+ context 'with split section where header is also split' do
+ let(:pre_text) { "#{section_start}this is " }
+ let(:text) { "a header\nand body" }
+
+ let(:lines) do
+ [
+ {
+ offset: 0,
+ content: [{ text: 'this is a header' }],
+ section: 'prepare-script',
+ section_header: true
+ },
+ {
+ offset: 61,
+ content: [{ text: 'and body' }],
+ section: 'prepare-script'
+ }
+ ]
+ end
+
+ it 'returns the full line' do
+ expect(pass2.lines).to eq(lines)
+ expect(pass2.append).to be_falsey
+ end
+ end
+
+ context 'with split section end' do
+ let(:pre_text) { "#{section_start}this is a header\nthe" }
+ let(:text) { " body\nthe end#{section_end}" }
+
+ let(:lines) do
+ [
+ {
+ offset: 61,
+ content: [{ text: 'the body' }],
+ section: 'prepare-script'
+ },
+ {
+ offset: 70,
+ content: [{ text: 'the end' }],
+ section: 'prepare-script'
+ },
+ {
+ offset: 77,
+ content: [],
+ section: 'prepare-script',
+ section_duration: '01:03'
+ },
+ {
+ offset: 77,
+ content: []
+ }
+ ]
+ end
+
+ it 'returns the full line' do
+ expect(pass2.lines).to eq(lines)
+ expect(pass2.append).to be_truthy
+ end
+ end
+ end
+ end
+
+ describe 'trucates' do
+ let(:text) { "Hello World" }
+ let(:stream) { StringIO.new(text) }
+ let(:subject) { described_class.convert(stream) }
+
+ before do
+ stream.seek(3, IO::SEEK_SET)
+ end
+
+ it "returns truncated output" do
+ expect(subject.truncated).to be_truthy
+ end
+
+ it "does not append output" do
+ expect(subject.append).to be_falsey
+ end
+ end
+
+ def convert_json(data)
+ stream = StringIO.new(data)
+ subject.convert(stream).lines
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb b/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb
index 24d17eb0fb3..73c3cad88bc 100644
--- a/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb
+++ b/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb
@@ -34,27 +34,32 @@ describe Gitlab::Ci::Build::Artifacts::Metadata::Entry do
describe '#basename' do
subject { |example| path(example).basename }
+
it { is_expected.to eq 'absolute_path' }
end
end
describe 'path/dir_1/', path: 'path/dir_1/' do
subject { |example| path(example) }
+
it { is_expected.to have_parent }
it { is_expected.to be_directory }
describe '#basename' do
subject { |example| path(example).basename }
+
it { is_expected.to eq 'dir_1/' }
end
describe '#name' do
subject { |example| path(example).name }
+
it { is_expected.to eq 'dir_1' }
end
describe '#parent' do
subject { |example| path(example).parent }
+
it { is_expected.to eq entry('path/') }
end
@@ -102,21 +107,25 @@ describe Gitlab::Ci::Build::Artifacts::Metadata::Entry do
describe '#nodes' do
subject { |example| path(example).nodes }
+
it { is_expected.to eq 2 }
end
describe '#exists?' do
subject { |example| path(example).exists? }
+
it { is_expected.to be true }
end
describe '#empty?' do
subject { |example| path(example).empty? }
+
it { is_expected.to be false }
end
describe '#total_size' do
subject { |example| path(example).total_size }
+
it { is_expected.to eq(30) }
end
end
@@ -124,10 +133,12 @@ describe Gitlab::Ci::Build::Artifacts::Metadata::Entry do
describe 'empty path', path: '' do
subject { |example| path(example) }
+
it { is_expected.not_to have_parent }
describe '#children' do
subject { |example| path(example).children }
+
it { expect(subject.count).to eq 3 }
end
end
@@ -135,6 +146,7 @@ describe Gitlab::Ci::Build::Artifacts::Metadata::Entry do
describe 'path/dir_1/subdir/subfile', path: 'path/dir_1/subdir/subfile' do
describe '#nodes' do
subject { |example| path(example).nodes }
+
it { is_expected.to eq 4 }
end
@@ -153,11 +165,13 @@ describe Gitlab::Ci::Build::Artifacts::Metadata::Entry do
describe 'non-existent/', path: 'non-existent/' do
describe '#empty?' do
subject { |example| path(example).empty? }
+
it { is_expected.to be true }
end
describe '#exists?' do
subject { |example| path(example).exists? }
+
it { is_expected.to be false }
end
end
@@ -165,6 +179,7 @@ describe Gitlab::Ci::Build::Artifacts::Metadata::Entry do
describe 'another_directory/', path: 'another_directory/' do
describe '#empty?' do
subject { |example| path(example).empty? }
+
it { is_expected.to be true }
end
end
diff --git a/spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb b/spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb
index ff189c4701e..bfa65c66b33 100644
--- a/spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb
+++ b/spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb
@@ -76,21 +76,25 @@ describe Gitlab::Ci::Build::Artifacts::Metadata do
describe '#to_entry' do
subject { metadata('').to_entry }
+
it { is_expected.to be_an_instance_of(Gitlab::Ci::Build::Artifacts::Metadata::Entry) }
end
describe '#full_version' do
subject { metadata('').full_version }
+
it { is_expected.to eq 'GitLab Build Artifacts Metadata 0.0.1' }
end
describe '#version' do
subject { metadata('').version }
+
it { is_expected.to eq '0.0.1' }
end
describe '#errors' do
subject { metadata('').errors }
+
it { is_expected.to eq({}) }
end
end
diff --git a/spec/lib/gitlab/ci/build/rules/rule/clause/changes_spec.rb b/spec/lib/gitlab/ci/build/rules/rule/clause/changes_spec.rb
new file mode 100644
index 00000000000..076de3646b0
--- /dev/null
+++ b/spec/lib/gitlab/ci/build/rules/rule/clause/changes_spec.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Ci::Build::Rules::Rule::Clause::Changes do
+ describe '#satisfied_by?' do
+ it_behaves_like 'a glob matching rule' do
+ let(:pipeline) { build(:ci_pipeline) }
+
+ before do
+ allow(pipeline).to receive(:modified_paths).and_return(files.keys)
+ end
+
+ subject { described_class.new(globs).satisfied_by?(pipeline, nil) }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/build/rules/rule/clause/exists_spec.rb b/spec/lib/gitlab/ci/build/rules/rule/clause/exists_spec.rb
new file mode 100644
index 00000000000..3605bac7dfc
--- /dev/null
+++ b/spec/lib/gitlab/ci/build/rules/rule/clause/exists_spec.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Ci::Build::Rules::Rule::Clause::Exists do
+ describe '#satisfied_by?' do
+ let(:pipeline) { build(:ci_pipeline, project: project, sha: project.repository.head_commit.sha) }
+
+ subject { described_class.new(globs).satisfied_by?(pipeline, nil) }
+
+ it_behaves_like 'a glob matching rule' do
+ let(:project) { create(:project, :custom_repo, files: files) }
+ end
+
+ context 'after pattern comparision limit is reached' do
+ let(:globs) { ['*definitely_not_a_matching_glob*'] }
+ let(:project) { create(:project, :repository) }
+
+ before do
+ stub_const('Gitlab::Ci::Build::Rules::Rule::Clause::Exists::MAX_PATTERN_COMPARISONS', 2)
+ expect(File).to receive(:fnmatch?).exactly(2).times.and_call_original
+ end
+
+ it { is_expected.to be_truthy }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/edge_stages_injector_spec.rb b/spec/lib/gitlab/ci/config/edge_stages_injector_spec.rb
new file mode 100644
index 00000000000..042f9b591b6
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/edge_stages_injector_spec.rb
@@ -0,0 +1,112 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+describe Gitlab::Ci::Config::EdgeStagesInjector do
+ describe '#call' do
+ subject { described_class.new(config).to_hash }
+
+ context 'without stages' do
+ let(:config) do
+ {
+ test: { script: 'test' }
+ }
+ end
+
+ it { is_expected.to match config }
+ end
+
+ context 'with values' do
+ let(:config) do
+ {
+ stages: %w[stage1 stage2],
+ test: { script: 'test' }
+ }
+ end
+
+ let(:expected_stages) do
+ %w[.pre stage1 stage2 .post]
+ end
+
+ it { is_expected.to match(config.merge(stages: expected_stages)) }
+ end
+
+ context 'with bad values' do
+ let(:config) do
+ {
+ stages: 'stage1',
+ test: { script: 'test' }
+ }
+ end
+
+ it { is_expected.to match(config) }
+ end
+
+ context 'with collision values' do
+ let(:config) do
+ {
+ stages: %w[.post stage1 .pre .post stage2],
+ test: { script: 'test' }
+ }
+ end
+
+ let(:expected_stages) do
+ %w[.pre stage1 stage2 .post]
+ end
+
+ it { is_expected.to match(config.merge(stages: expected_stages)) }
+ end
+
+ context 'with types' do
+ let(:config) do
+ {
+ types: %w[stage1 stage2],
+ test: { script: 'test' }
+ }
+ end
+
+ let(:expected_config) do
+ {
+ types: %w[.pre stage1 stage2 .post],
+ test: { script: 'test' }
+ }
+ end
+
+ it { is_expected.to match expected_config }
+ end
+
+ context 'with types' do
+ let(:config) do
+ {
+ types: %w[.post stage1 .pre .post stage2],
+ test: { script: 'test' }
+ }
+ end
+
+ let(:expected_config) do
+ {
+ types: %w[.pre stage1 stage2 .post],
+ test: { script: 'test' }
+ }
+ end
+
+ it { is_expected.to match expected_config }
+ end
+ end
+
+ describe '.wrap_stages' do
+ subject { described_class.wrap_stages(stages) }
+
+ context 'with empty value' do
+ let(:stages) {}
+
+ it { is_expected.to eq %w[.pre .post] }
+ end
+
+ context 'with values' do
+ let(:stages) { %w[s1 .pre] }
+
+ it { is_expected.to eq %w[.pre s1 .post] }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/entry/cache_spec.rb b/spec/lib/gitlab/ci/config/entry/cache_spec.rb
index 4cb63168ec7..9aab3664e1c 100644
--- a/spec/lib/gitlab/ci/config/entry/cache_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/cache_spec.rb
@@ -69,6 +69,7 @@ describe Gitlab::Ci::Config::Entry::Cache do
context 'when entry value is not correct' do
describe '#errors' do
subject { entry.errors }
+
context 'when is not a hash' do
let(:config) { 'ls' }
diff --git a/spec/lib/gitlab/ci/config/entry/coverage_spec.rb b/spec/lib/gitlab/ci/config/entry/coverage_spec.rb
index 48d0864cfca..877e3ec6216 100644
--- a/spec/lib/gitlab/ci/config/entry/coverage_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/coverage_spec.rb
@@ -11,11 +11,13 @@ describe Gitlab::Ci::Config::Entry::Coverage do
describe '#errors' do
subject { entry.errors }
+
it { is_expected.to include(/coverage config must be a regular expression/) }
end
describe '#valid?' do
subject { entry }
+
it { is_expected.not_to be_valid }
end
end
@@ -25,16 +27,19 @@ describe Gitlab::Ci::Config::Entry::Coverage do
describe '#value' do
subject { entry.value }
+
it { is_expected.to eq(config[1...-1]) }
end
describe '#errors' do
subject { entry.errors }
+
it { is_expected.to be_empty }
end
describe '#valid?' do
subject { entry }
+
it { is_expected.to be_valid }
end
end
@@ -44,11 +49,13 @@ describe Gitlab::Ci::Config::Entry::Coverage do
describe '#errors' do
subject { entry.errors }
+
it { is_expected.to include(/coverage config must be a regular expression/) }
end
describe '#valid?' do
subject { entry }
+
it { is_expected.not_to be_valid }
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/root_spec.rb b/spec/lib/gitlab/ci/config/entry/root_spec.rb
index 968dbb9c7f2..7e1a80414d4 100644
--- a/spec/lib/gitlab/ci/config/entry/root_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/root_spec.rb
@@ -215,7 +215,7 @@ describe Gitlab::Ci::Config::Entry::Root do
describe '#stages_value' do
it 'returns an array of root stages' do
- expect(root.stages_value).to eq %w[build test deploy]
+ expect(root.stages_value).to eq %w[.pre build test deploy .post]
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/rules/rule_spec.rb b/spec/lib/gitlab/ci/config/entry/rules/rule_spec.rb
index 18037a5612c..9d4f7153cd0 100644
--- a/spec/lib/gitlab/ci/config/entry/rules/rule_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/rules/rule_spec.rb
@@ -103,6 +103,52 @@ describe Gitlab::Ci::Config::Entry::Rules::Rule do
end
end
+ context 'when using a long list as an invalid changes: clause' do
+ let(:config) { { changes: ['app/'] * 51 } }
+
+ it { is_expected.not_to be_valid }
+
+ it 'returns errors' do
+ expect(subject.errors).to include(/changes is too long \(maximum is 50 characters\)/)
+ end
+ end
+
+ context 'when using a exists: clause' do
+ let(:config) { { exists: %w[app/ lib/ spec/ other/* paths/**/*.rb] } }
+
+ it { is_expected.to be_valid }
+ end
+
+ context 'when using a string as an invalid exists: clause' do
+ let(:config) { { exists: 'a regular string' } }
+
+ it { is_expected.not_to be_valid }
+
+ it 'reports an error about invalid policy' do
+ expect(subject.errors).to include(/should be an array of strings/)
+ end
+ end
+
+ context 'when using a list as an invalid exists: clause' do
+ let(:config) { { exists: [1, 2] } }
+
+ it { is_expected.not_to be_valid }
+
+ it 'returns errors' do
+ expect(subject.errors).to include(/exists should be an array of strings/)
+ end
+ end
+
+ context 'when using a long list as an invalid exists: clause' do
+ let(:config) { { exists: ['app/'] * 51 } }
+
+ it { is_expected.not_to be_valid }
+
+ it 'returns errors' do
+ expect(subject.errors).to include(/exists is too long \(maximum is 50 characters\)/)
+ end
+ end
+
context 'specifying a delayed job' do
let(:config) { { if: '$THIS || $THAT', when: 'delayed', start_in: '15 minutes' } }
@@ -198,6 +244,12 @@ describe Gitlab::Ci::Config::Entry::Rules::Rule do
expect(entry.value).to eq(config)
end
end
+
+ context 'when using a exists: clause' do
+ let(:config) { { exists: %w[app/ lib/ spec/ other/* paths/**/*.rb] } }
+
+ it { is_expected.to eq(config) }
+ end
end
describe '.default' do
diff --git a/spec/lib/gitlab/ci/config/entry/stages_spec.rb b/spec/lib/gitlab/ci/config/entry/stages_spec.rb
index 97970522104..3e6ff8eca28 100644
--- a/spec/lib/gitlab/ci/config/entry/stages_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/stages_spec.rb
@@ -42,7 +42,7 @@ describe Gitlab::Ci::Config::Entry::Stages do
describe '.default' do
it 'returns default stages' do
- expect(described_class.default).to eq %w[build test deploy]
+ expect(described_class.default).to eq %w[.pre build test deploy .post]
end
end
end
diff --git a/spec/lib/gitlab/ci/config/external/context_spec.rb b/spec/lib/gitlab/ci/config/external/context_spec.rb
new file mode 100644
index 00000000000..610646ca85a
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/external/context_spec.rb
@@ -0,0 +1,128 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+describe Gitlab::Ci::Config::External::Context do
+ let(:project) { double('Project') }
+ let(:user) { double('User') }
+ let(:sha) { '12345' }
+ let(:attributes) { { project: project, user: user, sha: sha } }
+
+ subject(:subject) { described_class.new(**attributes) }
+
+ describe 'attributes' do
+ context 'with values' do
+ it { is_expected.to have_attributes(**attributes) }
+ it { expect(subject.expandset).to eq(Set.new) }
+ it { expect(subject.execution_deadline).to eq(0) }
+ end
+
+ context 'without values' do
+ let(:attributes) { { project: nil, user: nil, sha: nil } }
+
+ it { is_expected.to have_attributes(**attributes) }
+ it { expect(subject.expandset).to eq(Set.new) }
+ it { expect(subject.execution_deadline).to eq(0) }
+ end
+ end
+
+ describe '#set_deadline' do
+ let(:stubbed_time) { 1 }
+
+ before do
+ allow(subject).to receive(:current_monotonic_time).and_return(stubbed_time)
+ end
+
+ context 'with a float value' do
+ let(:timeout_seconds) { 10.5.seconds }
+
+ it 'updates execution_deadline' do
+ expect { subject.set_deadline(timeout_seconds) }
+ .to change { subject.execution_deadline }
+ .to(timeout_seconds + stubbed_time)
+ end
+ end
+
+ context 'with nil as a value' do
+ let(:timeout_seconds) {}
+
+ it 'updates execution_deadline' do
+ expect { subject.set_deadline(timeout_seconds) }
+ .to change { subject.execution_deadline }
+ .to(stubbed_time)
+ end
+ end
+ end
+
+ describe '#check_execution_time!' do
+ before do
+ allow(subject).to receive(:current_monotonic_time).and_return(stubbed_time)
+ allow(subject).to receive(:execution_deadline).and_return(stubbed_deadline)
+ end
+
+ context 'when execution is expired' do
+ let(:stubbed_time) { 2 }
+ let(:stubbed_deadline) { 1 }
+
+ it 'raises an error' do
+ expect { subject.check_execution_time! }
+ .to raise_error(described_class::TimeoutError)
+ end
+ end
+
+ context 'when execution is not expired' do
+ let(:stubbed_time) { 1 }
+ let(:stubbed_deadline) { 2 }
+
+ it 'does not raises any errors' do
+ expect { subject.check_execution_time! }.not_to raise_error
+ end
+ end
+
+ context 'without setting a deadline' do
+ let(:stubbed_time) { 2 }
+ let(:stubbed_deadline) { 1 }
+
+ before do
+ allow(subject).to receive(:execution_deadline).and_call_original
+ end
+
+ it 'does not raises any errors' do
+ expect { subject.check_execution_time! }.not_to raise_error
+ end
+ end
+ end
+
+ describe '#mutate' do
+ shared_examples 'a mutated context' do
+ let(:mutated) { subject.mutate(new_attributes) }
+
+ before do
+ subject.expandset << :a_file
+ subject.set_deadline(15.seconds)
+ end
+
+ it { expect(mutated).not_to eq(subject) }
+ it { expect(mutated).to be_a(described_class) }
+ it { expect(mutated).to have_attributes(new_attributes) }
+ it { expect(mutated.expandset).to eq(subject.expandset) }
+ it { expect(mutated.execution_deadline).to eq(mutated.execution_deadline) }
+ end
+
+ context 'with attributes' do
+ let(:new_attributes) { { project: double, user: double, sha: '56789' } }
+
+ it_behaves_like 'a mutated context'
+ end
+
+ context 'without attributes' do
+ let(:new_attributes) { {} }
+
+ it_behaves_like 'a mutated context'
+ end
+ end
+
+ describe '#sentry_payload' do
+ it { expect(subject.sentry_payload).to match(a_hash_including(:project, :user)) }
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/external/file/base_spec.rb b/spec/lib/gitlab/ci/config/external/file/base_spec.rb
index af995f4869a..d472d6527e2 100644
--- a/spec/lib/gitlab/ci/config/external/file/base_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/file/base_spec.rb
@@ -1,13 +1,14 @@
# frozen_string_literal: true
-require 'fast_spec_helper'
+require 'spec_helper'
describe Gitlab::Ci::Config::External::File::Base do
- let(:context) { described_class::Context.new(nil, 'HEAD', nil, Set.new) }
+ let(:context_params) { { sha: 'HEAD' } }
+ let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) }
let(:test_class) do
Class.new(described_class) do
- def initialize(params, context = {})
+ def initialize(params, context)
@location = params
super
@@ -20,6 +21,9 @@ describe Gitlab::Ci::Config::External::File::Base do
before do
allow_any_instance_of(test_class)
.to receive(:content).and_return('key: value')
+
+ allow_any_instance_of(Gitlab::Ci::Config::External::Context)
+ .to receive(:check_execution_time!)
end
describe '#matching?' do
diff --git a/spec/lib/gitlab/ci/config/external/file/local_spec.rb b/spec/lib/gitlab/ci/config/external/file/local_spec.rb
index 9451db9522a..95f0c93e758 100644
--- a/spec/lib/gitlab/ci/config/external/file/local_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/file/local_spec.rb
@@ -7,10 +7,17 @@ describe Gitlab::Ci::Config::External::File::Local do
set(:user) { create(:user) }
let(:sha) { '12345' }
- let(:context) { described_class::Context.new(project, sha, user, Set.new) }
+ let(:context_params) { { project: project, sha: sha, user: user } }
+ let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) }
+
let(:params) { { local: location } }
let(:local_file) { described_class.new(params, context) }
+ before do
+ allow_any_instance_of(Gitlab::Ci::Config::External::Context)
+ .to receive(:check_execution_time!)
+ end
+
describe '#matching?' do
context 'when a local is specified' do
let(:params) { { local: 'file' } }
@@ -109,7 +116,7 @@ describe Gitlab::Ci::Config::External::File::Local do
describe '#expand_context' do
let(:location) { 'location.yml' }
- subject { local_file.send(:expand_context) }
+ subject { local_file.send(:expand_context_attrs) }
it 'inherits project, user and sha' do
is_expected.to include(user: user, project: project, sha: sha)
diff --git a/spec/lib/gitlab/ci/config/external/file/project_spec.rb b/spec/lib/gitlab/ci/config/external/file/project_spec.rb
index 4acb4f324d3..dd869c227a1 100644
--- a/spec/lib/gitlab/ci/config/external/file/project_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/file/project_spec.rb
@@ -8,11 +8,15 @@ describe Gitlab::Ci::Config::External::File::Project do
set(:user) { create(:user) }
let(:context_user) { user }
- let(:context) { described_class::Context.new(context_project, '12345', context_user, Set.new) }
+ let(:context_params) { { project: context_project, sha: '12345', user: context_user } }
+ let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) }
let(:project_file) { described_class.new(params, context) }
before do
project.add_developer(user)
+
+ allow_any_instance_of(Gitlab::Ci::Config::External::Context)
+ .to receive(:check_execution_time!)
end
describe '#matching?' do
@@ -145,7 +149,7 @@ describe Gitlab::Ci::Config::External::File::Project do
describe '#expand_context' do
let(:params) { { file: 'file.yml', project: project.full_path, ref: 'master' } }
- subject { project_file.send(:expand_context) }
+ subject { project_file.send(:expand_context_attrs) }
it 'inherits user, and target project and sha' do
is_expected.to include(user: user, project: project, sha: project.commit('master').id)
diff --git a/spec/lib/gitlab/ci/config/external/file/remote_spec.rb b/spec/lib/gitlab/ci/config/external/file/remote_spec.rb
index 4a097b59216..08db00dda9d 100644
--- a/spec/lib/gitlab/ci/config/external/file/remote_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/file/remote_spec.rb
@@ -5,7 +5,8 @@ require 'spec_helper'
describe Gitlab::Ci::Config::External::File::Remote do
include StubRequests
- let(:context) { described_class::Context.new(nil, '12345', nil, Set.new) }
+ let(:context_params) { { sha: '12345' } }
+ let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) }
let(:params) { { remote: location } }
let(:remote_file) { described_class.new(params, context) }
let(:location) { 'https://gitlab.com/gitlab-org/gitlab-foss/blob/1234/.gitlab-ci-1.yml' }
@@ -19,6 +20,11 @@ describe Gitlab::Ci::Config::External::File::Remote do
HEREDOC
end
+ before do
+ allow_any_instance_of(Gitlab::Ci::Config::External::Context)
+ .to receive(:check_execution_time!)
+ end
+
describe '#matching?' do
context 'when a remote is specified' do
let(:params) { { remote: 'http://remote' } }
@@ -187,10 +193,10 @@ describe Gitlab::Ci::Config::External::File::Remote do
describe '#expand_context' do
let(:params) { { remote: 'http://remote' } }
- subject { remote_file.send(:expand_context) }
+ subject { remote_file.send(:expand_context_attrs) }
it 'drops all parameters' do
- is_expected.to include(user: nil, project: nil, sha: nil)
+ is_expected.to be_empty
end
end
end
diff --git a/spec/lib/gitlab/ci/config/external/file/template_spec.rb b/spec/lib/gitlab/ci/config/external/file/template_spec.rb
index 1609b8fd66b..164b5800abf 100644
--- a/spec/lib/gitlab/ci/config/external/file/template_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/file/template_spec.rb
@@ -6,12 +6,18 @@ describe Gitlab::Ci::Config::External::File::Template do
set(:project) { create(:project) }
set(:user) { create(:user) }
- let(:context) { described_class::Context.new(project, '12345', user, Set.new) }
+ let(:context_params) { { project: project, sha: '12345', user: user } }
+ let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) }
let(:template) { 'Auto-DevOps.gitlab-ci.yml' }
let(:params) { { template: template } }
let(:template_file) { described_class.new(params, context) }
+ before do
+ allow_any_instance_of(Gitlab::Ci::Config::External::Context)
+ .to receive(:check_execution_time!)
+ end
+
describe '#matching?' do
context 'when a template is specified' do
let(:params) { { template: 'some-template' } }
@@ -97,10 +103,10 @@ describe Gitlab::Ci::Config::External::File::Template do
describe '#expand_context' do
let(:location) { 'location.yml' }
- subject { template_file.send(:expand_context) }
+ subject { template_file.send(:expand_context_attrs) }
it 'drops all parameters' do
- is_expected.to include(user: nil, project: nil, sha: nil)
+ is_expected.to be_empty
end
end
end
diff --git a/spec/lib/gitlab/ci/config/external/mapper_spec.rb b/spec/lib/gitlab/ci/config/external/mapper_spec.rb
index 43708852594..8d09aa47f12 100644
--- a/spec/lib/gitlab/ci/config/external/mapper_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/mapper_spec.rb
@@ -11,7 +11,8 @@ describe Gitlab::Ci::Config::External::Mapper do
let(:local_file) { '/lib/gitlab/ci/templates/non-existent-file.yml' }
let(:remote_url) { 'https://gitlab.com/gitlab-org/gitlab-foss/blob/1234/.gitlab-ci-1.yml' }
let(:template_file) { 'Auto-DevOps.gitlab-ci.yml' }
- let(:expandset) { Set.new }
+ let(:context_params) { { project: project, sha: '123456', user: user } }
+ let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) }
let(:file_content) do
<<~HEREDOC
@@ -21,10 +22,13 @@ describe Gitlab::Ci::Config::External::Mapper do
before do
stub_full_request(remote_url).to_return(body: file_content)
+
+ allow_any_instance_of(Gitlab::Ci::Config::External::Context)
+ .to receive(:check_execution_time!)
end
describe '#process' do
- subject { described_class.new(values, project: project, sha: '123456', user: user, expandset: expandset).process }
+ subject { described_class.new(values, context).process }
context "when single 'include' keyword is defined" do
context 'when the string is a local file' do
diff --git a/spec/lib/gitlab/ci/config/external/processor_spec.rb b/spec/lib/gitlab/ci/config/external/processor_spec.rb
index 3b1a1e804f0..bb2d3f66972 100644
--- a/spec/lib/gitlab/ci/config/external/processor_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/processor_spec.rb
@@ -9,12 +9,16 @@ describe Gitlab::Ci::Config::External::Processor do
set(:another_project) { create(:project, :repository) }
set(:user) { create(:user) }
- let(:expandset) { Set.new }
let(:sha) { '12345' }
- let(:processor) { described_class.new(values, project: project, sha: '12345', user: user, expandset: expandset) }
+ let(:context_params) { { project: project, sha: sha, user: user } }
+ let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) }
+ let(:processor) { described_class.new(values, context) }
before do
project.add_developer(user)
+
+ allow_any_instance_of(Gitlab::Ci::Config::External::Context)
+ .to receive(:check_execution_time!)
end
describe "#perform" do
diff --git a/spec/lib/gitlab/ci/config_spec.rb b/spec/lib/gitlab/ci/config_spec.rb
index 839b4f9261d..b254f9af2f1 100644
--- a/spec/lib/gitlab/ci/config_spec.rb
+++ b/spec/lib/gitlab/ci/config_spec.rb
@@ -7,6 +7,11 @@ describe Gitlab::Ci::Config do
set(:user) { create(:user) }
+ before do
+ allow_any_instance_of(Gitlab::Ci::Config::External::Context)
+ .to receive(:check_execution_time!)
+ end
+
let(:config) do
described_class.new(yml, project: nil, sha: nil, user: nil)
end
@@ -46,6 +51,54 @@ describe Gitlab::Ci::Config do
end
end
end
+
+ describe '#stages' do
+ subject(:subject) { config.stages }
+
+ context 'with default stages' do
+ let(:default_stages) do
+ %w[.pre build test deploy .post]
+ end
+
+ it { is_expected.to eq default_stages }
+ end
+
+ context 'with custom stages' do
+ let(:yml) do
+ <<-EOS
+ stages:
+ - stage1
+ - stage2
+ job1:
+ stage: stage1
+ script:
+ - ls
+ EOS
+ end
+
+ it { is_expected.to eq %w[.pre stage1 stage2 .post] }
+ end
+
+ context 'with feature disabled' do
+ before do
+ stub_feature_flags(ci_pre_post_pipeline_stages: false)
+ end
+
+ let(:yml) do
+ <<-EOS
+ stages:
+ - stage1
+ - stage2
+ job1:
+ stage: stage1
+ script:
+ - ls
+ EOS
+ end
+
+ it { is_expected.to eq %w[stage1 stage2] }
+ end
+ end
end
context 'when using extendable hash' do
@@ -303,6 +356,49 @@ describe Gitlab::Ci::Config do
end
end
+ context "when it takes too long to evaluate includes" do
+ before do
+ allow_any_instance_of(Gitlab::Ci::Config::External::Context)
+ .to receive(:check_execution_time!)
+ .and_call_original
+
+ allow_any_instance_of(Gitlab::Ci::Config::External::Context)
+ .to receive(:set_deadline)
+ .with(described_class::TIMEOUT_SECONDS)
+ .and_call_original
+
+ allow_any_instance_of(Gitlab::Ci::Config::External::Context)
+ .to receive(:execution_expired?)
+ .and_return(true)
+ end
+
+ it 'raises error TimeoutError' do
+ expect(Gitlab::Sentry).to receive(:track_exception)
+
+ expect { config }.to raise_error(
+ described_class::ConfigError,
+ 'Resolving config took longer than expected'
+ )
+ end
+ end
+
+ context 'when context expansion timeout is disabled' do
+ before do
+ allow_any_instance_of(Gitlab::Ci::Config::External::Context)
+ .to receive(:check_execution_time!)
+ .and_call_original
+
+ allow(Feature)
+ .to receive(:enabled?)
+ .with(:ci_limit_yaml_expansion, project, default_enabled: true)
+ .and_return(false)
+ end
+
+ it 'does not raises errors' do
+ expect { config }.not_to raise_error
+ end
+ end
+
describe 'external file version' do
context 'when external local file SHA is defined' do
it 'is using a defined value' do
diff --git a/spec/lib/gitlab/ci/parsers/test/junit_spec.rb b/spec/lib/gitlab/ci/parsers/test/junit_spec.rb
index 8ff60710f67..6a7fe7a5927 100644
--- a/spec/lib/gitlab/ci/parsers/test/junit_spec.rb
+++ b/spec/lib/gitlab/ci/parsers/test/junit_spec.rb
@@ -38,12 +38,14 @@ describe Gitlab::Ci::Parsers::Test::Junit do
end
end
- context 'when there is only one <testcase> in <testsuite>' do
+ context 'when there is only one <testsuite> in <testsuites>' do
let(:junit) do
<<-EOF.strip_heredoc
- <testsuite>
- <testcase classname='Calculator' name='sumTest1' time='0.01'></testcase>
- </testsuite>
+ <testsuites>
+ <testsuite>
+ <testcase classname='Calculator' name='sumTest1' time='0.01'></testcase>
+ </testsuite>
+ </testsuites>
EOF
end
@@ -56,23 +58,65 @@ describe Gitlab::Ci::Parsers::Test::Junit do
end
end
- context 'when there is only one <testsuite> in <testsuites>' do
+ context 'when there is <testcase>' do
let(:junit) do
<<-EOF.strip_heredoc
- <testsuites>
<testsuite>
- <testcase classname='Calculator' name='sumTest1' time='0.01'></testcase>
+ <testcase classname='Calculator' name='sumTest1' time='0.01'>
+ #{testcase_content}
+ </testcase>
</testsuite>
- </testsuites>
EOF
end
- it 'parses XML and adds a test case to a suite' do
+ let(:test_case) { test_cases[0] }
+
+ before do
expect { subject }.not_to raise_error
+ end
- expect(test_cases[0].classname).to eq('Calculator')
- expect(test_cases[0].name).to eq('sumTest1')
- expect(test_cases[0].execution_time).to eq(0.01)
+ shared_examples_for '<testcase> XML parser' do |status, output|
+ it 'parses XML and adds a test case to the suite' do
+ aggregate_failures do
+ expect(test_case.classname).to eq('Calculator')
+ expect(test_case.name).to eq('sumTest1')
+ expect(test_case.execution_time).to eq(0.01)
+ expect(test_case.status).to eq(status)
+ expect(test_case.system_output).to eq(output)
+ end
+ end
+ end
+
+ context 'and has failure' do
+ let(:testcase_content) { '<failure>Some failure</failure>' }
+
+ it_behaves_like '<testcase> XML parser',
+ ::Gitlab::Ci::Reports::TestCase::STATUS_FAILED,
+ 'Some failure'
+ end
+
+ context 'and has error' do
+ let(:testcase_content) { '<error>Some error</error>' }
+
+ it_behaves_like '<testcase> XML parser',
+ ::Gitlab::Ci::Reports::TestCase::STATUS_FAILED,
+ 'Some error'
+ end
+
+ context 'and has an unknown type' do
+ let(:testcase_content) { '<foo>Some foo</foo>' }
+
+ it_behaves_like '<testcase> XML parser',
+ ::Gitlab::Ci::Reports::TestCase::STATUS_SUCCESS,
+ nil
+ end
+
+ context 'and has no content' do
+ let(:testcase_content) { '' }
+
+ it_behaves_like '<testcase> XML parser',
+ ::Gitlab::Ci::Reports::TestCase::STATUS_SUCCESS,
+ nil
end
end
diff --git a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
index 023d7530b4b..945baf47b7b 100644
--- a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
@@ -117,6 +117,24 @@ describe Gitlab::Ci::Pipeline::Seed::Build do
context 'when job is not a bridge' do
it { is_expected.to be_a(::Ci::Build) }
it { is_expected.to be_valid }
+
+ context 'when job has environment name' do
+ let(:attributes) { { name: 'rspec', ref: 'master', environment: 'production' } }
+
+ it 'returns a job with deployment' do
+ expect(subject.deployment).not_to be_nil
+ expect(subject.deployment.deployable).to eq(subject)
+ expect(subject.deployment.environment.name).to eq('production')
+ end
+
+ context 'when the environment name is invalid' do
+ let(:attributes) { { name: 'rspec', ref: 'master', environment: '!!!' } }
+
+ it 'returns a job without deployment' do
+ expect(subject.deployment).to be_nil
+ end
+ end
+ end
end
context 'when job is a bridge' do
diff --git a/spec/lib/gitlab/ci/pipeline/seed/deployment_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/deployment_spec.rb
new file mode 100644
index 00000000000..4e63f60ea6b
--- /dev/null
+++ b/spec/lib/gitlab/ci/pipeline/seed/deployment_spec.rb
@@ -0,0 +1,76 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Ci::Pipeline::Seed::Deployment do
+ let_it_be(:project) { create(:project) }
+ let(:job) { build(:ci_build, project: project) }
+ let(:seed) { described_class.new(job) }
+ let(:attributes) { {} }
+
+ before do
+ job.assign_attributes(**attributes)
+ end
+
+ describe '#to_resource' do
+ subject { seed.to_resource }
+
+ context 'when job has environment attribute' do
+ let(:attributes) do
+ {
+ environment: 'production',
+ options: { environment: { name: 'production' } }
+ }
+ end
+
+ it 'returns a deployment object with environment' do
+ expect(subject).to be_a(Deployment)
+ expect(subject.iid).to be_present
+ expect(subject.environment.name).to eq('production')
+ expect(subject.cluster).to be_nil
+ end
+
+ context 'when environment has deployment platform' do
+ let!(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) }
+
+ it 'returns a deployment with cluster id' do
+ expect(subject.cluster).to eq(cluster)
+ end
+ end
+
+ context 'when environment has an invalid URL' do
+ let(:attributes) do
+ {
+ environment: '!!!',
+ options: { environment: { name: '!!!' } }
+ }
+ end
+
+ it 'returns nothing' do
+ is_expected.to be_nil
+ end
+ end
+
+ context 'when job has already deployment' do
+ let(:job) { build(:ci_build, :with_deployment, project: project, environment: 'production') }
+
+ it 'returns the persisted deployment' do
+ is_expected.to eq(job.deployment)
+ end
+ end
+ end
+
+ context 'when job has environment attribute with stop action' do
+ let(:attributes) do
+ {
+ environment: 'production',
+ options: { environment: { name: 'production', action: 'stop' } }
+ }
+ end
+
+ it 'returns nothing' do
+ is_expected.to be_nil
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/pipeline/seed/environment_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/environment_spec.rb
new file mode 100644
index 00000000000..71389999c6e
--- /dev/null
+++ b/spec/lib/gitlab/ci/pipeline/seed/environment_spec.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Ci::Pipeline::Seed::Environment do
+ let_it_be(:project) { create(:project) }
+ let(:job) { build(:ci_build, project: project) }
+ let(:seed) { described_class.new(job) }
+ let(:attributes) { {} }
+
+ before do
+ job.assign_attributes(**attributes)
+ end
+
+ describe '#to_resource' do
+ subject { seed.to_resource }
+
+ context 'when job has environment attribute' do
+ let(:attributes) do
+ {
+ environment: 'production',
+ options: { environment: { name: 'production' } }
+ }
+ end
+
+ it 'returns a persisted environment object' do
+ expect(subject).to be_a(Environment)
+ expect(subject).to be_persisted
+ expect(subject.project).to eq(project)
+ expect(subject.name).to eq('production')
+ end
+
+ context 'when environment has already existed' do
+ let!(:environment) { create(:environment, project: project, name: 'production') }
+
+ it 'returns the existing environment object' do
+ expect(subject).to be_persisted
+ expect(subject).to eq(environment)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/status/composite_spec.rb b/spec/lib/gitlab/ci/status/composite_spec.rb
new file mode 100644
index 00000000000..1725d954b92
--- /dev/null
+++ b/spec/lib/gitlab/ci/status/composite_spec.rb
@@ -0,0 +1,61 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Status::Composite do
+ set(:pipeline) { create(:ci_pipeline) }
+
+ before(:all) do
+ @statuses = HasStatus::STATUSES_ENUM.map do |status, idx|
+ [status, create(:ci_build, pipeline: pipeline, status: status, importing: true)]
+ end.to_h
+
+ @statuses_with_allow_failure = HasStatus::STATUSES_ENUM.map do |status, idx|
+ [status, create(:ci_build, pipeline: pipeline, status: status, allow_failure: true, importing: true)]
+ end.to_h
+ end
+
+ describe '#status' do
+ shared_examples 'compares composite with SQL status' do
+ it 'returns exactly the same result' do
+ builds = Ci::Build.where(id: all_statuses)
+
+ expect(composite_status.status).to eq(builds.legacy_status)
+ expect(composite_status.warnings?).to eq(builds.failed_but_allowed.any?)
+ end
+ end
+
+ shared_examples 'validate all combinations' do |perms|
+ HasStatus::STATUSES_ENUM.keys.combination(perms).each do |statuses|
+ context "with #{statuses.join(",")}" do
+ it_behaves_like 'compares composite with SQL status' do
+ let(:all_statuses) do
+ statuses.map { |status| @statuses[status] }
+ end
+
+ let(:composite_status) do
+ described_class.new(all_statuses)
+ end
+ end
+
+ HasStatus::STATUSES_ENUM.each do |allow_failure_status, _|
+ context "and allow_failure #{allow_failure_status}" do
+ it_behaves_like 'compares composite with SQL status' do
+ let(:all_statuses) do
+ statuses.map { |status| @statuses[status] } +
+ [@statuses_with_allow_failure[allow_failure_status]]
+ end
+
+ let(:composite_status) do
+ described_class.new(all_statuses)
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+
+ it_behaves_like 'validate all combinations', 0
+ it_behaves_like 'validate all combinations', 1
+ it_behaves_like 'validate all combinations', 2
+ end
+end
diff --git a/spec/lib/gitlab/ci/status/external/factory_spec.rb b/spec/lib/gitlab/ci/status/external/factory_spec.rb
index 3b90fb60cca..9d7dfc42848 100644
--- a/spec/lib/gitlab/ci/status/external/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/external/factory_spec.rb
@@ -22,7 +22,7 @@ describe Gitlab::Ci::Status::External::Factory do
end
let(:expected_status) do
- Gitlab::Ci::Status.const_get(simple_status.capitalize)
+ Gitlab::Ci::Status.const_get(simple_status.capitalize, false)
end
it "fabricates a core status #{simple_status}" do
diff --git a/spec/lib/gitlab/ci/status/factory_spec.rb b/spec/lib/gitlab/ci/status/factory_spec.rb
index b51c0bec47e..c6d7a1ec5d9 100644
--- a/spec/lib/gitlab/ci/status/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/factory_spec.rb
@@ -13,7 +13,7 @@ describe Gitlab::Ci::Status::Factory do
let(:resource) { double('resource', status: simple_status) }
let(:expected_status) do
- Gitlab::Ci::Status.const_get(simple_status.capitalize)
+ Gitlab::Ci::Status.const_get(simple_status.capitalize, false)
end
it "fabricates a core status #{simple_status}" do
diff --git a/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb b/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb
index 8a36cd1b658..3acc767ab7a 100644
--- a/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb
@@ -18,7 +18,7 @@ describe Gitlab::Ci::Status::Pipeline::Factory do
let(:pipeline) { create(:ci_pipeline, status: simple_status) }
let(:expected_status) do
- Gitlab::Ci::Status.const_get(simple_status.capitalize)
+ Gitlab::Ci::Status.const_get(simple_status.capitalize, false)
end
it "matches correct core status for #{simple_status}" do
diff --git a/spec/lib/gitlab/ci/status/preparing_spec.rb b/spec/lib/gitlab/ci/status/preparing_spec.rb
index 7211c0e506d..33f6bab8d65 100644
--- a/spec/lib/gitlab/ci/status/preparing_spec.rb
+++ b/spec/lib/gitlab/ci/status/preparing_spec.rb
@@ -16,11 +16,11 @@ describe Gitlab::Ci::Status::Preparing do
end
describe '#icon' do
- it { expect(subject.icon).to eq 'status_created' }
+ it { expect(subject.icon).to eq 'status_preparing' }
end
describe '#favicon' do
- it { expect(subject.favicon).to eq 'favicon_status_created' }
+ it { expect(subject.favicon).to eq 'favicon_status_preparing' }
end
describe '#group' do
diff --git a/spec/lib/gitlab/ci/status/stage/factory_spec.rb b/spec/lib/gitlab/ci/status/stage/factory_spec.rb
index 8f5b1ff62a5..dcb53712157 100644
--- a/spec/lib/gitlab/ci/status/stage/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/stage/factory_spec.rb
@@ -34,7 +34,7 @@ describe Gitlab::Ci::Status::Stage::Factory do
it "fabricates a core status #{core_status}" do
expect(status).to be_a(
- Gitlab::Ci::Status.const_get(core_status.capitalize))
+ Gitlab::Ci::Status.const_get(core_status.capitalize, false))
end
it 'extends core status with common stage methods' do
diff --git a/spec/lib/gitlab/ci/trace/stream_spec.rb b/spec/lib/gitlab/ci/trace/stream_spec.rb
index dd5f2f97ac9..1baea13299b 100644
--- a/spec/lib/gitlab/ci/trace/stream_spec.rb
+++ b/spec/lib/gitlab/ci/trace/stream_spec.rb
@@ -248,60 +248,6 @@ describe Gitlab::Ci::Trace::Stream, :clean_gitlab_redis_cache do
end
end
- describe '#html_with_state' do
- shared_examples_for 'html_with_states' do
- it 'returns html content with state' do
- result = stream.html_with_state
-
- expect(result.html).to eq("<span>1234</span>")
- end
-
- context 'follow-up state' do
- let!(:last_result) { stream.html_with_state }
-
- before do
- data_stream.seek(4, IO::SEEK_SET)
- data_stream.write("5678")
- stream.seek(0)
- end
-
- it "returns appended trace" do
- result = stream.html_with_state(last_result.state)
-
- expect(result.append).to be_truthy
- expect(result.html).to eq("<span>5678</span>")
- end
- end
- end
-
- context 'when stream is StringIO' do
- let(:data_stream) do
- StringIO.new("1234")
- end
-
- let(:stream) do
- described_class.new { data_stream }
- end
-
- it_behaves_like 'html_with_states'
- end
-
- context 'when stream is ChunkedIO' do
- let(:data_stream) do
- Gitlab::Ci::Trace::ChunkedIO.new(build).tap do |chunked_io|
- chunked_io.write("1234")
- chunked_io.seek(0, IO::SEEK_SET)
- end
- end
-
- let(:stream) do
- described_class.new { data_stream }
- end
-
- it_behaves_like 'html_with_states'
- end
- end
-
describe '#html' do
shared_examples_for 'htmls' do
it "returns html" do
diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb
index d43eb4e4b4a..cb5ebde16d7 100644
--- a/spec/lib/gitlab/ci/yaml_processor_spec.rb
+++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb
@@ -26,7 +26,7 @@ module Gitlab
it 'returns valid build attributes' do
expect(subject).to eq({
stage: "test",
- stage_idx: 1,
+ stage_idx: 2,
name: "rspec",
options: {
before_script: ["pwd"],
@@ -56,7 +56,7 @@ module Gitlab
it 'returns valid build attributes' do
expect(subject).to eq({
stage: 'test',
- stage_idx: 1,
+ stage_idx: 2,
name: 'rspec',
options: { script: ['rspec'] },
rules: [
@@ -209,13 +209,16 @@ module Gitlab
end
let(:attributes) do
- [{ name: "build",
+ [{ name: ".pre",
index: 0,
builds: [] },
- { name: "test",
+ { name: "build",
index: 1,
+ builds: [] },
+ { name: "test",
+ index: 2,
builds:
- [{ stage_idx: 1,
+ [{ stage_idx: 2,
stage: "test",
name: "rspec",
allow_failure: false,
@@ -225,9 +228,9 @@ module Gitlab
only: { refs: ["branches"] },
except: {} }] },
{ name: "deploy",
- index: 2,
+ index: 3,
builds:
- [{ stage_idx: 2,
+ [{ stage_idx: 3,
stage: "deploy",
name: "prod",
allow_failure: false,
@@ -235,7 +238,10 @@ module Gitlab
yaml_variables: [],
options: { script: ["cap prod"] },
only: { refs: ["tags"] },
- except: {} }] }]
+ except: {} }] },
+ { name: ".post",
+ index: 4,
+ builds: [] }]
end
it 'returns stages seed attributes' do
@@ -425,7 +431,7 @@ module Gitlab
expect(config_processor.stage_builds_attributes("test").size).to eq(1)
expect(config_processor.stage_builds_attributes("test").first).to eq({
stage: "test",
- stage_idx: 1,
+ stage_idx: 2,
name: "rspec",
options: {
before_script: ["pwd"],
@@ -456,7 +462,7 @@ module Gitlab
expect(config_processor.stage_builds_attributes("test").size).to eq(1)
expect(config_processor.stage_builds_attributes("test").first).to eq({
stage: "test",
- stage_idx: 1,
+ stage_idx: 2,
name: "rspec",
options: {
before_script: ["pwd"],
@@ -485,7 +491,7 @@ module Gitlab
expect(config_processor.stage_builds_attributes("test").size).to eq(1)
expect(config_processor.stage_builds_attributes("test").first).to eq({
stage: "test",
- stage_idx: 1,
+ stage_idx: 2,
name: "rspec",
options: {
before_script: ["pwd"],
@@ -510,7 +516,7 @@ module Gitlab
expect(config_processor.stage_builds_attributes("test").size).to eq(1)
expect(config_processor.stage_builds_attributes("test").first).to eq({
stage: "test",
- stage_idx: 1,
+ stage_idx: 2,
name: "rspec",
options: {
before_script: ["pwd"],
@@ -977,7 +983,7 @@ module Gitlab
expect(config_processor.stage_builds_attributes("test").size).to eq(1)
expect(config_processor.stage_builds_attributes("test").first).to eq({
stage: "test",
- stage_idx: 1,
+ stage_idx: 2,
name: "rspec",
options: {
before_script: ["pwd"],
@@ -1272,7 +1278,7 @@ module Gitlab
expect(subject.builds.size).to eq(5)
expect(subject.builds[0]).to eq(
stage: "build",
- stage_idx: 0,
+ stage_idx: 1,
name: "build1",
options: {
script: ["test"]
@@ -1283,7 +1289,7 @@ module Gitlab
)
expect(subject.builds[2]).to eq(
stage: "test",
- stage_idx: 1,
+ stage_idx: 2,
name: "test1",
options: {
script: ["test"],
@@ -1398,7 +1404,7 @@ module Gitlab
expect(subject.size).to eq(1)
expect(subject.first).to eq({
stage: "test",
- stage_idx: 1,
+ stage_idx: 2,
name: "normal_job",
options: {
script: ["test"]
@@ -1442,7 +1448,7 @@ module Gitlab
expect(subject.size).to eq(2)
expect(subject.first).to eq({
stage: "build",
- stage_idx: 0,
+ stage_idx: 1,
name: "job1",
options: {
script: ["execute-script-for-job"]
@@ -1453,7 +1459,7 @@ module Gitlab
})
expect(subject.second).to eq({
stage: "build",
- stage_idx: 0,
+ stage_idx: 1,
name: "job2",
options: {
script: ["execute-script-for-job"]
@@ -1665,14 +1671,14 @@ module Gitlab
config = YAML.dump({ rspec: { script: "test", type: "acceptance" } })
expect do
Gitlab::Ci::YamlProcessor.new(config)
- end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "rspec job: stage parameter should be build, test, deploy")
+ end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "rspec job: stage parameter should be .pre, build, test, deploy, .post")
end
it "returns errors if job stage is not a defined stage" do
config = YAML.dump({ types: %w(build test), rspec: { script: "test", type: "acceptance" } })
expect do
Gitlab::Ci::YamlProcessor.new(config)
- end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "rspec job: stage parameter should be build, test")
+ end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "rspec job: stage parameter should be .pre, build, test, .post")
end
it "returns errors if stages is not an array" do
diff --git a/spec/lib/gitlab/cleanup/project_uploads_spec.rb b/spec/lib/gitlab/cleanup/project_uploads_spec.rb
index 7bad788e44e..5787cce7d20 100644
--- a/spec/lib/gitlab/cleanup/project_uploads_spec.rb
+++ b/spec/lib/gitlab/cleanup/project_uploads_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
describe Gitlab::Cleanup::ProjectUploads do
subject { described_class.new(logger: logger) }
+
let(:logger) { double(:logger) }
before do
diff --git a/spec/lib/gitlab/cluster/mixins/puma_cluster_spec.rb b/spec/lib/gitlab/cluster/mixins/puma_cluster_spec.rb
new file mode 100644
index 00000000000..1eddf488c5d
--- /dev/null
+++ b/spec/lib/gitlab/cluster/mixins/puma_cluster_spec.rb
@@ -0,0 +1,96 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+# For easier debugging set `PUMA_DEBUG=1`
+
+describe Gitlab::Cluster::Mixins::PumaCluster do
+ PUMA_STARTUP_TIMEOUT = 30
+
+ context 'when running Puma in Cluster-mode' do
+ %i[USR1 USR2 INT HUP].each do |signal|
+ it "for #{signal} does execute phased restart block" do
+ with_puma(workers: 1) do |pid|
+ Process.kill(signal, pid)
+
+ child_pid, child_status = Process.wait2(pid)
+ expect(child_pid).to eq(pid)
+ expect(child_status).to be_exited
+ expect(child_status.exitstatus).to eq(140)
+ end
+ end
+ end
+ end
+
+ private
+
+ def with_puma(workers:, timeout: PUMA_STARTUP_TIMEOUT)
+ with_puma_config(workers: workers) do |puma_rb|
+ cmdline = [
+ "bundle", "exec", "puma",
+ "-C", puma_rb,
+ "-I", Rails.root.to_s
+ ]
+
+ IO.popen(cmdline) do |process|
+ # wait for process to start:
+ # [2123] * Listening on tcp://127.0.0.1:0
+ wait_for_output(process, /Listening on/, timeout: timeout)
+ consume_output(process)
+
+ yield(process.pid)
+ ensure
+ begin
+ Process.kill(:KILL, process.pid)
+ rescue Errno::ESRCH
+ end
+ end
+ end
+ end
+
+ def with_puma_config(workers:)
+ Dir.mktmpdir do |dir|
+ File.write "#{dir}/puma.rb", <<-EOF
+ require './lib/gitlab/cluster/lifecycle_events'
+ require './lib/gitlab/cluster/mixins/puma_cluster'
+
+ workers #{workers}
+ bind "tcp://127.0.0.1:0"
+ preload_app!
+
+ app -> (env) { [404, {}, ['']] }
+
+ Puma::Cluster.prepend(#{described_class})
+
+ Gitlab::Cluster::LifecycleEvents.on_before_phased_restart do
+ exit(140)
+ end
+
+ # redirect stderr to stdout
+ $stderr.reopen($stdout)
+ EOF
+
+ yield("#{dir}/puma.rb")
+ end
+ end
+
+ def wait_for_output(process, output, timeout:)
+ Timeout.timeout(timeout) do
+ loop do
+ line = process.readline
+ puts "PUMA_DEBUG: #{line}" if ENV['PUMA_DEBUG']
+ break if line =~ output
+ end
+ end
+ end
+
+ def consume_output(process)
+ Thread.new do
+ loop do
+ line = process.readline
+ puts "PUMA_DEBUG: #{line}" if ENV['PUMA_DEBUG']
+ end
+ rescue
+ end
+ end
+end
diff --git a/spec/lib/gitlab/cluster/mixins/unicorn_http_server_spec.rb b/spec/lib/gitlab/cluster/mixins/unicorn_http_server_spec.rb
new file mode 100644
index 00000000000..2b3a267991c
--- /dev/null
+++ b/spec/lib/gitlab/cluster/mixins/unicorn_http_server_spec.rb
@@ -0,0 +1,112 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+# For easier debugging set `UNICORN_DEBUG=1`
+
+describe Gitlab::Cluster::Mixins::UnicornHttpServer do
+ UNICORN_STARTUP_TIMEOUT = 10
+
+ context 'when running Unicorn' do
+ %i[USR2].each do |signal|
+ it "for #{signal} does execute phased restart block" do
+ with_unicorn(workers: 1) do |pid|
+ Process.kill(signal, pid)
+
+ child_pid, child_status = Process.wait2(pid)
+ expect(child_pid).to eq(pid)
+ expect(child_status).to be_exited
+ expect(child_status.exitstatus).to eq(140)
+ end
+ end
+ end
+
+ %i[QUIT TERM INT].each do |signal|
+ it "for #{signal} does not execute phased restart block" do
+ with_unicorn(workers: 1) do |pid|
+ Process.kill(signal, pid)
+
+ child_pid, child_status = Process.wait2(pid)
+ expect(child_pid).to eq(pid)
+ expect(child_status).to be_exited
+ expect(child_status.exitstatus).to eq(0)
+ end
+ end
+ end
+ end
+
+ private
+
+ def with_unicorn(workers:, timeout: UNICORN_STARTUP_TIMEOUT)
+ with_unicorn_configs(workers: workers) do |unicorn_rb, config_ru|
+ cmdline = [
+ "bundle", "exec", "unicorn",
+ "-I", Rails.root.to_s,
+ "-c", unicorn_rb,
+ config_ru
+ ]
+
+ IO.popen(cmdline) do |process|
+ # wait for process to start:
+ # I, [2019-10-15T13:21:27.565225 #3089] INFO -- : master process ready
+ wait_for_output(process, /master process ready/, timeout: timeout)
+ consume_output(process)
+
+ yield(process.pid)
+ ensure
+ begin
+ Process.kill(:KILL, process.pid)
+ rescue Errno::ESRCH
+ end
+ end
+ end
+ end
+
+ def with_unicorn_configs(workers:)
+ Dir.mktmpdir do |dir|
+ File.write "#{dir}/unicorn.rb", <<-EOF
+ require './lib/gitlab/cluster/lifecycle_events'
+ require './lib/gitlab/cluster/mixins/unicorn_http_server'
+
+ worker_processes #{workers}
+ listen "127.0.0.1:0"
+ preload_app true
+
+ Unicorn::HttpServer.prepend(#{described_class})
+
+ Gitlab::Cluster::LifecycleEvents.on_before_phased_restart do
+ exit(140)
+ end
+
+ # redirect stderr to stdout
+ $stderr.reopen($stdout)
+ EOF
+
+ File.write "#{dir}/config.ru", <<-EOF
+ run -> (env) { [404, {}, ['']] }
+ EOF
+
+ yield("#{dir}/unicorn.rb", "#{dir}/config.ru")
+ end
+ end
+
+ def wait_for_output(process, output, timeout:)
+ Timeout.timeout(timeout) do
+ loop do
+ line = process.readline
+ puts "UNICORN_DEBUG: #{line}" if ENV['UNICORN_DEBUG']
+ break if line =~ output
+ end
+ end
+ end
+
+ def consume_output(process)
+ Thread.new do
+ loop do
+ line = process.readline
+ puts "UNICORN_DEBUG: #{line}" if ENV['UNICORN_DEBUG']
+ end
+ rescue
+ end
+ end
+end
diff --git a/spec/lib/gitlab/config/entry/simplifiable_spec.rb b/spec/lib/gitlab/config/entry/simplifiable_spec.rb
index 65e18fe3f10..5c208cab449 100644
--- a/spec/lib/gitlab/config/entry/simplifiable_spec.rb
+++ b/spec/lib/gitlab/config/entry/simplifiable_spec.rb
@@ -24,9 +24,9 @@ describe Gitlab::Config::Entry::Simplifiable do
let(:unknown) { double('unknown strategy') }
before do
- stub_const("#{described_class.name}::Something", first)
- stub_const("#{described_class.name}::DifferentOne", second)
- stub_const("#{described_class.name}::UnknownStrategy", unknown)
+ entry::Something = first
+ entry::DifferentOne = second
+ entry::UnknownStrategy = unknown
end
context 'when first strategy should be used' do
diff --git a/spec/lib/gitlab/cycle_analytics/code_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/code_stage_spec.rb
index dd1d9ac0f16..aa12bc21d22 100644
--- a/spec/lib/gitlab/cycle_analytics/code_stage_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/code_stage_spec.rb
@@ -12,7 +12,8 @@ describe Gitlab::CycleAnalytics::CodeStage do
let(:issue_3) { create(:issue, project: project, created_at: 60.minutes.ago) }
let(:mr_1) { create(:merge_request, source_project: project, created_at: 15.minutes.ago) }
let(:mr_2) { create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'A') }
- let(:stage) { described_class.new(options: { from: 2.days.ago, current_user: project.creator, project: project }) }
+ let(:stage_options) { { from: 2.days.ago, current_user: project.creator, project: project } }
+ let(:stage) { described_class.new(options: stage_options) }
before do
issue_1.metrics.update!(first_associated_with_milestone_at: 60.minutes.ago, first_mentioned_in_commit_at: 45.minutes.ago)
@@ -25,6 +26,13 @@ describe Gitlab::CycleAnalytics::CodeStage do
it_behaves_like 'base stage'
+ context 'when using the new query backend' do
+ include_examples 'Gitlab::Analytics::CycleAnalytics::DataCollector backend examples' do
+ let(:expected_record_count) { 2 }
+ let(:expected_ordered_attribute_values) { [mr_2.title, mr_1.title] }
+ end
+ end
+
describe '#project_median' do
around do |example|
Timecop.freeze { example.run }
@@ -33,6 +41,8 @@ describe Gitlab::CycleAnalytics::CodeStage do
it 'counts median from issues with metrics' do
expect(stage.project_median).to eq(ISSUES_MEDIAN)
end
+
+ include_examples 'calculate #median with date range'
end
describe '#events' do
diff --git a/spec/lib/gitlab/cycle_analytics/issue_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/issue_stage_spec.rb
index 4dd21239cde..497db88d850 100644
--- a/spec/lib/gitlab/cycle_analytics/issue_stage_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/issue_stage_spec.rb
@@ -10,7 +10,8 @@ describe Gitlab::CycleAnalytics::IssueStage do
let(:issue_2) { create(:issue, project: project, created_at: 60.minutes.ago) }
let(:issue_3) { create(:issue, project: project, created_at: 30.minutes.ago) }
let!(:issue_without_milestone) { create(:issue, project: project, created_at: 1.minute.ago) }
- let(:stage) { described_class.new(options: { from: 2.days.ago, current_user: project.creator, project: project }) }
+ let(:stage_options) { { from: 2.days.ago, current_user: project.creator, project: project } }
+ let(:stage) { described_class.new(options: stage_options) }
before do
issue_1.metrics.update!(first_associated_with_milestone_at: 60.minutes.ago )
@@ -20,6 +21,13 @@ describe Gitlab::CycleAnalytics::IssueStage do
it_behaves_like 'base stage'
+ context 'when using the new query backend' do
+ include_examples 'Gitlab::Analytics::CycleAnalytics::DataCollector backend examples' do
+ let(:expected_record_count) { 3 }
+ let(:expected_ordered_attribute_values) { [issue_3.title, issue_2.title, issue_1.title] }
+ end
+ end
+
describe '#median' do
around do |example|
Timecop.freeze { example.run }
@@ -28,6 +36,8 @@ describe Gitlab::CycleAnalytics::IssueStage do
it 'counts median from issues with metrics' do
expect(stage.project_median).to eq(ISSUES_MEDIAN)
end
+
+ include_examples 'calculate #median with date range'
end
describe '#events' do
diff --git a/spec/lib/gitlab/cycle_analytics/plan_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/plan_stage_spec.rb
index 98d2593de66..01a46f5ba65 100644
--- a/spec/lib/gitlab/cycle_analytics/plan_stage_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/plan_stage_spec.rb
@@ -10,7 +10,8 @@ describe Gitlab::CycleAnalytics::PlanStage do
let!(:issue_2) { create(:issue, project: project, created_at: 60.minutes.ago) }
let!(:issue_3) { create(:issue, project: project, created_at: 30.minutes.ago) }
let!(:issue_without_milestone) { create(:issue, project: project, created_at: 1.minute.ago) }
- let(:stage) { described_class.new(options: { from: 2.days.ago, current_user: project.creator, project: project }) }
+ let(:stage_options) { { from: 2.days.ago, current_user: project.creator, project: project } }
+ let(:stage) { described_class.new(options: stage_options) }
before do
issue_1.metrics.update!(first_associated_with_milestone_at: 60.minutes.ago, first_mentioned_in_commit_at: 10.minutes.ago)
@@ -20,6 +21,13 @@ describe Gitlab::CycleAnalytics::PlanStage do
it_behaves_like 'base stage'
+ context 'when using the new query backend' do
+ include_examples 'Gitlab::Analytics::CycleAnalytics::DataCollector backend examples' do
+ let(:expected_record_count) { 2 }
+ let(:expected_ordered_attribute_values) { [issue_1.title, issue_2.title] }
+ end
+ end
+
describe '#project_median' do
around do |example|
Timecop.freeze { example.run }
@@ -28,6 +36,8 @@ describe Gitlab::CycleAnalytics::PlanStage do
it 'counts median from issues with metrics' do
expect(stage.project_median).to eq(ISSUES_MEDIAN)
end
+
+ include_examples 'calculate #median with date range'
end
describe '#events' do
diff --git a/spec/lib/gitlab/cycle_analytics/shared_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/shared_stage_spec.rb
index cf95741908f..c5b17aafdd2 100644
--- a/spec/lib/gitlab/cycle_analytics/shared_stage_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/shared_stage_spec.rb
@@ -32,3 +32,41 @@ shared_examples 'base stage' do
expect(stage.events).not_to be_nil
end
end
+
+shared_examples 'calculate #median with date range' do
+ context 'when valid date range is given' do
+ before do
+ stage_options[:from] = 5.days.ago
+ stage_options[:to] = 5.days.from_now
+ end
+
+ it { expect(stage.project_median).to eq(ISSUES_MEDIAN) }
+ end
+
+ context 'when records are out of the date range' do
+ before do
+ stage_options[:from] = 2.years.ago
+ stage_options[:to] = 1.year.ago
+ end
+
+ it { expect(stage.project_median).to eq(nil) }
+ end
+end
+
+shared_examples 'Gitlab::Analytics::CycleAnalytics::DataCollector backend examples' do
+ let(:stage_params) { Gitlab::Analytics::CycleAnalytics::DefaultStages.send("params_for_#{stage_name}_stage").merge(project: project) }
+ let(:stage) { Analytics::CycleAnalytics::ProjectStage.new(stage_params) }
+ let(:data_collector) { Gitlab::Analytics::CycleAnalytics::DataCollector.new(stage: stage, params: { from: stage_options[:from], current_user: project.creator }) }
+ let(:attribute_to_verify) { :title }
+
+ context 'provides the same results as the old implementation' do
+ it 'for the median' do
+ expect(data_collector.median.seconds).to eq(ISSUES_MEDIAN)
+ end
+
+ it 'for the list of event records' do
+ records = data_collector.records_fetcher.serialized_records
+ expect(records.map { |event| event[attribute_to_verify] }).to eq(expected_ordered_attribute_values)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb b/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb
index 778c2f479b5..8f9dac6d281 100644
--- a/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb
@@ -4,52 +4,98 @@ require 'spec_helper'
describe Gitlab::CycleAnalytics::StageSummary do
let(:project) { create(:project, :repository) }
- let(:from) { 1.day.ago }
+ let(:options) { { from: 1.day.ago, current_user: user } }
let(:user) { create(:user, :admin) }
- subject { described_class.new(project, from: Time.now, current_user: user).data }
+ let(:stage_summary) { described_class.new(project, options).data }
describe "#new_issues" do
+ subject { stage_summary.first[:value] }
+
it "finds the number of issues created after the 'from date'" do
Timecop.freeze(5.days.ago) { create(:issue, project: project) }
Timecop.freeze(5.days.from_now) { create(:issue, project: project) }
- expect(subject.first[:value]).to eq(1)
+ expect(subject).to eq(1)
end
it "doesn't find issues from other projects" do
Timecop.freeze(5.days.from_now) { create(:issue, project: create(:project)) }
- expect(subject.first[:value]).to eq(0)
+ expect(subject).to eq(0)
+ end
+
+ context 'when `to` parameter is given' do
+ before do
+ Timecop.freeze(5.days.ago) { create(:issue, project: project) }
+ Timecop.freeze(5.days.from_now) { create(:issue, project: project) }
+ end
+
+ it "doesn't find any record" do
+ options[:to] = Time.now
+
+ expect(subject).to eq(0)
+ end
+
+ it "finds records created between `from` and `to` range" do
+ options[:from] = 10.days.ago
+ options[:to] = 10.days.from_now
+
+ expect(subject).to eq(2)
+ end
end
end
describe "#commits" do
+ subject { stage_summary.second[:value] }
+
it "finds the number of commits created after the 'from date'" do
Timecop.freeze(5.days.ago) { create_commit("Test message", project, user, 'master') }
Timecop.freeze(5.days.from_now) { create_commit("Test message", project, user, 'master') }
- expect(subject.second[:value]).to eq(1)
+ expect(subject).to eq(1)
end
it "doesn't find commits from other projects" do
Timecop.freeze(5.days.from_now) { create_commit("Test message", create(:project, :repository), user, 'master') }
- expect(subject.second[:value]).to eq(0)
+ expect(subject).to eq(0)
end
it "finds a large (> 100) snumber of commits if present" do
Timecop.freeze(5.days.from_now) { create_commit("Test message", project, user, 'master', count: 100) }
- expect(subject.second[:value]).to eq(100)
+ expect(subject).to eq(100)
+ end
+
+ context 'when `to` parameter is given' do
+ before do
+ Timecop.freeze(5.days.ago) { create_commit("Test message", project, user, 'master') }
+ Timecop.freeze(5.days.from_now) { create_commit("Test message", project, user, 'master') }
+ end
+
+ it "doesn't find any record" do
+ options[:to] = Time.now
+
+ expect(subject).to eq(0)
+ end
+
+ it "finds records created between `from` and `to` range" do
+ options[:from] = 10.days.ago
+ options[:to] = 10.days.from_now
+
+ expect(subject).to eq(2)
+ end
end
end
describe "#deploys" do
+ subject { stage_summary.third[:value] }
+
it "finds the number of deploys made created after the 'from date'" do
Timecop.freeze(5.days.ago) { create(:deployment, :success, project: project) }
Timecop.freeze(5.days.from_now) { create(:deployment, :success, project: project) }
- expect(subject.third[:value]).to eq(1)
+ expect(subject).to eq(1)
end
it "doesn't find commits from other projects" do
@@ -57,7 +103,27 @@ describe Gitlab::CycleAnalytics::StageSummary do
create(:deployment, :success, project: create(:project, :repository))
end
- expect(subject.third[:value]).to eq(0)
+ expect(subject).to eq(0)
+ end
+
+ context 'when `to` parameter is given' do
+ before do
+ Timecop.freeze(5.days.ago) { create(:deployment, :success, project: project) }
+ Timecop.freeze(5.days.from_now) { create(:deployment, :success, project: project) }
+ end
+
+ it "doesn't find any record" do
+ options[:to] = Time.now
+
+ expect(subject).to eq(0)
+ end
+
+ it "finds records created between `from` and `to` range" do
+ options[:from] = 10.days.ago
+ options[:to] = 10.days.from_now
+
+ expect(subject).to eq(2)
+ end
end
end
end
diff --git a/spec/lib/gitlab/cycle_analytics/staging_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/staging_stage_spec.rb
index bd64c4aca42..306b08a60e1 100644
--- a/spec/lib/gitlab/cycle_analytics/staging_stage_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/staging_stage_spec.rb
@@ -16,7 +16,8 @@ describe Gitlab::CycleAnalytics::StagingStage do
let(:build_1) { create(:ci_build, project: project) }
let(:build_2) { create(:ci_build, project: project) }
- let(:stage) { described_class.new(options: { from: 2.days.ago, current_user: project.creator, project: project }) }
+ let(:stage_options) { { from: 2.days.ago, current_user: project.creator, project: project } }
+ let(:stage) { described_class.new(options: stage_options) }
before do
mr_1.metrics.update!(merged_at: 80.minutes.ago, first_deployed_to_production_at: 50.minutes.ago, pipeline_id: build_1.commit_id)
@@ -38,6 +39,8 @@ describe Gitlab::CycleAnalytics::StagingStage do
it 'counts median from issues with metrics' do
expect(stage.project_median).to eq(ISSUES_MEDIAN)
end
+
+ it_behaves_like 'calculate #median with date range'
end
describe '#events' do
diff --git a/spec/lib/gitlab/cycle_analytics/test_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/test_stage_spec.rb
index 9162686d17d..e347f36dfce 100644
--- a/spec/lib/gitlab/cycle_analytics/test_stage_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/test_stage_spec.rb
@@ -6,22 +6,26 @@ require 'lib/gitlab/cycle_analytics/shared_stage_spec'
describe Gitlab::CycleAnalytics::TestStage do
let(:stage_name) { :test }
let(:project) { create(:project) }
- let(:stage) { described_class.new(options: { from: 2.days.ago, current_user: project.creator, project: project }) }
+ let(:stage_options) { { from: 2.days.ago, current_user: project.creator, project: project } }
+ let(:stage) { described_class.new(options: stage_options) }
it_behaves_like 'base stage'
describe '#median' do
+ let(:mr_1) { create(:merge_request, :closed, source_project: project, created_at: 60.minutes.ago) }
+ let(:mr_2) { create(:merge_request, :closed, source_project: project, created_at: 40.minutes.ago, source_branch: 'A') }
+ let(:mr_3) { create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'B') }
+ let(:mr_4) { create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'C') }
+ let(:mr_5) { create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'D') }
+ let(:ci_build1) { create(:ci_build, project: project) }
+ let(:ci_build2) { create(:ci_build, project: project) }
+
before do
issue_1 = create(:issue, project: project, created_at: 90.minutes.ago)
issue_2 = create(:issue, project: project, created_at: 60.minutes.ago)
issue_3 = create(:issue, project: project, created_at: 60.minutes.ago)
- mr_1 = create(:merge_request, :closed, source_project: project, created_at: 60.minutes.ago)
- mr_2 = create(:merge_request, :closed, source_project: project, created_at: 40.minutes.ago, source_branch: 'A')
- mr_3 = create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'B')
- mr_4 = create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'C')
- mr_5 = create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'D')
- mr_1.metrics.update!(latest_build_started_at: 32.minutes.ago, latest_build_finished_at: 2.minutes.ago)
- mr_2.metrics.update!(latest_build_started_at: 62.minutes.ago, latest_build_finished_at: 32.minutes.ago)
+ mr_1.metrics.update!(latest_build_started_at: 32.minutes.ago, latest_build_finished_at: 2.minutes.ago, pipeline_id: ci_build1.commit_id)
+ mr_2.metrics.update!(latest_build_started_at: 62.minutes.ago, latest_build_finished_at: 32.minutes.ago, pipeline_id: ci_build2.commit_id)
mr_3.metrics.update!(latest_build_started_at: nil, latest_build_finished_at: nil)
mr_4.metrics.update!(latest_build_started_at: nil, latest_build_finished_at: nil)
mr_5.metrics.update!(latest_build_started_at: nil, latest_build_finished_at: nil)
@@ -40,5 +44,15 @@ describe Gitlab::CycleAnalytics::TestStage do
it 'counts median from issues with metrics' do
expect(stage.project_median).to eq(ISSUES_MEDIAN)
end
+
+ include_examples 'calculate #median with date range'
+
+ context 'when using the new query backend' do
+ include_examples 'Gitlab::Analytics::CycleAnalytics::DataCollector backend examples' do
+ let(:expected_record_count) { 2 }
+ let(:attribute_to_verify) { :id }
+ let(:expected_ordered_attribute_values) { [mr_1.metrics.pipeline.builds.first.id, mr_2.metrics.pipeline.builds.first.id] }
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/daemon_spec.rb b/spec/lib/gitlab/daemon_spec.rb
index 0372b770844..cf1f089c577 100644
--- a/spec/lib/gitlab/daemon_spec.rb
+++ b/spec/lib/gitlab/daemon_spec.rb
@@ -6,7 +6,7 @@ describe Gitlab::Daemon do
subject { described_class.new }
before do
- allow(subject).to receive(:start_working)
+ allow(subject).to receive(:run_thread)
allow(subject).to receive(:stop_working)
end
@@ -44,7 +44,7 @@ describe Gitlab::Daemon do
it 'starts the Daemon' do
expect { subject.start.join }.to change { subject.thread? }.from(false).to(true)
- expect(subject).to have_received(:start_working)
+ expect(subject).to have_received(:run_thread)
end
end
@@ -52,7 +52,21 @@ describe Gitlab::Daemon do
it "doesn't shutdown stopped Daemon" do
expect { subject.stop }.not_to change { subject.thread? }
- expect(subject).not_to have_received(:start_working)
+ expect(subject).not_to have_received(:run_thread)
+ end
+ end
+ end
+
+ describe '#start_working' do
+ context 'when start_working fails' do
+ before do
+ expect(subject).to receive(:start_working) { false }
+ end
+
+ it 'does not start thread' do
+ expect(subject).not_to receive(:run_thread)
+
+ expect(subject.start).to eq(nil)
end
end
end
@@ -66,7 +80,7 @@ describe Gitlab::Daemon do
it "doesn't start running Daemon" do
expect { subject.start.join }.not_to change { subject.thread }
- expect(subject).to have_received(:start_working).once
+ expect(subject).to have_received(:run_thread).once
end
end
@@ -79,7 +93,7 @@ describe Gitlab::Daemon do
context 'when stop_working raises exception' do
before do
- allow(subject).to receive(:start_working) do
+ allow(subject).to receive(:run_thread) do
sleep(1000)
end
end
@@ -108,7 +122,7 @@ describe Gitlab::Daemon do
expect(subject.start).to be_nil
expect { subject.start }.not_to change { subject.thread? }
- expect(subject).not_to have_received(:start_working)
+ expect(subject).not_to have_received(:run_thread)
end
end
diff --git a/spec/lib/gitlab/danger/helper_spec.rb b/spec/lib/gitlab/danger/helper_spec.rb
index 1b4d366ce7b..1696d3566ad 100644
--- a/spec/lib/gitlab/danger/helper_spec.rb
+++ b/spec/lib/gitlab/danger/helper_spec.rb
@@ -86,30 +86,30 @@ describe Gitlab::Danger::Helper do
describe '#ee?' do
subject { helper.ee? }
- it 'returns true if CI_PROJECT_NAME if set to gitlab-ee' do
- stub_env('CI_PROJECT_NAME', 'gitlab-ee')
- expect(File).not_to receive(:exist?)
+ it 'returns true if CI_PROJECT_NAME if set to gitlab' do
+ stub_env('CI_PROJECT_NAME', 'gitlab')
+ expect(Dir).not_to receive(:exist?)
is_expected.to be_truthy
end
it 'delegates to CHANGELOG-EE.md existence if CI_PROJECT_NAME is set to something else' do
stub_env('CI_PROJECT_NAME', 'something else')
- expect(File).to receive(:exist?).with('../../CHANGELOG-EE.md') { true }
+ expect(Dir).to receive(:exist?).with('../../ee') { true }
is_expected.to be_truthy
end
- it 'returns true if CHANGELOG-EE.md exists' do
+ it 'returns true if ee exists' do
stub_env('CI_PROJECT_NAME', nil)
- expect(File).to receive(:exist?).with('../../CHANGELOG-EE.md') { true }
+ expect(Dir).to receive(:exist?).with('../../ee') { true }
is_expected.to be_truthy
end
- it "returns false if CHANGELOG-EE.md doesn't exist" do
+ it "returns false if ee doesn't exist" do
stub_env('CI_PROJECT_NAME', nil)
- expect(File).to receive(:exist?).with('../../CHANGELOG-EE.md') { false }
+ expect(Dir).to receive(:exist?).with('../../ee') { false }
is_expected.to be_falsy
end
@@ -118,16 +118,16 @@ describe Gitlab::Danger::Helper do
describe '#project_name' do
subject { helper.project_name }
- it 'returns gitlab-ee if ee? returns true' do
+ it 'returns gitlab if ee? returns true' do
expect(helper).to receive(:ee?) { true }
- is_expected.to eq('gitlab-ee')
+ is_expected.to eq('gitlab')
end
it 'returns gitlab-ce if ee? returns false' do
expect(helper).to receive(:ee?) { false }
- is_expected.to eq('gitlab-ce')
+ is_expected.to eq('gitlab-foss')
end
end
@@ -273,7 +273,7 @@ describe Gitlab::Danger::Helper do
where(:category, :expected_label) do
:backend | '~backend'
:database | '~database'
- :docs | '~Documentation'
+ :docs | '~documentation'
:foo | '~foo'
:frontend | '~frontend'
:none | ''
diff --git a/spec/lib/gitlab/danger/roulette_spec.rb b/spec/lib/gitlab/danger/roulette_spec.rb
index 121c5d8ecd9..4d41e2c45aa 100644
--- a/spec/lib/gitlab/danger/roulette_spec.rb
+++ b/spec/lib/gitlab/danger/roulette_spec.rb
@@ -104,11 +104,13 @@ describe Gitlab::Danger::Roulette do
let(:person2) { Gitlab::Danger::Teammate.new('username' => 'godfat') }
let(:author) { Gitlab::Danger::Teammate.new('username' => 'filipa') }
let(:ooo) { Gitlab::Danger::Teammate.new('username' => 'jacopo-beschi') }
+ let(:no_capacity) { Gitlab::Danger::Teammate.new('username' => 'uncharged') }
before do
- stub_person_message(person1, 'making GitLab magic')
- stub_person_message(person2, 'making GitLab magic')
- stub_person_message(ooo, 'OOO till 15th')
+ stub_person_status(person1, message: 'making GitLab magic')
+ stub_person_status(person2, message: 'making GitLab magic')
+ stub_person_status(ooo, message: 'OOO till 15th')
+ stub_person_status(no_capacity, message: 'At capacity for the next few days', emoji: 'red_circle')
# we don't stub Filipa, as she is the author and
# we should not fire request checking for her
@@ -131,10 +133,14 @@ describe Gitlab::Danger::Roulette do
expect(subject.spin_for_person([author], random: Random.new)).to be_nil
end
+ it 'excludes person with no capacity' do
+ expect(subject.spin_for_person([no_capacity], random: Random.new)).to be_nil
+ end
+
private
- def stub_person_message(person, message)
- body = { message: message }.to_json
+ def stub_person_status(person, message: 'dummy message', emoji: 'unicorn')
+ body = { message: message, emoji: emoji }.to_json
WebMock
.stub_request(:get, "https://gitlab.com/api/v4/users/#{person.username}/status")
diff --git a/spec/lib/gitlab/danger/teammate_spec.rb b/spec/lib/gitlab/danger/teammate_spec.rb
index ca036390bde..bd1c2b10dc8 100644
--- a/spec/lib/gitlab/danger/teammate_spec.rb
+++ b/spec/lib/gitlab/danger/teammate_spec.rb
@@ -2,11 +2,14 @@
require 'fast_spec_helper'
+require 'rspec-parameterized'
+
require 'gitlab/danger/teammate'
describe Gitlab::Danger::Teammate do
- subject { described_class.new(options) }
- let(:options) { { 'projects' => projects, 'role' => role } }
+ subject { described_class.new(options.stringify_keys) }
+
+ let(:options) { { username: 'luigi', projects: projects, role: role } }
let(:projects) { { project => capabilities } }
let(:role) { 'Engineer, Manage' }
let(:labels) { [] }
@@ -95,4 +98,72 @@ describe Gitlab::Danger::Teammate do
expect(subject.maintainer?(project, :frontend, labels)).to be_falsey
end
end
+
+ describe '#status' do
+ let(:capabilities) { ['dish washing'] }
+
+ context 'with empty cache' do
+ context 'for successful request' do
+ it 'returns the response' do
+ mock_status = double(does_not: 'matter')
+ expect(Gitlab::Danger::RequestHelper).to receive(:http_get_json)
+ .and_return(mock_status)
+
+ expect(subject.status).to be mock_status
+ end
+ end
+
+ context 'for failing request' do
+ it 'returns nil' do
+ expect(Gitlab::Danger::RequestHelper).to receive(:http_get_json)
+ .and_raise(Gitlab::Danger::RequestHelper::HTTPError.new)
+
+ expect(subject.status).to be nil
+ end
+ end
+ end
+
+ context 'with filled cache' do
+ it 'returns the cached response' do
+ mock_status = double(does_not: 'matter')
+ expect(Gitlab::Danger::RequestHelper).to receive(:http_get_json)
+ .and_return(mock_status)
+ subject.status
+
+ expect(Gitlab::Danger::RequestHelper).not_to receive(:http_get_json)
+ expect(subject.status).to be mock_status
+ end
+ end
+ end
+
+ describe '#available?' do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:capabilities) { ['dry head'] }
+
+ where(:status, :result) do
+ {} | true
+ { message: 'dear reader' } | true
+ { message: 'OOO: massage' } | false
+ { message: 'love it SOOO much' } | false
+ { emoji: 'red_circle' } | false
+ end
+
+ with_them do
+ before do
+ expect(Gitlab::Danger::RequestHelper).to receive(:http_get_json)
+ .and_return(status&.stringify_keys)
+ end
+
+ it { expect(subject.available?).to be result }
+ end
+
+ it 'returns true if request fails' do
+ expect(Gitlab::Danger::RequestHelper).to receive(:http_get_json)
+ .exactly(2).times
+ .and_raise(Gitlab::Danger::RequestHelper::HTTPError.new)
+
+ expect(subject.available?).to be true
+ end
+ end
end
diff --git a/spec/lib/gitlab/data_builder/push_spec.rb b/spec/lib/gitlab/data_builder/push_spec.rb
index e8a9f0b06a8..58509b69463 100644
--- a/spec/lib/gitlab/data_builder/push_spec.rb
+++ b/spec/lib/gitlab/data_builder/push_spec.rb
@@ -90,4 +90,12 @@ describe Gitlab::DataBuilder::Push do
.not_to raise_error
end
end
+
+ describe '.build_bulk' do
+ subject do
+ described_class.build_bulk(action: :created, ref_type: :branch, changes: [double, double])
+ end
+
+ it { is_expected.to eq(action: :created, ref_count: 2, ref_type: :branch) }
+ end
end
diff --git a/spec/lib/gitlab/database_spec.rb b/spec/lib/gitlab/database_spec.rb
index 8d37de32179..15fb1503529 100644
--- a/spec/lib/gitlab/database_spec.rb
+++ b/spec/lib/gitlab/database_spec.rb
@@ -101,20 +101,6 @@ describe Gitlab::Database do
end
end
- describe '.join_lateral_supported?' do
- it 'returns false when using PostgreSQL 9.2' do
- allow(described_class).to receive(:version).and_return('9.2.1')
-
- expect(described_class.join_lateral_supported?).to eq(false)
- end
-
- it 'returns true when using PostgreSQL 9.3.0 or newer' do
- allow(described_class).to receive(:version).and_return('9.3.0')
-
- expect(described_class.join_lateral_supported?).to eq(true)
- end
- end
-
describe '.replication_slots_supported?' do
it 'returns false when using PostgreSQL 9.3' do
allow(described_class).to receive(:version).and_return('9.3.1')
diff --git a/spec/lib/gitlab/diff/file_collection/merge_request_diff_batch_spec.rb b/spec/lib/gitlab/diff/file_collection/merge_request_diff_batch_spec.rb
new file mode 100644
index 00000000000..265c6260ca9
--- /dev/null
+++ b/spec/lib/gitlab/diff/file_collection/merge_request_diff_batch_spec.rb
@@ -0,0 +1,126 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Diff::FileCollection::MergeRequestDiffBatch do
+ let(:merge_request) { create(:merge_request) }
+ let(:batch_page) { 1 }
+ let(:batch_size) { 10 }
+ let(:diffable) { merge_request.merge_request_diff }
+ let(:diff_files_relation) { diffable.merge_request_diff_files }
+
+ subject do
+ described_class.new(diffable,
+ batch_page,
+ batch_size,
+ diff_options: nil)
+ end
+
+ let(:diff_files) { subject.diff_files }
+
+ describe 'initialize' do
+ it 'memoizes pagination_data' do
+ expect(subject.pagination_data).to eq(current_page: 1, next_page: 2, total_pages: 2)
+ end
+ end
+
+ describe '#diff_files' do
+ let(:batch_size) { 3 }
+ let(:paginated_rel) { diff_files_relation.page(batch_page).per(batch_size) }
+
+ let(:expected_batch_files) do
+ paginated_rel.map(&:new_path)
+ end
+
+ it 'returns paginated diff files' do
+ expect(diff_files.size).to eq(3)
+ end
+
+ it 'returns a valid instance of a DiffCollection' do
+ expect(diff_files).to be_a(Gitlab::Git::DiffCollection)
+ end
+
+ context 'first page' do
+ it 'returns correct diff files' do
+ expect(diff_files.map(&:new_path)).to eq(expected_batch_files)
+ end
+ end
+
+ context 'another page' do
+ let(:batch_page) { 2 }
+
+ it 'returns correct diff files' do
+ expect(diff_files.map(&:new_path)).to eq(expected_batch_files)
+ end
+ end
+
+ context 'nil batch_page' do
+ let(:batch_page) { nil }
+
+ it 'returns correct diff files' do
+ expected_batch_files =
+ diff_files_relation.page(described_class::DEFAULT_BATCH_PAGE).per(batch_size).map(&:new_path)
+
+ expect(diff_files.map(&:new_path)).to eq(expected_batch_files)
+ end
+ end
+
+ context 'nil batch_size' do
+ let(:batch_size) { nil }
+
+ it 'returns correct diff files' do
+ expected_batch_files =
+ diff_files_relation.page(batch_page).per(described_class::DEFAULT_BATCH_SIZE).map(&:new_path)
+
+ expect(diff_files.map(&:new_path)).to eq(expected_batch_files)
+ end
+ end
+
+ context 'invalid page' do
+ let(:batch_page) { 999 }
+
+ it 'returns correct diff files' do
+ expect(diff_files.map(&:new_path)).to be_empty
+ end
+ end
+
+ context 'last page' do
+ it 'returns correct diff files' do
+ last_page = paginated_rel.total_pages
+ collection = described_class.new(diffable,
+ last_page,
+ batch_size,
+ diff_options: nil)
+
+ expected_batch_files = diff_files_relation.page(last_page).per(batch_size).map(&:new_path)
+
+ expect(collection.diff_files.map(&:new_path)).to eq(expected_batch_files)
+ end
+ end
+ end
+
+ it_behaves_like 'unfoldable diff' do
+ subject do
+ described_class.new(merge_request.merge_request_diff,
+ batch_page,
+ batch_size,
+ diff_options: nil)
+ end
+ end
+
+ it_behaves_like 'diff statistics' do
+ let(:collection_default_args) do
+ { diff_options: {} }
+ end
+
+ let(:diffable) { merge_request.merge_request_diff }
+ let(:stub_path) { '.gitignore' }
+
+ subject do
+ described_class.new(merge_request.merge_request_diff,
+ batch_page,
+ batch_size,
+ collection_default_args)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/diff/position_collection_spec.rb b/spec/lib/gitlab/diff/position_collection_spec.rb
new file mode 100644
index 00000000000..f2a8312587c
--- /dev/null
+++ b/spec/lib/gitlab/diff/position_collection_spec.rb
@@ -0,0 +1,86 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Diff::PositionCollection do
+ let(:merge_request) { build(:merge_request) }
+
+ def build_text_position(attrs = {})
+ attributes = {
+ old_path: "files/ruby/popen.rb",
+ new_path: "files/ruby/popen.rb",
+ old_line: nil,
+ new_line: 14,
+ diff_refs: merge_request.diff_refs
+ }.merge(attrs)
+
+ Gitlab::Diff::Position.new(attributes)
+ end
+
+ def build_image_position(attrs = {})
+ attributes = {
+ old_path: "files/images/any_image.png",
+ new_path: "files/images/any_image.png",
+ width: 10,
+ height: 10,
+ x: 1,
+ y: 1,
+ diff_refs: merge_request.diff_refs,
+ position_type: "image"
+ }.merge(attrs)
+
+ Gitlab::Diff::Position.new(attributes)
+ end
+
+ let(:text_position) { build_text_position }
+ let(:folded_text_position) { build_text_position(old_line: 1, new_line: 1) }
+ let(:image_position) { build_image_position }
+ let(:invalid_position) { 'a position' }
+ let(:head_sha) { merge_request.diff_head_sha }
+
+ let(:collection) do
+ described_class.new([text_position, folded_text_position, image_position, invalid_position], head_sha)
+ end
+
+ describe '#to_a' do
+ it 'returns all positions that are Gitlab::Diff::Position' do
+ expect(collection.to_a).to eq([text_position, folded_text_position, image_position])
+ end
+ end
+
+ describe '#unfoldable' do
+ it 'returns unfoldable diff positions' do
+ expect(collection.unfoldable).to eq([folded_text_position])
+ end
+
+ context 'when given head_sha does not match with positions head_sha' do
+ let(:head_sha) { 'unknown' }
+
+ it 'returns no position' do
+ expect(collection.unfoldable).to be_empty
+ end
+ end
+
+ context 'when given head_sha is nil' do
+ let(:head_sha) { nil }
+
+ it 'returns unfoldable diff positions unfiltered by head_sha' do
+ expect(collection.unfoldable).to eq([folded_text_position])
+ end
+ end
+ end
+
+ describe '#concat' do
+ let(:new_text_position) { build_text_position(old_line: 1, new_line: 1) }
+
+ it 'returns a Gitlab::Diff::Position' do
+ expect(collection.concat([new_text_position])).to be_a(described_class)
+ end
+
+ it 'concatenates the new position to the collection' do
+ collection.concat([new_text_position])
+
+ expect(collection.to_a).to eq([text_position, folded_text_position, image_position, new_text_position])
+ end
+ end
+end
diff --git a/spec/lib/gitlab/diff/position_spec.rb b/spec/lib/gitlab/diff/position_spec.rb
index 399787635c0..839780b53fe 100644
--- a/spec/lib/gitlab/diff/position_spec.rb
+++ b/spec/lib/gitlab/diff/position_spec.rb
@@ -130,6 +130,26 @@ describe Gitlab::Diff::Position do
expect(diff_file.new_path).to eq(subject.new_path)
expect(diff_file.diff_refs).to eq(subject.diff_refs)
end
+
+ context 'different folded positions in the same diff file' do
+ def diff_file(args = {})
+ described_class
+ .new(args_for_text.merge(args))
+ .diff_file(project.repository)
+ end
+
+ it 'expands the diff file', :request_store do
+ expect_any_instance_of(Gitlab::Diff::File)
+ .to receive(:unfold_diff_lines).and_call_original
+
+ diff_file(old_line: 1, new_line: 1, diff_refs: commit.diff_refs)
+
+ expect_any_instance_of(Gitlab::Diff::File)
+ .to receive(:unfold_diff_lines).and_call_original
+
+ diff_file(old_line: 5, new_line: 5, diff_refs: commit.diff_refs)
+ end
+ end
end
describe "#diff_line" do
diff --git a/spec/lib/gitlab/discussions_diff/file_collection_spec.rb b/spec/lib/gitlab/discussions_diff/file_collection_spec.rb
index 6ef1e41450f..a13727b62ea 100644
--- a/spec/lib/gitlab/discussions_diff/file_collection_spec.rb
+++ b/spec/lib/gitlab/discussions_diff/file_collection_spec.rb
@@ -40,6 +40,14 @@ describe Gitlab::DiscussionsDiff::FileCollection do
subject.load_highlight
end
+ it 'does not write cache for empty mapping' do
+ allow(subject).to receive(:highlighted_lines_by_ids).and_return([])
+
+ expect(Gitlab::DiscussionsDiff::HighlightCache).not_to receive(:write_multiple)
+
+ subject.load_highlight
+ end
+
it 'does not write cache for resolved notes' do
diff_note_a.update_column(:resolved_at, Time.now)
diff --git a/spec/lib/gitlab/downtime_check_spec.rb b/spec/lib/gitlab/downtime_check_spec.rb
index 56ad49d528f..5a5e34961a4 100644
--- a/spec/lib/gitlab/downtime_check_spec.rb
+++ b/spec/lib/gitlab/downtime_check_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
describe Gitlab::DowntimeCheck do
subject { described_class.new }
+
let(:path) { 'foo.rb' }
describe '#check' do
diff --git a/spec/lib/gitlab/email/message/repository_push_spec.rb b/spec/lib/gitlab/email/message/repository_push_spec.rb
index 84c5b38127e..b57764bceef 100644
--- a/spec/lib/gitlab/email/message/repository_push_spec.rb
+++ b/spec/lib/gitlab/email/message/repository_push_spec.rb
@@ -28,90 +28,107 @@ describe Gitlab::Email::Message::RepositoryPush do
describe '#project' do
subject { message.project }
+
it { is_expected.to eq project }
it { is_expected.to be_an_instance_of Project }
end
describe '#project_namespace' do
subject { message.project_namespace }
+
it { is_expected.to eq group }
it { is_expected.to be_kind_of Namespace }
end
describe '#project_name_with_namespace' do
subject { message.project_name_with_namespace }
+
it { is_expected.to eq "#{group.name} / #{project.path}" }
end
describe '#author' do
subject { message.author }
+
it { is_expected.to eq author }
it { is_expected.to be_an_instance_of User }
end
describe '#author_name' do
subject { message.author_name }
+
it { is_expected.to eq 'Author' }
end
describe '#commits' do
subject { message.commits }
+
it { is_expected.to be_kind_of Array }
it { is_expected.to all(be_instance_of Commit) }
end
describe '#diffs' do
subject { message.diffs }
+
it { is_expected.to all(be_an_instance_of Gitlab::Diff::File) }
end
describe '#diffs_count' do
subject { message.diffs_count }
+
it { is_expected.to eq raw_compare.diffs.size }
end
describe '#compare' do
subject { message.compare }
+
it { is_expected.to be_an_instance_of Compare }
end
describe '#compare_timeout' do
subject { message.compare_timeout }
+
it { is_expected.to eq raw_compare.diffs.overflow? }
end
describe '#reverse_compare?' do
subject { message.reverse_compare? }
+
it { is_expected.to eq false }
end
describe '#disable_diffs?' do
subject { message.disable_diffs? }
+
it { is_expected.to eq false }
end
describe '#send_from_committer_email?' do
subject { message.send_from_committer_email? }
+
it { is_expected.to eq true }
end
describe '#action_name' do
subject { message.action_name }
+
it { is_expected.to eq 'pushed to' }
end
describe '#ref_name' do
subject { message.ref_name }
+
it { is_expected.to eq 'master' }
end
describe '#ref_type' do
subject { message.ref_type }
+
it { is_expected.to eq 'branch' }
end
describe '#target_url' do
subject { message.target_url }
+
it { is_expected.to include 'compare' }
it { is_expected.to include compare.commits.first.parents.first.id }
it { is_expected.to include compare.commits.last.id }
@@ -119,6 +136,7 @@ describe Gitlab::Email::Message::RepositoryPush do
describe '#subject' do
subject { message.subject }
+
it { is_expected.to include "[Git][#{project.full_path}]" }
it { is_expected.to include "#{compare.commits.length} commits" }
it { is_expected.to include compare.commits.first.message.split("\n").first }
diff --git a/spec/lib/gitlab/email/receiver_spec.rb b/spec/lib/gitlab/email/receiver_spec.rb
index 6b5a355e598..43c73242f5f 100644
--- a/spec/lib/gitlab/email/receiver_spec.rb
+++ b/spec/lib/gitlab/email/receiver_spec.rb
@@ -40,7 +40,15 @@ describe Gitlab::Email::Receiver do
end
end
- context "when the email was auto generated" do
+ context "when the email was auto generated with Auto-Submitted header" do
+ let(:email_raw) { fixture_file("emails/auto_submitted.eml") }
+
+ it "raises an AutoGeneratedEmailError" do
+ expect { receiver.execute }.to raise_error(Gitlab::Email::AutoGeneratedEmailError)
+ end
+ end
+
+ context "when the email was auto generated with X-Autoreply header" do
let(:email_raw) { fixture_file("emails/auto_reply.eml") }
it "raises an AutoGeneratedEmailError" do
diff --git a/spec/lib/gitlab/experimentation_spec.rb b/spec/lib/gitlab/experimentation_spec.rb
new file mode 100644
index 00000000000..2e5fd16d370
--- /dev/null
+++ b/spec/lib/gitlab/experimentation_spec.rb
@@ -0,0 +1,157 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Experimentation::ControllerConcern, type: :controller do
+ controller(ApplicationController) do
+ include Gitlab::Experimentation::ControllerConcern
+
+ def index
+ head :ok
+ end
+ end
+
+ describe '#set_experimentation_subject_id_cookie' do
+ before do
+ get :index
+ end
+
+ context 'cookie is present' do
+ before do
+ cookies[:experimentation_subject_id] = 'test'
+ end
+
+ it 'does not change the cookie' do
+ expect(cookies[:experimentation_subject_id]).to eq 'test'
+ end
+ end
+
+ context 'cookie is not present' do
+ it 'sets a permanent signed cookie' do
+ expect(cookies.permanent.signed[:experimentation_subject_id]).to be_present
+ end
+ end
+ end
+
+ describe '#experiment_enabled?' do
+ context 'cookie is not present' do
+ it 'calls Gitlab::Experimentation.enabled? with the name of the experiment and an experimentation_subject_index of nil' do
+ expect(Gitlab::Experimentation).to receive(:enabled?).with(:test_experiment, nil)
+ controller.experiment_enabled?(:test_experiment)
+ end
+ end
+
+ context 'cookie is present' do
+ before do
+ cookies.permanent.signed[:experimentation_subject_id] = 'abcd-1234'
+ get :index
+ end
+
+ it 'calls Gitlab::Experimentation.enabled? with the name of the experiment and an experimentation_subject_index of the modulo 100 of the hex value of the uuid' do
+ # 'abcd1234'.hex % 100 = 76
+ expect(Gitlab::Experimentation).to receive(:enabled?).with(:test_experiment, 76)
+ controller.experiment_enabled?(:test_experiment)
+ end
+ end
+ end
+end
+
+describe Gitlab::Experimentation do
+ before do
+ stub_const('Gitlab::Experimentation::EXPERIMENTS', {
+ test_experiment: {
+ feature_toggle: feature_toggle,
+ environment: environment,
+ enabled_ratio: enabled_ratio
+ }
+ })
+
+ stub_feature_flags(feature_toggle => true)
+ end
+
+ let(:feature_toggle) { :test_experiment_toggle }
+ let(:environment) { Rails.env.test? }
+ let(:enabled_ratio) { 0.1 }
+
+ describe '.enabled?' do
+ subject { described_class.enabled?(:test_experiment, experimentation_subject_index) }
+
+ let(:experimentation_subject_index) { 9 }
+
+ context 'feature toggle is enabled, we are on the right environment and we are selected' do
+ it { is_expected.to be_truthy }
+ end
+
+ describe 'experiment is not defined' do
+ it 'returns false' do
+ expect(described_class.enabled?(:missing_experiment, experimentation_subject_index)).to be_falsey
+ end
+ end
+
+ describe 'feature toggle' do
+ context 'feature toggle is not set' do
+ let(:feature_toggle) { nil }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'feature toggle is not set, but a feature with the experiment key as name does exist' do
+ before do
+ stub_feature_flags(test_experiment: false)
+ end
+
+ let(:feature_toggle) { nil }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'feature toggle is disabled' do
+ before do
+ stub_feature_flags(feature_toggle => false)
+ end
+
+ it { is_expected.to be_falsey }
+ end
+ end
+
+ describe 'environment' do
+ context 'environment is not set' do
+ let(:environment) { nil }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'we are on the wrong environment' do
+ let(:environment) { ::Gitlab.com? }
+
+ it { is_expected.to be_falsey }
+ end
+ end
+
+ describe 'enabled ratio' do
+ context 'enabled ratio is not set' do
+ let(:enabled_ratio) { nil }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'experimentation_subject_index is not set' do
+ let(:experimentation_subject_index) { nil }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'experimentation_subject_index is an empty string' do
+ let(:experimentation_subject_index) { '' }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'experimentation_subject_index outside enabled ratio' do
+ let(:experimentation_subject_index) { 11 }
+
+ it { is_expected.to be_falsey }
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/favicon_spec.rb b/spec/lib/gitlab/favicon_spec.rb
index d221f39c2ed..617c0f88a89 100644
--- a/spec/lib/gitlab/favicon_spec.rb
+++ b/spec/lib/gitlab/favicon_spec.rb
@@ -57,6 +57,7 @@ RSpec.describe Gitlab::Favicon, :request_store do
favicon_status_manual
favicon_status_not_found
favicon_status_pending
+ favicon_status_preparing
favicon_status_running
favicon_status_scheduled
favicon_status_skipped
diff --git a/spec/lib/gitlab/file_markdown_link_builder_spec.rb b/spec/lib/gitlab/file_markdown_link_builder_spec.rb
index d9e2e162ae8..de0ac9733e6 100644
--- a/spec/lib/gitlab/file_markdown_link_builder_spec.rb
+++ b/spec/lib/gitlab/file_markdown_link_builder_spec.rb
@@ -27,19 +27,35 @@ describe Gitlab::FileMarkdownLinkBuilder do
end
end
- context 'when file is an image or video' do
- let(:filename) { 'dk.png' }
+ context 'when file is an image' do
+ let(:filename) { 'my_image.png' }
it 'returns preview markdown link' do
- expect(custom_class.markdown_link).to eq '![dk](/uploads/dk.png)'
+ expect(custom_class.markdown_link).to eq '![my_image](/uploads/my_image.png)'
end
end
- context 'when file is not an image or video' do
- let(:filename) { 'dk.zip' }
+ context 'when file is video' do
+ let(:filename) { 'my_video.mp4' }
+
+ it 'returns preview markdown link' do
+ expect(custom_class.markdown_link).to eq '![my_video](/uploads/my_video.mp4)'
+ end
+ end
+
+ context 'when file is audio' do
+ let(:filename) { 'my_audio.wav' }
+
+ it 'returns preview markdown link' do
+ expect(custom_class.markdown_link).to eq '![my_audio](/uploads/my_audio.wav)'
+ end
+ end
+
+ context 'when file is not embeddable' do
+ let(:filename) { 'my_zip.zip' }
it 'returns markdown link' do
- expect(custom_class.markdown_link).to eq '[dk.zip](/uploads/dk.zip)'
+ expect(custom_class.markdown_link).to eq '[my_zip.zip](/uploads/my_zip.zip)'
end
end
@@ -53,19 +69,35 @@ describe Gitlab::FileMarkdownLinkBuilder do
end
describe 'mardown_name' do
- context 'when file is an image or video' do
- let(:filename) { 'dk.png' }
+ context 'when file is an image' do
+ let(:filename) { 'my_image.png' }
+
+ it 'retrieves the name without the extension' do
+ expect(custom_class.markdown_name).to eq 'my_image'
+ end
+ end
+
+ context 'when file is video' do
+ let(:filename) { 'my_video.mp4' }
+
+ it 'retrieves the name without the extension' do
+ expect(custom_class.markdown_name).to eq 'my_video'
+ end
+ end
+
+ context 'when file is audio' do
+ let(:filename) { 'my_audio.wav' }
it 'retrieves the name without the extension' do
- expect(custom_class.markdown_name).to eq 'dk'
+ expect(custom_class.markdown_name).to eq 'my_audio'
end
end
- context 'when file is not an image or video' do
- let(:filename) { 'dk.zip' }
+ context 'when file is not embeddable' do
+ let(:filename) { 'my_zip.zip' }
it 'retrieves the name with the extesion' do
- expect(custom_class.markdown_name).to eq 'dk.zip'
+ expect(custom_class.markdown_name).to eq 'my_zip.zip'
end
end
diff --git a/spec/lib/gitlab/file_type_detection_spec.rb b/spec/lib/gitlab/file_type_detection_spec.rb
index 22ec7d414e8..05008bf895c 100644
--- a/spec/lib/gitlab/file_type_detection_spec.rb
+++ b/spec/lib/gitlab/file_type_detection_spec.rb
@@ -2,38 +2,298 @@
require 'spec_helper'
describe Gitlab::FileTypeDetection do
- def upload_fixture(filename)
- fixture_file_upload(File.join('spec', 'fixtures', filename))
- end
+ context 'when class is an uploader' do
+ let(:uploader) do
+ example_uploader = Class.new(CarrierWave::Uploader::Base) do
+ include Gitlab::FileTypeDetection
+
+ storage :file
+ end
- describe '#image_or_video?' do
- context 'when class is an uploader' do
- let(:uploader) do
- example_uploader = Class.new(CarrierWave::Uploader::Base) do
- include Gitlab::FileTypeDetection
+ example_uploader.new
+ end
+
+ def upload_fixture(filename)
+ fixture_file_upload(File.join('spec', 'fixtures', filename))
+ end
+
+ describe '#image?' do
+ it 'returns true for an image file' do
+ uploader.store!(upload_fixture('dk.png'))
+
+ expect(uploader).to be_image
+ end
+
+ it 'returns false if filename has a dangerous image extension' do
+ uploader.store!(upload_fixture('unsanitized.svg'))
+
+ expect(uploader).to be_dangerous_image
+ expect(uploader).not_to be_image
+ end
+
+ it 'returns false for a video file' do
+ uploader.store!(upload_fixture('video_sample.mp4'))
+
+ expect(uploader).not_to be_image
+ end
+
+ it 'returns false for an audio file' do
+ uploader.store!(upload_fixture('audio_sample.wav'))
+
+ expect(uploader).not_to be_image
+ end
+
+ it 'returns false if filename is blank' do
+ uploader.store!(upload_fixture('dk.png'))
+
+ allow(uploader).to receive(:filename).and_return(nil)
+
+ expect(uploader).not_to be_image
+ end
+ end
+
+ describe '#video?' do
+ it 'returns true for a video file' do
+ uploader.store!(upload_fixture('video_sample.mp4'))
+
+ expect(uploader).to be_video
+ end
+
+ it 'returns false for an image file' do
+ uploader.store!(upload_fixture('dk.png'))
+
+ expect(uploader).not_to be_video
+ end
+
+ it 'returns false for an audio file' do
+ uploader.store!(upload_fixture('audio_sample.wav'))
+
+ expect(uploader).not_to be_video
+ end
- storage :file
- end
+ it 'returns false if file has a dangerous image extension' do
+ uploader.store!(upload_fixture('unsanitized.svg'))
- example_uploader.new
+ expect(uploader).to be_dangerous_image
+ expect(uploader).not_to be_video
end
+ it 'returns false if filename is blank' do
+ uploader.store!(upload_fixture('video_sample.mp4'))
+
+ allow(uploader).to receive(:filename).and_return(nil)
+
+ expect(uploader).not_to be_video
+ end
+ end
+
+ describe '#audio?' do
+ it 'returns true for an audio file' do
+ uploader.store!(upload_fixture('audio_sample.wav'))
+
+ expect(uploader).to be_audio
+ end
+
+ it 'returns false for an image file' do
+ uploader.store!(upload_fixture('dk.png'))
+
+ expect(uploader).not_to be_audio
+ end
+
+ it 'returns false for a video file' do
+ uploader.store!(upload_fixture('video_sample.mp4'))
+
+ expect(uploader).not_to be_audio
+ end
+
+ it 'returns false if file has a dangerous image extension' do
+ uploader.store!(upload_fixture('unsanitized.svg'))
+
+ expect(uploader).to be_dangerous_image
+ expect(uploader).not_to be_audio
+ end
+
+ it 'returns false if filename is blank' do
+ uploader.store!(upload_fixture('audio_sample.wav'))
+
+ allow(uploader).to receive(:filename).and_return(nil)
+
+ expect(uploader).not_to be_audio
+ end
+ end
+
+ describe '#embeddable?' do
it 'returns true for an image file' do
uploader.store!(upload_fixture('dk.png'))
- expect(uploader).to be_image_or_video
+ expect(uploader).to be_embeddable
end
it 'returns true for a video file' do
uploader.store!(upload_fixture('video_sample.mp4'))
- expect(uploader).to be_image_or_video
+ expect(uploader).to be_embeddable
+ end
+
+ it 'returns true for an audio file' do
+ uploader.store!(upload_fixture('audio_sample.wav'))
+
+ expect(uploader).to be_embeddable
+ end
+
+ it 'returns false if not an embeddable file' do
+ uploader.store!(upload_fixture('doc_sample.txt'))
+
+ expect(uploader).not_to be_embeddable
+ end
+
+ it 'returns false if filename has a dangerous image extension' do
+ uploader.store!(upload_fixture('unsanitized.svg'))
+
+ expect(uploader).to be_dangerous_image
+ expect(uploader).not_to be_embeddable
+ end
+
+ it 'returns false if filename is blank' do
+ uploader.store!(upload_fixture('dk.png'))
+
+ allow(uploader).to receive(:filename).and_return(nil)
+
+ expect(uploader).not_to be_embeddable
+ end
+ end
+
+ describe '#dangerous_image?' do
+ it 'returns true if filename has a dangerous extension' do
+ uploader.store!(upload_fixture('unsanitized.svg'))
+
+ expect(uploader).to be_dangerous_image
+ end
+
+ it 'returns false for an image file' do
+ uploader.store!(upload_fixture('dk.png'))
+
+ expect(uploader).not_to be_dangerous_image
+ end
+
+ it 'returns false for a video file' do
+ uploader.store!(upload_fixture('video_sample.mp4'))
+
+ expect(uploader).not_to be_dangerous_image
+ end
+
+ it 'returns false for an audio file' do
+ uploader.store!(upload_fixture('audio_sample.wav'))
+
+ expect(uploader).not_to be_dangerous_image
+ end
+
+ it 'returns false if filename is blank' do
+ uploader.store!(upload_fixture('dk.png'))
+
+ allow(uploader).to receive(:filename).and_return(nil)
+
+ expect(uploader).not_to be_dangerous_image
+ end
+ end
+
+ describe '#dangerous_video?' do
+ it 'returns false for a safe video file' do
+ uploader.store!(upload_fixture('video_sample.mp4'))
+
+ expect(uploader).not_to be_dangerous_video
+ end
+
+ it 'returns false if filename is a dangerous image extension' do
+ uploader.store!(upload_fixture('unsanitized.svg'))
+
+ expect(uploader).not_to be_dangerous_video
+ end
+
+ it 'returns false for an image file' do
+ uploader.store!(upload_fixture('dk.png'))
+
+ expect(uploader).not_to be_dangerous_video
+ end
+
+ it 'returns false for an audio file' do
+ uploader.store!(upload_fixture('audio_sample.wav'))
+
+ expect(uploader).not_to be_dangerous_video
+ end
+
+ it 'returns false if filename is blank' do
+ uploader.store!(upload_fixture('dk.png'))
+
+ allow(uploader).to receive(:filename).and_return(nil)
+
+ expect(uploader).not_to be_dangerous_video
+ end
+ end
+
+ describe '#dangerous_audio?' do
+ it 'returns false for a safe audio file' do
+ uploader.store!(upload_fixture('audio_sample.wav'))
+
+ expect(uploader).not_to be_dangerous_audio
+ end
+
+ it 'returns false if filename is a dangerous image extension' do
+ uploader.store!(upload_fixture('unsanitized.svg'))
+
+ expect(uploader).not_to be_dangerous_audio
+ end
+
+ it 'returns false for an image file' do
+ uploader.store!(upload_fixture('dk.png'))
+
+ expect(uploader).not_to be_dangerous_audio
+ end
+
+ it 'returns false for an video file' do
+ uploader.store!(upload_fixture('video_sample.mp4'))
+
+ expect(uploader).not_to be_dangerous_audio
+ end
+
+ it 'returns false if filename is blank' do
+ uploader.store!(upload_fixture('dk.png'))
+
+ allow(uploader).to receive(:filename).and_return(nil)
+
+ expect(uploader).not_to be_dangerous_audio
+ end
+ end
+
+ describe '#dangerous_embeddable?' do
+ it 'returns true if filename has a dangerous image extension' do
+ uploader.store!(upload_fixture('unsanitized.svg'))
+
+ expect(uploader).to be_dangerous_embeddable
+ end
+
+ it 'returns false for an image file' do
+ uploader.store!(upload_fixture('dk.png'))
+
+ expect(uploader).not_to be_dangerous_embeddable
+ end
+
+ it 'returns false for a video file' do
+ uploader.store!(upload_fixture('video_sample.mp4'))
+
+ expect(uploader).not_to be_dangerous_embeddable
end
- it 'returns false for other extensions' do
+ it 'returns false for an audio file' do
+ uploader.store!(upload_fixture('audio_sample.wav'))
+
+ expect(uploader).not_to be_dangerous_embeddable
+ end
+
+ it 'returns false for a non-embeddable file' do
uploader.store!(upload_fixture('doc_sample.txt'))
- expect(uploader).not_to be_image_or_video
+ expect(uploader).not_to be_dangerous_embeddable
end
it 'returns false if filename is blank' do
@@ -41,41 +301,289 @@ describe Gitlab::FileTypeDetection do
allow(uploader).to receive(:filename).and_return(nil)
- expect(uploader).not_to be_image_or_video
+ expect(uploader).not_to be_dangerous_embeddable
end
end
+ end
- context 'when class is a regular class' do
- let(:custom_class) do
- custom_class = Class.new do
- include Gitlab::FileTypeDetection
- end
+ context 'when class is a regular class' do
+ let(:custom_class) do
+ custom_class = Class.new do
+ include Gitlab::FileTypeDetection
+ end
+
+ custom_class.new
+ end
+
+ describe '#image?' do
+ it 'returns true for an image file' do
+ allow(custom_class).to receive(:filename).and_return('dk.png')
+
+ expect(custom_class).to be_image
+ end
+
+ it 'returns false if file has a dangerous image extension' do
+ allow(custom_class).to receive(:filename).and_return('unsanitized.svg')
+
+ expect(custom_class).to be_dangerous_image
+ expect(custom_class).not_to be_image
+ end
+
+ it 'returns false for a video file' do
+ allow(custom_class).to receive(:filename).and_return('video_sample.mp4')
- custom_class.new
+ expect(custom_class).not_to be_image
end
+ it 'returns false for an audio file' do
+ allow(custom_class).to receive(:filename).and_return('audio_sample.wav')
+
+ expect(custom_class).not_to be_image
+ end
+
+ it 'returns false if filename is blank' do
+ allow(custom_class).to receive(:filename).and_return(nil)
+
+ expect(custom_class).not_to be_image
+ end
+ end
+
+ describe '#video?' do
+ it 'returns true for a video file' do
+ allow(custom_class).to receive(:filename).and_return('video_sample.mp4')
+
+ expect(custom_class).to be_video
+ end
+
+ it 'returns false for an image file' do
+ allow(custom_class).to receive(:filename).and_return('dk.png')
+
+ expect(custom_class).not_to be_video
+ end
+
+ it 'returns false for an audio file' do
+ allow(custom_class).to receive(:filename).and_return('audio_sample.wav')
+
+ expect(custom_class).not_to be_video
+ end
+
+ it 'returns false if file has a dangerous image extension' do
+ allow(custom_class).to receive(:filename).and_return('unsanitized.svg')
+
+ expect(custom_class).to be_dangerous_image
+ expect(custom_class).not_to be_video
+ end
+
+ it 'returns false if filename is blank' do
+ allow(custom_class).to receive(:filename).and_return(nil)
+
+ expect(custom_class).not_to be_video
+ end
+ end
+
+ describe '#audio?' do
+ it 'returns true for an audio file' do
+ allow(custom_class).to receive(:filename).and_return('audio_sample.wav')
+
+ expect(custom_class).to be_audio
+ end
+
+ it 'returns false for an image file' do
+ allow(custom_class).to receive(:filename).and_return('dk.png')
+
+ expect(custom_class).not_to be_audio
+ end
+
+ it 'returns false for a video file' do
+ allow(custom_class).to receive(:filename).and_return('video_sample.mp4')
+
+ expect(custom_class).not_to be_audio
+ end
+
+ it 'returns false if file has a dangerous image extension' do
+ allow(custom_class).to receive(:filename).and_return('unsanitized.svg')
+
+ expect(custom_class).to be_dangerous_image
+ expect(custom_class).not_to be_audio
+ end
+
+ it 'returns false if filename is blank' do
+ allow(custom_class).to receive(:filename).and_return(nil)
+
+ expect(custom_class).not_to be_audio
+ end
+ end
+
+ describe '#embeddable?' do
it 'returns true for an image file' do
allow(custom_class).to receive(:filename).and_return('dk.png')
- expect(custom_class).to be_image_or_video
+ expect(custom_class).to be_embeddable
end
it 'returns true for a video file' do
allow(custom_class).to receive(:filename).and_return('video_sample.mp4')
- expect(custom_class).to be_image_or_video
+ expect(custom_class).to be_embeddable
+ end
+
+ it 'returns true for an audio file' do
+ allow(custom_class).to receive(:filename).and_return('audio_sample.wav')
+
+ expect(custom_class).to be_embeddable
+ end
+
+ it 'returns false if not an embeddable file' do
+ allow(custom_class).to receive(:filename).and_return('doc_sample.txt')
+
+ expect(custom_class).not_to be_embeddable
+ end
+
+ it 'returns false if filename has a dangerous image extension' do
+ allow(custom_class).to receive(:filename).and_return('unsanitized.svg')
+
+ expect(custom_class).to be_dangerous_image
+ expect(custom_class).not_to be_embeddable
+ end
+
+ it 'returns false if filename is blank' do
+ allow(custom_class).to receive(:filename).and_return(nil)
+
+ expect(custom_class).not_to be_embeddable
+ end
+ end
+
+ describe '#dangerous_image?' do
+ it 'returns true if file has a dangerous image extension' do
+ allow(custom_class).to receive(:filename).and_return('unsanitized.svg')
+
+ expect(custom_class).to be_dangerous_image
+ end
+
+ it 'returns false for an image file' do
+ allow(custom_class).to receive(:filename).and_return('dk.png')
+
+ expect(custom_class).not_to be_dangerous_image
+ end
+
+ it 'returns false for a video file' do
+ allow(custom_class).to receive(:filename).and_return('video_sample.mp4')
+
+ expect(custom_class).not_to be_dangerous_image
+ end
+
+ it 'returns false for an audio file' do
+ allow(custom_class).to receive(:filename).and_return('audio_sample.wav')
+
+ expect(custom_class).not_to be_dangerous_image
+ end
+
+ it 'returns false if filename is blank' do
+ allow(custom_class).to receive(:filename).and_return(nil)
+
+ expect(custom_class).not_to be_dangerous_image
+ end
+ end
+
+ describe '#dangerous_video?' do
+ it 'returns false for a safe video file' do
+ allow(custom_class).to receive(:filename).and_return('video_sample.mp4')
+
+ expect(custom_class).not_to be_dangerous_video
+ end
+
+ it 'returns false for an image file' do
+ allow(custom_class).to receive(:filename).and_return('dk.png')
+
+ expect(custom_class).not_to be_dangerous_video
+ end
+
+ it 'returns false for an audio file' do
+ allow(custom_class).to receive(:filename).and_return('audio_sample.wav')
+
+ expect(custom_class).not_to be_dangerous_video
+ end
+
+ it 'returns false if file has a dangerous image extension' do
+ allow(custom_class).to receive(:filename).and_return('unsanitized.svg')
+
+ expect(custom_class).not_to be_dangerous_video
+ end
+
+ it 'returns false if filename is blank' do
+ allow(custom_class).to receive(:filename).and_return(nil)
+
+ expect(custom_class).not_to be_dangerous_video
+ end
+ end
+
+ describe '#dangerous_audio?' do
+ it 'returns false for a safe audio file' do
+ allow(custom_class).to receive(:filename).and_return('audio_sample.wav')
+
+ expect(custom_class).not_to be_dangerous_audio
+ end
+
+ it 'returns false for an image file' do
+ allow(custom_class).to receive(:filename).and_return('dk.png')
+
+ expect(custom_class).not_to be_dangerous_audio
+ end
+
+ it 'returns false for a video file' do
+ allow(custom_class).to receive(:filename).and_return('video_sample.mp4')
+
+ expect(custom_class).not_to be_dangerous_audio
+ end
+
+ it 'returns false if file has a dangerous image extension' do
+ allow(custom_class).to receive(:filename).and_return('unsanitized.svg')
+
+ expect(custom_class).not_to be_dangerous_audio
+ end
+
+ it 'returns false if filename is blank' do
+ allow(custom_class).to receive(:filename).and_return(nil)
+
+ expect(custom_class).not_to be_dangerous_audio
+ end
+ end
+
+ describe '#dangerous_embeddable?' do
+ it 'returns true if file has a dangerous image extension' do
+ allow(custom_class).to receive(:filename).and_return('unsanitized.svg')
+
+ expect(custom_class).to be_dangerous_embeddable
+ end
+
+ it 'returns false for an image file' do
+ allow(custom_class).to receive(:filename).and_return('dk.png')
+
+ expect(custom_class).not_to be_dangerous_embeddable
+ end
+
+ it 'returns false for a video file' do
+ allow(custom_class).to receive(:filename).and_return('video_sample.mp4')
+
+ expect(custom_class).not_to be_dangerous_embeddable
+ end
+
+ it 'returns false for an audio file' do
+ allow(custom_class).to receive(:filename).and_return('audio_sample.wav')
+
+ expect(custom_class).not_to be_dangerous_embeddable
end
- it 'returns false for other extensions' do
+ it 'returns false for a non-embeddable file' do
allow(custom_class).to receive(:filename).and_return('doc_sample.txt')
- expect(custom_class).not_to be_image_or_video
+ expect(custom_class).not_to be_dangerous_embeddable
end
it 'returns false if filename is blank' do
allow(custom_class).to receive(:filename).and_return(nil)
- expect(custom_class).not_to be_image_or_video
+ expect(custom_class).not_to be_dangerous_embeddable
end
end
end
diff --git a/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb b/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb
index d24f5c45107..eef3b9de476 100644
--- a/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb
+++ b/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb
@@ -84,11 +84,13 @@ describe Gitlab::Gfm::UploadsRewriter do
describe '#needs_rewrite?' do
subject { rewriter.needs_rewrite? }
+
it { is_expected.to eq true }
end
describe '#files' do
subject { rewriter.files }
+
it { is_expected.to be_an(Array) }
end
end
diff --git a/spec/lib/gitlab/git/branch_spec.rb b/spec/lib/gitlab/git/branch_spec.rb
index 0764e525ede..02ef7b92538 100644
--- a/spec/lib/gitlab/git/branch_spec.rb
+++ b/spec/lib/gitlab/git/branch_spec.rb
@@ -44,6 +44,7 @@ describe Gitlab::Git::Branch, :seed_helper do
describe '#size' do
subject { super().size }
+
it { is_expected.to eq(SeedRepo::Repo::BRANCHES.size) }
end
diff --git a/spec/lib/gitlab/git/changes_spec.rb b/spec/lib/gitlab/git/changes_spec.rb
new file mode 100644
index 00000000000..7f56d30cb48
--- /dev/null
+++ b/spec/lib/gitlab/git/changes_spec.rb
@@ -0,0 +1,81 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Git::Changes do
+ let(:changes) { described_class.new }
+
+ describe '#includes_branches?' do
+ subject { changes.includes_branches? }
+
+ context 'has changes for branches' do
+ before do
+ changes.add_branch_change(oldrev: 'abc123', newrev: 'def456', ref: 'branch')
+ end
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'has no changes for branches' do
+ before do
+ changes.add_tag_change(oldrev: 'abc123', newrev: 'def456', ref: 'tag')
+ end
+
+ it { is_expected.to be_falsey }
+ end
+ end
+
+ describe '#includes_tags?' do
+ subject { changes.includes_tags? }
+
+ context 'has changes for tags' do
+ before do
+ changes.add_tag_change(oldrev: 'abc123', newrev: 'def456', ref: 'tag')
+ end
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'has no changes for tags' do
+ before do
+ changes.add_branch_change(oldrev: 'abc123', newrev: 'def456', ref: 'branch')
+ end
+
+ it { is_expected.to be_falsey }
+ end
+ end
+
+ describe '#add_branch_change' do
+ let(:change) { { oldrev: 'abc123', newrev: 'def456', ref: 'branch' } }
+
+ subject { changes.add_branch_change(change) }
+
+ it 'adds the branch change to the collection' do
+ expect(subject).to include(change)
+ expect(subject.refs).to include(change[:ref])
+ expect(subject.repository_data).to include(before: change[:oldrev], after: change[:newrev], ref: change[:ref])
+ expect(subject.branch_changes).to include(change)
+ end
+
+ it 'does not add the change as a tag change' do
+ expect(subject.tag_changes).not_to include(change)
+ end
+ end
+
+ describe '#add_tag_change' do
+ let(:change) { { oldrev: 'abc123', newrev: 'def456', ref: 'tag' } }
+
+ subject { changes.add_tag_change(change) }
+
+ it 'adds the tag change to the collection' do
+ expect(subject).to include(change)
+ expect(subject.refs).to include(change[:ref])
+ expect(subject.repository_data).to include(before: change[:oldrev], after: change[:newrev], ref: change[:ref])
+ expect(subject.tag_changes).to include(change)
+ end
+
+ it 'does not add the change as a branch change' do
+ expect(subject.branch_changes).not_to include(change)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb
index 3f0e6b34291..23651e3d7f2 100644
--- a/spec/lib/gitlab/git/commit_spec.rb
+++ b/spec/lib/gitlab/git/commit_spec.rb
@@ -174,6 +174,7 @@ describe Gitlab::Git::Commit, :seed_helper do
describe '#id' do
subject { super().id }
+
it { is_expected.to eq(SeedRepo::LastCommit::ID) }
end
end
@@ -183,6 +184,7 @@ describe Gitlab::Git::Commit, :seed_helper do
describe '#id' do
subject { super().id }
+
it { is_expected.to eq(SeedRepo::Commit::ID) }
end
end
@@ -192,6 +194,7 @@ describe Gitlab::Git::Commit, :seed_helper do
describe '#id' do
subject { super().id }
+
it { is_expected.to eq(SeedRepo::BigCommit::ID) }
end
end
@@ -544,11 +547,13 @@ describe Gitlab::Git::Commit, :seed_helper do
describe '#id' do
subject { super().id }
+
it { is_expected.to eq(sample_commit_hash[:id])}
end
describe '#message' do
subject { super().message }
+
it { is_expected.to eq(sample_commit_hash[:message])}
end
end
@@ -558,16 +563,19 @@ describe Gitlab::Git::Commit, :seed_helper do
describe '#additions' do
subject { super().additions }
+
it { is_expected.to eq(11) }
end
describe '#deletions' do
subject { super().deletions }
+
it { is_expected.to eq(6) }
end
describe '#total' do
subject { super().total }
+
it { is_expected.to eq(17) }
end
end
@@ -596,6 +604,7 @@ describe Gitlab::Git::Commit, :seed_helper do
describe '#keys' do
subject { super().keys.sort }
+
it { is_expected.to match(sample_commit_hash.keys.sort) }
end
end
diff --git a/spec/lib/gitlab/git/diff_collection_spec.rb b/spec/lib/gitlab/git/diff_collection_spec.rb
index be6ab0c1200..ce45d6e24ba 100644
--- a/spec/lib/gitlab/git/diff_collection_spec.rb
+++ b/spec/lib/gitlab/git/diff_collection_spec.rb
@@ -10,6 +10,7 @@ describe Gitlab::Git::DiffCollection, :seed_helper do
expanded: expanded
)
end
+
let(:iterator) { MutatingConstantIterator.new(file_count, fake_diff(line_length, line_count)) }
let(:file_count) { 0 }
let(:line_length) { 1 }
@@ -21,6 +22,7 @@ describe Gitlab::Git::DiffCollection, :seed_helper do
describe '#to_a' do
subject { super().to_a }
+
it { is_expected.to be_kind_of ::Array }
end
@@ -52,16 +54,19 @@ describe Gitlab::Git::DiffCollection, :seed_helper do
describe '#overflow?' do
subject { super().overflow? }
+
it { is_expected.to be_falsey }
end
describe '#empty?' do
subject { super().empty? }
+
it { is_expected.to be_falsey }
end
describe '#real_size' do
subject { super().real_size }
+
it { is_expected.to eq('3') }
end
@@ -76,6 +81,7 @@ describe Gitlab::Git::DiffCollection, :seed_helper do
describe '#line_count' do
subject { super().line_count }
+
it { is_expected.to eq file_count * line_count }
end
@@ -84,16 +90,19 @@ describe Gitlab::Git::DiffCollection, :seed_helper do
describe '#overflow?' do
subject { super().overflow? }
+
it { is_expected.to be_falsey }
end
describe '#empty?' do
subject { super().empty? }
+
it { is_expected.to be_falsey }
end
describe '#real_size' do
subject { super().real_size }
+
it { is_expected.to eq('3') }
end
@@ -108,6 +117,7 @@ describe Gitlab::Git::DiffCollection, :seed_helper do
describe '#line_count' do
subject { super().line_count }
+
it { is_expected.to eq file_count * line_count }
end
end
@@ -118,21 +128,25 @@ describe Gitlab::Git::DiffCollection, :seed_helper do
describe '#overflow?' do
subject { super().overflow? }
+
it { is_expected.to be_truthy }
end
describe '#empty?' do
subject { super().empty? }
+
it { is_expected.to be_falsey }
end
describe '#real_size' do
subject { super().real_size }
+
it { is_expected.to eq('0+') }
end
describe '#line_count' do
subject { super().line_count }
+
it { is_expected.to eq 1000 }
end
@@ -143,21 +157,25 @@ describe Gitlab::Git::DiffCollection, :seed_helper do
describe '#overflow?' do
subject { super().overflow? }
+
it { is_expected.to be_falsey }
end
describe '#empty?' do
subject { super().empty? }
+
it { is_expected.to be_falsey }
end
describe '#real_size' do
subject { super().real_size }
+
it { is_expected.to eq('3') }
end
describe '#line_count' do
subject { super().line_count }
+
it { is_expected.to eq file_count * line_count }
end
@@ -174,21 +192,25 @@ describe Gitlab::Git::DiffCollection, :seed_helper do
describe '#overflow?' do
subject { super().overflow? }
+
it { is_expected.to be_truthy }
end
describe '#empty?' do
subject { super().empty? }
+
it { is_expected.to be_falsey }
end
describe '#real_size' do
subject { super().real_size }
+
it { is_expected.to eq('10+') }
end
describe '#line_count' do
subject { super().line_count }
+
it { is_expected.to eq 10 }
end
@@ -199,21 +221,25 @@ describe Gitlab::Git::DiffCollection, :seed_helper do
describe '#overflow?' do
subject { super().overflow? }
+
it { is_expected.to be_falsey }
end
describe '#empty?' do
subject { super().empty? }
+
it { is_expected.to be_falsey }
end
describe '#real_size' do
subject { super().real_size }
+
it { is_expected.to eq('11') }
end
describe '#line_count' do
subject { super().line_count }
+
it { is_expected.to eq file_count * line_count }
end
@@ -226,21 +252,25 @@ describe Gitlab::Git::DiffCollection, :seed_helper do
describe '#overflow?' do
subject { super().overflow? }
+
it { is_expected.to be_truthy }
end
describe '#empty?' do
subject { super().empty? }
+
it { is_expected.to be_falsey }
end
describe '#real_size' do
subject { super().real_size }
+
it { is_expected.to eq('3+') }
end
describe '#line_count' do
subject { super().line_count }
+
it { is_expected.to eq 120 }
end
@@ -251,21 +281,25 @@ describe Gitlab::Git::DiffCollection, :seed_helper do
describe '#overflow?' do
subject { super().overflow? }
+
it { is_expected.to be_falsey }
end
describe '#empty?' do
subject { super().empty? }
+
it { is_expected.to be_falsey }
end
describe '#real_size' do
subject { super().real_size }
+
it { is_expected.to eq('11') }
end
describe '#line_count' do
subject { super().line_count }
+
it { is_expected.to eq file_count * line_count }
end
@@ -282,21 +316,25 @@ describe Gitlab::Git::DiffCollection, :seed_helper do
describe '#overflow?' do
subject { super().overflow? }
+
it { is_expected.to be_falsey }
end
describe '#empty?' do
subject { super().empty? }
+
it { is_expected.to be_falsey }
end
describe '#real_size' do
subject { super().real_size }
+
it { is_expected.to eq('10') }
end
describe '#line_count' do
subject { super().line_count }
+
it { is_expected.to eq file_count * line_count }
end
@@ -310,21 +348,25 @@ describe Gitlab::Git::DiffCollection, :seed_helper do
describe '#overflow?' do
subject { super().overflow? }
+
it { is_expected.to be_truthy }
end
describe '#empty?' do
subject { super().empty? }
+
it { is_expected.to be_falsey }
end
describe '#real_size' do
subject { super().real_size }
+
it { is_expected.to eq('9+') }
end
describe '#line_count' do
subject { super().line_count }
+
it { is_expected.to eq file_count * line_count }
end
@@ -335,21 +377,25 @@ describe Gitlab::Git::DiffCollection, :seed_helper do
describe '#overflow?' do
subject { super().overflow? }
+
it { is_expected.to be_falsey }
end
describe '#empty?' do
subject { super().empty? }
+
it { is_expected.to be_falsey }
end
describe '#real_size' do
subject { super().real_size }
+
it { is_expected.to eq('10') }
end
describe '#line_count' do
subject { super().line_count }
+
it { is_expected.to eq file_count * line_count }
end
@@ -363,26 +409,31 @@ describe Gitlab::Git::DiffCollection, :seed_helper do
describe '#overflow?' do
subject { super().overflow? }
+
it { is_expected.to be_falsey }
end
describe '#empty?' do
subject { super().empty? }
+
it { is_expected.to be_truthy }
end
describe '#size' do
subject { super().size }
+
it { is_expected.to eq(0) }
end
describe '#real_size' do
subject { super().real_size }
+
it { is_expected.to eq('0')}
end
describe '#line_count' do
subject { super().line_count }
+
it { is_expected.to eq 0 }
end
end
@@ -537,6 +588,70 @@ describe Gitlab::Git::DiffCollection, :seed_helper do
end
end
end
+
+ context 'when offset_index is given' do
+ subject do
+ Gitlab::Git::DiffCollection.new(
+ iterator,
+ max_files: max_files,
+ max_lines: max_lines,
+ limits: limits,
+ offset_index: 2,
+ expanded: expanded
+ )
+ end
+
+ def diff(raw)
+ raw['diff']
+ end
+
+ let(:iterator) do
+ [
+ fake_diff(1, 1),
+ fake_diff(2, 2),
+ fake_diff(3, 3),
+ fake_diff(4, 4)
+ ]
+ end
+
+ it 'does not yield diffs before the offset' do
+ expect(subject.to_a.map(&:diff)).to eq(
+ [
+ diff(fake_diff(3, 3)),
+ diff(fake_diff(4, 4))
+ ]
+ )
+ end
+
+ context 'when go over safe limits on bytes' do
+ let(:iterator) do
+ [
+ fake_diff(1, 10), # 10
+ fake_diff(1, 10), # 20
+ fake_diff(1, 15), # 35
+ fake_diff(1, 20), # 55
+ fake_diff(1, 45), # 100 - limit hit
+ fake_diff(1, 45),
+ fake_diff(1, 20480),
+ fake_diff(1, 1)
+ ]
+ end
+
+ before do
+ stub_const('Gitlab::Git::DiffCollection::DEFAULT_LIMITS',
+ { max_files: max_files, max_lines: 80 })
+ end
+
+ it 'considers size of diffs before the offset for prunning' do
+ expect(subject.to_a.map(&:diff)).to eq(
+ [
+ diff(fake_diff(1, 15)),
+ diff(fake_diff(1, 20))
+ ]
+ )
+ end
+ end
+ end
end
def fake_diff(line_length, line_count)
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index dcb7401b695..44c41da7560 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -93,6 +93,7 @@ describe Gitlab::Git::Repository, :seed_helper do
describe '#last' do
subject { super().last }
+
it { is_expected.to eq("v1.2.1") }
end
it { is_expected.to include("v1.0.0") }
@@ -215,11 +216,13 @@ describe Gitlab::Git::Repository, :seed_helper do
describe '#first' do
subject { super().first }
+
it { is_expected.to eq('feature') }
end
describe '#last' do
subject { super().last }
+
it { is_expected.to eq('v1.2.1') }
end
end
@@ -2236,4 +2239,45 @@ describe Gitlab::Git::Repository, :seed_helper do
expect(repository.commit(new_commit.oid).id).to eq(new_commit.oid)
end
end
+
+ describe '#rename' do
+ let(:project) { create(:project, :repository)}
+ let(:repository) { project.repository }
+
+ it 'moves the repository' do
+ checksum = repository.checksum
+ new_relative_path = "rename_test/relative/path"
+ renamed_repository = Gitlab::Git::Repository.new(repository.storage, new_relative_path, nil, nil)
+
+ repository.rename(new_relative_path)
+
+ expect(renamed_repository.checksum).to eq(checksum)
+ expect(repository.exists?).to be false
+ end
+ end
+
+ describe '#remove' do
+ let(:project) { create(:project, :repository) }
+ let(:repository) { project.repository }
+
+ it 'removes the repository' do
+ expect(repository.exists?).to be true
+
+ repository.remove
+
+ expect(repository.raw_repository.exists?).to be false
+ end
+
+ context 'when the repository does not exist' do
+ let(:repository) { create(:project).repository }
+
+ it 'is idempotent' do
+ expect(repository.exists?).to be false
+
+ repository.remove
+
+ expect(repository.raw_repository.exists?).to be false
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb
index d584cdbe280..81dc96b538a 100644
--- a/spec/lib/gitlab/git_access_spec.rb
+++ b/spec/lib/gitlab/git_access_spec.rb
@@ -541,6 +541,13 @@ describe Gitlab::GitAccess do
expect { pull_access_check }.to raise_unauthorized('Your account has been blocked.')
end
+ it 'disallows deactivated users to pull' do
+ project.add_maintainer(user)
+ user.deactivate!
+
+ expect { pull_access_check }.to raise_unauthorized("Your account has been deactivated by your administrator. Please log back in from a web browser to reactivate your account at #{Gitlab.config.gitlab.url}")
+ end
+
context 'when the project repository does not exist' do
it 'returns not found' do
project.add_guest(user)
@@ -925,6 +932,12 @@ describe Gitlab::GitAccess do
project.add_developer(user)
end
+ it 'does not allow deactivated users to push' do
+ user.deactivate!
+
+ expect { push_access_check }.to raise_unauthorized("Your account has been deactivated by your administrator. Please log back in from a web browser to reactivate your account at #{Gitlab.config.gitlab.url}")
+ end
+
it 'cleans up the files' do
expect(project.repository).to receive(:clean_stale_repository_files).and_call_original
expect { push_access_check }.not_to raise_error
diff --git a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
index ba6abba4e61..71489adb373 100644
--- a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
@@ -252,31 +252,6 @@ describe Gitlab::GitalyClient::CommitService do
end
end
- describe '#patch' do
- let(:request) do
- Gitaly::CommitPatchRequest.new(
- repository: repository_message, revision: revision
- )
- end
- let(:response) { [double(data: "my "), double(data: "diff")] }
-
- subject { described_class.new(repository).patch(revision) }
-
- it 'sends an RPC request' do
- expect_any_instance_of(Gitaly::DiffService::Stub).to receive(:commit_patch)
- .with(request, kind_of(Hash)).and_return([])
-
- subject
- end
-
- it 'concatenates the responses data' do
- allow_any_instance_of(Gitaly::DiffService::Stub).to receive(:commit_patch)
- .with(request, kind_of(Hash)).and_return(response)
-
- expect(subject).to eq("my diff")
- end
- end
-
describe '#commit_stats' do
let(:request) do
Gitaly::CommitStatsRequest.new(
diff --git a/spec/lib/gitlab/gitaly_client/conflict_files_stitcher_spec.rb b/spec/lib/gitlab/gitaly_client/conflict_files_stitcher_spec.rb
index 1c933410bd5..a3602463756 100644
--- a/spec/lib/gitlab/gitaly_client/conflict_files_stitcher_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/conflict_files_stitcher_spec.rb
@@ -32,7 +32,7 @@ describe Gitlab::GitalyClient::ConflictFilesStitcher do
double(files: [double(header: nil, content: content_2[11..-1])])
]
- conflict_files = described_class.new(messages).to_a
+ conflict_files = described_class.new(messages, target_repository.gitaly_repository).to_a
expect(conflict_files.size).to be(2)
diff --git a/spec/lib/gitlab/gitaly_client/repository_service_spec.rb b/spec/lib/gitlab/gitaly_client/repository_service_spec.rb
index a3808adb376..f4b73931f21 100644
--- a/spec/lib/gitlab/gitaly_client/repository_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/repository_service_spec.rb
@@ -272,4 +272,26 @@ describe Gitlab::GitalyClient::RepositoryService do
end
end
end
+
+ describe 'remove' do
+ it 'sends a remove_repository message' do
+ expect_any_instance_of(Gitaly::RepositoryService::Stub)
+ .to receive(:remove_repository)
+ .with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
+ .and_return(double(value: true))
+
+ client.remove
+ end
+ end
+
+ describe 'rename' do
+ it 'sends a rename_repository message' do
+ expect_any_instance_of(Gitaly::RepositoryService::Stub)
+ .to receive(:rename_repository)
+ .with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
+ .and_return(double(value: true))
+
+ client.rename('some/new/path')
+ end
+ end
end
diff --git a/spec/lib/gitlab/gitaly_client/storage_service_spec.rb b/spec/lib/gitlab/gitaly_client/storage_service_spec.rb
deleted file mode 100644
index 6c25e2d6ebd..00000000000
--- a/spec/lib/gitlab/gitaly_client/storage_service_spec.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::GitalyClient::StorageService do
- describe '#delete_all_repositories' do
- let!(:project) { create(:project, :repository) }
-
- it 'removes all repositories' do
- described_class.new(project.repository_storage).delete_all_repositories
-
- expect(project.repository.exists?).to be(false)
- end
- end
-end
diff --git a/spec/lib/gitlab/gitaly_client/storage_settings_spec.rb b/spec/lib/gitlab/gitaly_client/storage_settings_spec.rb
index f2f53982b09..2f83e5a5221 100644
--- a/spec/lib/gitlab/gitaly_client/storage_settings_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/storage_settings_spec.rb
@@ -27,6 +27,38 @@ describe Gitlab::GitalyClient::StorageSettings do
end
end
+ describe '.gitaly_address' do
+ context 'when the storage settings have no gitaly address but one is requested' do
+ it 'raises an error' do
+ expect do
+ described_class.new("path" => Rails.root).gitaly_address
+ end.to raise_error("key not found: \"gitaly_address\"")
+ end
+ end
+
+ context 'when the storage settings have a gitaly address and one is requested' do
+ it 'returns the setting value' do
+ expect(described_class.new("path" => Rails.root, "gitaly_address" => "test").gitaly_address).to eq("test")
+ end
+ end
+
+ context 'when the storage settings have a gitaly address keyed symbolically' do
+ it 'raises no error' do
+ expect do
+ described_class.new("path" => Rails.root, :gitaly_address => "test").gitaly_address
+ end.not_to raise_error
+ end
+ end
+
+ context 'when the storage settings have a gitaly address keyed with a string' do
+ it 'raises no error' do
+ expect do
+ described_class.new("path" => Rails.root, "gitaly_address" => "test").gitaly_address
+ end.not_to raise_error
+ end
+ end
+ end
+
describe '.disk_access_denied?' do
context 'when Rugged is enabled', :enable_rugged do
it 'returns false' do
diff --git a/spec/lib/gitlab/gitaly_client_spec.rb b/spec/lib/gitlab/gitaly_client_spec.rb
index 1c5f72a4396..ea3bb12d049 100644
--- a/spec/lib/gitlab/gitaly_client_spec.rb
+++ b/spec/lib/gitlab/gitaly_client_spec.rb
@@ -182,24 +182,24 @@ describe Gitlab::GitalyClient do
end
it 'sets the gitaly-session-id in the metadata' do
- results = described_class.request_kwargs('default', nil)
+ results = described_class.request_kwargs('default', timeout: 1)
expect(results[:metadata]).to include('gitaly-session-id')
end
context 'when RequestStore is not enabled' do
it 'sets a different gitaly-session-id per request' do
- gitaly_session_id = described_class.request_kwargs('default', nil)[:metadata]['gitaly-session-id']
+ gitaly_session_id = described_class.request_kwargs('default', timeout: 1)[:metadata]['gitaly-session-id']
- expect(described_class.request_kwargs('default', nil)[:metadata]['gitaly-session-id']).not_to eq(gitaly_session_id)
+ expect(described_class.request_kwargs('default', timeout: 1)[:metadata]['gitaly-session-id']).not_to eq(gitaly_session_id)
end
end
context 'when RequestStore is enabled', :request_store do
it 'sets the same gitaly-session-id on every outgoing request metadata' do
- gitaly_session_id = described_class.request_kwargs('default', nil)[:metadata]['gitaly-session-id']
+ gitaly_session_id = described_class.request_kwargs('default', timeout: 1)[:metadata]['gitaly-session-id']
3.times do
- expect(described_class.request_kwargs('default', nil)[:metadata]['gitaly-session-id']).to eq(gitaly_session_id)
+ expect(described_class.request_kwargs('default', timeout: 1)[:metadata]['gitaly-session-id']).to eq(gitaly_session_id)
end
end
end
diff --git a/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb b/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb
index 6d614c6527a..8331f0b6bc7 100644
--- a/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb
@@ -311,10 +311,11 @@ describe Gitlab::GithubImport::Importer::PullRequestImporter, :clean_gitlab_redi
end
end
- it 'creates the merge request diffs' do
+ it 'creates a merge request diff and sets it as the latest' do
mr = insert_git_data
expect(mr.merge_request_diffs.exists?).to eq(true)
+ expect(mr.reload.latest_merge_request_diff_id).to eq(mr.merge_request_diffs.first.id)
end
it 'creates the merge request diff commits' do
diff --git a/spec/lib/gitlab/github_import/importer/releases_importer_spec.rb b/spec/lib/gitlab/github_import/importer/releases_importer_spec.rb
index 0e5419e6c5e..6a31c57a73d 100644
--- a/spec/lib/gitlab/github_import/importer/releases_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/releases_importer_spec.rb
@@ -4,17 +4,17 @@ describe Gitlab::GithubImport::Importer::ReleasesImporter do
let(:project) { create(:project) }
let(:client) { double(:client) }
let(:importer) { described_class.new(project, client) }
+ let(:github_release_name) { 'Initial Release' }
let(:created_at) { Time.new(2017, 1, 1, 12, 00) }
- let(:updated_at) { Time.new(2017, 1, 1, 12, 15) }
let(:released_at) { Time.new(2017, 1, 1, 12, 00) }
- let(:release) do
+ let(:github_release) do
double(
- :release,
+ :github_release,
tag_name: '1.0',
+ name: github_release_name,
body: 'This is my release',
created_at: created_at,
- updated_at: updated_at,
published_at: released_at
)
end
@@ -25,7 +25,7 @@ describe Gitlab::GithubImport::Importer::ReleasesImporter do
tag_name: '1.0',
description: 'This is my release',
created_at: created_at,
- updated_at: updated_at,
+ updated_at: created_at,
released_at: released_at
}
@@ -34,11 +34,27 @@ describe Gitlab::GithubImport::Importer::ReleasesImporter do
importer.execute
end
+
+ it 'imports draft releases' do
+ release_double = double(
+ name: 'Test',
+ body: 'This is description',
+ tag_name: '1.0',
+ description: 'This is my release',
+ created_at: created_at,
+ updated_at: created_at,
+ published_at: nil
+ )
+
+ expect(importer).to receive(:each_release).and_return([release_double])
+
+ expect { importer.execute }.to change { Release.count }.by(1)
+ end
end
describe '#build_releases' do
- it 'returns an Array containnig release rows' do
- expect(importer).to receive(:each_release).and_return([release])
+ it 'returns an Array containing release rows' do
+ expect(importer).to receive(:each_release).and_return([github_release])
rows = importer.build_releases
@@ -49,13 +65,13 @@ describe Gitlab::GithubImport::Importer::ReleasesImporter do
it 'does not create releases that already exist' do
create(:release, project: project, tag: '1.0', description: '1.0')
- expect(importer).to receive(:each_release).and_return([release])
+ expect(importer).to receive(:each_release).and_return([github_release])
expect(importer.build_releases).to be_empty
end
it 'uses a default release description if none is provided' do
- expect(release).to receive(:body).and_return('')
- expect(importer).to receive(:each_release).and_return([release])
+ expect(github_release).to receive(:body).and_return('')
+ expect(importer).to receive(:each_release).and_return([github_release])
release = importer.build_releases.first
@@ -64,7 +80,7 @@ describe Gitlab::GithubImport::Importer::ReleasesImporter do
end
describe '#build' do
- let(:release_hash) { importer.build(release) }
+ let(:release_hash) { importer.build(github_release) }
it 'returns the attributes of the release as a Hash' do
expect(release_hash).to be_an_instance_of(Hash)
@@ -88,13 +104,17 @@ describe Gitlab::GithubImport::Importer::ReleasesImporter do
end
it 'includes the updated timestamp' do
- expect(release_hash[:updated_at]).to eq(updated_at)
+ expect(release_hash[:updated_at]).to eq(created_at)
+ end
+
+ it 'includes the release name' do
+ expect(release_hash[:name]).to eq(github_release_name)
end
end
end
describe '#each_release' do
- let(:release) { double(:release) }
+ let(:github_release) { double(:github_release) }
before do
allow(project).to receive(:import_source).and_return('foo/bar')
@@ -102,7 +122,7 @@ describe Gitlab::GithubImport::Importer::ReleasesImporter do
allow(client)
.to receive(:releases)
.with('foo/bar')
- .and_return([release].to_enum)
+ .and_return([github_release].to_enum)
end
it 'returns an Enumerator' do
@@ -110,19 +130,19 @@ describe Gitlab::GithubImport::Importer::ReleasesImporter do
end
it 'yields every release to the Enumerator' do
- expect(importer.each_release.next).to eq(release)
+ expect(importer.each_release.next).to eq(github_release)
end
end
describe '#description_for' do
it 'returns the description when present' do
- expect(importer.description_for(release)).to eq(release.body)
+ expect(importer.description_for(github_release)).to eq(github_release.body)
end
it 'returns a generated description when one is not present' do
- allow(release).to receive(:body).and_return('')
+ allow(github_release).to receive(:body).and_return('')
- expect(importer.description_for(release)).to eq('Release for tag 1.0')
+ expect(importer.description_for(github_release)).to eq('Release for tag 1.0')
end
end
end
diff --git a/spec/lib/gitlab/gitlab_import/client_spec.rb b/spec/lib/gitlab/gitlab_import/client_spec.rb
index 22ad88e28cb..0f1745fcc02 100644
--- a/spec/lib/gitlab/gitlab_import/client_spec.rb
+++ b/spec/lib/gitlab/gitlab_import/client_spec.rb
@@ -52,6 +52,7 @@ describe Gitlab::GitlabImport::Client do
describe '#projects' do
subject(:method) { :projects }
+
let(:args) { [] }
let(:element_list) { build_list(:project, 2) }
@@ -67,6 +68,7 @@ describe Gitlab::GitlabImport::Client do
describe '#issues' do
subject(:method) { :issues }
+
let(:args) { [1] }
let(:element_list) { build_list(:issue, 2) }
@@ -82,6 +84,7 @@ describe Gitlab::GitlabImport::Client do
describe '#issue_comments' do
subject(:method) { :issue_comments }
+
let(:args) { [1, 1] }
let(:element_list) { build_list(:note_on_issue, 2) }
diff --git a/spec/lib/gitlab/gl_repository/repo_type_spec.rb b/spec/lib/gitlab/gl_repository/repo_type_spec.rb
index f06a2448ff7..9e09e1411ab 100644
--- a/spec/lib/gitlab/gl_repository/repo_type_spec.rb
+++ b/spec/lib/gitlab/gl_repository/repo_type_spec.rb
@@ -4,36 +4,6 @@ require 'spec_helper'
describe Gitlab::GlRepository::RepoType do
set(:project) { create(:project) }
- shared_examples 'a repo type' do
- describe "#identifier_for_subject" do
- subject { described_class.identifier_for_subject(project) }
-
- it { is_expected.to eq(expected_identifier) }
- end
-
- describe "#fetch_id" do
- it "finds an id match in the identifier" do
- expect(described_class.fetch_id(expected_identifier)).to eq(expected_id)
- end
-
- it 'does not break on other identifiers' do
- expect(described_class.fetch_id("wiki-noid")).to eq(nil)
- end
- end
-
- describe "#path_suffix" do
- subject { described_class.path_suffix }
-
- it { is_expected.to eq(expected_suffix) }
- end
-
- describe "#repository_for" do
- it "finds the repository for the repo type" do
- expect(described_class.repository_for(project)).to eq(expected_repository)
- end
- end
- end
-
describe Gitlab::GlRepository::PROJECT do
it_behaves_like 'a repo type' do
let(:expected_identifier) { "project-#{project.id}" }
diff --git a/spec/lib/gitlab/gpg/commit_spec.rb b/spec/lib/gitlab/gpg/commit_spec.rb
index fa47cfd519b..8401b683fd5 100644
--- a/spec/lib/gitlab/gpg/commit_spec.rb
+++ b/spec/lib/gitlab/gpg/commit_spec.rb
@@ -370,5 +370,33 @@ describe Gitlab::Gpg::Commit do
it_behaves_like 'returns the cached signature on second call'
end
+
+ context 'multiple commits with signatures' do
+ let(:first_signature) { create(:gpg_signature) }
+
+ let(:gpg_key) { create(:gpg_key, key: GpgHelpers::User2.public_key) }
+ let(:second_signature) { create(:gpg_signature, gpg_key: gpg_key) }
+
+ let!(:first_commit) { create(:commit, project: project, sha: first_signature.commit_sha) }
+ let!(:second_commit) { create(:commit, project: project, sha: second_signature.commit_sha) }
+
+ let(:commits) do
+ [first_commit, second_commit].map do |commit|
+ gpg_commit = described_class.new(commit)
+
+ allow(gpg_commit).to receive(:has_signature?).and_return(true)
+
+ gpg_commit
+ end
+ end
+
+ it 'does an aggregated sql request instead of 2 separate ones' do
+ recorder = ActiveRecord::QueryRecorder.new do
+ commits.each(&:signature)
+ end
+
+ expect(recorder.count).to eq(1)
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/graphs/commits_spec.rb b/spec/lib/gitlab/graphs/commits_spec.rb
index 530d4a981bf..09654e0439e 100644
--- a/spec/lib/gitlab/graphs/commits_spec.rb
+++ b/spec/lib/gitlab/graphs/commits_spec.rb
@@ -11,12 +11,14 @@ describe Gitlab::Graphs::Commits do
describe '#commit_per_day' do
context 'when range is only commits from today' do
subject { described_class.new([commit2, commit1]).commit_per_day }
+
it { is_expected.to eq 2 }
end
end
context 'when range is only commits from today' do
subject { described_class.new([commit2, commit1]) }
+
describe '#commit_per_day' do
it { expect(subject.commit_per_day).to eq 2 }
end
@@ -28,6 +30,7 @@ describe Gitlab::Graphs::Commits do
context 'with commits from yesterday and today' do
subject { described_class.new([commit2, commit1_yesterday]) }
+
describe '#commit_per_day' do
it { expect(subject.commit_per_day).to eq 1.0 }
end
diff --git a/spec/lib/gitlab/health_checks/gitaly_check_spec.rb b/spec/lib/gitlab/health_checks/gitaly_check_spec.rb
index 4912cd48761..36e2fd04aeb 100644
--- a/spec/lib/gitlab/health_checks/gitaly_check_spec.rb
+++ b/spec/lib/gitlab/health_checks/gitaly_check_spec.rb
@@ -18,18 +18,19 @@ describe Gitlab::HealthChecks::GitalyCheck do
context 'Gitaly server is up' do
let(:gitaly_check) { double(check: { success: true }) }
- it { is_expected.to eq([result_class.new(true, nil, shard: 'default')]) }
+ it { is_expected.to eq([result_class.new('gitaly_check', true, nil, shard: 'default')]) }
end
context 'Gitaly server is down' do
let(:gitaly_check) { double(check: { success: false, message: 'Connection refused' }) }
- it { is_expected.to eq([result_class.new(false, 'Connection refused', shard: 'default')]) }
+ it { is_expected.to eq([result_class.new('gitaly_check', false, 'Connection refused', shard: 'default')]) }
end
end
describe '#metrics' do
subject { described_class.metrics }
+
let(:server) { double(storage: 'default', read_writeable?: up) }
before do
diff --git a/spec/lib/gitlab/health_checks/probes/collection_spec.rb b/spec/lib/gitlab/health_checks/probes/collection_spec.rb
new file mode 100644
index 00000000000..33efc640257
--- /dev/null
+++ b/spec/lib/gitlab/health_checks/probes/collection_spec.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::HealthChecks::Probes::Collection do
+ let(:readiness) { described_class.new(*checks) }
+
+ describe '#call' do
+ subject { readiness.execute }
+
+ context 'with all checks' do
+ let(:checks) do
+ [
+ Gitlab::HealthChecks::DbCheck,
+ Gitlab::HealthChecks::Redis::RedisCheck,
+ Gitlab::HealthChecks::Redis::CacheCheck,
+ Gitlab::HealthChecks::Redis::QueuesCheck,
+ Gitlab::HealthChecks::Redis::SharedStateCheck,
+ Gitlab::HealthChecks::GitalyCheck
+ ]
+ end
+
+ it 'responds with readiness checks data' do
+ expect(subject.http_status).to eq(200)
+
+ expect(subject.json[:status]).to eq('ok')
+ expect(subject.json['db_check']).to contain_exactly(status: 'ok')
+ expect(subject.json['cache_check']).to contain_exactly(status: 'ok')
+ expect(subject.json['queues_check']).to contain_exactly(status: 'ok')
+ expect(subject.json['shared_state_check']).to contain_exactly(status: 'ok')
+ expect(subject.json['gitaly_check']).to contain_exactly(
+ status: 'ok', labels: { shard: 'default' })
+ end
+
+ context 'when Redis fails' do
+ before do
+ allow(Gitlab::HealthChecks::Redis::RedisCheck).to receive(:readiness).and_return(
+ Gitlab::HealthChecks::Result.new('redis_check', false, "check error"))
+ end
+
+ it 'responds with failure' do
+ expect(subject.http_status).to eq(503)
+
+ expect(subject.json[:status]).to eq('failed')
+ expect(subject.json['cache_check']).to contain_exactly(status: 'ok')
+ expect(subject.json['redis_check']).to contain_exactly(
+ status: 'failed', message: 'check error')
+ end
+ end
+ end
+
+ context 'without checks' do
+ let(:checks) { [] }
+
+ it 'responds with success' do
+ expect(subject.http_status).to eq(200)
+
+ expect(subject.json).to eq(status: 'ok')
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/health_checks/prometheus_text_format_spec.rb b/spec/lib/gitlab/health_checks/prometheus_text_format_spec.rb
deleted file mode 100644
index ed757ed60d8..00000000000
--- a/spec/lib/gitlab/health_checks/prometheus_text_format_spec.rb
+++ /dev/null
@@ -1,41 +0,0 @@
-describe Gitlab::HealthChecks::PrometheusTextFormat do
- let(:metric_class) { Gitlab::HealthChecks::Metric }
- subject { described_class.new }
-
- describe '#marshal' do
- let(:sample_metrics) do
- [metric_class.new('metric1', 1),
- metric_class.new('metric2', 2)]
- end
-
- it 'marshal to text with non repeating type definition' do
- expected = <<-EXPECTED.strip_heredoc
- # TYPE metric1 gauge
- metric1 1
- # TYPE metric2 gauge
- metric2 2
- EXPECTED
-
- expect(subject.marshal(sample_metrics)).to eq(expected)
- end
-
- context 'metrics where name repeats' do
- let(:sample_metrics) do
- [metric_class.new('metric1', 1),
- metric_class.new('metric1', 2),
- metric_class.new('metric2', 3)]
- end
-
- it 'marshal to text with non repeating type definition' do
- expected = <<-EXPECTED.strip_heredoc
- # TYPE metric1 gauge
- metric1 1
- metric1 2
- # TYPE metric2 gauge
- metric2 3
- EXPECTED
- expect(subject.marshal(sample_metrics)).to eq(expected)
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/health_checks/puma_check_spec.rb b/spec/lib/gitlab/health_checks/puma_check_spec.rb
new file mode 100644
index 00000000000..71b6386b174
--- /dev/null
+++ b/spec/lib/gitlab/health_checks/puma_check_spec.rb
@@ -0,0 +1,65 @@
+require 'spec_helper'
+
+describe Gitlab::HealthChecks::PumaCheck do
+ let(:result_class) { Gitlab::HealthChecks::Result }
+ let(:readiness) { described_class.readiness }
+ let(:metrics) { described_class.metrics }
+
+ shared_examples 'with state' do |(state, message)|
+ it "does provide readiness" do
+ expect(readiness).to eq(result_class.new('puma_check', state, message))
+ end
+
+ it "does provide metrics" do
+ expect(metrics).to include(
+ an_object_having_attributes(name: 'puma_check_success', value: state ? 1 : 0))
+ expect(metrics).to include(
+ an_object_having_attributes(name: 'puma_check_latency_seconds', value: be >= 0))
+ end
+ end
+
+ context 'when Puma is not loaded' do
+ before do
+ hide_const('Puma')
+ end
+
+ it "does not provide readiness and metrics" do
+ expect(readiness).to be_nil
+ expect(metrics).to be_nil
+ end
+ end
+
+ context 'when Puma is loaded' do
+ before do
+ stub_const('Puma', Module.new)
+ end
+
+ context 'when stats are missing' do
+ before do
+ expect(Puma).to receive(:stats).and_raise(NoMethodError)
+ end
+
+ it_behaves_like 'with state', [false, 'unexpected Puma check result: 0']
+ end
+
+ context 'for Single mode' do
+ before do
+ expect(Puma).to receive(:stats) do
+ '{}'
+ end
+ end
+
+ it_behaves_like 'with state', true
+ end
+
+ context 'for Cluster mode' do
+ before do
+ expect(Puma).to receive(:stats) do
+ '{"workers":2}'
+ end
+ end
+
+ it_behaves_like 'with state', true
+ end
+ end
+end
diff --git a/spec/lib/gitlab/health_checks/simple_check_shared.rb b/spec/lib/gitlab/health_checks/simple_check_shared.rb
index e2643458aca..03a7cf249cf 100644
--- a/spec/lib/gitlab/health_checks/simple_check_shared.rb
+++ b/spec/lib/gitlab/health_checks/simple_check_shared.rb
@@ -1,6 +1,7 @@
shared_context 'simple_check' do |metrics_prefix, check_name, success_result|
describe '#metrics' do
subject { described_class.metrics }
+
context 'Check is passing' do
before do
allow(described_class).to receive(:check).and_return success_result
@@ -34,6 +35,7 @@ shared_context 'simple_check' do |metrics_prefix, check_name, success_result|
describe '#readiness' do
subject { described_class.readiness }
+
context 'Check returns ok' do
before do
allow(described_class).to receive(:check).and_return success_result
@@ -57,10 +59,13 @@ shared_context 'simple_check' do |metrics_prefix, check_name, success_result|
it { is_expected.to have_attributes(success: false, message: "#{described_class.human_name} check timed out") }
end
- end
- describe '#liveness' do
- subject { described_class.readiness }
- it { is_expected.to eq(Gitlab::HealthChecks::Result.new(true)) }
+ context 'Check is raising an unhandled exception' do
+ before do
+ allow(described_class).to receive(:check ).and_raise "unexpected error"
+ end
+
+ it { is_expected.to have_attributes(success: false, message: "unexpected #{described_class.human_name} check result: unexpected error") }
+ end
end
end
diff --git a/spec/lib/gitlab/health_checks/unicorn_check_spec.rb b/spec/lib/gitlab/health_checks/unicorn_check_spec.rb
new file mode 100644
index 00000000000..c02d0c37738
--- /dev/null
+++ b/spec/lib/gitlab/health_checks/unicorn_check_spec.rb
@@ -0,0 +1,63 @@
+require 'spec_helper'
+
+describe Gitlab::HealthChecks::UnicornCheck do
+ let(:result_class) { Gitlab::HealthChecks::Result }
+ let(:readiness) { described_class.readiness }
+ let(:metrics) { described_class.metrics }
+
+ before do
+ described_class.clear_memoization(:http_servers)
+ end
+
+ shared_examples 'with state' do |(state, message)|
+ it "does provide readiness" do
+ expect(readiness).to eq(result_class.new('unicorn_check', state, message))
+ end
+
+ it "does provide metrics" do
+ expect(metrics).to include(
+ an_object_having_attributes(name: 'unicorn_check_success', value: state ? 1 : 0))
+ expect(metrics).to include(
+ an_object_having_attributes(name: 'unicorn_check_latency_seconds', value: be >= 0))
+ end
+ end
+
+ context 'when Unicorn is not loaded' do
+ before do
+ hide_const('Unicorn')
+ end
+
+ it "does not provide readiness and metrics" do
+ expect(readiness).to be_nil
+ expect(metrics).to be_nil
+ end
+ end
+
+ context 'when Unicorn is loaded' do
+ let(:http_server_class) { Struct.new(:worker_processes) }
+
+ before do
+ stub_const('Unicorn::HttpServer', http_server_class)
+ end
+
+ context 'when no servers are running' do
+ it_behaves_like 'with state', [false, 'unexpected Unicorn check result: 0']
+ end
+
+ context 'when servers without workers are running' do
+ before do
+ http_server_class.new(0)
+ end
+
+ it_behaves_like 'with state', [false, 'unexpected Unicorn check result: 0']
+ end
+
+ context 'when servers with workers are running' do
+ before do
+ http_server_class.new(1)
+ end
+
+ it_behaves_like 'with state', true
+ end
+ end
+end
diff --git a/spec/lib/gitlab/hook_data/issue_builder_spec.rb b/spec/lib/gitlab/hook_data/issue_builder_spec.rb
index 6013fb78bc7..ebd7feb0055 100644
--- a/spec/lib/gitlab/hook_data/issue_builder_spec.rb
+++ b/spec/lib/gitlab/hook_data/issue_builder_spec.rb
@@ -26,7 +26,7 @@ describe Gitlab::HookData::IssueBuilder do
duplicated_to_id
project_id
relative_position
- state
+ state_id
time_estimate
title
updated_at
@@ -41,6 +41,7 @@ describe Gitlab::HookData::IssueBuilder do
expect(data).to include(:human_time_estimate)
expect(data).to include(:human_total_time_spent)
expect(data).to include(:assignee_ids)
+ expect(data).to include(:state)
expect(data).to include('labels' => [label.hook_attrs])
end
diff --git a/spec/lib/gitlab/import/merge_request_creator_spec.rb b/spec/lib/gitlab/import/merge_request_creator_spec.rb
index 7c73e9b39f7..ff2c3032dbf 100644
--- a/spec/lib/gitlab/import/merge_request_creator_spec.rb
+++ b/spec/lib/gitlab/import/merge_request_creator_spec.rb
@@ -21,8 +21,11 @@ describe Gitlab::Import::MergeRequestCreator do
subject.execute(attributes)
- expect(merge_request.reload.merge_request_diffs.count).to eq(1)
- expect(merge_request.reload.merge_request_diffs.first.commits.count).to eq(commits_count)
+ merge_request.reload
+
+ expect(merge_request.merge_request_diffs.count).to eq(1)
+ expect(merge_request.merge_request_diffs.first.commits.count).to eq(commits_count)
+ expect(merge_request.latest_merge_request_diff_id).to eq(merge_request.merge_request_diffs.first.id)
end
end
diff --git a/spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_spec.rb b/spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_spec.rb
index 9a442de2900..a3d2880182d 100644
--- a/spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_spec.rb
+++ b/spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_spec.rb
@@ -24,14 +24,22 @@ describe Gitlab::ImportExport::AfterExportStrategies::BaseAfterExportStrategy do
service.execute(user, project)
- expect(lock_path_exist?).to be_truthy
+ expect(service.locks_present?).to be_truthy
end
context 'when the method succeeds' do
it 'removes the lock file' do
service.execute(user, project)
- expect(lock_path_exist?).to be_falsey
+ expect(service.locks_present?).to be_falsey
+ end
+
+ it 'removes the archive path' do
+ FileUtils.mkdir_p(shared.archive_path)
+
+ service.execute(user, project)
+
+ expect(File.exist?(shared.archive_path)).to be_falsey
end
end
@@ -62,13 +70,21 @@ describe Gitlab::ImportExport::AfterExportStrategies::BaseAfterExportStrategy do
service.execute(user, project)
end
+
+ it 'removes the archive path' do
+ FileUtils.mkdir_p(shared.archive_path)
+
+ service.execute(user, project)
+
+ expect(File.exist?(shared.archive_path)).to be_falsey
+ end
end
context 'when an exception is raised' do
it 'removes the lock' do
expect { service.execute(user, project) }.to raise_error(NotImplementedError)
- expect(lock_path_exist?).to be_falsey
+ expect(service.locks_present?).to be_falsey
end
end
end
@@ -97,8 +113,4 @@ describe Gitlab::ImportExport::AfterExportStrategies::BaseAfterExportStrategy do
expect(described_class.new(params).to_json).to eq result
end
end
-
- def lock_path_exist?
- File.exist?(described_class.lock_file_path(project))
- end
end
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index d3be1e86539..4fd61383c6b 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -25,6 +25,10 @@ issues:
- epic
- designs
- design_versions
+- description_versions
+- prometheus_alerts
+- prometheus_alert_events
+- self_managed_prometheus_alert_events
events:
- author
- project
@@ -79,6 +83,7 @@ releases:
- links
- milestone_releases
- milestones
+- evidence
links:
- release
project_members:
@@ -128,6 +133,7 @@ merge_requests:
- blocks_as_blockee
- blocking_merge_requests
- blocked_merge_requests
+- description_versions
external_pull_requests:
- project
merge_request_diff:
@@ -172,7 +178,7 @@ ci_pipelines:
- downstream_bridges
- job_artifacts
- vulnerabilities_occurrence_pipelines
-- vulnerabilities
+- vulnerability_findings
pipeline_variables:
- pipeline
stages:
@@ -250,11 +256,11 @@ project:
- cluster
- clusters
- cluster_project
-- cluster_ingresses
- creator
- cycle_analytics_stages
- group
- namespace
+- management_clusters
- boards
- last_event
- services
@@ -349,6 +355,7 @@ project:
- members_and_requesters
- build_trace_section_names
- build_trace_chunks
+- job_artifacts
- root_of_fork_network
- fork_network_member
- fork_network
@@ -389,6 +396,7 @@ project:
- sourced_pipelines
- prometheus_metrics
- vulnerabilities
+- vulnerability_findings
- vulnerability_feedback
- vulnerability_identifiers
- vulnerability_scanners
@@ -396,6 +404,7 @@ project:
- operations_feature_flags_client
- prometheus_alerts
- prometheus_alert_events
+- self_managed_prometheus_alert_events
- software_license_policies
- project_registry
- packages
@@ -409,6 +418,9 @@ project:
- designs
- project_aliases
- external_pull_requests
+- pages_metadatum
+- alerts_service
+- grafana_integration
award_emoji:
- awardable
- user
@@ -466,6 +478,8 @@ prometheus_alerts:
- prometheus_alert_events
prometheus_alert_events:
- project
+self_managed_prometheus_alert_events:
+- project
epic_issues:
- issue
- epic
@@ -499,3 +513,19 @@ lists:
milestone_releases:
- milestone
- release
+evidences:
+- release
+design: &design
+- issue
+- actions
+- versions
+- notes
+designs: *design
+actions:
+- design
+- version
+versions: &version
+- issue
+- designs
+- actions
+design_versions: *version
diff --git a/spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb b/spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb
index d23b27c9d8e..934e676d020 100644
--- a/spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb
+++ b/spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb
@@ -1,7 +1,12 @@
require 'spec_helper'
describe Gitlab::ImportExport::FastHashSerializer do
- subject { described_class.new(project, tree).execute }
+ # FastHashSerializer#execute generates the hash which is not easily accessible
+ # and includes `JSONBatchRelation` items which are serialized at this point.
+ # Wrapping the result into JSON generating/parsing is for making
+ # the testing more convenient. Doing this, we can check that
+ # all items are properly serialized while traversing the simple hash.
+ subject { JSON.parse(JSON.generate(described_class.new(project, tree).execute)) }
let!(:project) { setup_project }
let(:user) { create(:user) }
diff --git a/spec/lib/gitlab/import_export/model_configuration_spec.rb b/spec/lib/gitlab/import_export/model_configuration_spec.rb
index 3442e22c11f..4426e68b474 100644
--- a/spec/lib/gitlab/import_export/model_configuration_spec.rb
+++ b/spec/lib/gitlab/import_export/model_configuration_spec.rb
@@ -12,9 +12,9 @@ describe 'Import/Export model configuration' do
# Remove duplicated or add missing models
# - project is not part of the tree, so it has to be added manually.
- # - milestone, labels have both singular and plural versions in the tree, so remove the duplicates.
+ # - milestone, labels, merge_request have both singular and plural versions in the tree, so remove the duplicates.
# - User, Author... Models we do not care about for checking models
- names.flatten.uniq - %w(milestones labels user author) + ['project']
+ names.flatten.uniq - %w(milestones labels user author merge_request) + ['project']
end
let(:all_models_yml) { 'spec/lib/gitlab/import_export/all_models.yml' }
let(:all_models_hash) { YAML.load_file(all_models_yml) }
diff --git a/spec/lib/gitlab/import_export/project.group.json b/spec/lib/gitlab/import_export/project.group.json
deleted file mode 100644
index 66f5bb4c87b..00000000000
--- a/spec/lib/gitlab/import_export/project.group.json
+++ /dev/null
@@ -1,186 +0,0 @@
-{
- "description": "Nisi et repellendus ut enim quo accusamus vel magnam.",
- "visibility_level": 10,
- "archived": false,
- "milestones": [
- {
- "id": 1,
- "title": "Project milestone",
- "project_id": 8,
- "description": "Project-level milestone",
- "due_date": null,
- "created_at": "2016-06-14T15:02:04.415Z",
- "updated_at": "2016-06-14T15:02:04.415Z",
- "state": "active",
- "iid": 1,
- "group_id": null
- }
- ],
- "labels": [
- {
- "id": 2,
- "title": "A project label",
- "color": "#428bca",
- "project_id": 8,
- "created_at": "2016-07-22T08:55:44.161Z",
- "updated_at": "2016-07-22T08:55:44.161Z",
- "template": false,
- "description": "",
- "type": "ProjectLabel",
- "priorities": [
- {
- "id": 1,
- "project_id": 5,
- "label_id": 1,
- "priority": 1,
- "created_at": "2016-10-18T09:35:43.338Z",
- "updated_at": "2016-10-18T09:35:43.338Z"
- }
- ]
- }
- ],
- "issues": [
- {
- "id": 1,
- "title": "Fugiat est minima quae maxime non similique.",
- "assignee_id": null,
- "project_id": 8,
- "author_id": 1,
- "created_at": "2017-07-07T18:13:01.138Z",
- "updated_at": "2017-08-15T18:37:40.807Z",
- "branch_name": null,
- "description": "Quam totam fuga numquam in eveniet.",
- "state": "opened",
- "iid": 1,
- "updated_by_id": 1,
- "confidential": false,
- "due_date": null,
- "moved_to_id": null,
- "lock_version": null,
- "time_estimate": 0,
- "closed_at": null,
- "last_edited_at": null,
- "last_edited_by_id": null,
- "group_milestone_id": null,
- "milestone": {
- "id": 1,
- "title": "Project milestone",
- "project_id": 8,
- "description": "Project-level milestone",
- "due_date": null,
- "created_at": "2016-06-14T15:02:04.415Z",
- "updated_at": "2016-06-14T15:02:04.415Z",
- "state": "active",
- "iid": 1,
- "group_id": null
- },
- "label_links": [
- {
- "id": 11,
- "label_id": 6,
- "target_id": 1,
- "target_type": "Issue",
- "created_at": "2017-08-15T18:37:40.795Z",
- "updated_at": "2017-08-15T18:37:40.795Z",
- "label": {
- "id": 6,
- "title": "group label",
- "color": "#A8D695",
- "project_id": null,
- "created_at": "2017-08-15T18:37:19.698Z",
- "updated_at": "2017-08-15T18:37:19.698Z",
- "template": false,
- "description": "",
- "group_id": 5,
- "type": "GroupLabel",
- "priorities": []
- }
- },
- {
- "id": 11,
- "label_id": 2,
- "target_id": 1,
- "target_type": "Issue",
- "created_at": "2017-08-15T18:37:40.795Z",
- "updated_at": "2017-08-15T18:37:40.795Z",
- "label": {
- "id": 6,
- "title": "A project label",
- "color": "#A8D695",
- "project_id": null,
- "created_at": "2017-08-15T18:37:19.698Z",
- "updated_at": "2017-08-15T18:37:19.698Z",
- "template": false,
- "description": "",
- "group_id": 5,
- "type": "ProjectLabel",
- "priorities": []
- }
- }
- ]
- },
- {
- "id": 2,
- "title": "Fugiat est minima quae maxime non similique.",
- "assignee_id": null,
- "project_id": 8,
- "author_id": 1,
- "created_at": "2017-07-07T18:13:01.138Z",
- "updated_at": "2017-08-15T18:37:40.807Z",
- "branch_name": null,
- "description": "Quam totam fuga numquam in eveniet.",
- "state": "opened",
- "iid": 2,
- "updated_by_id": 1,
- "confidential": false,
- "due_date": null,
- "moved_to_id": null,
- "lock_version": null,
- "time_estimate": 0,
- "closed_at": null,
- "last_edited_at": null,
- "last_edited_by_id": null,
- "group_milestone_id": null,
- "milestone": {
- "id": 2,
- "title": "A group milestone",
- "description": "Group-level milestone",
- "due_date": null,
- "created_at": "2016-06-14T15:02:04.415Z",
- "updated_at": "2016-06-14T15:02:04.415Z",
- "state": "active",
- "iid": 1,
- "group_id": 100
- },
- "label_links": [
- {
- "id": 11,
- "label_id": 2,
- "target_id": 1,
- "target_type": "Issue",
- "created_at": "2017-08-15T18:37:40.795Z",
- "updated_at": "2017-08-15T18:37:40.795Z",
- "label": {
- "id": 2,
- "title": "A project label",
- "color": "#A8D695",
- "project_id": null,
- "created_at": "2017-08-15T18:37:19.698Z",
- "updated_at": "2017-08-15T18:37:19.698Z",
- "template": false,
- "description": "",
- "group_id": 5,
- "type": "ProjectLabel",
- "priorities": []
- }
- }
- ]
- }
- ],
- "snippets": [
-
- ],
- "hooks": [
-
- ]
-}
diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json
deleted file mode 100644
index 5f4bf18c743..00000000000
--- a/spec/lib/gitlab/import_export/project.json
+++ /dev/null
@@ -1,7225 +0,0 @@
-{
- "description": "Nisi et repellendus ut enim quo accusamus vel magnam.",
- "import_type": "gitlab_project",
- "creator_id": 123,
- "visibility_level": 10,
- "archived": false,
- "labels": [
- {
- "id": 2,
- "title": "test2",
- "color": "#428bca",
- "project_id": 8,
- "created_at": "2016-07-22T08:55:44.161Z",
- "updated_at": "2016-07-22T08:55:44.161Z",
- "template": false,
- "description": "",
- "type": "ProjectLabel",
- "priorities": []
- },
- {
- "id": 3,
- "title": "test3",
- "color": "#428bca",
- "group_id": 8,
- "created_at": "2016-07-22T08:55:44.161Z",
- "updated_at": "2016-07-22T08:55:44.161Z",
- "template": false,
- "description": "",
- "project_id": null,
- "type": "GroupLabel",
- "priorities": [
- {
- "id": 1,
- "project_id": 5,
- "label_id": 1,
- "priority": 1,
- "created_at": "2016-10-18T09:35:43.338Z",
- "updated_at": "2016-10-18T09:35:43.338Z"
- }
- ]
- }
- ],
- "issues": [
- {
- "id": 40,
- "title": "Voluptatem",
- "author_id": 22,
- "project_id": 5,
- "created_at": "2016-06-14T15:02:08.340Z",
- "updated_at": "2016-06-14T15:02:47.967Z",
- "position": 0,
- "branch_name": null,
- "description": "Aliquam enim illo et possimus.",
- "state": "opened",
- "iid": 10,
- "updated_by_id": null,
- "confidential": false,
- "due_date": null,
- "moved_to_id": null,
- "test_ee_field": "test",
- "issue_assignees": [
- {
- "user_id": 1,
- "issue_id": 40
- },
- {
- "user_id": 15,
- "issue_id": 40
- },
- {
- "user_id": 16,
- "issue_id": 40
- },
- {
- "user_id": 16,
- "issue_id": 40
- },
- {
- "user_id": 6,
- "issue_id": 40
- }
- ],
- "milestone": {
- "id": 1,
- "title": "test milestone",
- "project_id": 8,
- "description": "test milestone",
- "due_date": null,
- "created_at": "2016-06-14T15:02:04.415Z",
- "updated_at": "2016-06-14T15:02:04.415Z",
- "state": "active",
- "iid": 1,
- "events": [
- {
- "id": 487,
- "target_type": "Milestone",
- "target_id": 1,
- "project_id": 46,
- "created_at": "2016-06-14T15:02:04.418Z",
- "updated_at": "2016-06-14T15:02:04.418Z",
- "action": 1,
- "author_id": 18
- }
- ]
- },
- "label_links": [
- {
- "id": 2,
- "label_id": 2,
- "target_id": 40,
- "target_type": "Issue",
- "created_at": "2016-07-22T08:57:02.840Z",
- "updated_at": "2016-07-22T08:57:02.840Z",
- "label": {
- "id": 2,
- "title": "test2",
- "color": "#428bca",
- "project_id": 8,
- "created_at": "2016-07-22T08:55:44.161Z",
- "updated_at": "2016-07-22T08:55:44.161Z",
- "template": false,
- "description": "",
- "type": "ProjectLabel"
- }
- },
- {
- "id": 3,
- "label_id": 3,
- "target_id": 40,
- "target_type": "Issue",
- "created_at": "2016-07-22T08:57:02.841Z",
- "updated_at": "2016-07-22T08:57:02.841Z",
- "label": {
- "id": 3,
- "title": "test3",
- "color": "#428bca",
- "group_id": 8,
- "created_at": "2016-07-22T08:55:44.161Z",
- "updated_at": "2016-07-22T08:55:44.161Z",
- "template": false,
- "description": "",
- "project_id": null,
- "type": "GroupLabel",
- "priorities": [
- {
- "id": 1,
- "project_id": 5,
- "label_id": 1,
- "priority": 1,
- "created_at": "2016-10-18T09:35:43.338Z",
- "updated_at": "2016-10-18T09:35:43.338Z"
- }
- ]
- }
- }
- ],
- "notes": [
- {
- "id": 351,
- "note": "Quo reprehenderit aliquam qui dicta impedit cupiditate eligendi.",
- "note_html": "<p>something else entirely</p>",
- "cached_markdown_version": 917504,
- "noteable_type": "Issue",
- "author_id": 26,
- "created_at": "2016-06-14T15:02:47.770Z",
- "updated_at": "2016-06-14T15:02:47.770Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 40,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "User 4"
- },
- "events": []
- },
- {
- "id": 352,
- "note": "Est reprehenderit quas aut aspernatur autem recusandae voluptatem.",
- "noteable_type": "Issue",
- "author_id": 25,
- "created_at": "2016-06-14T15:02:47.795Z",
- "updated_at": "2016-06-14T15:02:47.795Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 40,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "User 3"
- },
- "events": []
- },
- {
- "id": 353,
- "note": "Perspiciatis suscipit voluptates in eius nihil.",
- "noteable_type": "Issue",
- "author_id": 22,
- "created_at": "2016-06-14T15:02:47.823Z",
- "updated_at": "2016-06-14T15:02:47.823Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 40,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "User 0"
- },
- "events": []
- },
- {
- "id": 354,
- "note": "Aut vel voluptas corrupti nisi provident laboriosam magnam aut.",
- "noteable_type": "Issue",
- "author_id": 20,
- "created_at": "2016-06-14T15:02:47.850Z",
- "updated_at": "2016-06-14T15:02:47.850Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 40,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Ottis Schuster II"
- },
- "events": []
- },
- {
- "id": 355,
- "note": "Officia dolore consequatur in saepe cum magni.",
- "noteable_type": "Issue",
- "author_id": 16,
- "created_at": "2016-06-14T15:02:47.876Z",
- "updated_at": "2016-06-14T15:02:47.876Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 40,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Rhett Emmerich IV"
- },
- "events": []
- },
- {
- "id": 356,
- "note": "Cum ipsum rem voluptas eaque et ea.",
- "noteable_type": "Issue",
- "author_id": 15,
- "created_at": "2016-06-14T15:02:47.908Z",
- "updated_at": "2016-06-14T15:02:47.908Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 40,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Burdette Bernier"
- },
- "events": []
- },
- {
- "id": 357,
- "note": "Recusandae excepturi asperiores suscipit autem nostrum.",
- "noteable_type": "Issue",
- "author_id": 6,
- "created_at": "2016-06-14T15:02:47.937Z",
- "updated_at": "2016-06-14T15:02:47.937Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 40,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Ari Wintheiser"
- },
- "events": []
- },
- {
- "id": 358,
- "note": "Et hic est id similique et non nesciunt voluptate.",
- "noteable_type": "Issue",
- "author_id": 1,
- "created_at": "2016-06-14T15:02:47.965Z",
- "updated_at": "2016-06-14T15:02:47.965Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 40,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Administrator"
- },
- "events": []
- }
- ],
- "resource_label_events": [
- {
- "id":244,
- "action":"remove",
- "issue_id":40,
- "merge_request_id":null,
- "label_id":2,
- "user_id":1,
- "created_at":"2018-08-28T08:24:00.494Z",
- "label": {
- "id": 2,
- "title": "test2",
- "color": "#428bca",
- "project_id": 8,
- "created_at": "2016-07-22T08:55:44.161Z",
- "updated_at": "2016-07-22T08:55:44.161Z",
- "template": false,
- "description": "",
- "type": "ProjectLabel"
- }
- }
- ]
- },
- {
- "id": 39,
- "title": "Issue without assignees",
- "author_id": 22,
- "project_id": 5,
- "created_at": "2016-06-14T15:02:08.233Z",
- "updated_at": "2016-06-14T15:02:48.194Z",
- "position": 0,
- "branch_name": null,
- "description": "Voluptate vel reprehenderit facilis omnis voluptas magnam tenetur.",
- "state": "opened",
- "iid": 9,
- "updated_by_id": null,
- "confidential": false,
- "due_date": null,
- "moved_to_id": null,
- "issue_assignees": [],
- "milestone": {
- "id": 1,
- "title": "test milestone",
- "project_id": 8,
- "description": "test milestone",
- "due_date": null,
- "created_at": "2016-06-14T15:02:04.415Z",
- "updated_at": "2016-06-14T15:02:04.415Z",
- "state": "active",
- "iid": 1,
- "events": [
- {
- "id": 487,
- "target_type": "Milestone",
- "target_id": 1,
- "project_id": 46,
- "created_at": "2016-06-14T15:02:04.418Z",
- "updated_at": "2016-06-14T15:02:04.418Z",
- "action": 1,
- "author_id": 18
- }
- ]
- },
- "notes": [
- {
- "id": 359,
- "note": "Quo eius velit quia et id quam.",
- "noteable_type": "Issue",
- "author_id": 26,
- "created_at": "2016-06-14T15:02:48.009Z",
- "updated_at": "2016-06-14T15:02:48.009Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 39,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "User 4"
- },
- "events": []
- },
- {
- "id": 360,
- "note": "Nulla commodi ratione cumque id autem.",
- "noteable_type": "Issue",
- "author_id": 25,
- "created_at": "2016-06-14T15:02:48.032Z",
- "updated_at": "2016-06-14T15:02:48.032Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 39,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "User 3"
- },
- "events": []
- },
- {
- "id": 361,
- "note": "Illum non ea sed dolores corrupti.",
- "noteable_type": "Issue",
- "author_id": 22,
- "created_at": "2016-06-14T15:02:48.056Z",
- "updated_at": "2016-06-14T15:02:48.056Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 39,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "User 0"
- },
- "events": []
- },
- {
- "id": 362,
- "note": "Facere dolores ipsum dolorum maiores omnis occaecati ab.",
- "noteable_type": "Issue",
- "author_id": 20,
- "created_at": "2016-06-14T15:02:48.082Z",
- "updated_at": "2016-06-14T15:02:48.082Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 39,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Ottis Schuster II"
- },
- "events": []
- },
- {
- "id": 363,
- "note": "Quod laudantium similique sint aut est ducimus.",
- "noteable_type": "Issue",
- "author_id": 16,
- "created_at": "2016-06-14T15:02:48.113Z",
- "updated_at": "2016-06-14T15:02:48.113Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 39,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Rhett Emmerich IV"
- },
- "events": []
- },
- {
- "id": 364,
- "note": "Aut omnis eos esse incidunt vero reiciendis.",
- "noteable_type": "Issue",
- "author_id": 15,
- "created_at": "2016-06-14T15:02:48.139Z",
- "updated_at": "2016-06-14T15:02:48.139Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 39,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Burdette Bernier"
- },
- "events": []
- },
- {
- "id": 365,
- "note": "Beatae dolore et doloremque asperiores sunt.",
- "noteable_type": "Issue",
- "author_id": 6,
- "created_at": "2016-06-14T15:02:48.162Z",
- "updated_at": "2016-06-14T15:02:48.162Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 39,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Ari Wintheiser"
- },
- "events": []
- },
- {
- "id": 366,
- "note": "Doloribus ipsam ex delectus rerum libero recusandae modi repellendus.",
- "noteable_type": "Issue",
- "author_id": 1,
- "created_at": "2016-06-14T15:02:48.192Z",
- "updated_at": "2016-06-14T15:02:48.192Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 39,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Administrator"
- },
- "events": []
- }
- ]
- },
- {
- "id": 38,
- "title": "Quasi adipisci non cupiditate dolorem quo qui earum sed.",
- "author_id": 6,
- "project_id": 5,
- "created_at": "2016-06-14T15:02:08.154Z",
- "updated_at": "2016-06-14T15:02:48.614Z",
- "position": 0,
- "branch_name": null,
- "description": "Ea recusandae neque autem tempora.",
- "state": "closed",
- "iid": 8,
- "updated_by_id": null,
- "confidential": false,
- "due_date": null,
- "moved_to_id": null,
- "label_links": [
- {
- "id": 99,
- "label_id": 2,
- "target_id": 38,
- "target_type": "Issue",
- "created_at": "2016-07-22T08:57:02.840Z",
- "updated_at": "2016-07-22T08:57:02.840Z",
- "label": {
- "id": 2,
- "title": "test2",
- "color": "#428bca",
- "project_id": 8,
- "created_at": "2016-07-22T08:55:44.161Z",
- "updated_at": "2016-07-22T08:55:44.161Z",
- "template": false,
- "description": "",
- "type": "ProjectLabel"
- }
- }
- ],
- "notes": [
- {
- "id": 367,
- "note": "Accusantium fugiat et eaque quisquam esse corporis.",
- "noteable_type": "Issue",
- "author_id": 26,
- "created_at": "2016-06-14T15:02:48.235Z",
- "updated_at": "2016-06-14T15:02:48.235Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 38,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "User 4"
- },
- "events": []
- },
- {
- "id": 368,
- "note": "Ea labore eum nam qui laboriosam.",
- "noteable_type": "Issue",
- "author_id": 25,
- "created_at": "2016-06-14T15:02:48.261Z",
- "updated_at": "2016-06-14T15:02:48.261Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 38,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "User 3"
- },
- "events": []
- },
- {
- "id": 369,
- "note": "Accusantium quis sed molestiae et.",
- "noteable_type": "Issue",
- "author_id": 22,
- "created_at": "2016-06-14T15:02:48.294Z",
- "updated_at": "2016-06-14T15:02:48.294Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 38,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "User 0"
- },
- "events": []
- },
- {
- "id": 370,
- "note": "Corporis numquam a voluptatem pariatur asperiores dolorem delectus autem.",
- "noteable_type": "Issue",
- "author_id": 20,
- "created_at": "2016-06-14T15:02:48.523Z",
- "updated_at": "2016-06-14T15:02:48.523Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 38,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Ottis Schuster II"
- },
- "events": []
- },
- {
- "id": 371,
- "note": "Ea accusantium maxime voluptas rerum.",
- "noteable_type": "Issue",
- "author_id": 16,
- "created_at": "2016-06-14T15:02:48.546Z",
- "updated_at": "2016-06-14T15:02:48.546Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 38,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Rhett Emmerich IV"
- },
- "events": []
- },
- {
- "id": 372,
- "note": "Pariatur iusto et et excepturi similique ipsam eum.",
- "noteable_type": "Issue",
- "author_id": 15,
- "created_at": "2016-06-14T15:02:48.569Z",
- "updated_at": "2016-06-14T15:02:48.569Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 38,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Burdette Bernier"
- },
- "events": []
- },
- {
- "id": 373,
- "note": "Aliquam et culpa officia iste eius.",
- "noteable_type": "Issue",
- "author_id": 6,
- "created_at": "2016-06-14T15:02:48.591Z",
- "updated_at": "2016-06-14T15:02:48.591Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 38,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Ari Wintheiser"
- },
- "events": []
- },
- {
- "id": 374,
- "note": "Ab id velit id unde laborum.",
- "noteable_type": "Issue",
- "author_id": 1,
- "created_at": "2016-06-14T15:02:48.613Z",
- "updated_at": "2016-06-14T15:02:48.613Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 38,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Administrator"
- },
- "events": []
- }
- ]
- },
- {
- "id": 37,
- "title": "Cupiditate quo aut ducimus minima molestiae vero numquam possimus.",
- "author_id": 20,
- "project_id": 5,
- "created_at": "2016-06-14T15:02:08.051Z",
- "updated_at": "2016-06-14T15:02:48.854Z",
- "position": 0,
- "branch_name": null,
- "description": "Maiores architecto quos in dolorem.",
- "state": "opened",
- "iid": 7,
- "updated_by_id": null,
- "confidential": false,
- "due_date": null,
- "moved_to_id": null,
- "notes": [
- {
- "id": 375,
- "note": "Quasi fugit qui sed eligendi aut quia.",
- "noteable_type": "Issue",
- "author_id": 26,
- "created_at": "2016-06-14T15:02:48.647Z",
- "updated_at": "2016-06-14T15:02:48.647Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 37,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "User 4"
- },
- "events": []
- },
- {
- "id": 376,
- "note": "Esse nesciunt voluptatem ex vero est consequatur.",
- "noteable_type": "Issue",
- "author_id": 25,
- "created_at": "2016-06-14T15:02:48.674Z",
- "updated_at": "2016-06-14T15:02:48.674Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 37,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "User 3"
- },
- "events": []
- },
- {
- "id": 377,
- "note": "Similique qui quas non aut et velit sequi in.",
- "noteable_type": "Issue",
- "author_id": 22,
- "created_at": "2016-06-14T15:02:48.696Z",
- "updated_at": "2016-06-14T15:02:48.696Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 37,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "User 0"
- },
- "events": []
- },
- {
- "id": 378,
- "note": "Eveniet ut cupiditate repellendus numquam in esse eius.",
- "noteable_type": "Issue",
- "author_id": 20,
- "created_at": "2016-06-14T15:02:48.720Z",
- "updated_at": "2016-06-14T15:02:48.720Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 37,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Ottis Schuster II"
- },
- "events": []
- },
- {
- "id": 379,
- "note": "Velit est dolorem adipisci rerum sed iure.",
- "noteable_type": "Issue",
- "author_id": 16,
- "created_at": "2016-06-14T15:02:48.755Z",
- "updated_at": "2016-06-14T15:02:48.755Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 37,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Rhett Emmerich IV"
- },
- "events": []
- },
- {
- "id": 380,
- "note": "Voluptatem ullam ab ut illo ut quo.",
- "noteable_type": "Issue",
- "author_id": 15,
- "created_at": "2016-06-14T15:02:48.793Z",
- "updated_at": "2016-06-14T15:02:48.793Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 37,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Burdette Bernier"
- },
- "events": []
- },
- {
- "id": 381,
- "note": "Voluptatem impedit beatae quasi ipsa earum consectetur.",
- "noteable_type": "Issue",
- "author_id": 6,
- "created_at": "2016-06-14T15:02:48.823Z",
- "updated_at": "2016-06-14T15:02:48.823Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 37,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Ari Wintheiser"
- },
- "events": []
- },
- {
- "id": 382,
- "note": "Nihil officiis eaque incidunt sunt voluptatum excepturi.",
- "noteable_type": "Issue",
- "author_id": 1,
- "created_at": "2016-06-14T15:02:48.852Z",
- "updated_at": "2016-06-14T15:02:48.852Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 37,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Administrator"
- },
- "events": []
- }
- ]
- },
- {
- "id": 36,
- "title": "Necessitatibus dolor est enim quia rem suscipit quidem voluptas ullam.",
- "author_id": 16,
- "project_id": 5,
- "created_at": "2016-06-14T15:02:07.958Z",
- "updated_at": "2016-06-14T15:02:49.044Z",
- "position": 0,
- "branch_name": null,
- "description": "Ut aut ut et tenetur velit aut id modi.",
- "state": "opened",
- "iid": 6,
- "updated_by_id": null,
- "confidential": false,
- "due_date": null,
- "moved_to_id": null,
- "notes": [
- {
- "id": 383,
- "note": "Excepturi deleniti sunt rerum nesciunt vero fugiat possimus.",
- "noteable_type": "Issue",
- "author_id": 26,
- "created_at": "2016-06-14T15:02:48.885Z",
- "updated_at": "2016-06-14T15:02:48.885Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 36,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "User 4"
- },
- "events": []
- },
- {
- "id": 384,
- "note": "Et est nemo sed nam sed.",
- "noteable_type": "Issue",
- "author_id": 25,
- "created_at": "2016-06-14T15:02:48.910Z",
- "updated_at": "2016-06-14T15:02:48.910Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 36,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "User 3"
- },
- "events": []
- },
- {
- "id": 385,
- "note": "Animi mollitia nulla facere amet aut quaerat.",
- "noteable_type": "Issue",
- "author_id": 22,
- "created_at": "2016-06-14T15:02:48.934Z",
- "updated_at": "2016-06-14T15:02:48.934Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 36,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "User 0"
- },
- "events": []
- },
- {
- "id": 386,
- "note": "Excepturi id voluptas ut odio officiis omnis.",
- "noteable_type": "Issue",
- "author_id": 20,
- "created_at": "2016-06-14T15:02:48.955Z",
- "updated_at": "2016-06-14T15:02:48.955Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 36,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Ottis Schuster II"
- },
- "events": []
- },
- {
- "id": 387,
- "note": "Molestiae labore officiis magni et eligendi quasi maxime.",
- "noteable_type": "Issue",
- "author_id": 16,
- "created_at": "2016-06-14T15:02:48.978Z",
- "updated_at": "2016-06-14T15:02:48.978Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 36,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Rhett Emmerich IV"
- },
- "events": []
- },
- {
- "id": 388,
- "note": "Officia tenetur praesentium rem nam non.",
- "noteable_type": "Issue",
- "author_id": 15,
- "created_at": "2016-06-14T15:02:49.001Z",
- "updated_at": "2016-06-14T15:02:49.001Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 36,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Burdette Bernier"
- },
- "events": []
- },
- {
- "id": 389,
- "note": "Et et et molestiae reprehenderit.",
- "noteable_type": "Issue",
- "author_id": 6,
- "created_at": "2016-06-14T15:02:49.022Z",
- "updated_at": "2016-06-14T15:02:49.022Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 36,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Ari Wintheiser"
- },
- "events": []
- },
- {
- "id": 390,
- "note": "Aperiam in consequatur est sunt cum quia.",
- "noteable_type": "Issue",
- "author_id": 1,
- "created_at": "2016-06-14T15:02:49.043Z",
- "updated_at": "2016-06-14T15:02:49.043Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 36,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Administrator"
- },
- "events": []
- }
- ]
- },
- {
- "id": 35,
- "title": "Repellat praesentium deserunt maxime incidunt harum porro qui.",
- "author_id": 20,
- "project_id": 5,
- "created_at": "2016-06-14T15:02:07.832Z",
- "updated_at": "2016-06-14T15:02:49.226Z",
- "position": 0,
- "branch_name": null,
- "description": "Dicta nisi nihil non ipsa velit.",
- "state": "closed",
- "iid": 5,
- "updated_by_id": null,
- "confidential": false,
- "due_date": null,
- "moved_to_id": null,
- "notes": [
- {
- "id": 391,
- "note": "Qui magnam et assumenda quod id dicta necessitatibus.",
- "noteable_type": "Issue",
- "author_id": 26,
- "created_at": "2016-06-14T15:02:49.075Z",
- "updated_at": "2016-06-14T15:02:49.075Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 35,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "User 4"
- },
- "events": []
- },
- {
- "id": 392,
- "note": "Consectetur deserunt possimus dolor est odio.",
- "noteable_type": "Issue",
- "author_id": 25,
- "created_at": "2016-06-14T15:02:49.095Z",
- "updated_at": "2016-06-14T15:02:49.095Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 35,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "User 3"
- },
- "events": []
- },
- {
- "id": 393,
- "note": "Labore nisi quo cumque voluptas consequatur aut qui.",
- "noteable_type": "Issue",
- "author_id": 22,
- "created_at": "2016-06-14T15:02:49.117Z",
- "updated_at": "2016-06-14T15:02:49.117Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 35,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "User 0"
- },
- "events": []
- },
- {
- "id": 394,
- "note": "Et totam facilis voluptas et enim.",
- "noteable_type": "Issue",
- "author_id": 20,
- "created_at": "2016-06-14T15:02:49.138Z",
- "updated_at": "2016-06-14T15:02:49.138Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 35,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Ottis Schuster II"
- },
- "events": []
- },
- {
- "id": 395,
- "note": "Ratione sint pariatur sed omnis eligendi quo libero exercitationem.",
- "noteable_type": "Issue",
- "author_id": 16,
- "created_at": "2016-06-14T15:02:49.160Z",
- "updated_at": "2016-06-14T15:02:49.160Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 35,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Rhett Emmerich IV"
- },
- "events": []
- },
- {
- "id": 396,
- "note": "Iure hic autem id voluptatem.",
- "noteable_type": "Issue",
- "author_id": 15,
- "created_at": "2016-06-14T15:02:49.182Z",
- "updated_at": "2016-06-14T15:02:49.182Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 35,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Burdette Bernier"
- },
- "events": []
- },
- {
- "id": 397,
- "note": "Excepturi eum laboriosam delectus repellendus odio nisi et voluptatem.",
- "noteable_type": "Issue",
- "author_id": 6,
- "created_at": "2016-06-14T15:02:49.205Z",
- "updated_at": "2016-06-14T15:02:49.205Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 35,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Ari Wintheiser"
- },
- "events": []
- },
- {
- "id": 398,
- "note": "Ut quis ex soluta consequatur et blanditiis.",
- "noteable_type": "Issue",
- "author_id": 1,
- "created_at": "2016-06-14T15:02:49.225Z",
- "updated_at": "2016-06-14T15:02:49.225Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 35,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Administrator"
- },
- "events": []
- }
- ]
- },
- {
- "id": 34,
- "title": "Ullam expedita deserunt libero consequatur quia dolor harum perferendis facere quidem.",
- "author_id": 1,
- "project_id": 5,
- "created_at": "2016-06-14T15:02:07.717Z",
- "updated_at": "2016-06-14T15:02:49.416Z",
- "position": 0,
- "branch_name": null,
- "description": "Ut et explicabo vel voluptatem consequuntur ut sed.",
- "state": "closed",
- "iid": 4,
- "updated_by_id": null,
- "confidential": false,
- "due_date": null,
- "moved_to_id": null,
- "notes": [
- {
- "id": 399,
- "note": "Dolor iste tempora tenetur non vitae maiores voluptatibus.",
- "noteable_type": "Issue",
- "author_id": 26,
- "created_at": "2016-06-14T15:02:49.256Z",
- "updated_at": "2016-06-14T15:02:49.256Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 34,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "User 4"
- },
- "events": []
- },
- {
- "id": 400,
- "note": "Aut sit quidem qui adipisci maxime excepturi iusto.",
- "noteable_type": "Issue",
- "author_id": 25,
- "created_at": "2016-06-14T15:02:49.284Z",
- "updated_at": "2016-06-14T15:02:49.284Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 34,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "User 3"
- },
- "events": []
- },
- {
- "id": 401,
- "note": "Et a necessitatibus autem quidem animi sunt voluptatum rerum.",
- "noteable_type": "Issue",
- "author_id": 22,
- "created_at": "2016-06-14T15:02:49.305Z",
- "updated_at": "2016-06-14T15:02:49.305Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 34,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "User 0"
- },
- "events": []
- },
- {
- "id": 402,
- "note": "Esse laboriosam quo voluptatem quis molestiae.",
- "noteable_type": "Issue",
- "author_id": 20,
- "created_at": "2016-06-14T15:02:49.328Z",
- "updated_at": "2016-06-14T15:02:49.328Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 34,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Ottis Schuster II"
- },
- "events": []
- },
- {
- "id": 403,
- "note": "Nemo magnam distinctio est ut voluptate ea.",
- "noteable_type": "Issue",
- "author_id": 16,
- "created_at": "2016-06-14T15:02:49.350Z",
- "updated_at": "2016-06-14T15:02:49.350Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 34,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Rhett Emmerich IV"
- },
- "events": []
- },
- {
- "id": 404,
- "note": "Omnis sed rerum neque rerum quae quam nulla officiis.",
- "noteable_type": "Issue",
- "author_id": 15,
- "created_at": "2016-06-14T15:02:49.372Z",
- "updated_at": "2016-06-14T15:02:49.372Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 34,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Burdette Bernier"
- },
- "events": []
- },
- {
- "id": 405,
- "note": "Quo soluta dolorem vitae ad consequatur qui aut dicta.",
- "noteable_type": "Issue",
- "author_id": 6,
- "created_at": "2016-06-14T15:02:49.394Z",
- "updated_at": "2016-06-14T15:02:49.394Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 34,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Ari Wintheiser"
- },
- "events": []
- },
- {
- "id": 406,
- "note": "Magni minus est aut aut totam ut.",
- "noteable_type": "Issue",
- "author_id": 1,
- "created_at": "2016-06-14T15:02:49.414Z",
- "updated_at": "2016-06-14T15:02:49.414Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 34,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Administrator"
- },
- "events": []
- }
- ]
- },
- {
- "id": 33,
- "title": "Numquam accusamus eos iste exercitationem magni non inventore.",
- "author_id": 26,
- "project_id": 5,
- "created_at": "2016-06-14T15:02:07.611Z",
- "updated_at": "2016-06-14T15:02:49.661Z",
- "position": 0,
- "branch_name": null,
- "description": "Non asperiores velit accusantium voluptate.",
- "state": "closed",
- "iid": 3,
- "updated_by_id": null,
- "confidential": false,
- "due_date": null,
- "moved_to_id": null,
- "notes": [
- {
- "id": 407,
- "note": "Quod ea et possimus architecto.",
- "noteable_type": "Issue",
- "author_id": 26,
- "created_at": "2016-06-14T15:02:49.450Z",
- "updated_at": "2016-06-14T15:02:49.450Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 33,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "User 4"
- },
- "events": []
- },
- {
- "id": 408,
- "note": "Reiciendis est et unde perferendis dicta ut praesentium quasi.",
- "noteable_type": "Issue",
- "author_id": 25,
- "created_at": "2016-06-14T15:02:49.503Z",
- "updated_at": "2016-06-14T15:02:49.503Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 33,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "User 3"
- },
- "events": []
- },
- {
- "id": 409,
- "note": "Magni quia odio blanditiis pariatur voluptas.",
- "noteable_type": "Issue",
- "author_id": 22,
- "created_at": "2016-06-14T15:02:49.527Z",
- "updated_at": "2016-06-14T15:02:49.527Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 33,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "User 0"
- },
- "events": []
- },
- {
- "id": 410,
- "note": "Enim quam ut et et et.",
- "noteable_type": "Issue",
- "author_id": 20,
- "created_at": "2016-06-14T15:02:49.551Z",
- "updated_at": "2016-06-14T15:02:49.551Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 33,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Ottis Schuster II"
- },
- "events": []
- },
- {
- "id": 411,
- "note": "Fugit voluptatem ratione maxime expedita.",
- "noteable_type": "Issue",
- "author_id": 16,
- "created_at": "2016-06-14T15:02:49.578Z",
- "updated_at": "2016-06-14T15:02:49.578Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 33,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Rhett Emmerich IV"
- },
- "events": []
- },
- {
- "id": 412,
- "note": "Voluptatem enim aut ipsa et et ducimus.",
- "noteable_type": "Issue",
- "author_id": 15,
- "created_at": "2016-06-14T15:02:49.604Z",
- "updated_at": "2016-06-14T15:02:49.604Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 33,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Burdette Bernier"
- },
- "events": []
- },
- {
- "id": 413,
- "note": "Quia repellat fugiat consectetur quidem.",
- "noteable_type": "Issue",
- "author_id": 6,
- "created_at": "2016-06-14T15:02:49.631Z",
- "updated_at": "2016-06-14T15:02:49.631Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 33,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Ari Wintheiser"
- },
- "events": []
- },
- {
- "id": 414,
- "note": "Corporis ipsum et ea necessitatibus quod assumenda repudiandae quam.",
- "noteable_type": "Issue",
- "author_id": 1,
- "created_at": "2016-06-14T15:02:49.659Z",
- "updated_at": "2016-06-14T15:02:49.659Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 33,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Administrator"
- },
- "events": []
- }
- ]
- },
- {
- "id": 32,
- "title": "Necessitatibus magnam qui at velit consequatur perspiciatis.",
- "author_id": 15,
- "project_id": 5,
- "created_at": "2016-06-14T15:02:07.431Z",
- "updated_at": "2016-06-14T15:02:49.884Z",
- "position": 0,
- "branch_name": null,
- "description": "Molestiae corporis magnam et fugit aliquid nulla quia.",
- "state": "closed",
- "iid": 2,
- "updated_by_id": null,
- "confidential": false,
- "due_date": null,
- "moved_to_id": null,
- "notes": [
- {
- "id": 415,
- "note": "Nemo consequatur sed blanditiis qui id iure dolores.",
- "noteable_type": "Issue",
- "author_id": 26,
- "created_at": "2016-06-14T15:02:49.694Z",
- "updated_at": "2016-06-14T15:02:49.694Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 32,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "User 4"
- },
- "events": []
- },
- {
- "id": 416,
- "note": "Voluptas ab accusantium dicta in.",
- "noteable_type": "Issue",
- "author_id": 25,
- "created_at": "2016-06-14T15:02:49.718Z",
- "updated_at": "2016-06-14T15:02:49.718Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 32,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "User 3"
- },
- "events": []
- },
- {
- "id": 417,
- "note": "Esse odit qui a et eum ducimus.",
- "noteable_type": "Issue",
- "author_id": 22,
- "created_at": "2016-06-14T15:02:49.741Z",
- "updated_at": "2016-06-14T15:02:49.741Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 32,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "User 0"
- },
- "events": []
- },
- {
- "id": 418,
- "note": "Sequi dolor doloribus ratione placeat repellendus.",
- "noteable_type": "Issue",
- "author_id": 20,
- "created_at": "2016-06-14T15:02:49.767Z",
- "updated_at": "2016-06-14T15:02:49.767Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 32,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Ottis Schuster II"
- },
- "events": []
- },
- {
- "id": 419,
- "note": "Quae aspernatur rem est similique.",
- "noteable_type": "Issue",
- "author_id": 16,
- "created_at": "2016-06-14T15:02:49.796Z",
- "updated_at": "2016-06-14T15:02:49.796Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 32,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Rhett Emmerich IV"
- },
- "events": []
- },
- {
- "id": 420,
- "note": "Voluptate omnis et id rerum non nesciunt laudantium assumenda.",
- "noteable_type": "Issue",
- "author_id": 15,
- "created_at": "2016-06-14T15:02:49.825Z",
- "updated_at": "2016-06-14T15:02:49.825Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 32,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Burdette Bernier"
- },
- "events": []
- },
- {
- "id": 421,
- "note": "Quia enim ab et eligendi.",
- "noteable_type": "Issue",
- "author_id": 6,
- "created_at": "2016-06-14T15:02:49.853Z",
- "updated_at": "2016-06-14T15:02:49.853Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 32,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Ari Wintheiser"
- },
- "events": []
- },
- {
- "id": 422,
- "note": "In fugiat rerum voluptas quas officia.",
- "noteable_type": "Issue",
- "author_id": 1,
- "created_at": "2016-06-14T15:02:49.881Z",
- "updated_at": "2016-06-14T15:02:49.881Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 32,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Administrator"
- },
- "events": []
- }
- ]
- },
- {
- "id": 31,
- "title": "Libero nam magnam incidunt eaque placeat error et.",
- "author_id": 16,
- "project_id": 5,
- "created_at": "2016-06-14T15:02:07.280Z",
- "updated_at": "2016-06-14T15:02:50.134Z",
- "position": 0,
- "branch_name": null,
- "description": "Quod ad architecto qui est sed quia.",
- "state": "closed",
- "iid": 1,
- "updated_by_id": null,
- "confidential": false,
- "due_date": null,
- "moved_to_id": null,
- "notes": [
- {
- "id": 423,
- "note": "A mollitia qui iste consequatur eaque iure omnis sunt.",
- "noteable_type": "Issue",
- "author_id": 26,
- "created_at": "2016-06-14T15:02:49.933Z",
- "updated_at": "2016-06-14T15:02:49.933Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 31,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "User 4"
- },
- "events": []
- },
- {
- "id": 424,
- "note": "Eveniet est et blanditiis sequi alias.",
- "noteable_type": "Issue",
- "author_id": 25,
- "created_at": "2016-06-14T15:02:49.965Z",
- "updated_at": "2016-06-14T15:02:49.965Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 31,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "User 3"
- },
- "events": []
- },
- {
- "id": 425,
- "note": "Commodi tempore voluptas doloremque est.",
- "noteable_type": "Issue",
- "author_id": 22,
- "created_at": "2016-06-14T15:02:49.996Z",
- "updated_at": "2016-06-14T15:02:49.996Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 31,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "User 0"
- },
- "events": []
- },
- {
- "id": 426,
- "note": "Quo libero impedit odio debitis rerum aspernatur.",
- "noteable_type": "Issue",
- "author_id": 20,
- "created_at": "2016-06-14T15:02:50.024Z",
- "updated_at": "2016-06-14T15:02:50.024Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 31,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Ottis Schuster II"
- },
- "events": []
- },
- {
- "id": 427,
- "note": "Dolorem voluptatem qui labore deserunt.",
- "noteable_type": "Issue",
- "author_id": 16,
- "created_at": "2016-06-14T15:02:50.049Z",
- "updated_at": "2016-06-14T15:02:50.049Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 31,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Rhett Emmerich IV"
- },
- "events": []
- },
- {
- "id": 428,
- "note": "Est blanditiis laboriosam enim ipsam.",
- "noteable_type": "Issue",
- "author_id": 15,
- "created_at": "2016-06-14T15:02:50.077Z",
- "updated_at": "2016-06-14T15:02:50.077Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 31,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Burdette Bernier"
- },
- "events": []
- },
- {
- "id": 429,
- "note": "Et in voluptatem animi dolorem eos.",
- "noteable_type": "Issue",
- "author_id": 6,
- "created_at": "2016-06-14T15:02:50.107Z",
- "updated_at": "2016-06-14T15:02:50.107Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 31,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Ari Wintheiser"
- },
- "events": []
- },
- {
- "id": 430,
- "note": "Unde culpa voluptate qui sint quos.",
- "noteable_type": "Issue",
- "author_id": 1,
- "created_at": "2016-06-14T15:02:50.132Z",
- "updated_at": "2016-06-14T15:02:50.132Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 31,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Administrator"
- },
- "events": []
- }
- ]
- }
- ],
- "milestones": [
- {
- "id": 1,
- "title": "test milestone",
- "project_id": 8,
- "description": "test milestone",
- "due_date": null,
- "created_at": "2016-06-14T15:02:04.415Z",
- "updated_at": "2016-06-14T15:02:04.415Z",
- "state": "active",
- "iid": 1,
- "events": [
- {
- "id": 487,
- "target_type": "Milestone",
- "target_id": 1,
- "project_id": 46,
- "created_at": "2016-06-14T15:02:04.418Z",
- "updated_at": "2016-06-14T15:02:04.418Z",
- "action": 1,
- "author_id": 18
- }
- ]
- },
- {
- "id": 20,
- "title": "v4.0",
- "project_id": 5,
- "description": "Totam quam laborum id magnam natus eaque aspernatur.",
- "due_date": null,
- "created_at": "2016-06-14T15:02:04.590Z",
- "updated_at": "2016-06-14T15:02:04.590Z",
- "state": "active",
- "iid": 5,
- "events": [
- {
- "id": 240,
- "target_type": "Milestone",
- "target_id": 20,
- "project_id": 36,
- "created_at": "2016-06-14T15:02:04.593Z",
- "updated_at": "2016-06-14T15:02:04.593Z",
- "action": 1,
- "author_id": 1
- },
- {
- "id": 60,
- "target_type": "Milestone",
- "target_id": 20,
- "project_id": 5,
- "created_at": "2016-06-14T15:02:04.593Z",
- "updated_at": "2016-06-14T15:02:04.593Z",
- "action": 1,
- "author_id": 20
- }
- ]
- },
- {
- "id": 19,
- "title": "v3.0",
- "project_id": 5,
- "description": "Rerum at autem exercitationem ea voluptates harum quam placeat.",
- "due_date": null,
- "created_at": "2016-06-14T15:02:04.583Z",
- "updated_at": "2016-06-14T15:02:04.583Z",
- "state": "active",
- "iid": 4,
- "events": [
- {
- "id": 241,
- "target_type": "Milestone",
- "target_id": 19,
- "project_id": 36,
- "created_at": "2016-06-14T15:02:04.585Z",
- "updated_at": "2016-06-14T15:02:04.585Z",
- "action": 1,
- "author_id": 1
- },
- {
- "id": 59,
- "target_type": "Milestone",
- "target_id": 19,
- "project_id": 5,
- "created_at": "2016-06-14T15:02:04.585Z",
- "updated_at": "2016-06-14T15:02:04.585Z",
- "action": 1,
- "author_id": 25
- }
- ]
- }
- ],
- "snippets": [],
- "releases": [],
- "project_members": [
- {
- "id": 36,
- "access_level": 40,
- "source_id": 5,
- "source_type": "Project",
- "user_id": 16,
- "notification_level": 3,
- "created_at": "2016-06-14T15:02:03.834Z",
- "updated_at": "2016-06-14T15:02:03.834Z",
- "created_by_id": null,
- "invite_email": null,
- "invite_token": null,
- "invite_accepted_at": null,
- "requested_at": null,
- "user": {
- "id": 16,
- "email": "maritza_schoen@block.ca",
- "username": "bernard_willms"
- }
- },
- {
- "id": 35,
- "access_level": 10,
- "source_id": 5,
- "source_type": "Project",
- "user_id": 6,
- "notification_level": 3,
- "created_at": "2016-06-14T15:02:03.811Z",
- "updated_at": "2016-06-14T15:02:03.811Z",
- "created_by_id": null,
- "invite_email": null,
- "invite_token": null,
- "invite_accepted_at": null,
- "requested_at": null,
- "user": {
- "id": 6,
- "email": "shaina@koelpindenesik.com",
- "username": "saul_will"
- }
- },
- {
- "id": 34,
- "access_level": 20,
- "source_id": 5,
- "source_type": "Project",
- "user_id": 15,
- "notification_level": 3,
- "created_at": "2016-06-14T15:02:03.776Z",
- "updated_at": "2016-06-14T15:02:03.776Z",
- "created_by_id": null,
- "invite_email": null,
- "invite_token": null,
- "invite_accepted_at": null,
- "requested_at": null,
- "user": {
- "id": 15,
- "email": "breanna_sanford@wolf.com",
- "username": "emmet.schamberger"
- }
- },
- {
- "id": 33,
- "access_level": 20,
- "source_id": 5,
- "source_type": "Project",
- "user_id": 26,
- "notification_level": 3,
- "created_at": "2016-06-14T15:02:03.742Z",
- "updated_at": "2016-06-14T15:02:03.742Z",
- "created_by_id": null,
- "invite_email": null,
- "invite_token": null,
- "invite_accepted_at": null,
- "requested_at": null,
- "user": {
- "id": 26,
- "email": "user4@example.com",
- "username": "user4"
- }
- }
- ],
- "merge_requests": [
- {
- "id": 27,
- "target_branch": "feature",
- "source_branch": "feature_conflict",
- "source_project_id": 999,
- "author_id": 1,
- "assignee_id": null,
- "title": "MR1",
- "created_at": "2016-06-14T15:02:36.568Z",
- "updated_at": "2016-06-14T15:02:56.815Z",
- "state": "opened",
- "merge_status": "unchecked",
- "target_project_id": 5,
- "iid": 9,
- "description": null,
- "position": 0,
- "updated_by_id": null,
- "merge_error": null,
- "diff_head_sha": "HEAD",
- "source_branch_sha": "ABCD",
- "target_branch_sha": "DCBA",
- "merge_params": {
- "force_remove_source_branch": null
- },
- "merge_when_pipeline_succeeds": true,
- "merge_user_id": null,
- "merge_commit_sha": null,
- "notes": [
- {
- "id": 671,
- "note": "Sit voluptatibus eveniet architecto quidem.",
- "note_html": "<p>something else entirely</p>",
- "cached_markdown_version": 917504,
- "noteable_type": "MergeRequest",
- "author_id": 26,
- "created_at": "2016-06-14T15:02:56.632Z",
- "updated_at": "2016-06-14T15:02:56.632Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 27,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "User 4"
- },
- "events": []
- },
- {
- "id": 672,
- "note": "Odio maxime ratione voluptatibus sed.",
- "noteable_type": "MergeRequest",
- "author_id": 25,
- "created_at": "2016-06-14T15:02:56.656Z",
- "updated_at": "2016-06-14T15:02:56.656Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 27,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "User 3"
- },
- "events": []
- },
- {
- "id": 673,
- "note": "Et deserunt et omnis nihil excepturi accusantium.",
- "noteable_type": "MergeRequest",
- "author_id": 22,
- "created_at": "2016-06-14T15:02:56.679Z",
- "updated_at": "2016-06-14T15:02:56.679Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 27,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "User 0"
- },
- "events": []
- },
- {
- "id": 674,
- "note": "Saepe asperiores exercitationem non dignissimos laborum reiciendis et ipsum.",
- "noteable_type": "MergeRequest",
- "author_id": 20,
- "created_at": "2016-06-14T15:02:56.700Z",
- "updated_at": "2016-06-14T15:02:56.700Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 27,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Ottis Schuster II"
- },
- "events": [],
- "suggestions": [
- {
- "id": 1,
- "note_id": 674,
- "relative_order": 0,
- "applied": false,
- "commit_id": null,
- "from_content": "Original line\n",
- "to_content": "New line\n",
- "lines_above": 0,
- "lines_below": 0,
- "outdated": false
- }
- ]
- },
- {
- "id": 675,
- "note": "Numquam est at dolor quo et sed eligendi similique.",
- "noteable_type": "MergeRequest",
- "author_id": 16,
- "created_at": "2016-06-14T15:02:56.720Z",
- "updated_at": "2016-06-14T15:02:56.720Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 27,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Rhett Emmerich IV"
- },
- "events": []
- },
- {
- "id": 676,
- "note": "Et perferendis aliquam sunt nisi labore delectus.",
- "noteable_type": "MergeRequest",
- "author_id": 15,
- "created_at": "2016-06-14T15:02:56.742Z",
- "updated_at": "2016-06-14T15:02:56.742Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 27,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Burdette Bernier"
- },
- "events": []
- },
- {
- "id": 677,
- "note": "Aut ex rerum et in.",
- "noteable_type": "MergeRequest",
- "author_id": 6,
- "created_at": "2016-06-14T15:02:56.791Z",
- "updated_at": "2016-06-14T15:02:56.791Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 27,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Ari Wintheiser"
- },
- "events": []
- },
- {
- "id": 678,
- "note": "Dolor laborum earum ut exercitationem.",
- "noteable_type": "MergeRequest",
- "author_id": 1,
- "created_at": "2016-06-14T15:02:56.814Z",
- "updated_at": "2016-06-14T15:02:56.814Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 27,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Administrator"
- },
- "events": []
- }
- ],
- "resource_label_events": [
- {
- "id":243,
- "action":"add",
- "issue_id":null,
- "merge_request_id":27,
- "label_id":null,
- "user_id":1,
- "created_at":"2018-08-28T08:24:00.494Z"
- }
- ],
- "merge_request_diff": {
- "id": 27,
- "state": "collected",
- "merge_request_diff_commits": [
- {
- "merge_request_diff_id": 27,
- "relative_order": 0,
- "sha": "bb5206fee213d983da88c47f9cf4cc6caf9c66dc",
- "message": "Feature conflict added\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
- "authored_date": "2014-08-06T08:35:52.000+02:00",
- "author_name": "Dmitriy Zaporozhets",
- "author_email": "dmitriy.zaporozhets@gmail.com",
- "committed_date": "2014-08-06T08:35:52.000+02:00",
- "committer_name": "Dmitriy Zaporozhets",
- "committer_email": "dmitriy.zaporozhets@gmail.com"
- },
- {
- "merge_request_diff_id": 27,
- "relative_order": 1,
- "sha": "5937ac0a7beb003549fc5fd26fc247adbce4a52e",
- "message": "Add submodule from gitlab.com\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
- "authored_date": "2014-02-27T10:01:38.000+01:00",
- "author_name": "Dmitriy Zaporozhets",
- "author_email": "dmitriy.zaporozhets@gmail.com",
- "committed_date": "2014-02-27T10:01:38.000+01:00",
- "committer_name": "Dmitriy Zaporozhets",
- "committer_email": "dmitriy.zaporozhets@gmail.com"
- },
- {
- "merge_request_diff_id": 27,
- "relative_order": 2,
- "sha": "570e7b2abdd848b95f2f578043fc23bd6f6fd24d",
- "message": "Change some files\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
- "authored_date": "2014-02-27T09:57:31.000+01:00",
- "author_name": "Dmitriy Zaporozhets",
- "author_email": "dmitriy.zaporozhets@gmail.com",
- "committed_date": "2014-02-27T09:57:31.000+01:00",
- "committer_name": "Dmitriy Zaporozhets",
- "committer_email": "dmitriy.zaporozhets@gmail.com"
- },
- {
- "merge_request_diff_id": 27,
- "relative_order": 3,
- "sha": "6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9",
- "message": "More submodules\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
- "authored_date": "2014-02-27T09:54:21.000+01:00",
- "author_name": "Dmitriy Zaporozhets",
- "author_email": "dmitriy.zaporozhets@gmail.com",
- "committed_date": "2014-02-27T09:54:21.000+01:00",
- "committer_name": "Dmitriy Zaporozhets",
- "committer_email": "dmitriy.zaporozhets@gmail.com"
- },
- {
- "merge_request_diff_id": 27,
- "relative_order": 4,
- "sha": "d14d6c0abdd253381df51a723d58691b2ee1ab08",
- "message": "Remove ds_store files\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
- "authored_date": "2014-02-27T09:49:50.000+01:00",
- "author_name": "Dmitriy Zaporozhets",
- "author_email": "dmitriy.zaporozhets@gmail.com",
- "committed_date": "2014-02-27T09:49:50.000+01:00",
- "committer_name": "Dmitriy Zaporozhets",
- "committer_email": "dmitriy.zaporozhets@gmail.com"
- },
- {
- "merge_request_diff_id": 27,
- "relative_order": 5,
- "sha": "c1acaa58bbcbc3eafe538cb8274ba387047b69f8",
- "message": "Ignore DS files\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
- "authored_date": "2014-02-27T09:48:32.000+01:00",
- "author_name": "Dmitriy Zaporozhets",
- "author_email": "dmitriy.zaporozhets@gmail.com",
- "committed_date": "2014-02-27T09:48:32.000+01:00",
- "committer_name": "Dmitriy Zaporozhets",
- "committer_email": "dmitriy.zaporozhets@gmail.com"
- }
- ],
- "merge_request_diff_files": [
- {
- "merge_request_diff_id": 27,
- "relative_order": 0,
- "utf8_diff": "Binary files a/.DS_Store and /dev/null differ\n",
- "new_path": ".DS_Store",
- "old_path": ".DS_Store",
- "a_mode": "100644",
- "b_mode": "0",
- "new_file": false,
- "renamed_file": false,
- "deleted_file": true,
- "too_large": false
- },
- {
- "merge_request_diff_id": 27,
- "relative_order": 1,
- "utf8_diff": "--- a/.gitignore\n+++ b/.gitignore\n@@ -17,3 +17,4 @@ rerun.txt\n pickle-email-*.html\n .project\n config/initializers/secret_token.rb\n+.DS_Store\n",
- "new_path": ".gitignore",
- "old_path": ".gitignore",
- "a_mode": "100644",
- "b_mode": "100644",
- "new_file": false,
- "renamed_file": false,
- "deleted_file": false,
- "too_large": false
- },
- {
- "merge_request_diff_id": 27,
- "relative_order": 2,
- "utf8_diff": "--- a/.gitmodules\n+++ b/.gitmodules\n@@ -1,3 +1,9 @@\n [submodule \"six\"]\n \tpath = six\n \turl = git://github.com/randx/six.git\n+[submodule \"gitlab-shell\"]\n+\tpath = gitlab-shell\n+\turl = https://github.com/gitlabhq/gitlab-shell.git\n+[submodule \"gitlab-grack\"]\n+\tpath = gitlab-grack\n+\turl = https://gitlab.com/gitlab-org/gitlab-grack.git\n",
- "new_path": ".gitmodules",
- "old_path": ".gitmodules",
- "a_mode": "100644",
- "b_mode": "100644",
- "new_file": false,
- "renamed_file": false,
- "deleted_file": false,
- "too_large": false
- },
- {
- "merge_request_diff_id": 27,
- "relative_order": 3,
- "utf8_diff": "Binary files a/files/.DS_Store and /dev/null differ\n",
- "new_path": "files/.DS_Store",
- "old_path": "files/.DS_Store",
- "a_mode": "100644",
- "b_mode": "0",
- "new_file": false,
- "renamed_file": false,
- "deleted_file": true,
- "too_large": false
- },
- {
- "merge_request_diff_id": 27,
- "relative_order": 4,
- "utf8_diff": "--- /dev/null\n+++ b/files/ruby/feature.rb\n@@ -0,0 +1,4 @@\n+# This file was changed in feature branch\n+# We put different code here to make merge conflict\n+class Conflict\n+end\n",
- "new_path": "files/ruby/feature.rb",
- "old_path": "files/ruby/feature.rb",
- "a_mode": "0",
- "b_mode": "100644",
- "new_file": true,
- "renamed_file": false,
- "deleted_file": false,
- "too_large": false
- },
- {
- "merge_request_diff_id": 27,
- "relative_order": 5,
- "utf8_diff": "--- a/files/ruby/popen.rb\n+++ b/files/ruby/popen.rb\n@@ -6,12 +6,18 @@ module Popen\n \n def popen(cmd, path=nil)\n unless cmd.is_a?(Array)\n- raise \"System commands must be given as an array of strings\"\n+ raise RuntimeError, \"System commands must be given as an array of strings\"\n end\n \n path ||= Dir.pwd\n- vars = { \"PWD\" => path }\n- options = { chdir: path }\n+\n+ vars = {\n+ \"PWD\" => path\n+ }\n+\n+ options = {\n+ chdir: path\n+ }\n \n unless File.directory?(path)\n FileUtils.mkdir_p(path)\n@@ -19,6 +25,7 @@ module Popen\n \n @cmd_output = \"\"\n @cmd_status = 0\n+\n Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|\n @cmd_output << stdout.read\n @cmd_output << stderr.read\n",
- "new_path": "files/ruby/popen.rb",
- "old_path": "files/ruby/popen.rb",
- "a_mode": "100644",
- "b_mode": "100644",
- "new_file": false,
- "renamed_file": false,
- "deleted_file": false,
- "too_large": false
- },
- {
- "merge_request_diff_id": 27,
- "relative_order": 6,
- "utf8_diff": "--- a/files/ruby/regex.rb\n+++ b/files/ruby/regex.rb\n@@ -19,14 +19,12 @@ module Gitlab\n end\n \n def archive_formats_regex\n- #|zip|tar| tar.gz | tar.bz2 |\n- /(zip|tar|tar\\.gz|tgz|gz|tar\\.bz2|tbz|tbz2|tb2|bz2)/\n+ /(zip|tar|7z|tar\\.gz|tgz|gz|tar\\.bz2|tbz|tbz2|tb2|bz2)/\n end\n \n def git_reference_regex\n # Valid git ref regex, see:\n # https://www.kernel.org/pub/software/scm/git/docs/git-check-ref-format.html\n-\n %r{\n (?!\n (?# doesn't begins with)\n",
- "new_path": "files/ruby/regex.rb",
- "old_path": "files/ruby/regex.rb",
- "a_mode": "100644",
- "b_mode": "100644",
- "new_file": false,
- "renamed_file": false,
- "deleted_file": false,
- "too_large": false
- },
- {
- "merge_request_diff_id": 27,
- "relative_order": 7,
- "utf8_diff": "--- /dev/null\n+++ b/gitlab-grack\n@@ -0,0 +1 @@\n+Subproject commit 645f6c4c82fd3f5e06f67134450a570b795e55a6\n",
- "new_path": "gitlab-grack",
- "old_path": "gitlab-grack",
- "a_mode": "0",
- "b_mode": "160000",
- "new_file": true,
- "renamed_file": false,
- "deleted_file": false,
- "too_large": false
- },
- {
- "merge_request_diff_id": 27,
- "relative_order": 8,
- "utf8_diff": "--- /dev/null\n+++ b/gitlab-shell\n@@ -0,0 +1 @@\n+Subproject commit 79bceae69cb5750d6567b223597999bfa91cb3b9\n",
- "new_path": "gitlab-shell",
- "old_path": "gitlab-shell",
- "a_mode": "0",
- "b_mode": "160000",
- "new_file": true,
- "renamed_file": false,
- "deleted_file": false,
- "too_large": false
- }
- ],
- "merge_request_id": 27,
- "created_at": "2016-06-14T15:02:36.572Z",
- "updated_at": "2016-06-14T15:02:36.658Z",
- "base_commit_sha": "ae73cb07c9eeaf35924a10f713b364d32b2dd34f",
- "real_size": "9"
- },
- "events": [
- {
- "id": 221,
- "target_type": "MergeRequest",
- "target_id": 27,
- "project_id": 36,
- "created_at": "2016-06-14T15:02:36.703Z",
- "updated_at": "2016-06-14T15:02:36.703Z",
- "action": 1,
- "author_id": 1
- },
- {
- "id": 187,
- "target_type": "MergeRequest",
- "target_id": 27,
- "project_id": 5,
- "created_at": "2016-06-14T15:02:36.703Z",
- "updated_at": "2016-06-14T15:02:36.703Z",
- "action": 1,
- "author_id": 1
- }
- ],
- "approvals_before_merge": 1
- },
- {
- "id": 26,
- "target_branch": "master",
- "source_branch": "feature",
- "source_project_id": 4,
- "author_id": 1,
- "assignee_id": null,
- "title": "MR2",
- "created_at": "2016-06-14T15:02:36.418Z",
- "updated_at": "2016-06-14T15:02:57.013Z",
- "state": "opened",
- "merge_status": "unchecked",
- "target_project_id": 5,
- "iid": 8,
- "description": null,
- "position": 0,
- "updated_by_id": null,
- "merge_error": null,
- "merge_params": {
- "force_remove_source_branch": null
- },
- "merge_when_pipeline_succeeds": false,
- "merge_user_id": null,
- "merge_commit_sha": null,
- "notes": [
- {
- "id": 679,
- "note": "Qui rerum totam nisi est.",
- "noteable_type": "MergeRequest",
- "author_id": 26,
- "created_at": "2016-06-14T15:02:56.848Z",
- "updated_at": "2016-06-14T15:02:56.848Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 26,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "User 4"
- },
- "events": []
- },
- {
- "id": 680,
- "note": "Pariatur magni corrupti consequatur debitis minima error beatae voluptatem.",
- "noteable_type": "MergeRequest",
- "author_id": 25,
- "created_at": "2016-06-14T15:02:56.871Z",
- "updated_at": "2016-06-14T15:02:56.871Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 26,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "User 3"
- },
- "events": []
- },
- {
- "id": 681,
- "note": "Qui quis ut modi eos rerum ratione.",
- "noteable_type": "MergeRequest",
- "author_id": 22,
- "created_at": "2016-06-14T15:02:56.895Z",
- "updated_at": "2016-06-14T15:02:56.895Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 26,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "User 0"
- },
- "events": []
- },
- {
- "id": 682,
- "note": "Illum quidem expedita mollitia fugit.",
- "noteable_type": "MergeRequest",
- "author_id": 20,
- "created_at": "2016-06-14T15:02:56.918Z",
- "updated_at": "2016-06-14T15:02:56.918Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 26,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Ottis Schuster II"
- },
- "events": []
- },
- {
- "id": 683,
- "note": "Consectetur voluptate sit sint possimus veritatis quod.",
- "noteable_type": "MergeRequest",
- "author_id": 16,
- "created_at": "2016-06-14T15:02:56.942Z",
- "updated_at": "2016-06-14T15:02:56.942Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 26,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Rhett Emmerich IV"
- },
- "events": []
- },
- {
- "id": 684,
- "note": "Natus libero quibusdam rem assumenda deleniti accusamus sed earum.",
- "noteable_type": "MergeRequest",
- "author_id": 15,
- "created_at": "2016-06-14T15:02:56.966Z",
- "updated_at": "2016-06-14T15:02:56.966Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 26,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Burdette Bernier"
- },
- "events": []
- },
- {
- "id": 685,
- "note": "Tenetur autem nihil rerum odit.",
- "noteable_type": "MergeRequest",
- "author_id": 6,
- "created_at": "2016-06-14T15:02:56.989Z",
- "updated_at": "2016-06-14T15:02:56.989Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 26,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Ari Wintheiser"
- },
- "events": []
- },
- {
- "id": 686,
- "note": "Quia maiores et odio sed.",
- "noteable_type": "MergeRequest",
- "author_id": 1,
- "created_at": "2016-06-14T15:02:57.012Z",
- "updated_at": "2016-06-14T15:02:57.012Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 26,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Administrator"
- },
- "events": []
- }
- ],
- "merge_request_diff": {
- "id": 26,
- "state": "collected",
- "merge_request_diff_commits": [
- {
- "merge_request_diff_id": 26,
- "sha": "0b4bc9a49b562e85de7cc9e834518ea6828729b9",
- "relative_order": 0,
- "message": "Feature added\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
- "authored_date": "2014-02-27T09:26:01.000+01:00",
- "author_name": "Dmitriy Zaporozhets",
- "author_email": "dmitriy.zaporozhets@gmail.com",
- "committed_date": "2014-02-27T09:26:01.000+01:00",
- "committer_name": "Dmitriy Zaporozhets",
- "committer_email": "dmitriy.zaporozhets@gmail.com"
- }
- ],
- "merge_request_diff_files": [
- {
- "merge_request_diff_id": 26,
- "relative_order": 0,
- "utf8_diff": "--- /dev/null\n+++ b/files/ruby/feature.rb\n@@ -0,0 +1,5 @@\n+class Feature\n+ def foo\n+ puts 'bar'\n+ end\n+end\n",
- "new_path": "files/ruby/feature.rb",
- "old_path": "files/ruby/feature.rb",
- "a_mode": "0",
- "b_mode": "100644",
- "new_file": true,
- "renamed_file": false,
- "deleted_file": false,
- "too_large": false
- }
- ],
- "merge_request_id": 26,
- "created_at": "2016-06-14T15:02:36.421Z",
- "updated_at": "2016-06-14T15:02:36.474Z",
- "base_commit_sha": "ae73cb07c9eeaf35924a10f713b364d32b2dd34f",
- "real_size": "1"
- },
- "events": [
- {
- "id": 222,
- "target_type": "MergeRequest",
- "target_id": 26,
- "project_id": 36,
- "created_at": "2016-06-14T15:02:36.496Z",
- "updated_at": "2016-06-14T15:02:36.496Z",
- "action": 1,
- "author_id": 1
- },
- {
- "id": 186,
- "target_type": "MergeRequest",
- "target_id": 26,
- "project_id": 5,
- "created_at": "2016-06-14T15:02:36.496Z",
- "updated_at": "2016-06-14T15:02:36.496Z",
- "action": 1,
- "author_id": 1
- }
- ]
- },
- {
- "id": 15,
- "target_branch": "test-7",
- "source_branch": "test-1",
- "source_project_id": 5,
- "author_id": 22,
- "assignee_id": 16,
- "title": "Qui accusantium et inventore facilis doloribus occaecati officiis.",
- "created_at": "2016-06-14T15:02:25.168Z",
- "updated_at": "2016-06-14T15:02:59.521Z",
- "state": "opened",
- "merge_status": "unchecked",
- "target_project_id": 5,
- "iid": 7,
- "description": "Et commodi deserunt aspernatur vero rerum. Ut non dolorum alias in odit est libero. Voluptatibus eos in et vitae repudiandae facilis ex mollitia.",
- "position": 0,
- "updated_by_id": null,
- "merge_error": null,
- "merge_params": {
- "force_remove_source_branch": null
- },
- "merge_when_pipeline_succeeds": false,
- "merge_user_id": null,
- "merge_commit_sha": null,
- "notes": [
- {
- "id": 777,
- "note": "Pariatur voluptas placeat aspernatur culpa suscipit soluta.",
- "noteable_type": "MergeRequest",
- "author_id": 26,
- "created_at": "2016-06-14T15:02:59.348Z",
- "updated_at": "2016-06-14T15:02:59.348Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 15,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "User 4"
- },
- "events": []
- },
- {
- "id": 778,
- "note": "Alias et iure mollitia suscipit molestiae voluptatum nostrum asperiores.",
- "noteable_type": "MergeRequest",
- "author_id": 25,
- "created_at": "2016-06-14T15:02:59.372Z",
- "updated_at": "2016-06-14T15:02:59.372Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 15,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "User 3"
- },
- "events": []
- },
- {
- "id": 779,
- "note": "Laudantium qui eum qui sunt.",
- "noteable_type": "MergeRequest",
- "author_id": 22,
- "created_at": "2016-06-14T15:02:59.395Z",
- "updated_at": "2016-06-14T15:02:59.395Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 15,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "User 0"
- },
- "events": []
- },
- {
- "id": 780,
- "note": "Quas rem est iusto ut delectus fugiat recusandae mollitia.",
- "noteable_type": "MergeRequest",
- "author_id": 20,
- "created_at": "2016-06-14T15:02:59.418Z",
- "updated_at": "2016-06-14T15:02:59.418Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 15,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Ottis Schuster II"
- },
- "events": []
- },
- {
- "id": 781,
- "note": "Repellendus ab et qui nesciunt.",
- "noteable_type": "MergeRequest",
- "author_id": 16,
- "created_at": "2016-06-14T15:02:59.444Z",
- "updated_at": "2016-06-14T15:02:59.444Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 15,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Rhett Emmerich IV"
- },
- "events": []
- },
- {
- "id": 782,
- "note": "Non possimus voluptatum odio qui ut.",
- "noteable_type": "MergeRequest",
- "author_id": 15,
- "created_at": "2016-06-14T15:02:59.469Z",
- "updated_at": "2016-06-14T15:02:59.469Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 15,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Burdette Bernier"
- },
- "events": []
- },
- {
- "id": 783,
- "note": "Dolores repellendus eum ducimus quam ab dolorem quia.",
- "noteable_type": "MergeRequest",
- "author_id": 6,
- "created_at": "2016-06-14T15:02:59.494Z",
- "updated_at": "2016-06-14T15:02:59.494Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 15,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Ari Wintheiser"
- },
- "events": []
- },
- {
- "id": 784,
- "note": "Facilis dolorem aut corrupti id ratione occaecati.",
- "noteable_type": "MergeRequest",
- "author_id": 1,
- "created_at": "2016-06-14T15:02:59.520Z",
- "updated_at": "2016-06-14T15:02:59.520Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 15,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Administrator"
- },
- "events": []
- }
- ],
- "merge_request_diff": {
- "id": 15,
- "state": "collected",
- "merge_request_diff_commits": [
- {
- "merge_request_diff_id": 15,
- "relative_order": 0,
- "sha": "94b8d581c48d894b86661718582fecbc5e3ed2eb",
- "message": "fixes #10\n",
- "authored_date": "2016-01-19T13:22:56.000+01:00",
- "author_name": "James Lopez",
- "author_email": "james@jameslopez.es",
- "committed_date": "2016-01-19T13:22:56.000+01:00",
- "committer_name": "James Lopez",
- "committer_email": "james@jameslopez.es"
- }
- ],
- "merge_request_diff_files": [
- {
- "merge_request_diff_id": 15,
- "relative_order": 0,
- "utf8_diff": "--- /dev/null\n+++ b/test\n",
- "new_path": "test",
- "old_path": "test",
- "a_mode": "0",
- "b_mode": "100644",
- "new_file": true,
- "renamed_file": false,
- "deleted_file": false,
- "too_large": false
- }
- ],
- "merge_request_id": 15,
- "created_at": "2016-06-14T15:02:25.171Z",
- "updated_at": "2016-06-14T15:02:25.230Z",
- "base_commit_sha": "be93687618e4b132087f430a4d8fc3a609c9b77c",
- "real_size": "1"
- },
- "events": [
- {
- "id": 223,
- "target_type": "MergeRequest",
- "target_id": 15,
- "project_id": 36,
- "created_at": "2016-06-14T15:02:25.262Z",
- "updated_at": "2016-06-14T15:02:25.262Z",
- "action": 1,
- "author_id": 1
- },
- {
- "id": 175,
- "target_type": "MergeRequest",
- "target_id": 15,
- "project_id": 5,
- "created_at": "2016-06-14T15:02:25.262Z",
- "updated_at": "2016-06-14T15:02:25.262Z",
- "action": 1,
- "author_id": 22
- }
- ]
- },
- {
- "id": 14,
- "target_branch": "fix",
- "source_branch": "test-3",
- "source_project_id": 5,
- "author_id": 20,
- "assignee_id": 20,
- "title": "In voluptas aut sequi voluptatem ullam vel corporis illum consequatur.",
- "created_at": "2016-06-14T15:02:24.760Z",
- "updated_at": "2016-06-14T15:02:59.749Z",
- "state": "opened",
- "merge_status": "unchecked",
- "target_project_id": 5,
- "iid": 6,
- "description": "Dicta magnam non voluptates nam dignissimos nostrum deserunt. Dolorum et suscipit iure quae doloremque. Necessitatibus saepe aut labore sed.",
- "position": 0,
- "updated_by_id": null,
- "merge_error": null,
- "merge_params": {
- "force_remove_source_branch": null
- },
- "merge_when_pipeline_succeeds": false,
- "merge_user_id": null,
- "merge_commit_sha": null,
- "notes": [
- {
- "id": 785,
- "note": "Atque cupiditate necessitatibus deserunt minus natus odit.",
- "noteable_type": "MergeRequest",
- "author_id": 26,
- "created_at": "2016-06-14T15:02:59.559Z",
- "updated_at": "2016-06-14T15:02:59.559Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 14,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "User 4"
- },
- "events": []
- },
- {
- "id": 786,
- "note": "Non dolorem provident mollitia nesciunt optio ex eveniet.",
- "noteable_type": "MergeRequest",
- "author_id": 25,
- "created_at": "2016-06-14T15:02:59.587Z",
- "updated_at": "2016-06-14T15:02:59.587Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 14,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "User 3"
- },
- "events": []
- },
- {
- "id": 787,
- "note": "Similique officia nemo quasi commodi accusantium quae qui.",
- "noteable_type": "MergeRequest",
- "author_id": 22,
- "created_at": "2016-06-14T15:02:59.621Z",
- "updated_at": "2016-06-14T15:02:59.621Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 14,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "User 0"
- },
- "events": []
- },
- {
- "id": 788,
- "note": "Et est et alias ad dolor qui.",
- "noteable_type": "MergeRequest",
- "author_id": 20,
- "created_at": "2016-06-14T15:02:59.650Z",
- "updated_at": "2016-06-14T15:02:59.650Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 14,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Ottis Schuster II"
- },
- "events": []
- },
- {
- "id": 789,
- "note": "Numquam temporibus ratione voluptatibus aliquid.",
- "noteable_type": "MergeRequest",
- "author_id": 16,
- "created_at": "2016-06-14T15:02:59.675Z",
- "updated_at": "2016-06-14T15:02:59.675Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 14,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Rhett Emmerich IV"
- },
- "events": []
- },
- {
- "id": 790,
- "note": "Ut ex aliquam consectetur perferendis est hic aut quia.",
- "noteable_type": "MergeRequest",
- "author_id": 15,
- "created_at": "2016-06-14T15:02:59.703Z",
- "updated_at": "2016-06-14T15:02:59.703Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 14,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Burdette Bernier"
- },
- "events": []
- },
- {
- "id": 791,
- "note": "Esse eos quam quaerat aut ut asperiores officiis.",
- "noteable_type": "MergeRequest",
- "author_id": 6,
- "created_at": "2016-06-14T15:02:59.726Z",
- "updated_at": "2016-06-14T15:02:59.726Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 14,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Ari Wintheiser"
- },
- "events": []
- },
- {
- "id": 792,
- "note": "Sint facilis accusantium iure blanditiis.",
- "noteable_type": "MergeRequest",
- "author_id": 1,
- "created_at": "2016-06-14T15:02:59.748Z",
- "updated_at": "2016-06-14T15:02:59.748Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 14,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Administrator"
- },
- "events": []
- }
- ],
- "merge_request_diff": {
- "id": 14,
- "state": "collected",
- "merge_request_diff_commits": [
- {
- "merge_request_diff_id": 14,
- "relative_order": 0,
- "sha": "ddd4ff416a931589c695eb4f5b23f844426f6928",
- "message": "fixes #10\n",
- "authored_date": "2016-01-19T14:14:43.000+01:00",
- "author_name": "James Lopez",
- "author_email": "james@jameslopez.es",
- "committed_date": "2016-01-19T14:14:43.000+01:00",
- "committer_name": "James Lopez",
- "committer_email": "james@jameslopez.es"
- },
- {
- "merge_request_diff_id": 14,
- "relative_order": 1,
- "sha": "be93687618e4b132087f430a4d8fc3a609c9b77c",
- "message": "Merge branch 'master' into 'master'\r\n\r\nLFS object pointer.\r\n\r\n\r\n\r\nSee merge request !6",
- "authored_date": "2015-12-07T12:52:12.000+01:00",
- "author_name": "Marin Jankovski",
- "author_email": "marin@gitlab.com",
- "committed_date": "2015-12-07T12:52:12.000+01:00",
- "committer_name": "Marin Jankovski",
- "committer_email": "marin@gitlab.com"
- },
- {
- "merge_request_diff_id": 14,
- "relative_order": 2,
- "sha": "048721d90c449b244b7b4c53a9186b04330174ec",
- "message": "LFS object pointer.\n",
- "authored_date": "2015-12-07T11:54:28.000+01:00",
- "author_name": "Marin Jankovski",
- "author_email": "maxlazio@gmail.com",
- "committed_date": "2015-12-07T11:54:28.000+01:00",
- "committer_name": "Marin Jankovski",
- "committer_email": "maxlazio@gmail.com"
- },
- {
- "merge_request_diff_id": 14,
- "relative_order": 3,
- "sha": "5f923865dde3436854e9ceb9cdb7815618d4e849",
- "message": "GitLab currently doesn't support patches that involve a merge commit: add a commit here\n",
- "authored_date": "2015-11-13T16:27:12.000+01:00",
- "author_name": "Stan Hu",
- "author_email": "stanhu@gmail.com",
- "committed_date": "2015-11-13T16:27:12.000+01:00",
- "committer_name": "Stan Hu",
- "committer_email": "stanhu@gmail.com"
- },
- {
- "merge_request_diff_id": 14,
- "relative_order": 4,
- "sha": "d2d430676773caa88cdaf7c55944073b2fd5561a",
- "message": "Merge branch 'add-svg' into 'master'\r\n\r\nAdd GitLab SVG\r\n\r\nAdded to test preview of sanitized SVG images\r\n\r\nSee merge request !5",
- "authored_date": "2015-11-13T08:50:17.000+01:00",
- "author_name": "Stan Hu",
- "author_email": "stanhu@gmail.com",
- "committed_date": "2015-11-13T08:50:17.000+01:00",
- "committer_name": "Stan Hu",
- "committer_email": "stanhu@gmail.com"
- },
- {
- "merge_request_diff_id": 14,
- "relative_order": 5,
- "sha": "2ea1f3dec713d940208fb5ce4a38765ecb5d3f73",
- "message": "Add GitLab SVG\n",
- "authored_date": "2015-11-13T08:39:43.000+01:00",
- "author_name": "Stan Hu",
- "author_email": "stanhu@gmail.com",
- "committed_date": "2015-11-13T08:39:43.000+01:00",
- "committer_name": "Stan Hu",
- "committer_email": "stanhu@gmail.com"
- },
- {
- "merge_request_diff_id": 14,
- "relative_order": 6,
- "sha": "59e29889be61e6e0e5e223bfa9ac2721d31605b8",
- "message": "Merge branch 'whitespace' into 'master'\r\n\r\nadd whitespace test file\r\n\r\nSorry, I did a mistake.\r\nGit ignore empty files.\r\nSo I add a new whitespace test file.\r\n\r\nSee merge request !4",
- "authored_date": "2015-11-13T07:21:40.000+01:00",
- "author_name": "Stan Hu",
- "author_email": "stanhu@gmail.com",
- "committed_date": "2015-11-13T07:21:40.000+01:00",
- "committer_name": "Stan Hu",
- "committer_email": "stanhu@gmail.com"
- },
- {
- "merge_request_diff_id": 14,
- "relative_order": 7,
- "sha": "66eceea0db202bb39c4e445e8ca28689645366c5",
- "message": "add spaces in whitespace file\n",
- "authored_date": "2015-11-13T06:01:27.000+01:00",
- "author_name": "윤민식",
- "author_email": "minsik.yoon@samsung.com",
- "committed_date": "2015-11-13T06:01:27.000+01:00",
- "committer_name": "윤민식",
- "committer_email": "minsik.yoon@samsung.com"
- },
- {
- "merge_request_diff_id": 14,
- "relative_order": 8,
- "sha": "08f22f255f082689c0d7d39d19205085311542bc",
- "message": "remove empty file.(beacase git ignore empty file)\nadd whitespace test file.\n",
- "authored_date": "2015-11-13T06:00:16.000+01:00",
- "author_name": "윤민식",
- "author_email": "minsik.yoon@samsung.com",
- "committed_date": "2015-11-13T06:00:16.000+01:00",
- "committer_name": "윤민식",
- "committer_email": "minsik.yoon@samsung.com"
- },
- {
- "merge_request_diff_id": 14,
- "relative_order": 9,
- "sha": "19e2e9b4ef76b422ce1154af39a91323ccc57434",
- "message": "Merge branch 'whitespace' into 'master'\r\n\r\nadd spaces\r\n\r\nTo test this pull request.(https://github.com/gitlabhq/gitlabhq/pull/9757)\r\nJust add whitespaces.\r\n\r\nSee merge request !3",
- "authored_date": "2015-11-13T05:23:14.000+01:00",
- "author_name": "Stan Hu",
- "author_email": "stanhu@gmail.com",
- "committed_date": "2015-11-13T05:23:14.000+01:00",
- "committer_name": "Stan Hu",
- "committer_email": "stanhu@gmail.com"
- },
- {
- "merge_request_diff_id": 14,
- "relative_order": 10,
- "sha": "c642fe9b8b9f28f9225d7ea953fe14e74748d53b",
- "message": "add whitespace in empty\n",
- "authored_date": "2015-11-13T05:08:45.000+01:00",
- "author_name": "윤민식",
- "author_email": "minsik.yoon@samsung.com",
- "committed_date": "2015-11-13T05:08:45.000+01:00",
- "committer_name": "윤민식",
- "committer_email": "minsik.yoon@samsung.com"
- },
- {
- "merge_request_diff_id": 14,
- "relative_order": 11,
- "sha": "9a944d90955aaf45f6d0c88f30e27f8d2c41cec0",
- "message": "add empty file\n",
- "authored_date": "2015-11-13T05:08:04.000+01:00",
- "author_name": "윤민식",
- "author_email": "minsik.yoon@samsung.com",
- "committed_date": "2015-11-13T05:08:04.000+01:00",
- "committer_name": "윤민식",
- "committer_email": "minsik.yoon@samsung.com"
- },
- {
- "merge_request_diff_id": 14,
- "relative_order": 12,
- "sha": "c7fbe50c7c7419d9701eebe64b1fdacc3df5b9dd",
- "message": "Add ISO-8859 test file\n",
- "authored_date": "2015-08-25T17:53:12.000+02:00",
- "author_name": "Stan Hu",
- "author_email": "stanhu@packetzoom.com",
- "committed_date": "2015-08-25T17:53:12.000+02:00",
- "committer_name": "Stan Hu",
- "committer_email": "stanhu@packetzoom.com"
- },
- {
- "merge_request_diff_id": 14,
- "relative_order": 13,
- "sha": "e56497bb5f03a90a51293fc6d516788730953899",
- "message": "Merge branch 'tree_helper_spec' into 'master'\n\nAdd directory structure for tree_helper spec\n\nThis directory structure is needed for a testing the method flatten_tree(tree) in the TreeHelper module\n\nSee [merge request #275](https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/275#note_732774)\n\nSee merge request !2\n",
- "authored_date": "2015-01-10T22:23:29.000+01:00",
- "author_name": "Sytse Sijbrandij",
- "author_email": "sytse@gitlab.com",
- "committed_date": "2015-01-10T22:23:29.000+01:00",
- "committer_name": "Sytse Sijbrandij",
- "committer_email": "sytse@gitlab.com"
- },
- {
- "merge_request_diff_id": 14,
- "relative_order": 14,
- "sha": "4cd80ccab63c82b4bad16faa5193fbd2aa06df40",
- "message": "add directory structure for tree_helper spec\n",
- "authored_date": "2015-01-10T21:28:18.000+01:00",
- "author_name": "marmis85",
- "author_email": "marmis85@gmail.com",
- "committed_date": "2015-01-10T21:28:18.000+01:00",
- "committer_name": "marmis85",
- "committer_email": "marmis85@gmail.com"
- },
- {
- "merge_request_diff_id": 14,
- "relative_order": 15,
- "sha": "5937ac0a7beb003549fc5fd26fc247adbce4a52e",
- "message": "Add submodule from gitlab.com\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
- "authored_date": "2014-02-27T10:01:38.000+01:00",
- "author_name": "Dmitriy Zaporozhets",
- "author_email": "dmitriy.zaporozhets@gmail.com",
- "committed_date": "2014-02-27T10:01:38.000+01:00",
- "committer_name": "Dmitriy Zaporozhets",
- "committer_email": "dmitriy.zaporozhets@gmail.com"
- },
- {
- "merge_request_diff_id": 14,
- "relative_order": 16,
- "sha": "570e7b2abdd848b95f2f578043fc23bd6f6fd24d",
- "message": "Change some files\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
- "authored_date": "2014-02-27T09:57:31.000+01:00",
- "author_name": "Dmitriy Zaporozhets",
- "author_email": "dmitriy.zaporozhets@gmail.com",
- "committed_date": "2014-02-27T09:57:31.000+01:00",
- "committer_name": "Dmitriy Zaporozhets",
- "committer_email": "dmitriy.zaporozhets@gmail.com"
- },
- {
- "merge_request_diff_id": 14,
- "relative_order": 17,
- "sha": "6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9",
- "message": "More submodules\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
- "authored_date": "2014-02-27T09:54:21.000+01:00",
- "author_name": "Dmitriy Zaporozhets",
- "author_email": "dmitriy.zaporozhets@gmail.com",
- "committed_date": "2014-02-27T09:54:21.000+01:00",
- "committer_name": "Dmitriy Zaporozhets",
- "committer_email": "dmitriy.zaporozhets@gmail.com"
- },
- {
- "merge_request_diff_id": 14,
- "relative_order": 18,
- "sha": "d14d6c0abdd253381df51a723d58691b2ee1ab08",
- "message": "Remove ds_store files\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
- "authored_date": "2014-02-27T09:49:50.000+01:00",
- "author_name": "Dmitriy Zaporozhets",
- "author_email": "dmitriy.zaporozhets@gmail.com",
- "committed_date": "2014-02-27T09:49:50.000+01:00",
- "committer_name": "Dmitriy Zaporozhets",
- "committer_email": "dmitriy.zaporozhets@gmail.com"
- },
- {
- "merge_request_diff_id": 14,
- "relative_order": 19,
- "sha": "c1acaa58bbcbc3eafe538cb8274ba387047b69f8",
- "message": "Ignore DS files\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
- "authored_date": "2014-02-27T09:48:32.000+01:00",
- "author_name": "Dmitriy Zaporozhets",
- "author_email": "dmitriy.zaporozhets@gmail.com",
- "committed_date": "2014-02-27T09:48:32.000+01:00",
- "committer_name": "Dmitriy Zaporozhets",
- "committer_email": "dmitriy.zaporozhets@gmail.com"
- }
- ],
- "merge_request_diff_files": [
- {
- "merge_request_diff_id": 14,
- "relative_order": 0,
- "utf8_diff": "Binary files a/.DS_Store and /dev/null differ\n",
- "new_path": ".DS_Store",
- "old_path": ".DS_Store",
- "a_mode": "100644",
- "b_mode": "0",
- "new_file": false,
- "renamed_file": false,
- "deleted_file": true,
- "too_large": false
- },
- {
- "merge_request_diff_id": 14,
- "relative_order": 1,
- "utf8_diff": "--- a/.gitignore\n+++ b/.gitignore\n@@ -17,3 +17,4 @@ rerun.txt\n pickle-email-*.html\n .project\n config/initializers/secret_token.rb\n+.DS_Store\n",
- "new_path": ".gitignore",
- "old_path": ".gitignore",
- "a_mode": "100644",
- "b_mode": "100644",
- "new_file": false,
- "renamed_file": false,
- "deleted_file": false,
- "too_large": false
- },
- {
- "merge_request_diff_id": 14,
- "relative_order": 2,
- "utf8_diff": "--- a/.gitmodules\n+++ b/.gitmodules\n@@ -1,3 +1,9 @@\n [submodule \"six\"]\n \tpath = six\n \turl = git://github.com/randx/six.git\n+[submodule \"gitlab-shell\"]\n+\tpath = gitlab-shell\n+\turl = https://github.com/gitlabhq/gitlab-shell.git\n+[submodule \"gitlab-grack\"]\n+\tpath = gitlab-grack\n+\turl = https://gitlab.com/gitlab-org/gitlab-grack.git\n",
- "new_path": ".gitmodules",
- "old_path": ".gitmodules",
- "a_mode": "100644",
- "b_mode": "100644",
- "new_file": false,
- "renamed_file": false,
- "deleted_file": false,
- "too_large": false
- },
- {
- "merge_request_diff_id": 14,
- "relative_order": 3,
- "utf8_diff": "--- a/CHANGELOG\n+++ b/CHANGELOG\n@@ -1,4 +1,6 @@\n-v 6.7.0\n+v6.8.0\n+\n+v6.7.0\n - Add support for Gemnasium as a Project Service (Olivier Gonzalez)\n - Add edit file button to MergeRequest diff\n - Public groups (Jason Hollingsworth)\n",
- "new_path": "CHANGELOG",
- "old_path": "CHANGELOG",
- "a_mode": "100644",
- "b_mode": "100644",
- "new_file": false,
- "renamed_file": false,
- "deleted_file": false,
- "too_large": false
- },
- {
- "merge_request_diff_id": 14,
- "relative_order": 4,
- "utf8_diff": "--- /dev/null\n+++ b/encoding/iso8859.txt\n@@ -0,0 +1 @@\n+Äü\n",
- "new_path": "encoding/iso8859.txt",
- "old_path": "encoding/iso8859.txt",
- "a_mode": "0",
- "b_mode": "100644",
- "new_file": true,
- "renamed_file": false,
- "deleted_file": false,
- "too_large": false
- },
- {
- "merge_request_diff_id": 14,
- "relative_order": 5,
- "utf8_diff": "Binary files a/files/.DS_Store and /dev/null differ\n",
- "new_path": "files/.DS_Store",
- "old_path": "files/.DS_Store",
- "a_mode": "100644",
- "b_mode": "0",
- "new_file": false,
- "renamed_file": false,
- "deleted_file": true,
- "too_large": false
- },
- {
- "merge_request_diff_id": 14,
- "relative_order": 6,
- "utf8_diff": "--- /dev/null\n+++ b/files/images/wm.svg\n@@ -0,0 +1,78 @@\n+<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n+<svg width=\"1300px\" height=\"680px\" viewBox=\"0 0 1300 680\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xmlns:sketch=\"http://www.bohemiancoding.com/sketch/ns\">\n+ <!-- Generator: Sketch 3.2.2 (9983) - http://www.bohemiancoding.com/sketch -->\n+ <title>wm</title>\n+ <desc>Created with Sketch.</desc>\n+ <defs>\n+ <path id=\"path-1\" d=\"M-69.8,1023.54607 L1675.19996,1023.54607 L1675.19996,0 L-69.8,0 L-69.8,1023.54607 L-69.8,1023.54607 Z\"></path>\n+ </defs>\n+ <g id=\"Page-1\" stroke=\"none\" stroke-width=\"1\" fill=\"none\" fill-rule=\"evenodd\" sketch:type=\"MSPage\">\n+ <path d=\"M1300,680 L0,680 L0,0 L1300,0 L1300,680 L1300,680 Z\" id=\"bg\" fill=\"#30353E\" sketch:type=\"MSShapeGroup\"></path>\n+ <g id=\"gitlab_logo\" sketch:type=\"MSLayerGroup\" transform=\"translate(-262.000000, -172.000000)\">\n+ <g id=\"g10\" transform=\"translate(872.500000, 512.354581) scale(1, -1) translate(-872.500000, -512.354581) translate(0.000000, 0.290751)\">\n+ <g id=\"g12\" transform=\"translate(1218.022652, 440.744871)\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\">\n+ <path d=\"M-50.0233338,141.900706 L-69.07059,141.900706 L-69.0100967,0.155858152 L8.04444805,0.155858152 L8.04444805,17.6840847 L-49.9628405,17.6840847 L-50.0233338,141.900706 L-50.0233338,141.900706 Z\" id=\"path14\"></path>\n+ </g>\n+ <g id=\"g16\">\n+ <g id=\"g18-Clipped\">\n+ <mask id=\"mask-2\" sketch:name=\"path22\" fill=\"white\">\n+ <use xlink:href=\"#path-1\"></use>\n+ </mask>\n+ <g id=\"path22\"></g>\n+ <g id=\"g18\" mask=\"url(#mask-2)\">\n+ <g transform=\"translate(382.736659, 312.879425)\">\n+ <g id=\"g24\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(852.718192, 124.992771)\">\n+ <path d=\"M63.9833317,27.9148929 C59.2218085,22.9379001 51.2134221,17.9597442 40.3909323,17.9597442 C25.8888194,17.9597442 20.0453962,25.1013043 20.0453962,34.4074318 C20.0453962,48.4730484 29.7848226,55.1819277 50.5642821,55.1819277 C54.4602853,55.1819277 60.7364685,54.7492469 63.9833317,54.1002256 L63.9833317,27.9148929 L63.9833317,27.9148929 Z M44.2869356,113.827628 C28.9053426,113.827628 14.7975996,108.376082 3.78897657,99.301416 L10.5211864,87.6422957 C18.3131929,92.1866076 27.8374026,96.7320827 41.4728323,96.7320827 C57.0568452,96.7320827 63.9833317,88.7239978 63.9833317,75.3074024 L63.9833317,68.3821827 C60.9528485,69.0312039 54.6766653,69.4650479 50.7806621,69.4650479 C17.4476729,69.4650479 0.565379986,57.7791759 0.565379986,33.3245665 C0.565379986,11.4683685 13.9844297,0.43151772 34.3299658,0.43151772 C48.0351955,0.43151772 61.1692285,6.70771614 65.7143717,16.8780421 L69.1776149,3.02876588 L82.5978279,3.02876588 L82.5978279,75.5237428 C82.5978279,98.462806 72.6408582,113.827628 44.2869356,113.827628 L44.2869356,113.827628 Z\" id=\"path26\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g28\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(959.546624, 124.857151)\">\n+ <path d=\"M37.2266657,17.4468081 C30.0837992,17.4468081 23.8064527,18.3121698 19.0449295,20.4767371 L19.0449295,79.2306079 L19.0449295,86.0464943 C25.538656,91.457331 33.5470425,95.3526217 43.7203922,95.3526217 C62.1173451,95.3526217 69.2602116,82.3687072 69.2602116,61.3767077 C69.2602116,31.5135879 57.7885819,17.4468081 37.2266657,17.4468081 M45.2315622,113.963713 C28.208506,113.963713 19.0449295,102.384849 19.0449295,102.384849 L19.0449295,120.67143 L18.9844362,144.908535 L10.3967097,144.908535 L0.371103324,144.908535 L0.431596656,6.62629771 C9.73826309,2.73100702 22.5081728,0.567602823 36.3611458,0.567602823 C71.8579349,0.567602823 88.9566078,23.2891625 88.9566078,62.4584098 C88.9566078,93.4043948 73.1527248,113.963713 45.2315622,113.963713\" id=\"path30\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g32\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(509.576747, 125.294950)\">\n+ <path d=\"M68.636665,129.10638 C85.5189579,129.10638 96.3414476,123.480366 103.484314,117.853189 L111.669527,132.029302 C100.513161,141.811145 85.5073245,147.06845 69.5021849,147.06845 C29.0274926,147.06845 0.673569983,122.3975 0.673569983,72.6252464 C0.673569983,20.4709215 31.2622559,0.12910638 66.2553217,0.12910638 C83.7879179,0.12910638 98.7227909,4.24073748 108.462217,8.35236859 L108.063194,64.0763105 L108.063194,70.6502677 L108.063194,81.6057001 L56.1168719,81.6057001 L56.1168719,64.0763105 L89.2323178,64.0763105 L89.6313411,21.7701271 C85.3025779,19.6055598 77.7269514,17.8748364 67.554765,17.8748364 C39.4172223,17.8748364 20.5863462,35.5717154 20.5863462,72.8415868 C20.5863462,110.711628 40.0663623,129.10638 68.636665,129.10638\" id=\"path34\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g36\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(692.388992, 124.376085)\">\n+ <path d=\"M19.7766662,145.390067 L1.16216997,145.390067 L1.2226633,121.585642 L1.2226633,111.846834 L1.2226633,106.170806 L1.2226633,96.2656714 L1.2226633,39.5681976 L1.2226633,39.3518572 C1.2226633,16.4127939 11.1796331,1.04797161 39.5335557,1.04797161 C43.4504989,1.04797161 47.2836822,1.40388649 51.0051854,2.07965952 L51.0051854,18.7925385 C48.3109055,18.3796307 45.4351455,18.1446804 42.3476589,18.1446804 C26.763646,18.1446804 19.8371595,26.1516022 19.8371595,39.5681976 L19.8371595,96.2656714 L51.0051854,96.2656714 L51.0051854,111.846834 L19.8371595,111.846834 L19.7766662,145.390067 L19.7766662,145.390067 Z\" id=\"path38\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <path d=\"M646.318899,128.021188 L664.933395,128.021188 L664.933395,236.223966 L646.318899,236.223966 L646.318899,128.021188 L646.318899,128.021188 Z\" id=\"path40\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+ <path d=\"M646.318899,251.154944 L664.933395,251.154944 L664.933395,269.766036 L646.318899,269.766036 L646.318899,251.154944 L646.318899,251.154944 Z\" id=\"path42\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+ <g id=\"g44\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(0.464170, 0.676006)\">\n+ <path d=\"M429.269989,169.815599 L405.225053,243.802859 L357.571431,390.440955 C355.120288,397.984955 344.444378,397.984955 341.992071,390.440955 L294.337286,243.802859 L136.094873,243.802859 L88.4389245,390.440955 C85.9877812,397.984955 75.3118715,397.984955 72.8595648,390.440955 L25.2059427,243.802859 L1.16216997,169.815599 C-1.03187664,163.067173 1.37156997,155.674379 7.11261982,151.503429 L215.215498,0.336141836 L423.319539,151.503429 C429.060589,155.674379 431.462873,163.067173 429.269989,169.815599\" id=\"path46\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g48\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(135.410135, 1.012147)\">\n+ <path d=\"M80.269998,0 L80.269998,0 L159.391786,243.466717 L1.14820997,243.466717 L80.269998,0 L80.269998,0 Z\" id=\"path50\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g52\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012147)\">\n+ <g id=\"path54\"></g>\n+ </g>\n+ <g id=\"g56\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(24.893471, 1.012613)\">\n+ <path d=\"M190.786662,0 L111.664874,243.465554 L0.777106647,243.465554 L190.786662,0 L190.786662,0 Z\" id=\"path58\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g60\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012613)\">\n+ <g id=\"path62\"></g>\n+ </g>\n+ <g id=\"g64\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(0.077245, 0.223203)\">\n+ <path d=\"M25.5933327,244.255313 L25.5933327,244.255313 L1.54839663,170.268052 C-0.644486651,163.519627 1.75779662,156.126833 7.50000981,151.957046 L215.602888,0.789758846 L25.5933327,244.255313 L25.5933327,244.255313 Z\" id=\"path66\" fill=\"#FCA326\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g68\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012147)\">\n+ <g id=\"path70\"></g>\n+ </g>\n+ <g id=\"g72\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(25.670578, 244.478283)\">\n+ <path d=\"M0,0 L110.887767,0 L63.2329818,146.638096 C60.7806751,154.183259 50.1047654,154.183259 47.6536221,146.638096 L0,0 L0,0 Z\" id=\"path74\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g76\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012613)\">\n+ <path d=\"M0,0 L79.121788,243.465554 L190.009555,243.465554 L0,0 L0,0 Z\" id=\"path78\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g80\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(214.902910, 0.223203)\">\n+ <path d=\"M190.786662,244.255313 L190.786662,244.255313 L214.831598,170.268052 C217.024481,163.519627 214.622198,156.126833 208.879985,151.957046 L0.777106647,0.789758846 L190.786662,244.255313 L190.786662,244.255313 Z\" id=\"path82\" fill=\"#FCA326\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g84\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(294.009575, 244.478283)\">\n+ <path d=\"M111.679997,0 L0.79222998,0 L48.4470155,146.638096 C50.8993221,154.183259 61.5752318,154.183259 64.0263751,146.638096 L111.679997,0 L111.679997,0 Z\" id=\"path86\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ </g>\n+ </g>\n+ </g>\n+ </g>\n+ </g>\n+ </g>\n+ </g>\n+</svg>\n\\ No newline at end of file\n",
- "new_path": "files/images/wm.svg",
- "old_path": "files/images/wm.svg",
- "a_mode": "0",
- "b_mode": "100644",
- "new_file": true,
- "renamed_file": false,
- "deleted_file": false,
- "too_large": false
- },
- {
- "merge_request_diff_id": 14,
- "relative_order": 7,
- "utf8_diff": "--- /dev/null\n+++ b/files/lfs/lfs_object.iso\n@@ -0,0 +1,4 @@\n+version https://git-lfs.github.com/spec/v1\n+oid sha256:91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897\n+size 1575078\n+\n",
- "new_path": "files/lfs/lfs_object.iso",
- "old_path": "files/lfs/lfs_object.iso",
- "a_mode": "0",
- "b_mode": "100644",
- "new_file": true,
- "renamed_file": false,
- "deleted_file": false,
- "too_large": false
- },
- {
- "merge_request_diff_id": 14,
- "relative_order": 8,
- "utf8_diff": "--- a/files/ruby/popen.rb\n+++ b/files/ruby/popen.rb\n@@ -6,12 +6,18 @@ module Popen\n \n def popen(cmd, path=nil)\n unless cmd.is_a?(Array)\n- raise \"System commands must be given as an array of strings\"\n+ raise RuntimeError, \"System commands must be given as an array of strings\"\n end\n \n path ||= Dir.pwd\n- vars = { \"PWD\" => path }\n- options = { chdir: path }\n+\n+ vars = {\n+ \"PWD\" => path\n+ }\n+\n+ options = {\n+ chdir: path\n+ }\n \n unless File.directory?(path)\n FileUtils.mkdir_p(path)\n@@ -19,6 +25,7 @@ module Popen\n \n @cmd_output = \"\"\n @cmd_status = 0\n+\n Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|\n @cmd_output << stdout.read\n @cmd_output << stderr.read\n",
- "new_path": "files/ruby/popen.rb",
- "old_path": "files/ruby/popen.rb",
- "a_mode": "100644",
- "b_mode": "100644",
- "new_file": false,
- "renamed_file": false,
- "deleted_file": false,
- "too_large": false
- },
- {
- "merge_request_diff_id": 14,
- "relative_order": 9,
- "utf8_diff": "--- a/files/ruby/regex.rb\n+++ b/files/ruby/regex.rb\n@@ -19,14 +19,12 @@ module Gitlab\n end\n \n def archive_formats_regex\n- #|zip|tar| tar.gz | tar.bz2 |\n- /(zip|tar|tar\\.gz|tgz|gz|tar\\.bz2|tbz|tbz2|tb2|bz2)/\n+ /(zip|tar|7z|tar\\.gz|tgz|gz|tar\\.bz2|tbz|tbz2|tb2|bz2)/\n end\n \n def git_reference_regex\n # Valid git ref regex, see:\n # https://www.kernel.org/pub/software/scm/git/docs/git-check-ref-format.html\n-\n %r{\n (?!\n (?# doesn't begins with)\n",
- "new_path": "files/ruby/regex.rb",
- "old_path": "files/ruby/regex.rb",
- "a_mode": "100644",
- "b_mode": "100644",
- "new_file": false,
- "renamed_file": false,
- "deleted_file": false,
- "too_large": false
- },
- {
- "merge_request_diff_id": 14,
- "relative_order": 10,
- "utf8_diff": "--- /dev/null\n+++ b/files/whitespace\n@@ -0,0 +1 @@\n+test \n",
- "new_path": "files/whitespace",
- "old_path": "files/whitespace",
- "a_mode": "0",
- "b_mode": "100644",
- "new_file": true,
- "renamed_file": false,
- "deleted_file": false,
- "too_large": false
- },
- {
- "merge_request_diff_id": 14,
- "relative_order": 11,
- "utf8_diff": "--- /dev/null\n+++ b/foo/bar/.gitkeep\n",
- "new_path": "foo/bar/.gitkeep",
- "old_path": "foo/bar/.gitkeep",
- "a_mode": "0",
- "b_mode": "100644",
- "new_file": true,
- "renamed_file": false,
- "deleted_file": false,
- "too_large": false
- },
- {
- "merge_request_diff_id": 14,
- "relative_order": 12,
- "utf8_diff": "--- /dev/null\n+++ b/gitlab-grack\n@@ -0,0 +1 @@\n+Subproject commit 645f6c4c82fd3f5e06f67134450a570b795e55a6\n",
- "new_path": "gitlab-grack",
- "old_path": "gitlab-grack",
- "a_mode": "0",
- "b_mode": "160000",
- "new_file": true,
- "renamed_file": false,
- "deleted_file": false,
- "too_large": false
- },
- {
- "merge_request_diff_id": 14,
- "relative_order": 13,
- "utf8_diff": "--- /dev/null\n+++ b/gitlab-shell\n@@ -0,0 +1 @@\n+Subproject commit 79bceae69cb5750d6567b223597999bfa91cb3b9\n",
- "new_path": "gitlab-shell",
- "old_path": "gitlab-shell",
- "a_mode": "0",
- "b_mode": "160000",
- "new_file": true,
- "renamed_file": false,
- "deleted_file": false,
- "too_large": false
- },
- {
- "merge_request_diff_id": 14,
- "relative_order": 14,
- "utf8_diff": "--- /dev/null\n+++ b/test\n",
- "new_path": "test",
- "old_path": "test",
- "a_mode": "0",
- "b_mode": "100644",
- "new_file": true,
- "renamed_file": false,
- "deleted_file": false,
- "too_large": false
- }
- ],
- "merge_request_id": 14,
- "created_at": "2016-06-14T15:02:24.770Z",
- "updated_at": "2016-06-14T15:02:25.007Z",
- "base_commit_sha": "ae73cb07c9eeaf35924a10f713b364d32b2dd34f",
- "real_size": "15"
- },
- "events": [
- {
- "id": 224,
- "target_type": "MergeRequest",
- "target_id": 14,
- "project_id": 36,
- "created_at": "2016-06-14T15:02:25.113Z",
- "updated_at": "2016-06-14T15:02:25.113Z",
- "action": 1,
- "author_id": 1
- },
- {
- "id": 174,
- "target_type": "MergeRequest",
- "target_id": 14,
- "project_id": 5,
- "created_at": "2016-06-14T15:02:25.113Z",
- "updated_at": "2016-06-14T15:02:25.113Z",
- "action": 1,
- "author_id": 20
- }
- ]
- },
- {
- "id": 13,
- "target_branch": "improve/awesome",
- "source_branch": "test-8",
- "source_project_id": 5,
- "author_id": 16,
- "assignee_id": 25,
- "title": "Voluptates consequatur eius nemo amet libero animi illum delectus tempore.",
- "created_at": "2016-06-14T15:02:24.415Z",
- "updated_at": "2016-06-14T15:02:59.958Z",
- "state": "opened",
- "merge_status": "unchecked",
- "target_project_id": 5,
- "iid": 5,
- "description": "Est eaque quasi qui qui. Similique voluptatem impedit iusto ratione reprehenderit. Itaque est illum ut nulla aut.",
- "position": 0,
- "updated_by_id": null,
- "merge_error": null,
- "merge_params": {
- "force_remove_source_branch": null
- },
- "merge_when_pipeline_succeeds": false,
- "merge_user_id": null,
- "merge_commit_sha": null,
- "notes": [
- {
- "id": 793,
- "note": "In illum maxime aperiam nulla est aspernatur.",
- "noteable_type": "MergeRequest",
- "author_id": 26,
- "created_at": "2016-06-14T15:02:59.782Z",
- "updated_at": "2016-06-14T15:02:59.782Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 13,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "User 4"
- },
- "events": [
- {
- "merge_request_diff_id": 14,
- "id": 529,
- "target_type": "Note",
- "target_id": 793,
- "project_id": 4,
- "created_at": "2016-07-07T14:35:12.128Z",
- "updated_at": "2016-07-07T14:35:12.128Z",
- "action": 6,
- "author_id": 1
- }
- ]
- },
- {
- "id": 794,
- "note": "Enim quia perferendis cum distinctio tenetur optio voluptas veniam.",
- "noteable_type": "MergeRequest",
- "author_id": 25,
- "created_at": "2016-06-14T15:02:59.807Z",
- "updated_at": "2016-06-14T15:02:59.807Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 13,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "User 3"
- },
- "events": []
- },
- {
- "id": 795,
- "note": "Dolor ad quia quis pariatur ducimus.",
- "noteable_type": "MergeRequest",
- "author_id": 22,
- "created_at": "2016-06-14T15:02:59.831Z",
- "updated_at": "2016-06-14T15:02:59.831Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 13,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "User 0"
- },
- "events": []
- },
- {
- "id": 796,
- "note": "Et a odio voluptate aut.",
- "noteable_type": "MergeRequest",
- "author_id": 20,
- "created_at": "2016-06-14T15:02:59.854Z",
- "updated_at": "2016-06-14T15:02:59.854Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 13,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Ottis Schuster II"
- },
- "events": []
- },
- {
- "id": 797,
- "note": "Quis nihil temporibus voluptatum modi minima a ut.",
- "noteable_type": "MergeRequest",
- "author_id": 16,
- "created_at": "2016-06-14T15:02:59.879Z",
- "updated_at": "2016-06-14T15:02:59.879Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 13,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Rhett Emmerich IV"
- },
- "events": []
- },
- {
- "id": 798,
- "note": "Ut alias consequatur in nostrum.",
- "noteable_type": "MergeRequest",
- "author_id": 15,
- "created_at": "2016-06-14T15:02:59.904Z",
- "updated_at": "2016-06-14T15:02:59.904Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 13,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Burdette Bernier"
- },
- "events": []
- },
- {
- "id": 799,
- "note": "Voluptatibus aperiam assumenda et neque sint libero.",
- "noteable_type": "MergeRequest",
- "author_id": 6,
- "created_at": "2016-06-14T15:02:59.926Z",
- "updated_at": "2016-06-14T15:02:59.926Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 13,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Ari Wintheiser"
- },
- "events": []
- },
- {
- "id": 800,
- "note": "Veritatis voluptatem dolor dolores magni quo ut ipsa fuga.",
- "noteable_type": "MergeRequest",
- "author_id": 1,
- "created_at": "2016-06-14T15:02:59.956Z",
- "updated_at": "2016-06-14T15:02:59.956Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 13,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Administrator"
- },
- "events": []
- }
- ],
- "merge_request_diff": {
- "id": 13,
- "state": "collected",
- "merge_request_diff_commits": [
- {
- "merge_request_diff_id": 13,
- "relative_order": 0,
- "sha": "0bfedc29d30280c7e8564e19f654584b459e5868",
- "message": "fixes #10\n",
- "authored_date": "2016-01-19T15:25:23.000+01:00",
- "author_name": "James Lopez",
- "author_email": "james@jameslopez.es",
- "committed_date": "2016-01-19T15:25:23.000+01:00",
- "committer_name": "James Lopez",
- "committer_email": "james@jameslopez.es"
- },
- {
- "merge_request_diff_id": 13,
- "relative_order": 1,
- "sha": "be93687618e4b132087f430a4d8fc3a609c9b77c",
- "message": "Merge branch 'master' into 'master'\r\n\r\nLFS object pointer.\r\n\r\n\r\n\r\nSee merge request !6",
- "authored_date": "2015-12-07T12:52:12.000+01:00",
- "author_name": "Marin Jankovski",
- "author_email": "marin@gitlab.com",
- "committed_date": "2015-12-07T12:52:12.000+01:00",
- "committer_name": "Marin Jankovski",
- "committer_email": "marin@gitlab.com"
- },
- {
- "merge_request_diff_id": 13,
- "relative_order": 2,
- "sha": "048721d90c449b244b7b4c53a9186b04330174ec",
- "message": "LFS object pointer.\n",
- "authored_date": "2015-12-07T11:54:28.000+01:00",
- "author_name": "Marin Jankovski",
- "author_email": "maxlazio@gmail.com",
- "committed_date": "2015-12-07T11:54:28.000+01:00",
- "committer_name": "Marin Jankovski",
- "committer_email": "maxlazio@gmail.com"
- },
- {
- "merge_request_diff_id": 13,
- "relative_order": 3,
- "sha": "5f923865dde3436854e9ceb9cdb7815618d4e849",
- "message": "GitLab currently doesn't support patches that involve a merge commit: add a commit here\n",
- "authored_date": "2015-11-13T16:27:12.000+01:00",
- "author_name": "Stan Hu",
- "author_email": "stanhu@gmail.com",
- "committed_date": "2015-11-13T16:27:12.000+01:00",
- "committer_name": "Stan Hu",
- "committer_email": "stanhu@gmail.com"
- },
- {
- "merge_request_diff_id": 13,
- "relative_order": 4,
- "sha": "d2d430676773caa88cdaf7c55944073b2fd5561a",
- "message": "Merge branch 'add-svg' into 'master'\r\n\r\nAdd GitLab SVG\r\n\r\nAdded to test preview of sanitized SVG images\r\n\r\nSee merge request !5",
- "authored_date": "2015-11-13T08:50:17.000+01:00",
- "author_name": "Stan Hu",
- "author_email": "stanhu@gmail.com",
- "committed_date": "2015-11-13T08:50:17.000+01:00",
- "committer_name": "Stan Hu",
- "committer_email": "stanhu@gmail.com"
- },
- {
- "merge_request_diff_id": 13,
- "relative_order": 5,
- "sha": "2ea1f3dec713d940208fb5ce4a38765ecb5d3f73",
- "message": "Add GitLab SVG\n",
- "authored_date": "2015-11-13T08:39:43.000+01:00",
- "author_name": "Stan Hu",
- "author_email": "stanhu@gmail.com",
- "committed_date": "2015-11-13T08:39:43.000+01:00",
- "committer_name": "Stan Hu",
- "committer_email": "stanhu@gmail.com"
- },
- {
- "merge_request_diff_id": 13,
- "relative_order": 6,
- "sha": "59e29889be61e6e0e5e223bfa9ac2721d31605b8",
- "message": "Merge branch 'whitespace' into 'master'\r\n\r\nadd whitespace test file\r\n\r\nSorry, I did a mistake.\r\nGit ignore empty files.\r\nSo I add a new whitespace test file.\r\n\r\nSee merge request !4",
- "authored_date": "2015-11-13T07:21:40.000+01:00",
- "author_name": "Stan Hu",
- "author_email": "stanhu@gmail.com",
- "committed_date": "2015-11-13T07:21:40.000+01:00",
- "committer_name": "Stan Hu",
- "committer_email": "stanhu@gmail.com"
- },
- {
- "merge_request_diff_id": 13,
- "relative_order": 7,
- "sha": "66eceea0db202bb39c4e445e8ca28689645366c5",
- "message": "add spaces in whitespace file\n",
- "authored_date": "2015-11-13T06:01:27.000+01:00",
- "author_name": "윤민식",
- "author_email": "minsik.yoon@samsung.com",
- "committed_date": "2015-11-13T06:01:27.000+01:00",
- "committer_name": "윤민식",
- "committer_email": "minsik.yoon@samsung.com"
- },
- {
- "merge_request_diff_id": 13,
- "relative_order": 8,
- "sha": "08f22f255f082689c0d7d39d19205085311542bc",
- "message": "remove empty file.(beacase git ignore empty file)\nadd whitespace test file.\n",
- "authored_date": "2015-11-13T06:00:16.000+01:00",
- "author_name": "윤민식",
- "author_email": "minsik.yoon@samsung.com",
- "committed_date": "2015-11-13T06:00:16.000+01:00",
- "committer_name": "윤민식",
- "committer_email": "minsik.yoon@samsung.com"
- },
- {
- "merge_request_diff_id": 13,
- "relative_order": 9,
- "sha": "19e2e9b4ef76b422ce1154af39a91323ccc57434",
- "message": "Merge branch 'whitespace' into 'master'\r\n\r\nadd spaces\r\n\r\nTo test this pull request.(https://github.com/gitlabhq/gitlabhq/pull/9757)\r\nJust add whitespaces.\r\n\r\nSee merge request !3",
- "authored_date": "2015-11-13T05:23:14.000+01:00",
- "author_name": "Stan Hu",
- "author_email": "stanhu@gmail.com",
- "committed_date": "2015-11-13T05:23:14.000+01:00",
- "committer_name": "Stan Hu",
- "committer_email": "stanhu@gmail.com"
- },
- {
- "merge_request_diff_id": 13,
- "relative_order": 10,
- "sha": "c642fe9b8b9f28f9225d7ea953fe14e74748d53b",
- "message": "add whitespace in empty\n",
- "authored_date": "2015-11-13T05:08:45.000+01:00",
- "author_name": "윤민식",
- "author_email": "minsik.yoon@samsung.com",
- "committed_date": "2015-11-13T05:08:45.000+01:00",
- "committer_name": "윤민식",
- "committer_email": "minsik.yoon@samsung.com"
- },
- {
- "merge_request_diff_id": 13,
- "relative_order": 11,
- "sha": "9a944d90955aaf45f6d0c88f30e27f8d2c41cec0",
- "message": "add empty file\n",
- "authored_date": "2015-11-13T05:08:04.000+01:00",
- "author_name": "윤민식",
- "author_email": "minsik.yoon@samsung.com",
- "committed_date": "2015-11-13T05:08:04.000+01:00",
- "committer_name": "윤민식",
- "committer_email": "minsik.yoon@samsung.com"
- },
- {
- "merge_request_diff_id": 13,
- "relative_order": 12,
- "sha": "c7fbe50c7c7419d9701eebe64b1fdacc3df5b9dd",
- "message": "Add ISO-8859 test file\n",
- "authored_date": "2015-08-25T17:53:12.000+02:00",
- "author_name": "Stan Hu",
- "author_email": "stanhu@packetzoom.com",
- "committed_date": "2015-08-25T17:53:12.000+02:00",
- "committer_name": "Stan Hu",
- "committer_email": "stanhu@packetzoom.com"
- },
- {
- "merge_request_diff_id": 13,
- "relative_order": 13,
- "sha": "e56497bb5f03a90a51293fc6d516788730953899",
- "message": "Merge branch 'tree_helper_spec' into 'master'\n\nAdd directory structure for tree_helper spec\n\nThis directory structure is needed for a testing the method flatten_tree(tree) in the TreeHelper module\n\nSee [merge request #275](https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/275#note_732774)\n\nSee merge request !2\n",
- "authored_date": "2015-01-10T22:23:29.000+01:00",
- "author_name": "Sytse Sijbrandij",
- "author_email": "sytse@gitlab.com",
- "committed_date": "2015-01-10T22:23:29.000+01:00",
- "committer_name": "Sytse Sijbrandij",
- "committer_email": "sytse@gitlab.com"
- },
- {
- "merge_request_diff_id": 13,
- "relative_order": 14,
- "sha": "4cd80ccab63c82b4bad16faa5193fbd2aa06df40",
- "message": "add directory structure for tree_helper spec\n",
- "authored_date": "2015-01-10T21:28:18.000+01:00",
- "author_name": "marmis85",
- "author_email": "marmis85@gmail.com",
- "committed_date": "2015-01-10T21:28:18.000+01:00",
- "committer_name": "marmis85",
- "committer_email": "marmis85@gmail.com"
- }
- ],
- "merge_request_diff_files": [
- {
- "merge_request_diff_id": 13,
- "relative_order": 0,
- "utf8_diff": "--- a/CHANGELOG\n+++ b/CHANGELOG\n@@ -1,4 +1,6 @@\n-v 6.7.0\n+v6.8.0\n+\n+v6.7.0\n - Add support for Gemnasium as a Project Service (Olivier Gonzalez)\n - Add edit file button to MergeRequest diff\n - Public groups (Jason Hollingsworth)\n",
- "new_path": "CHANGELOG",
- "old_path": "CHANGELOG",
- "a_mode": "100644",
- "b_mode": "100644",
- "new_file": false,
- "renamed_file": false,
- "deleted_file": false,
- "too_large": false
- },
- {
- "merge_request_diff_id": 13,
- "relative_order": 1,
- "utf8_diff": "--- /dev/null\n+++ b/encoding/iso8859.txt\n@@ -0,0 +1 @@\n+Äü\n",
- "new_path": "encoding/iso8859.txt",
- "old_path": "encoding/iso8859.txt",
- "a_mode": "0",
- "b_mode": "100644",
- "new_file": true,
- "renamed_file": false,
- "deleted_file": false,
- "too_large": false
- },
- {
- "merge_request_diff_id": 13,
- "relative_order": 2,
- "utf8_diff": "--- /dev/null\n+++ b/files/images/wm.svg\n@@ -0,0 +1,78 @@\n+<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n+<svg width=\"1300px\" height=\"680px\" viewBox=\"0 0 1300 680\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xmlns:sketch=\"http://www.bohemiancoding.com/sketch/ns\">\n+ <!-- Generator: Sketch 3.2.2 (9983) - http://www.bohemiancoding.com/sketch -->\n+ <title>wm</title>\n+ <desc>Created with Sketch.</desc>\n+ <defs>\n+ <path id=\"path-1\" d=\"M-69.8,1023.54607 L1675.19996,1023.54607 L1675.19996,0 L-69.8,0 L-69.8,1023.54607 L-69.8,1023.54607 Z\"></path>\n+ </defs>\n+ <g id=\"Page-1\" stroke=\"none\" stroke-width=\"1\" fill=\"none\" fill-rule=\"evenodd\" sketch:type=\"MSPage\">\n+ <path d=\"M1300,680 L0,680 L0,0 L1300,0 L1300,680 L1300,680 Z\" id=\"bg\" fill=\"#30353E\" sketch:type=\"MSShapeGroup\"></path>\n+ <g id=\"gitlab_logo\" sketch:type=\"MSLayerGroup\" transform=\"translate(-262.000000, -172.000000)\">\n+ <g id=\"g10\" transform=\"translate(872.500000, 512.354581) scale(1, -1) translate(-872.500000, -512.354581) translate(0.000000, 0.290751)\">\n+ <g id=\"g12\" transform=\"translate(1218.022652, 440.744871)\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\">\n+ <path d=\"M-50.0233338,141.900706 L-69.07059,141.900706 L-69.0100967,0.155858152 L8.04444805,0.155858152 L8.04444805,17.6840847 L-49.9628405,17.6840847 L-50.0233338,141.900706 L-50.0233338,141.900706 Z\" id=\"path14\"></path>\n+ </g>\n+ <g id=\"g16\">\n+ <g id=\"g18-Clipped\">\n+ <mask id=\"mask-2\" sketch:name=\"path22\" fill=\"white\">\n+ <use xlink:href=\"#path-1\"></use>\n+ </mask>\n+ <g id=\"path22\"></g>\n+ <g id=\"g18\" mask=\"url(#mask-2)\">\n+ <g transform=\"translate(382.736659, 312.879425)\">\n+ <g id=\"g24\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(852.718192, 124.992771)\">\n+ <path d=\"M63.9833317,27.9148929 C59.2218085,22.9379001 51.2134221,17.9597442 40.3909323,17.9597442 C25.8888194,17.9597442 20.0453962,25.1013043 20.0453962,34.4074318 C20.0453962,48.4730484 29.7848226,55.1819277 50.5642821,55.1819277 C54.4602853,55.1819277 60.7364685,54.7492469 63.9833317,54.1002256 L63.9833317,27.9148929 L63.9833317,27.9148929 Z M44.2869356,113.827628 C28.9053426,113.827628 14.7975996,108.376082 3.78897657,99.301416 L10.5211864,87.6422957 C18.3131929,92.1866076 27.8374026,96.7320827 41.4728323,96.7320827 C57.0568452,96.7320827 63.9833317,88.7239978 63.9833317,75.3074024 L63.9833317,68.3821827 C60.9528485,69.0312039 54.6766653,69.4650479 50.7806621,69.4650479 C17.4476729,69.4650479 0.565379986,57.7791759 0.565379986,33.3245665 C0.565379986,11.4683685 13.9844297,0.43151772 34.3299658,0.43151772 C48.0351955,0.43151772 61.1692285,6.70771614 65.7143717,16.8780421 L69.1776149,3.02876588 L82.5978279,3.02876588 L82.5978279,75.5237428 C82.5978279,98.462806 72.6408582,113.827628 44.2869356,113.827628 L44.2869356,113.827628 Z\" id=\"path26\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g28\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(959.546624, 124.857151)\">\n+ <path d=\"M37.2266657,17.4468081 C30.0837992,17.4468081 23.8064527,18.3121698 19.0449295,20.4767371 L19.0449295,79.2306079 L19.0449295,86.0464943 C25.538656,91.457331 33.5470425,95.3526217 43.7203922,95.3526217 C62.1173451,95.3526217 69.2602116,82.3687072 69.2602116,61.3767077 C69.2602116,31.5135879 57.7885819,17.4468081 37.2266657,17.4468081 M45.2315622,113.963713 C28.208506,113.963713 19.0449295,102.384849 19.0449295,102.384849 L19.0449295,120.67143 L18.9844362,144.908535 L10.3967097,144.908535 L0.371103324,144.908535 L0.431596656,6.62629771 C9.73826309,2.73100702 22.5081728,0.567602823 36.3611458,0.567602823 C71.8579349,0.567602823 88.9566078,23.2891625 88.9566078,62.4584098 C88.9566078,93.4043948 73.1527248,113.963713 45.2315622,113.963713\" id=\"path30\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g32\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(509.576747, 125.294950)\">\n+ <path d=\"M68.636665,129.10638 C85.5189579,129.10638 96.3414476,123.480366 103.484314,117.853189 L111.669527,132.029302 C100.513161,141.811145 85.5073245,147.06845 69.5021849,147.06845 C29.0274926,147.06845 0.673569983,122.3975 0.673569983,72.6252464 C0.673569983,20.4709215 31.2622559,0.12910638 66.2553217,0.12910638 C83.7879179,0.12910638 98.7227909,4.24073748 108.462217,8.35236859 L108.063194,64.0763105 L108.063194,70.6502677 L108.063194,81.6057001 L56.1168719,81.6057001 L56.1168719,64.0763105 L89.2323178,64.0763105 L89.6313411,21.7701271 C85.3025779,19.6055598 77.7269514,17.8748364 67.554765,17.8748364 C39.4172223,17.8748364 20.5863462,35.5717154 20.5863462,72.8415868 C20.5863462,110.711628 40.0663623,129.10638 68.636665,129.10638\" id=\"path34\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g36\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(692.388992, 124.376085)\">\n+ <path d=\"M19.7766662,145.390067 L1.16216997,145.390067 L1.2226633,121.585642 L1.2226633,111.846834 L1.2226633,106.170806 L1.2226633,96.2656714 L1.2226633,39.5681976 L1.2226633,39.3518572 C1.2226633,16.4127939 11.1796331,1.04797161 39.5335557,1.04797161 C43.4504989,1.04797161 47.2836822,1.40388649 51.0051854,2.07965952 L51.0051854,18.7925385 C48.3109055,18.3796307 45.4351455,18.1446804 42.3476589,18.1446804 C26.763646,18.1446804 19.8371595,26.1516022 19.8371595,39.5681976 L19.8371595,96.2656714 L51.0051854,96.2656714 L51.0051854,111.846834 L19.8371595,111.846834 L19.7766662,145.390067 L19.7766662,145.390067 Z\" id=\"path38\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <path d=\"M646.318899,128.021188 L664.933395,128.021188 L664.933395,236.223966 L646.318899,236.223966 L646.318899,128.021188 L646.318899,128.021188 Z\" id=\"path40\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+ <path d=\"M646.318899,251.154944 L664.933395,251.154944 L664.933395,269.766036 L646.318899,269.766036 L646.318899,251.154944 L646.318899,251.154944 Z\" id=\"path42\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+ <g id=\"g44\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(0.464170, 0.676006)\">\n+ <path d=\"M429.269989,169.815599 L405.225053,243.802859 L357.571431,390.440955 C355.120288,397.984955 344.444378,397.984955 341.992071,390.440955 L294.337286,243.802859 L136.094873,243.802859 L88.4389245,390.440955 C85.9877812,397.984955 75.3118715,397.984955 72.8595648,390.440955 L25.2059427,243.802859 L1.16216997,169.815599 C-1.03187664,163.067173 1.37156997,155.674379 7.11261982,151.503429 L215.215498,0.336141836 L423.319539,151.503429 C429.060589,155.674379 431.462873,163.067173 429.269989,169.815599\" id=\"path46\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g48\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(135.410135, 1.012147)\">\n+ <path d=\"M80.269998,0 L80.269998,0 L159.391786,243.466717 L1.14820997,243.466717 L80.269998,0 L80.269998,0 Z\" id=\"path50\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g52\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012147)\">\n+ <g id=\"path54\"></g>\n+ </g>\n+ <g id=\"g56\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(24.893471, 1.012613)\">\n+ <path d=\"M190.786662,0 L111.664874,243.465554 L0.777106647,243.465554 L190.786662,0 L190.786662,0 Z\" id=\"path58\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g60\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012613)\">\n+ <g id=\"path62\"></g>\n+ </g>\n+ <g id=\"g64\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(0.077245, 0.223203)\">\n+ <path d=\"M25.5933327,244.255313 L25.5933327,244.255313 L1.54839663,170.268052 C-0.644486651,163.519627 1.75779662,156.126833 7.50000981,151.957046 L215.602888,0.789758846 L25.5933327,244.255313 L25.5933327,244.255313 Z\" id=\"path66\" fill=\"#FCA326\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g68\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012147)\">\n+ <g id=\"path70\"></g>\n+ </g>\n+ <g id=\"g72\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(25.670578, 244.478283)\">\n+ <path d=\"M0,0 L110.887767,0 L63.2329818,146.638096 C60.7806751,154.183259 50.1047654,154.183259 47.6536221,146.638096 L0,0 L0,0 Z\" id=\"path74\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g76\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012613)\">\n+ <path d=\"M0,0 L79.121788,243.465554 L190.009555,243.465554 L0,0 L0,0 Z\" id=\"path78\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g80\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(214.902910, 0.223203)\">\n+ <path d=\"M190.786662,244.255313 L190.786662,244.255313 L214.831598,170.268052 C217.024481,163.519627 214.622198,156.126833 208.879985,151.957046 L0.777106647,0.789758846 L190.786662,244.255313 L190.786662,244.255313 Z\" id=\"path82\" fill=\"#FCA326\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g84\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(294.009575, 244.478283)\">\n+ <path d=\"M111.679997,0 L0.79222998,0 L48.4470155,146.638096 C50.8993221,154.183259 61.5752318,154.183259 64.0263751,146.638096 L111.679997,0 L111.679997,0 Z\" id=\"path86\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ </g>\n+ </g>\n+ </g>\n+ </g>\n+ </g>\n+ </g>\n+ </g>\n+</svg>\n\\ No newline at end of file\n",
- "new_path": "files/images/wm.svg",
- "old_path": "files/images/wm.svg",
- "a_mode": "0",
- "b_mode": "100644",
- "new_file": true,
- "renamed_file": false,
- "deleted_file": false,
- "too_large": false
- },
- {
- "merge_request_diff_id": 13,
- "relative_order": 3,
- "utf8_diff": "--- /dev/null\n+++ b/files/lfs/lfs_object.iso\n@@ -0,0 +1,4 @@\n+version https://git-lfs.github.com/spec/v1\n+oid sha256:91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897\n+size 1575078\n+\n",
- "new_path": "files/lfs/lfs_object.iso",
- "old_path": "files/lfs/lfs_object.iso",
- "a_mode": "0",
- "b_mode": "100644",
- "new_file": true,
- "renamed_file": false,
- "deleted_file": false,
- "too_large": false
- },
- {
- "merge_request_diff_id": 13,
- "relative_order": 4,
- "utf8_diff": "--- /dev/null\n+++ b/files/whitespace\n@@ -0,0 +1 @@\n+test \n",
- "new_path": "files/whitespace",
- "old_path": "files/whitespace",
- "a_mode": "0",
- "b_mode": "100644",
- "new_file": true,
- "renamed_file": false,
- "deleted_file": false,
- "too_large": false
- },
- {
- "merge_request_diff_id": 13,
- "relative_order": 5,
- "utf8_diff": "--- /dev/null\n+++ b/foo/bar/.gitkeep\n",
- "new_path": "foo/bar/.gitkeep",
- "old_path": "foo/bar/.gitkeep",
- "a_mode": "0",
- "b_mode": "100644",
- "new_file": true,
- "renamed_file": false,
- "deleted_file": false,
- "too_large": false
- },
- {
- "merge_request_diff_id": 13,
- "relative_order": 6,
- "utf8_diff": "--- /dev/null\n+++ b/test\n",
- "new_path": "test",
- "old_path": "test",
- "a_mode": "0",
- "b_mode": "100644",
- "new_file": true,
- "renamed_file": false,
- "deleted_file": false,
- "too_large": false
- }
- ],
- "merge_request_id": 13,
- "created_at": "2016-06-14T15:02:24.420Z",
- "updated_at": "2016-06-14T15:02:24.561Z",
- "base_commit_sha": "5937ac0a7beb003549fc5fd26fc247adbce4a52e",
- "real_size": "7"
- },
- "events": [
- {
- "id": 225,
- "target_type": "MergeRequest",
- "target_id": 13,
- "project_id": 36,
- "created_at": "2016-06-14T15:02:24.636Z",
- "updated_at": "2016-06-14T15:02:24.636Z",
- "action": 1,
- "author_id": 16
- },
- {
- "id": 173,
- "target_type": "MergeRequest",
- "target_id": 13,
- "project_id": 5,
- "created_at": "2016-06-14T15:02:24.636Z",
- "updated_at": "2016-06-14T15:02:24.636Z",
- "action": 1,
- "author_id": 16
- }
- ]
- },
- {
- "id": 12,
- "target_branch": "flatten-dirs",
- "source_branch": "test-2",
- "source_project_id": 5,
- "author_id": 1,
- "assignee_id": 22,
- "title": "In a rerum harum nihil accusamus aut quia nobis non.",
- "created_at": "2016-06-14T15:02:24.000Z",
- "updated_at": "2016-06-14T15:03:00.225Z",
- "state": "opened",
- "merge_status": "unchecked",
- "target_project_id": 5,
- "iid": 4,
- "description": "Nam magnam odit velit rerum. Sapiente dolore sunt saepe debitis. Culpa maiores ut ad dolores dolorem et.",
- "position": 0,
- "updated_by_id": null,
- "merge_error": null,
- "merge_params": {
- "force_remove_source_branch": null
- },
- "merge_when_pipeline_succeeds": false,
- "merge_user_id": null,
- "merge_commit_sha": null,
- "notes": [
- {
- "id": 801,
- "note": "Nihil dicta molestias expedita atque.",
- "noteable_type": "MergeRequest",
- "author_id": 26,
- "created_at": "2016-06-14T15:03:00.001Z",
- "updated_at": "2016-06-14T15:03:00.001Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 12,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "User 4"
- },
- "events": []
- },
- {
- "id": 802,
- "note": "Illum culpa voluptas enim accusantium deserunt.",
- "noteable_type": "MergeRequest",
- "author_id": 25,
- "created_at": "2016-06-14T15:03:00.034Z",
- "updated_at": "2016-06-14T15:03:00.034Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 12,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "User 3"
- },
- "events": []
- },
- {
- "id": 803,
- "note": "Dicta esse aliquam laboriosam unde alias.",
- "noteable_type": "MergeRequest",
- "author_id": 22,
- "created_at": "2016-06-14T15:03:00.065Z",
- "updated_at": "2016-06-14T15:03:00.065Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 12,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "User 0"
- },
- "events": []
- },
- {
- "id": 804,
- "note": "Dicta autem et sed molestiae ut quae.",
- "noteable_type": "MergeRequest",
- "author_id": 20,
- "created_at": "2016-06-14T15:03:00.097Z",
- "updated_at": "2016-06-14T15:03:00.097Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 12,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Ottis Schuster II"
- },
- "events": []
- },
- {
- "id": 805,
- "note": "Ut ut temporibus voluptas dolore quia velit.",
- "noteable_type": "MergeRequest",
- "author_id": 16,
- "created_at": "2016-06-14T15:03:00.129Z",
- "updated_at": "2016-06-14T15:03:00.129Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 12,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Rhett Emmerich IV"
- },
- "events": []
- },
- {
- "id": 806,
- "note": "Dolores similique sint pariatur error id quia fugit aut.",
- "noteable_type": "MergeRequest",
- "author_id": 15,
- "created_at": "2016-06-14T15:03:00.162Z",
- "updated_at": "2016-06-14T15:03:00.162Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 12,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Burdette Bernier"
- },
- "events": []
- },
- {
- "id": 807,
- "note": "Quisquam provident nihil aperiam voluptatem.",
- "noteable_type": "MergeRequest",
- "author_id": 6,
- "created_at": "2016-06-14T15:03:00.193Z",
- "updated_at": "2016-06-14T15:03:00.193Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 12,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Ari Wintheiser"
- },
- "events": []
- },
- {
- "id": 808,
- "note": "Similique quo vero expedita deserunt ipsam earum.",
- "noteable_type": "MergeRequest",
- "author_id": 1,
- "created_at": "2016-06-14T15:03:00.224Z",
- "updated_at": "2016-06-14T15:03:00.224Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 12,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Administrator"
- },
- "events": []
- }
- ],
- "merge_request_diff": {
- "id": 12,
- "state": "collected",
- "merge_request_diff_commits": [
- {
- "merge_request_diff_id": 12,
- "relative_order": 0,
- "sha": "97a0df9696e2aebf10c31b3016f40214e0e8f243",
- "message": "fixes #10\n",
- "authored_date": "2016-01-19T14:08:21.000+01:00",
- "author_name": "James Lopez",
- "author_email": "james@jameslopez.es",
- "committed_date": "2016-01-19T14:08:21.000+01:00",
- "committer_name": "James Lopez",
- "committer_email": "james@jameslopez.es"
- },
- {
- "merge_request_diff_id": 12,
- "relative_order": 1,
- "sha": "be93687618e4b132087f430a4d8fc3a609c9b77c",
- "message": "Merge branch 'master' into 'master'\r\n\r\nLFS object pointer.\r\n\r\n\r\n\r\nSee merge request !6",
- "authored_date": "2015-12-07T12:52:12.000+01:00",
- "author_name": "Marin Jankovski",
- "author_email": "marin@gitlab.com",
- "committed_date": "2015-12-07T12:52:12.000+01:00",
- "committer_name": "Marin Jankovski",
- "committer_email": "marin@gitlab.com"
- },
- {
- "merge_request_diff_id": 12,
- "relative_order": 2,
- "sha": "048721d90c449b244b7b4c53a9186b04330174ec",
- "message": "LFS object pointer.\n",
- "authored_date": "2015-12-07T11:54:28.000+01:00",
- "author_name": "Marin Jankovski",
- "author_email": "maxlazio@gmail.com",
- "committed_date": "2015-12-07T11:54:28.000+01:00",
- "committer_name": "Marin Jankovski",
- "committer_email": "maxlazio@gmail.com"
- },
- {
- "merge_request_diff_id": 12,
- "relative_order": 3,
- "sha": "5f923865dde3436854e9ceb9cdb7815618d4e849",
- "message": "GitLab currently doesn't support patches that involve a merge commit: add a commit here\n",
- "authored_date": "2015-11-13T16:27:12.000+01:00",
- "author_name": "Stan Hu",
- "author_email": "stanhu@gmail.com",
- "committed_date": "2015-11-13T16:27:12.000+01:00",
- "committer_name": "Stan Hu",
- "committer_email": "stanhu@gmail.com"
- },
- {
- "merge_request_diff_id": 12,
- "relative_order": 4,
- "sha": "d2d430676773caa88cdaf7c55944073b2fd5561a",
- "message": "Merge branch 'add-svg' into 'master'\r\n\r\nAdd GitLab SVG\r\n\r\nAdded to test preview of sanitized SVG images\r\n\r\nSee merge request !5",
- "authored_date": "2015-11-13T08:50:17.000+01:00",
- "author_name": "Stan Hu",
- "author_email": "stanhu@gmail.com",
- "committed_date": "2015-11-13T08:50:17.000+01:00",
- "committer_name": "Stan Hu",
- "committer_email": "stanhu@gmail.com"
- },
- {
- "merge_request_diff_id": 12,
- "relative_order": 5,
- "sha": "2ea1f3dec713d940208fb5ce4a38765ecb5d3f73",
- "message": "Add GitLab SVG\n",
- "authored_date": "2015-11-13T08:39:43.000+01:00",
- "author_name": "Stan Hu",
- "author_email": "stanhu@gmail.com",
- "committed_date": "2015-11-13T08:39:43.000+01:00",
- "committer_name": "Stan Hu",
- "committer_email": "stanhu@gmail.com"
- },
- {
- "merge_request_diff_id": 12,
- "relative_order": 6,
- "sha": "59e29889be61e6e0e5e223bfa9ac2721d31605b8",
- "message": "Merge branch 'whitespace' into 'master'\r\n\r\nadd whitespace test file\r\n\r\nSorry, I did a mistake.\r\nGit ignore empty files.\r\nSo I add a new whitespace test file.\r\n\r\nSee merge request !4",
- "authored_date": "2015-11-13T07:21:40.000+01:00",
- "author_name": "Stan Hu",
- "author_email": "stanhu@gmail.com",
- "committed_date": "2015-11-13T07:21:40.000+01:00",
- "committer_name": "Stan Hu",
- "committer_email": "stanhu@gmail.com"
- },
- {
- "merge_request_diff_id": 12,
- "relative_order": 7,
- "sha": "66eceea0db202bb39c4e445e8ca28689645366c5",
- "message": "add spaces in whitespace file\n",
- "authored_date": "2015-11-13T06:01:27.000+01:00",
- "author_name": "윤민식",
- "author_email": "minsik.yoon@samsung.com",
- "committed_date": "2015-11-13T06:01:27.000+01:00",
- "committer_name": "윤민식",
- "committer_email": "minsik.yoon@samsung.com"
- },
- {
- "merge_request_diff_id": 12,
- "relative_order": 8,
- "sha": "08f22f255f082689c0d7d39d19205085311542bc",
- "message": "remove empty file.(beacase git ignore empty file)\nadd whitespace test file.\n",
- "authored_date": "2015-11-13T06:00:16.000+01:00",
- "author_name": "윤민식",
- "author_email": "minsik.yoon@samsung.com",
- "committed_date": "2015-11-13T06:00:16.000+01:00",
- "committer_name": "윤민식",
- "committer_email": "minsik.yoon@samsung.com"
- },
- {
- "merge_request_diff_id": 12,
- "relative_order": 9,
- "sha": "19e2e9b4ef76b422ce1154af39a91323ccc57434",
- "message": "Merge branch 'whitespace' into 'master'\r\n\r\nadd spaces\r\n\r\nTo test this pull request.(https://github.com/gitlabhq/gitlabhq/pull/9757)\r\nJust add whitespaces.\r\n\r\nSee merge request !3",
- "authored_date": "2015-11-13T05:23:14.000+01:00",
- "author_name": "Stan Hu",
- "author_email": "stanhu@gmail.com",
- "committed_date": "2015-11-13T05:23:14.000+01:00",
- "committer_name": "Stan Hu",
- "committer_email": "stanhu@gmail.com"
- },
- {
- "merge_request_diff_id": 12,
- "relative_order": 10,
- "sha": "c642fe9b8b9f28f9225d7ea953fe14e74748d53b",
- "message": "add whitespace in empty\n",
- "authored_date": "2015-11-13T05:08:45.000+01:00",
- "author_name": "윤민식",
- "author_email": "minsik.yoon@samsung.com",
- "committed_date": "2015-11-13T05:08:45.000+01:00",
- "committer_name": "윤민식",
- "committer_email": "minsik.yoon@samsung.com"
- },
- {
- "merge_request_diff_id": 12,
- "relative_order": 11,
- "sha": "9a944d90955aaf45f6d0c88f30e27f8d2c41cec0",
- "message": "add empty file\n",
- "authored_date": "2015-11-13T05:08:04.000+01:00",
- "author_name": "윤민식",
- "author_email": "minsik.yoon@samsung.com",
- "committed_date": "2015-11-13T05:08:04.000+01:00",
- "committer_name": "윤민식",
- "committer_email": "minsik.yoon@samsung.com"
- },
- {
- "merge_request_diff_id": 12,
- "relative_order": 12,
- "sha": "c7fbe50c7c7419d9701eebe64b1fdacc3df5b9dd",
- "message": "Add ISO-8859 test file\n",
- "authored_date": "2015-08-25T17:53:12.000+02:00",
- "author_name": "Stan Hu",
- "author_email": "stanhu@packetzoom.com",
- "committed_date": "2015-08-25T17:53:12.000+02:00",
- "committer_name": "Stan Hu",
- "committer_email": "stanhu@packetzoom.com"
- }
- ],
- "merge_request_diff_files": [
- {
- "merge_request_diff_id": 12,
- "relative_order": 0,
- "utf8_diff": "--- a/CHANGELOG\n+++ b/CHANGELOG\n@@ -1,4 +1,6 @@\n-v 6.7.0\n+v6.8.0\n+\n+v6.7.0\n - Add support for Gemnasium as a Project Service (Olivier Gonzalez)\n - Add edit file button to MergeRequest diff\n - Public groups (Jason Hollingsworth)\n",
- "new_path": "CHANGELOG",
- "old_path": "CHANGELOG",
- "a_mode": "100644",
- "b_mode": "100644",
- "new_file": false,
- "renamed_file": false,
- "deleted_file": false,
- "too_large": false
- },
- {
- "merge_request_diff_id": 12,
- "relative_order": 1,
- "utf8_diff": "--- /dev/null\n+++ b/encoding/iso8859.txt\n@@ -0,0 +1 @@\n+Äü\n",
- "new_path": "encoding/iso8859.txt",
- "old_path": "encoding/iso8859.txt",
- "a_mode": "0",
- "b_mode": "100644",
- "new_file": true,
- "renamed_file": false,
- "deleted_file": false,
- "too_large": false
- },
- {
- "merge_request_diff_id": 12,
- "relative_order": 2,
- "utf8_diff": "--- /dev/null\n+++ b/files/images/wm.svg\n@@ -0,0 +1,78 @@\n+<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n+<svg width=\"1300px\" height=\"680px\" viewBox=\"0 0 1300 680\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xmlns:sketch=\"http://www.bohemiancoding.com/sketch/ns\">\n+ <!-- Generator: Sketch 3.2.2 (9983) - http://www.bohemiancoding.com/sketch -->\n+ <title>wm</title>\n+ <desc>Created with Sketch.</desc>\n+ <defs>\n+ <path id=\"path-1\" d=\"M-69.8,1023.54607 L1675.19996,1023.54607 L1675.19996,0 L-69.8,0 L-69.8,1023.54607 L-69.8,1023.54607 Z\"></path>\n+ </defs>\n+ <g id=\"Page-1\" stroke=\"none\" stroke-width=\"1\" fill=\"none\" fill-rule=\"evenodd\" sketch:type=\"MSPage\">\n+ <path d=\"M1300,680 L0,680 L0,0 L1300,0 L1300,680 L1300,680 Z\" id=\"bg\" fill=\"#30353E\" sketch:type=\"MSShapeGroup\"></path>\n+ <g id=\"gitlab_logo\" sketch:type=\"MSLayerGroup\" transform=\"translate(-262.000000, -172.000000)\">\n+ <g id=\"g10\" transform=\"translate(872.500000, 512.354581) scale(1, -1) translate(-872.500000, -512.354581) translate(0.000000, 0.290751)\">\n+ <g id=\"g12\" transform=\"translate(1218.022652, 440.744871)\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\">\n+ <path d=\"M-50.0233338,141.900706 L-69.07059,141.900706 L-69.0100967,0.155858152 L8.04444805,0.155858152 L8.04444805,17.6840847 L-49.9628405,17.6840847 L-50.0233338,141.900706 L-50.0233338,141.900706 Z\" id=\"path14\"></path>\n+ </g>\n+ <g id=\"g16\">\n+ <g id=\"g18-Clipped\">\n+ <mask id=\"mask-2\" sketch:name=\"path22\" fill=\"white\">\n+ <use xlink:href=\"#path-1\"></use>\n+ </mask>\n+ <g id=\"path22\"></g>\n+ <g id=\"g18\" mask=\"url(#mask-2)\">\n+ <g transform=\"translate(382.736659, 312.879425)\">\n+ <g id=\"g24\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(852.718192, 124.992771)\">\n+ <path d=\"M63.9833317,27.9148929 C59.2218085,22.9379001 51.2134221,17.9597442 40.3909323,17.9597442 C25.8888194,17.9597442 20.0453962,25.1013043 20.0453962,34.4074318 C20.0453962,48.4730484 29.7848226,55.1819277 50.5642821,55.1819277 C54.4602853,55.1819277 60.7364685,54.7492469 63.9833317,54.1002256 L63.9833317,27.9148929 L63.9833317,27.9148929 Z M44.2869356,113.827628 C28.9053426,113.827628 14.7975996,108.376082 3.78897657,99.301416 L10.5211864,87.6422957 C18.3131929,92.1866076 27.8374026,96.7320827 41.4728323,96.7320827 C57.0568452,96.7320827 63.9833317,88.7239978 63.9833317,75.3074024 L63.9833317,68.3821827 C60.9528485,69.0312039 54.6766653,69.4650479 50.7806621,69.4650479 C17.4476729,69.4650479 0.565379986,57.7791759 0.565379986,33.3245665 C0.565379986,11.4683685 13.9844297,0.43151772 34.3299658,0.43151772 C48.0351955,0.43151772 61.1692285,6.70771614 65.7143717,16.8780421 L69.1776149,3.02876588 L82.5978279,3.02876588 L82.5978279,75.5237428 C82.5978279,98.462806 72.6408582,113.827628 44.2869356,113.827628 L44.2869356,113.827628 Z\" id=\"path26\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g28\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(959.546624, 124.857151)\">\n+ <path d=\"M37.2266657,17.4468081 C30.0837992,17.4468081 23.8064527,18.3121698 19.0449295,20.4767371 L19.0449295,79.2306079 L19.0449295,86.0464943 C25.538656,91.457331 33.5470425,95.3526217 43.7203922,95.3526217 C62.1173451,95.3526217 69.2602116,82.3687072 69.2602116,61.3767077 C69.2602116,31.5135879 57.7885819,17.4468081 37.2266657,17.4468081 M45.2315622,113.963713 C28.208506,113.963713 19.0449295,102.384849 19.0449295,102.384849 L19.0449295,120.67143 L18.9844362,144.908535 L10.3967097,144.908535 L0.371103324,144.908535 L0.431596656,6.62629771 C9.73826309,2.73100702 22.5081728,0.567602823 36.3611458,0.567602823 C71.8579349,0.567602823 88.9566078,23.2891625 88.9566078,62.4584098 C88.9566078,93.4043948 73.1527248,113.963713 45.2315622,113.963713\" id=\"path30\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g32\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(509.576747, 125.294950)\">\n+ <path d=\"M68.636665,129.10638 C85.5189579,129.10638 96.3414476,123.480366 103.484314,117.853189 L111.669527,132.029302 C100.513161,141.811145 85.5073245,147.06845 69.5021849,147.06845 C29.0274926,147.06845 0.673569983,122.3975 0.673569983,72.6252464 C0.673569983,20.4709215 31.2622559,0.12910638 66.2553217,0.12910638 C83.7879179,0.12910638 98.7227909,4.24073748 108.462217,8.35236859 L108.063194,64.0763105 L108.063194,70.6502677 L108.063194,81.6057001 L56.1168719,81.6057001 L56.1168719,64.0763105 L89.2323178,64.0763105 L89.6313411,21.7701271 C85.3025779,19.6055598 77.7269514,17.8748364 67.554765,17.8748364 C39.4172223,17.8748364 20.5863462,35.5717154 20.5863462,72.8415868 C20.5863462,110.711628 40.0663623,129.10638 68.636665,129.10638\" id=\"path34\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g36\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(692.388992, 124.376085)\">\n+ <path d=\"M19.7766662,145.390067 L1.16216997,145.390067 L1.2226633,121.585642 L1.2226633,111.846834 L1.2226633,106.170806 L1.2226633,96.2656714 L1.2226633,39.5681976 L1.2226633,39.3518572 C1.2226633,16.4127939 11.1796331,1.04797161 39.5335557,1.04797161 C43.4504989,1.04797161 47.2836822,1.40388649 51.0051854,2.07965952 L51.0051854,18.7925385 C48.3109055,18.3796307 45.4351455,18.1446804 42.3476589,18.1446804 C26.763646,18.1446804 19.8371595,26.1516022 19.8371595,39.5681976 L19.8371595,96.2656714 L51.0051854,96.2656714 L51.0051854,111.846834 L19.8371595,111.846834 L19.7766662,145.390067 L19.7766662,145.390067 Z\" id=\"path38\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <path d=\"M646.318899,128.021188 L664.933395,128.021188 L664.933395,236.223966 L646.318899,236.223966 L646.318899,128.021188 L646.318899,128.021188 Z\" id=\"path40\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+ <path d=\"M646.318899,251.154944 L664.933395,251.154944 L664.933395,269.766036 L646.318899,269.766036 L646.318899,251.154944 L646.318899,251.154944 Z\" id=\"path42\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+ <g id=\"g44\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(0.464170, 0.676006)\">\n+ <path d=\"M429.269989,169.815599 L405.225053,243.802859 L357.571431,390.440955 C355.120288,397.984955 344.444378,397.984955 341.992071,390.440955 L294.337286,243.802859 L136.094873,243.802859 L88.4389245,390.440955 C85.9877812,397.984955 75.3118715,397.984955 72.8595648,390.440955 L25.2059427,243.802859 L1.16216997,169.815599 C-1.03187664,163.067173 1.37156997,155.674379 7.11261982,151.503429 L215.215498,0.336141836 L423.319539,151.503429 C429.060589,155.674379 431.462873,163.067173 429.269989,169.815599\" id=\"path46\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g48\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(135.410135, 1.012147)\">\n+ <path d=\"M80.269998,0 L80.269998,0 L159.391786,243.466717 L1.14820997,243.466717 L80.269998,0 L80.269998,0 Z\" id=\"path50\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g52\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012147)\">\n+ <g id=\"path54\"></g>\n+ </g>\n+ <g id=\"g56\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(24.893471, 1.012613)\">\n+ <path d=\"M190.786662,0 L111.664874,243.465554 L0.777106647,243.465554 L190.786662,0 L190.786662,0 Z\" id=\"path58\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g60\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012613)\">\n+ <g id=\"path62\"></g>\n+ </g>\n+ <g id=\"g64\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(0.077245, 0.223203)\">\n+ <path d=\"M25.5933327,244.255313 L25.5933327,244.255313 L1.54839663,170.268052 C-0.644486651,163.519627 1.75779662,156.126833 7.50000981,151.957046 L215.602888,0.789758846 L25.5933327,244.255313 L25.5933327,244.255313 Z\" id=\"path66\" fill=\"#FCA326\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g68\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012147)\">\n+ <g id=\"path70\"></g>\n+ </g>\n+ <g id=\"g72\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(25.670578, 244.478283)\">\n+ <path d=\"M0,0 L110.887767,0 L63.2329818,146.638096 C60.7806751,154.183259 50.1047654,154.183259 47.6536221,146.638096 L0,0 L0,0 Z\" id=\"path74\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g76\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012613)\">\n+ <path d=\"M0,0 L79.121788,243.465554 L190.009555,243.465554 L0,0 L0,0 Z\" id=\"path78\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g80\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(214.902910, 0.223203)\">\n+ <path d=\"M190.786662,244.255313 L190.786662,244.255313 L214.831598,170.268052 C217.024481,163.519627 214.622198,156.126833 208.879985,151.957046 L0.777106647,0.789758846 L190.786662,244.255313 L190.786662,244.255313 Z\" id=\"path82\" fill=\"#FCA326\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g84\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(294.009575, 244.478283)\">\n+ <path d=\"M111.679997,0 L0.79222998,0 L48.4470155,146.638096 C50.8993221,154.183259 61.5752318,154.183259 64.0263751,146.638096 L111.679997,0 L111.679997,0 Z\" id=\"path86\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ </g>\n+ </g>\n+ </g>\n+ </g>\n+ </g>\n+ </g>\n+ </g>\n+</svg>\n\\ No newline at end of file\n",
- "new_path": "files/images/wm.svg",
- "old_path": "files/images/wm.svg",
- "a_mode": "0",
- "b_mode": "100644",
- "new_file": true,
- "renamed_file": false,
- "deleted_file": false,
- "too_large": false
- },
- {
- "merge_request_diff_id": 12,
- "relative_order": 3,
- "utf8_diff": "--- /dev/null\n+++ b/files/lfs/lfs_object.iso\n@@ -0,0 +1,4 @@\n+version https://git-lfs.github.com/spec/v1\n+oid sha256:91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897\n+size 1575078\n+\n",
- "new_path": "files/lfs/lfs_object.iso",
- "old_path": "files/lfs/lfs_object.iso",
- "a_mode": "0",
- "b_mode": "100644",
- "new_file": true,
- "renamed_file": false,
- "deleted_file": false,
- "too_large": false
- },
- {
- "merge_request_diff_id": 12,
- "relative_order": 4,
- "utf8_diff": "--- /dev/null\n+++ b/files/whitespace\n@@ -0,0 +1 @@\n+test \n",
- "new_path": "files/whitespace",
- "old_path": "files/whitespace",
- "a_mode": "0",
- "b_mode": "100644",
- "new_file": true,
- "renamed_file": false,
- "deleted_file": false,
- "too_large": false
- },
- {
- "merge_request_diff_id": 12,
- "relative_order": 5,
- "utf8_diff": "--- /dev/null\n+++ b/test\n",
- "new_path": "test",
- "old_path": "test",
- "a_mode": "0",
- "b_mode": "100644",
- "new_file": true,
- "renamed_file": false,
- "deleted_file": false,
- "too_large": false
- }
- ],
- "merge_request_id": 12,
- "created_at": "2016-06-14T15:02:24.006Z",
- "updated_at": "2016-06-14T15:02:24.169Z",
- "base_commit_sha": "e56497bb5f03a90a51293fc6d516788730953899",
- "real_size": "6"
- },
- "events": [
- {
- "id": 226,
- "target_type": "MergeRequest",
- "target_id": 12,
- "project_id": 36,
- "created_at": "2016-06-14T15:02:24.253Z",
- "updated_at": "2016-06-14T15:02:24.253Z",
- "action": 1,
- "author_id": 1
- },
- {
- "id": 172,
- "target_type": "MergeRequest",
- "target_id": 12,
- "project_id": 5,
- "created_at": "2016-06-14T15:02:24.253Z",
- "updated_at": "2016-06-14T15:02:24.253Z",
- "action": 1,
- "author_id": 1
- }
- ]
- },
- {
- "id": 11,
- "target_branch": "test-15",
- "source_branch": "'test'",
- "source_project_id": 5,
- "author_id": 16,
- "assignee_id": 16,
- "title": "Corporis provident similique perspiciatis dolores eos animi.",
- "created_at": "2016-06-14T15:02:23.767Z",
- "updated_at": "2016-06-14T15:03:00.475Z",
- "state": "opened",
- "merge_status": "unchecked",
- "target_project_id": 5,
- "iid": 3,
- "description": "Libero nesciunt mollitia quis odit eos vero quasi. Iure voluptatem ut sint pariatur voluptates ut aut. Laborum possimus unde illum ipsum eum.",
- "position": 0,
- "updated_by_id": null,
- "merge_error": null,
- "merge_params": {
- "force_remove_source_branch": null
- },
- "merge_when_pipeline_succeeds": false,
- "merge_user_id": null,
- "merge_commit_sha": null,
- "notes": [
- {
- "id": 809,
- "note": "Omnis ratione laboriosam dolores qui.",
- "noteable_type": "MergeRequest",
- "author_id": 26,
- "created_at": "2016-06-14T15:03:00.260Z",
- "updated_at": "2016-06-14T15:03:00.260Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 11,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "User 4"
- },
- "events": []
- },
- {
- "id": 810,
- "note": "Voluptas voluptates pariatur dolores maxime est voluptas.",
- "noteable_type": "MergeRequest",
- "author_id": 25,
- "created_at": "2016-06-14T15:03:00.290Z",
- "updated_at": "2016-06-14T15:03:00.290Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 11,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "User 3"
- },
- "events": []
- },
- {
- "id": 811,
- "note": "Sit perspiciatis facilis ipsum consequatur.",
- "noteable_type": "MergeRequest",
- "author_id": 22,
- "created_at": "2016-06-14T15:03:00.323Z",
- "updated_at": "2016-06-14T15:03:00.323Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 11,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "User 0"
- },
- "events": []
- },
- {
- "id": 812,
- "note": "Ut neque aliquam nam et est.",
- "noteable_type": "MergeRequest",
- "author_id": 20,
- "created_at": "2016-06-14T15:03:00.349Z",
- "updated_at": "2016-06-14T15:03:00.349Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 11,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Ottis Schuster II"
- },
- "events": []
- },
- {
- "id": 813,
- "note": "Et debitis rerum minima sit aut dolorem.",
- "noteable_type": "MergeRequest",
- "author_id": 16,
- "created_at": "2016-06-14T15:03:00.374Z",
- "updated_at": "2016-06-14T15:03:00.374Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 11,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Rhett Emmerich IV"
- },
- "events": []
- },
- {
- "id": 814,
- "note": "Ea nisi earum fugit iste aperiam consequatur.",
- "noteable_type": "MergeRequest",
- "author_id": 15,
- "created_at": "2016-06-14T15:03:00.397Z",
- "updated_at": "2016-06-14T15:03:00.397Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 11,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Burdette Bernier"
- },
- "events": []
- },
- {
- "id": 815,
- "note": "Amet ratione consequatur laudantium rerum voluptas est nobis.",
- "noteable_type": "MergeRequest",
- "author_id": 6,
- "created_at": "2016-06-14T15:03:00.450Z",
- "updated_at": "2016-06-14T15:03:00.450Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 11,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Ari Wintheiser"
- },
- "events": []
- },
- {
- "id": 816,
- "note": "Ab ducimus cumque quia dolorem vitae sint beatae rerum.",
- "noteable_type": "MergeRequest",
- "author_id": 1,
- "created_at": "2016-06-14T15:03:00.474Z",
- "updated_at": "2016-06-14T15:03:00.474Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 11,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Administrator"
- },
- "events": []
- }
- ],
- "merge_request_diff": {
- "id": 11,
- "state": "empty",
- "merge_request_diff_commits": [],
- "merge_request_diff_files": [],
- "merge_request_id": 11,
- "created_at": "2016-06-14T15:02:23.772Z",
- "updated_at": "2016-06-14T15:02:23.833Z",
- "base_commit_sha": "e56497bb5f03a90a51293fc6d516788730953899",
- "real_size": null
- },
- "events": [
- {
- "id": 227,
- "target_type": "MergeRequest",
- "target_id": 11,
- "project_id": 36,
- "created_at": "2016-06-14T15:02:23.865Z",
- "updated_at": "2016-06-14T15:02:23.865Z",
- "action": 1,
- "author_id": 16
- },
- {
- "id": 171,
- "target_type": "MergeRequest",
- "target_id": 11,
- "project_id": 5,
- "created_at": "2016-06-14T15:02:23.865Z",
- "updated_at": "2016-06-14T15:02:23.865Z",
- "action": 1,
- "author_id": 16
- }
- ]
- },
- {
- "id": 10,
- "target_branch": "feature",
- "source_branch": "test-5",
- "source_project_id": 5,
- "author_id": 20,
- "assignee_id": 25,
- "title": "Eligendi reprehenderit doloribus quia et sit id.",
- "created_at": "2016-06-14T15:02:23.014Z",
- "updated_at": "2016-06-14T15:03:00.685Z",
- "state": "opened",
- "merge_status": "unchecked",
- "target_project_id": 5,
- "iid": 2,
- "description": "Ut dolor quia aliquid dolore et nisi. Est minus suscipit enim quaerat sapiente consequatur rerum. Eveniet provident consequatur dolor accusantium reiciendis.",
- "position": 0,
- "updated_by_id": null,
- "merge_error": null,
- "merge_params": {
- "force_remove_source_branch": null
- },
- "merge_when_pipeline_succeeds": false,
- "merge_user_id": null,
- "merge_commit_sha": null,
- "notes": [
- {
- "id": 817,
- "note": "Recusandae et voluptas enim qui et.",
- "noteable_type": "MergeRequest",
- "author_id": 26,
- "created_at": "2016-06-14T15:03:00.510Z",
- "updated_at": "2016-06-14T15:03:00.510Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 10,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "User 4"
- },
- "events": []
- },
- {
- "id": 818,
- "note": "Asperiores dolorem rerum ipsum totam.",
- "noteable_type": "MergeRequest",
- "author_id": 25,
- "created_at": "2016-06-14T15:03:00.538Z",
- "updated_at": "2016-06-14T15:03:00.538Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 10,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "User 3"
- },
- "events": []
- },
- {
- "id": 819,
- "note": "Qui quam et iure quasi provident cumque itaque sequi.",
- "noteable_type": "MergeRequest",
- "author_id": 22,
- "created_at": "2016-06-14T15:03:00.562Z",
- "updated_at": "2016-06-14T15:03:00.562Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 10,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "User 0"
- },
- "events": []
- },
- {
- "id": 820,
- "note": "Sint accusantium aliquid iste qui iusto minus vel.",
- "noteable_type": "MergeRequest",
- "author_id": 20,
- "created_at": "2016-06-14T15:03:00.585Z",
- "updated_at": "2016-06-14T15:03:00.585Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 10,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Ottis Schuster II"
- },
- "events": []
- },
- {
- "id": 821,
- "note": "Dolor corrupti dolorem blanditiis voluptas.",
- "noteable_type": "MergeRequest",
- "author_id": 16,
- "created_at": "2016-06-14T15:03:00.610Z",
- "updated_at": "2016-06-14T15:03:00.610Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 10,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Rhett Emmerich IV"
- },
- "events": []
- },
- {
- "id": 822,
- "note": "Est perferendis assumenda aliquam aliquid sit ipsum ullam aut.",
- "noteable_type": "MergeRequest",
- "author_id": 15,
- "created_at": "2016-06-14T15:03:00.635Z",
- "updated_at": "2016-06-14T15:03:00.635Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 10,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Burdette Bernier"
- },
- "events": []
- },
- {
- "id": 823,
- "note": "Hic neque reiciendis quaerat maiores.",
- "noteable_type": "MergeRequest",
- "author_id": 6,
- "created_at": "2016-06-14T15:03:00.659Z",
- "updated_at": "2016-06-14T15:03:00.659Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 10,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Ari Wintheiser"
- },
- "events": []
- },
- {
- "id": 824,
- "note": "Sequi architecto doloribus ut vel autem.",
- "noteable_type": "MergeRequest",
- "author_id": 1,
- "created_at": "2016-06-14T15:03:00.683Z",
- "updated_at": "2016-06-14T15:03:00.683Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 10,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Administrator"
- },
- "events": []
- }
- ],
- "merge_request_diff": {
- "id": 10,
- "state": "collected",
- "merge_request_diff_commits": [
- {
- "merge_request_diff_id": 10,
- "relative_order": 0,
- "sha": "f998ac87ac9244f15e9c15109a6f4e62a54b779d",
- "message": "fixes #10\n",
- "authored_date": "2016-01-19T14:43:23.000+01:00",
- "author_name": "James Lopez",
- "author_email": "james@jameslopez.es",
- "committed_date": "2016-01-19T14:43:23.000+01:00",
- "committer_name": "James Lopez",
- "committer_email": "james@jameslopez.es"
- },
- {
- "merge_request_diff_id": 10,
- "relative_order": 1,
- "sha": "be93687618e4b132087f430a4d8fc3a609c9b77c",
- "message": "Merge branch 'master' into 'master'\r\n\r\nLFS object pointer.\r\n\r\n\r\n\r\nSee merge request !6",
- "authored_date": "2015-12-07T12:52:12.000+01:00",
- "author_name": "Marin Jankovski",
- "author_email": "marin@gitlab.com",
- "committed_date": "2015-12-07T12:52:12.000+01:00",
- "committer_name": "Marin Jankovski",
- "committer_email": "marin@gitlab.com"
- },
- {
- "merge_request_diff_id": 10,
- "relative_order": 2,
- "sha": "048721d90c449b244b7b4c53a9186b04330174ec",
- "message": "LFS object pointer.\n",
- "authored_date": "2015-12-07T11:54:28.000+01:00",
- "author_name": "Marin Jankovski",
- "author_email": "maxlazio@gmail.com",
- "committed_date": "2015-12-07T11:54:28.000+01:00",
- "committer_name": "Marin Jankovski",
- "committer_email": "maxlazio@gmail.com"
- },
- {
- "merge_request_diff_id": 10,
- "relative_order": 3,
- "sha": "5f923865dde3436854e9ceb9cdb7815618d4e849",
- "message": "GitLab currently doesn't support patches that involve a merge commit: add a commit here\n",
- "authored_date": "2015-11-13T16:27:12.000+01:00",
- "author_name": "Stan Hu",
- "author_email": "stanhu@gmail.com",
- "committed_date": "2015-11-13T16:27:12.000+01:00",
- "committer_name": "Stan Hu",
- "committer_email": "stanhu@gmail.com"
- },
- {
- "merge_request_diff_id": 10,
- "relative_order": 4,
- "sha": "d2d430676773caa88cdaf7c55944073b2fd5561a",
- "message": "Merge branch 'add-svg' into 'master'\r\n\r\nAdd GitLab SVG\r\n\r\nAdded to test preview of sanitized SVG images\r\n\r\nSee merge request !5",
- "authored_date": "2015-11-13T08:50:17.000+01:00",
- "author_name": "Stan Hu",
- "author_email": "stanhu@gmail.com",
- "committed_date": "2015-11-13T08:50:17.000+01:00",
- "committer_name": "Stan Hu",
- "committer_email": "stanhu@gmail.com"
- },
- {
- "merge_request_diff_id": 10,
- "relative_order": 5,
- "sha": "2ea1f3dec713d940208fb5ce4a38765ecb5d3f73",
- "message": "Add GitLab SVG\n",
- "authored_date": "2015-11-13T08:39:43.000+01:00",
- "author_name": "Stan Hu",
- "author_email": "stanhu@gmail.com",
- "committed_date": "2015-11-13T08:39:43.000+01:00",
- "committer_name": "Stan Hu",
- "committer_email": "stanhu@gmail.com"
- },
- {
- "merge_request_diff_id": 10,
- "relative_order": 6,
- "sha": "59e29889be61e6e0e5e223bfa9ac2721d31605b8",
- "message": "Merge branch 'whitespace' into 'master'\r\n\r\nadd whitespace test file\r\n\r\nSorry, I did a mistake.\r\nGit ignore empty files.\r\nSo I add a new whitespace test file.\r\n\r\nSee merge request !4",
- "authored_date": "2015-11-13T07:21:40.000+01:00",
- "author_name": "Stan Hu",
- "author_email": "stanhu@gmail.com",
- "committed_date": "2015-11-13T07:21:40.000+01:00",
- "committer_name": "Stan Hu",
- "committer_email": "stanhu@gmail.com"
- },
- {
- "merge_request_diff_id": 10,
- "relative_order": 7,
- "sha": "66eceea0db202bb39c4e445e8ca28689645366c5",
- "message": "add spaces in whitespace file\n",
- "authored_date": "2015-11-13T06:01:27.000+01:00",
- "author_name": "윤민식",
- "author_email": "minsik.yoon@samsung.com",
- "committed_date": "2015-11-13T06:01:27.000+01:00",
- "committer_name": "윤민식",
- "committer_email": "minsik.yoon@samsung.com"
- },
- {
- "merge_request_diff_id": 10,
- "relative_order": 8,
- "sha": "08f22f255f082689c0d7d39d19205085311542bc",
- "message": "remove empty file.(beacase git ignore empty file)\nadd whitespace test file.\n",
- "authored_date": "2015-11-13T06:00:16.000+01:00",
- "author_name": "윤민식",
- "author_email": "minsik.yoon@samsung.com",
- "committed_date": "2015-11-13T06:00:16.000+01:00",
- "committer_name": "윤민식",
- "committer_email": "minsik.yoon@samsung.com"
- },
- {
- "merge_request_diff_id": 10,
- "relative_order": 9,
- "sha": "19e2e9b4ef76b422ce1154af39a91323ccc57434",
- "message": "Merge branch 'whitespace' into 'master'\r\n\r\nadd spaces\r\n\r\nTo test this pull request.(https://github.com/gitlabhq/gitlabhq/pull/9757)\r\nJust add whitespaces.\r\n\r\nSee merge request !3",
- "authored_date": "2015-11-13T05:23:14.000+01:00",
- "author_name": "Stan Hu",
- "author_email": "stanhu@gmail.com",
- "committed_date": "2015-11-13T05:23:14.000+01:00",
- "committer_name": "Stan Hu",
- "committer_email": "stanhu@gmail.com"
- },
- {
- "merge_request_diff_id": 10,
- "relative_order": 10,
- "sha": "c642fe9b8b9f28f9225d7ea953fe14e74748d53b",
- "message": "add whitespace in empty\n",
- "authored_date": "2015-11-13T05:08:45.000+01:00",
- "author_name": "윤민식",
- "author_email": "minsik.yoon@samsung.com",
- "committed_date": "2015-11-13T05:08:45.000+01:00",
- "committer_name": "윤민식",
- "committer_email": "minsik.yoon@samsung.com"
- },
- {
- "merge_request_diff_id": 10,
- "relative_order": 11,
- "sha": "9a944d90955aaf45f6d0c88f30e27f8d2c41cec0",
- "message": "add empty file\n",
- "authored_date": "2015-11-13T05:08:04.000+01:00",
- "author_name": "윤민식",
- "author_email": "minsik.yoon@samsung.com",
- "committed_date": "2015-11-13T05:08:04.000+01:00",
- "committer_name": "윤민식",
- "committer_email": "minsik.yoon@samsung.com"
- },
- {
- "merge_request_diff_id": 10,
- "relative_order": 12,
- "sha": "c7fbe50c7c7419d9701eebe64b1fdacc3df5b9dd",
- "message": "Add ISO-8859 test file\n",
- "authored_date": "2015-08-25T17:53:12.000+02:00",
- "author_name": "Stan Hu",
- "author_email": "stanhu@packetzoom.com",
- "committed_date": "2015-08-25T17:53:12.000+02:00",
- "committer_name": "Stan Hu",
- "committer_email": "stanhu@packetzoom.com"
- },
- {
- "merge_request_diff_id": 10,
- "relative_order": 13,
- "sha": "e56497bb5f03a90a51293fc6d516788730953899",
- "message": "Merge branch 'tree_helper_spec' into 'master'\n\nAdd directory structure for tree_helper spec\n\nThis directory structure is needed for a testing the method flatten_tree(tree) in the TreeHelper module\n\nSee [merge request #275](https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/275#note_732774)\n\nSee merge request !2\n",
- "authored_date": "2015-01-10T22:23:29.000+01:00",
- "author_name": "Sytse Sijbrandij",
- "author_email": "sytse@gitlab.com",
- "committed_date": "2015-01-10T22:23:29.000+01:00",
- "committer_name": "Sytse Sijbrandij",
- "committer_email": "sytse@gitlab.com"
- },
- {
- "merge_request_diff_id": 10,
- "relative_order": 14,
- "sha": "4cd80ccab63c82b4bad16faa5193fbd2aa06df40",
- "message": "add directory structure for tree_helper spec\n",
- "authored_date": "2015-01-10T21:28:18.000+01:00",
- "author_name": "marmis85",
- "author_email": "marmis85@gmail.com",
- "committed_date": "2015-01-10T21:28:18.000+01:00",
- "committer_name": "marmis85",
- "committer_email": "marmis85@gmail.com"
- },
- {
- "merge_request_diff_id": 10,
- "relative_order": 16,
- "sha": "5937ac0a7beb003549fc5fd26fc247adbce4a52e",
- "message": "Add submodule from gitlab.com\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
- "authored_date": "2014-02-27T10:01:38.000+01:00",
- "author_name": "Dmitriy Zaporozhets",
- "author_email": "dmitriy.zaporozhets@gmail.com",
- "committed_date": "2014-02-27T10:01:38.000+01:00",
- "committer_name": "Dmitriy Zaporozhets",
- "committer_email": "dmitriy.zaporozhets@gmail.com"
- },
- {
- "merge_request_diff_id": 10,
- "relative_order": 17,
- "sha": "570e7b2abdd848b95f2f578043fc23bd6f6fd24d",
- "message": "Change some files\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
- "authored_date": "2014-02-27T09:57:31.000+01:00",
- "author_name": "Dmitriy Zaporozhets",
- "author_email": "dmitriy.zaporozhets@gmail.com",
- "committed_date": "2014-02-27T09:57:31.000+01:00",
- "committer_name": "Dmitriy Zaporozhets",
- "committer_email": "dmitriy.zaporozhets@gmail.com"
- },
- {
- "merge_request_diff_id": 10,
- "relative_order": 18,
- "sha": "6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9",
- "message": "More submodules\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
- "authored_date": "2014-02-27T09:54:21.000+01:00",
- "author_name": "Dmitriy Zaporozhets",
- "author_email": "dmitriy.zaporozhets@gmail.com",
- "committed_date": "2014-02-27T09:54:21.000+01:00",
- "committer_name": "Dmitriy Zaporozhets",
- "committer_email": "dmitriy.zaporozhets@gmail.com"
- },
- {
- "merge_request_diff_id": 10,
- "relative_order": 19,
- "sha": "d14d6c0abdd253381df51a723d58691b2ee1ab08",
- "message": "Remove ds_store files\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
- "authored_date": "2014-02-27T09:49:50.000+01:00",
- "author_name": "Dmitriy Zaporozhets",
- "author_email": "dmitriy.zaporozhets@gmail.com",
- "committed_date": "2014-02-27T09:49:50.000+01:00",
- "committer_name": "Dmitriy Zaporozhets",
- "committer_email": "dmitriy.zaporozhets@gmail.com"
- },
- {
- "merge_request_diff_id": 10,
- "relative_order": 20,
- "sha": "c1acaa58bbcbc3eafe538cb8274ba387047b69f8",
- "message": "Ignore DS files\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
- "authored_date": "2014-02-27T09:48:32.000+01:00",
- "author_name": "Dmitriy Zaporozhets",
- "author_email": "dmitriy.zaporozhets@gmail.com",
- "committed_date": "2014-02-27T09:48:32.000+01:00",
- "committer_name": "Dmitriy Zaporozhets",
- "committer_email": "dmitriy.zaporozhets@gmail.com"
- }
- ],
- "merge_request_diff_files": [
- {
- "merge_request_diff_id": 10,
- "relative_order": 0,
- "utf8_diff": "Binary files a/.DS_Store and /dev/null differ\n",
- "new_path": ".DS_Store",
- "old_path": ".DS_Store",
- "a_mode": "100644",
- "b_mode": "0",
- "new_file": false,
- "renamed_file": false,
- "deleted_file": true,
- "too_large": false
- },
- {
- "merge_request_diff_id": 10,
- "relative_order": 1,
- "utf8_diff": "--- a/.gitignore\n+++ b/.gitignore\n@@ -17,3 +17,4 @@ rerun.txt\n pickle-email-*.html\n .project\n config/initializers/secret_token.rb\n+.DS_Store\n",
- "new_path": ".gitignore",
- "old_path": ".gitignore",
- "a_mode": "100644",
- "b_mode": "100644",
- "new_file": false,
- "renamed_file": false,
- "deleted_file": false,
- "too_large": false
- },
- {
- "merge_request_diff_id": 10,
- "relative_order": 2,
- "utf8_diff": "--- a/.gitmodules\n+++ b/.gitmodules\n@@ -1,3 +1,9 @@\n [submodule \"six\"]\n \tpath = six\n \turl = git://github.com/randx/six.git\n+[submodule \"gitlab-shell\"]\n+\tpath = gitlab-shell\n+\turl = https://github.com/gitlabhq/gitlab-shell.git\n+[submodule \"gitlab-grack\"]\n+\tpath = gitlab-grack\n+\turl = https://gitlab.com/gitlab-org/gitlab-grack.git\n",
- "new_path": ".gitmodules",
- "old_path": ".gitmodules",
- "a_mode": "100644",
- "b_mode": "100644",
- "new_file": false,
- "renamed_file": false,
- "deleted_file": false,
- "too_large": false
- },
- {
- "merge_request_diff_id": 10,
- "relative_order": 3,
- "utf8_diff": "--- a/CHANGELOG\n+++ b/CHANGELOG\n@@ -1,4 +1,6 @@\n-v 6.7.0\n+v6.8.0\n+\n+v6.7.0\n - Add support for Gemnasium as a Project Service (Olivier Gonzalez)\n - Add edit file button to MergeRequest diff\n - Public groups (Jason Hollingsworth)\n",
- "new_path": "CHANGELOG",
- "old_path": "CHANGELOG",
- "a_mode": "100644",
- "b_mode": "100644",
- "new_file": false,
- "renamed_file": false,
- "deleted_file": false,
- "too_large": false
- },
- {
- "merge_request_diff_id": 10,
- "relative_order": 4,
- "utf8_diff": "--- /dev/null\n+++ b/encoding/iso8859.txt\n@@ -0,0 +1 @@\n+Äü\n",
- "new_path": "encoding/iso8859.txt",
- "old_path": "encoding/iso8859.txt",
- "a_mode": "0",
- "b_mode": "100644",
- "new_file": true,
- "renamed_file": false,
- "deleted_file": false,
- "too_large": false
- },
- {
- "merge_request_diff_id": 10,
- "relative_order": 5,
- "utf8_diff": "Binary files a/files/.DS_Store and /dev/null differ\n",
- "new_path": "files/.DS_Store",
- "old_path": "files/.DS_Store",
- "a_mode": "100644",
- "b_mode": "0",
- "new_file": false,
- "renamed_file": false,
- "deleted_file": true,
- "too_large": false
- },
- {
- "merge_request_diff_id": 10,
- "relative_order": 6,
- "utf8_diff": "--- /dev/null\n+++ b/files/images/wm.svg\n@@ -0,0 +1,78 @@\n+<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n+<svg width=\"1300px\" height=\"680px\" viewBox=\"0 0 1300 680\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xmlns:sketch=\"http://www.bohemiancoding.com/sketch/ns\">\n+ <!-- Generator: Sketch 3.2.2 (9983) - http://www.bohemiancoding.com/sketch -->\n+ <title>wm</title>\n+ <desc>Created with Sketch.</desc>\n+ <defs>\n+ <path id=\"path-1\" d=\"M-69.8,1023.54607 L1675.19996,1023.54607 L1675.19996,0 L-69.8,0 L-69.8,1023.54607 L-69.8,1023.54607 Z\"></path>\n+ </defs>\n+ <g id=\"Page-1\" stroke=\"none\" stroke-width=\"1\" fill=\"none\" fill-rule=\"evenodd\" sketch:type=\"MSPage\">\n+ <path d=\"M1300,680 L0,680 L0,0 L1300,0 L1300,680 L1300,680 Z\" id=\"bg\" fill=\"#30353E\" sketch:type=\"MSShapeGroup\"></path>\n+ <g id=\"gitlab_logo\" sketch:type=\"MSLayerGroup\" transform=\"translate(-262.000000, -172.000000)\">\n+ <g id=\"g10\" transform=\"translate(872.500000, 512.354581) scale(1, -1) translate(-872.500000, -512.354581) translate(0.000000, 0.290751)\">\n+ <g id=\"g12\" transform=\"translate(1218.022652, 440.744871)\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\">\n+ <path d=\"M-50.0233338,141.900706 L-69.07059,141.900706 L-69.0100967,0.155858152 L8.04444805,0.155858152 L8.04444805,17.6840847 L-49.9628405,17.6840847 L-50.0233338,141.900706 L-50.0233338,141.900706 Z\" id=\"path14\"></path>\n+ </g>\n+ <g id=\"g16\">\n+ <g id=\"g18-Clipped\">\n+ <mask id=\"mask-2\" sketch:name=\"path22\" fill=\"white\">\n+ <use xlink:href=\"#path-1\"></use>\n+ </mask>\n+ <g id=\"path22\"></g>\n+ <g id=\"g18\" mask=\"url(#mask-2)\">\n+ <g transform=\"translate(382.736659, 312.879425)\">\n+ <g id=\"g24\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(852.718192, 124.992771)\">\n+ <path d=\"M63.9833317,27.9148929 C59.2218085,22.9379001 51.2134221,17.9597442 40.3909323,17.9597442 C25.8888194,17.9597442 20.0453962,25.1013043 20.0453962,34.4074318 C20.0453962,48.4730484 29.7848226,55.1819277 50.5642821,55.1819277 C54.4602853,55.1819277 60.7364685,54.7492469 63.9833317,54.1002256 L63.9833317,27.9148929 L63.9833317,27.9148929 Z M44.2869356,113.827628 C28.9053426,113.827628 14.7975996,108.376082 3.78897657,99.301416 L10.5211864,87.6422957 C18.3131929,92.1866076 27.8374026,96.7320827 41.4728323,96.7320827 C57.0568452,96.7320827 63.9833317,88.7239978 63.9833317,75.3074024 L63.9833317,68.3821827 C60.9528485,69.0312039 54.6766653,69.4650479 50.7806621,69.4650479 C17.4476729,69.4650479 0.565379986,57.7791759 0.565379986,33.3245665 C0.565379986,11.4683685 13.9844297,0.43151772 34.3299658,0.43151772 C48.0351955,0.43151772 61.1692285,6.70771614 65.7143717,16.8780421 L69.1776149,3.02876588 L82.5978279,3.02876588 L82.5978279,75.5237428 C82.5978279,98.462806 72.6408582,113.827628 44.2869356,113.827628 L44.2869356,113.827628 Z\" id=\"path26\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g28\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(959.546624, 124.857151)\">\n+ <path d=\"M37.2266657,17.4468081 C30.0837992,17.4468081 23.8064527,18.3121698 19.0449295,20.4767371 L19.0449295,79.2306079 L19.0449295,86.0464943 C25.538656,91.457331 33.5470425,95.3526217 43.7203922,95.3526217 C62.1173451,95.3526217 69.2602116,82.3687072 69.2602116,61.3767077 C69.2602116,31.5135879 57.7885819,17.4468081 37.2266657,17.4468081 M45.2315622,113.963713 C28.208506,113.963713 19.0449295,102.384849 19.0449295,102.384849 L19.0449295,120.67143 L18.9844362,144.908535 L10.3967097,144.908535 L0.371103324,144.908535 L0.431596656,6.62629771 C9.73826309,2.73100702 22.5081728,0.567602823 36.3611458,0.567602823 C71.8579349,0.567602823 88.9566078,23.2891625 88.9566078,62.4584098 C88.9566078,93.4043948 73.1527248,113.963713 45.2315622,113.963713\" id=\"path30\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g32\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(509.576747, 125.294950)\">\n+ <path d=\"M68.636665,129.10638 C85.5189579,129.10638 96.3414476,123.480366 103.484314,117.853189 L111.669527,132.029302 C100.513161,141.811145 85.5073245,147.06845 69.5021849,147.06845 C29.0274926,147.06845 0.673569983,122.3975 0.673569983,72.6252464 C0.673569983,20.4709215 31.2622559,0.12910638 66.2553217,0.12910638 C83.7879179,0.12910638 98.7227909,4.24073748 108.462217,8.35236859 L108.063194,64.0763105 L108.063194,70.6502677 L108.063194,81.6057001 L56.1168719,81.6057001 L56.1168719,64.0763105 L89.2323178,64.0763105 L89.6313411,21.7701271 C85.3025779,19.6055598 77.7269514,17.8748364 67.554765,17.8748364 C39.4172223,17.8748364 20.5863462,35.5717154 20.5863462,72.8415868 C20.5863462,110.711628 40.0663623,129.10638 68.636665,129.10638\" id=\"path34\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g36\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(692.388992, 124.376085)\">\n+ <path d=\"M19.7766662,145.390067 L1.16216997,145.390067 L1.2226633,121.585642 L1.2226633,111.846834 L1.2226633,106.170806 L1.2226633,96.2656714 L1.2226633,39.5681976 L1.2226633,39.3518572 C1.2226633,16.4127939 11.1796331,1.04797161 39.5335557,1.04797161 C43.4504989,1.04797161 47.2836822,1.40388649 51.0051854,2.07965952 L51.0051854,18.7925385 C48.3109055,18.3796307 45.4351455,18.1446804 42.3476589,18.1446804 C26.763646,18.1446804 19.8371595,26.1516022 19.8371595,39.5681976 L19.8371595,96.2656714 L51.0051854,96.2656714 L51.0051854,111.846834 L19.8371595,111.846834 L19.7766662,145.390067 L19.7766662,145.390067 Z\" id=\"path38\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <path d=\"M646.318899,128.021188 L664.933395,128.021188 L664.933395,236.223966 L646.318899,236.223966 L646.318899,128.021188 L646.318899,128.021188 Z\" id=\"path40\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+ <path d=\"M646.318899,251.154944 L664.933395,251.154944 L664.933395,269.766036 L646.318899,269.766036 L646.318899,251.154944 L646.318899,251.154944 Z\" id=\"path42\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+ <g id=\"g44\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(0.464170, 0.676006)\">\n+ <path d=\"M429.269989,169.815599 L405.225053,243.802859 L357.571431,390.440955 C355.120288,397.984955 344.444378,397.984955 341.992071,390.440955 L294.337286,243.802859 L136.094873,243.802859 L88.4389245,390.440955 C85.9877812,397.984955 75.3118715,397.984955 72.8595648,390.440955 L25.2059427,243.802859 L1.16216997,169.815599 C-1.03187664,163.067173 1.37156997,155.674379 7.11261982,151.503429 L215.215498,0.336141836 L423.319539,151.503429 C429.060589,155.674379 431.462873,163.067173 429.269989,169.815599\" id=\"path46\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g48\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(135.410135, 1.012147)\">\n+ <path d=\"M80.269998,0 L80.269998,0 L159.391786,243.466717 L1.14820997,243.466717 L80.269998,0 L80.269998,0 Z\" id=\"path50\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g52\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012147)\">\n+ <g id=\"path54\"></g>\n+ </g>\n+ <g id=\"g56\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(24.893471, 1.012613)\">\n+ <path d=\"M190.786662,0 L111.664874,243.465554 L0.777106647,243.465554 L190.786662,0 L190.786662,0 Z\" id=\"path58\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g60\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012613)\">\n+ <g id=\"path62\"></g>\n+ </g>\n+ <g id=\"g64\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(0.077245, 0.223203)\">\n+ <path d=\"M25.5933327,244.255313 L25.5933327,244.255313 L1.54839663,170.268052 C-0.644486651,163.519627 1.75779662,156.126833 7.50000981,151.957046 L215.602888,0.789758846 L25.5933327,244.255313 L25.5933327,244.255313 Z\" id=\"path66\" fill=\"#FCA326\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g68\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012147)\">\n+ <g id=\"path70\"></g>\n+ </g>\n+ <g id=\"g72\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(25.670578, 244.478283)\">\n+ <path d=\"M0,0 L110.887767,0 L63.2329818,146.638096 C60.7806751,154.183259 50.1047654,154.183259 47.6536221,146.638096 L0,0 L0,0 Z\" id=\"path74\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g76\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012613)\">\n+ <path d=\"M0,0 L79.121788,243.465554 L190.009555,243.465554 L0,0 L0,0 Z\" id=\"path78\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g80\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(214.902910, 0.223203)\">\n+ <path d=\"M190.786662,244.255313 L190.786662,244.255313 L214.831598,170.268052 C217.024481,163.519627 214.622198,156.126833 208.879985,151.957046 L0.777106647,0.789758846 L190.786662,244.255313 L190.786662,244.255313 Z\" id=\"path82\" fill=\"#FCA326\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g84\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(294.009575, 244.478283)\">\n+ <path d=\"M111.679997,0 L0.79222998,0 L48.4470155,146.638096 C50.8993221,154.183259 61.5752318,154.183259 64.0263751,146.638096 L111.679997,0 L111.679997,0 Z\" id=\"path86\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ </g>\n+ </g>\n+ </g>\n+ </g>\n+ </g>\n+ </g>\n+ </g>\n+</svg>\n\\ No newline at end of file\n",
- "new_path": "files/images/wm.svg",
- "old_path": "files/images/wm.svg",
- "a_mode": "0",
- "b_mode": "100644",
- "new_file": true,
- "renamed_file": false,
- "deleted_file": false,
- "too_large": false
- },
- {
- "merge_request_diff_id": 10,
- "relative_order": 7,
- "utf8_diff": "--- /dev/null\n+++ b/files/lfs/lfs_object.iso\n@@ -0,0 +1,4 @@\n+version https://git-lfs.github.com/spec/v1\n+oid sha256:91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897\n+size 1575078\n+\n",
- "new_path": "files/lfs/lfs_object.iso",
- "old_path": "files/lfs/lfs_object.iso",
- "a_mode": "0",
- "b_mode": "100644",
- "new_file": true,
- "renamed_file": false,
- "deleted_file": false,
- "too_large": false
- },
- {
- "merge_request_diff_id": 10,
- "relative_order": 8,
- "utf8_diff": "--- a/files/ruby/popen.rb\n+++ b/files/ruby/popen.rb\n@@ -6,12 +6,18 @@ module Popen\n \n def popen(cmd, path=nil)\n unless cmd.is_a?(Array)\n- raise \"System commands must be given as an array of strings\"\n+ raise RuntimeError, \"System commands must be given as an array of strings\"\n end\n \n path ||= Dir.pwd\n- vars = { \"PWD\" => path }\n- options = { chdir: path }\n+\n+ vars = {\n+ \"PWD\" => path\n+ }\n+\n+ options = {\n+ chdir: path\n+ }\n \n unless File.directory?(path)\n FileUtils.mkdir_p(path)\n@@ -19,6 +25,7 @@ module Popen\n \n @cmd_output = \"\"\n @cmd_status = 0\n+\n Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|\n @cmd_output << stdout.read\n @cmd_output << stderr.read\n",
- "new_path": "files/ruby/popen.rb",
- "old_path": "files/ruby/popen.rb",
- "a_mode": "100644",
- "b_mode": "100644",
- "new_file": false,
- "renamed_file": false,
- "deleted_file": false,
- "too_large": false
- },
- {
- "merge_request_diff_id": 10,
- "relative_order": 9,
- "utf8_diff": "--- a/files/ruby/regex.rb\n+++ b/files/ruby/regex.rb\n@@ -19,14 +19,12 @@ module Gitlab\n end\n \n def archive_formats_regex\n- #|zip|tar| tar.gz | tar.bz2 |\n- /(zip|tar|tar\\.gz|tgz|gz|tar\\.bz2|tbz|tbz2|tb2|bz2)/\n+ /(zip|tar|7z|tar\\.gz|tgz|gz|tar\\.bz2|tbz|tbz2|tb2|bz2)/\n end\n \n def git_reference_regex\n # Valid git ref regex, see:\n # https://www.kernel.org/pub/software/scm/git/docs/git-check-ref-format.html\n-\n %r{\n (?!\n (?# doesn't begins with)\n",
- "new_path": "files/ruby/regex.rb",
- "old_path": "files/ruby/regex.rb",
- "a_mode": "100644",
- "b_mode": "100644",
- "new_file": false,
- "renamed_file": false,
- "deleted_file": false,
- "too_large": false
- },
- {
- "merge_request_diff_id": 10,
- "relative_order": 10,
- "utf8_diff": "--- /dev/null\n+++ b/files/whitespace\n@@ -0,0 +1 @@\n+test \n",
- "new_path": "files/whitespace",
- "old_path": "files/whitespace",
- "a_mode": "0",
- "b_mode": "100644",
- "new_file": true,
- "renamed_file": false,
- "deleted_file": false,
- "too_large": false
- },
- {
- "merge_request_diff_id": 10,
- "relative_order": 11,
- "utf8_diff": "--- /dev/null\n+++ b/foo/bar/.gitkeep\n",
- "new_path": "foo/bar/.gitkeep",
- "old_path": "foo/bar/.gitkeep",
- "a_mode": "0",
- "b_mode": "100644",
- "new_file": true,
- "renamed_file": false,
- "deleted_file": false,
- "too_large": false
- },
- {
- "merge_request_diff_id": 10,
- "relative_order": 12,
- "utf8_diff": "--- /dev/null\n+++ b/gitlab-grack\n@@ -0,0 +1 @@\n+Subproject commit 645f6c4c82fd3f5e06f67134450a570b795e55a6\n",
- "new_path": "gitlab-grack",
- "old_path": "gitlab-grack",
- "a_mode": "0",
- "b_mode": "160000",
- "new_file": true,
- "renamed_file": false,
- "deleted_file": false,
- "too_large": false
- },
- {
- "merge_request_diff_id": 10,
- "relative_order": 13,
- "utf8_diff": "--- /dev/null\n+++ b/gitlab-shell\n@@ -0,0 +1 @@\n+Subproject commit 79bceae69cb5750d6567b223597999bfa91cb3b9\n",
- "new_path": "gitlab-shell",
- "old_path": "gitlab-shell",
- "a_mode": "0",
- "b_mode": "160000",
- "new_file": true,
- "renamed_file": false,
- "deleted_file": false,
- "too_large": false
- },
- {
- "merge_request_diff_id": 10,
- "relative_order": 14,
- "utf8_diff": "--- /dev/null\n+++ b/test\n",
- "new_path": "test",
- "old_path": "test",
- "a_mode": "0",
- "b_mode": "100644",
- "new_file": true,
- "renamed_file": false,
- "deleted_file": false,
- "too_large": false
- }
- ],
- "merge_request_id": 10,
- "created_at": "2016-06-14T15:02:23.019Z",
- "updated_at": "2016-06-14T15:02:23.493Z",
- "base_commit_sha": "ae73cb07c9eeaf35924a10f713b364d32b2dd34f",
- "real_size": "15"
- },
- "events": [
- {
- "id": 228,
- "target_type": "MergeRequest",
- "target_id": 10,
- "project_id": 36,
- "created_at": "2016-06-14T15:02:23.660Z",
- "updated_at": "2016-06-14T15:02:23.660Z",
- "action": 1,
- "author_id": 1
- },
- {
- "id": 170,
- "target_type": "MergeRequest",
- "target_id": 10,
- "project_id": 5,
- "created_at": "2016-06-14T15:02:23.660Z",
- "updated_at": "2016-06-14T15:02:23.660Z",
- "action": 1,
- "author_id": 20
- }
- ]
- },
- {
- "id": 9,
- "target_branch": "test-6",
- "source_branch": "test-12",
- "source_project_id": 5,
- "author_id": 16,
- "assignee_id": 6,
- "title": "Et ipsam voluptas velit sequi illum ut.",
- "created_at": "2016-06-14T15:02:22.825Z",
- "updated_at": "2016-06-14T15:03:00.904Z",
- "state": "opened",
- "merge_status": "unchecked",
- "target_project_id": 5,
- "iid": 1,
- "description": "Eveniet nihil ratione veniam similique qui aut sapiente tempora. Sed praesentium iusto dignissimos possimus id repudiandae quo nihil. Qui doloremque autem et iure fugit.",
- "position": 0,
- "updated_by_id": null,
- "merge_error": null,
- "merge_params": {
- "force_remove_source_branch": null
- },
- "merge_when_pipeline_succeeds": false,
- "merge_user_id": null,
- "merge_commit_sha": null,
- "notes": [
- {
- "id": 825,
- "note": "Aliquid voluptatem consequatur voluptas ex perspiciatis.",
- "noteable_type": "MergeRequest",
- "author_id": 26,
- "created_at": "2016-06-14T15:03:00.722Z",
- "updated_at": "2016-06-14T15:03:00.722Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 9,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "User 4"
- },
- "events": []
- },
- {
- "id": 826,
- "note": "Itaque optio voluptatem praesentium voluptas.",
- "noteable_type": "MergeRequest",
- "author_id": 25,
- "created_at": "2016-06-14T15:03:00.745Z",
- "updated_at": "2016-06-14T15:03:00.745Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 9,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "User 3"
- },
- "events": []
- },
- {
- "id": 827,
- "note": "Ut est corporis fuga asperiores delectus excepturi aperiam.",
- "noteable_type": "MergeRequest",
- "author_id": 22,
- "created_at": "2016-06-14T15:03:00.771Z",
- "updated_at": "2016-06-14T15:03:00.771Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 9,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "User 0"
- },
- "events": []
- },
- {
- "id": 828,
- "note": "Similique ea dolore officiis temporibus.",
- "noteable_type": "MergeRequest",
- "author_id": 20,
- "created_at": "2016-06-14T15:03:00.798Z",
- "updated_at": "2016-06-14T15:03:00.798Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 9,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Ottis Schuster II"
- },
- "events": []
- },
- {
- "id": 829,
- "note": "Qui laudantium qui quae quis.",
- "noteable_type": "MergeRequest",
- "author_id": 16,
- "created_at": "2016-06-14T15:03:00.828Z",
- "updated_at": "2016-06-14T15:03:00.828Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 9,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Rhett Emmerich IV"
- },
- "events": []
- },
- {
- "id": 830,
- "note": "Et vel voluptas amet laborum qui soluta.",
- "noteable_type": "MergeRequest",
- "author_id": 15,
- "created_at": "2016-06-14T15:03:00.850Z",
- "updated_at": "2016-06-14T15:03:00.850Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 9,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Burdette Bernier"
- },
- "events": []
- },
- {
- "id": 831,
- "note": "Enim ad consequuntur assumenda provident voluptatem similique deleniti.",
- "noteable_type": "MergeRequest",
- "author_id": 6,
- "created_at": "2016-06-14T15:03:00.876Z",
- "updated_at": "2016-06-14T15:03:00.876Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 9,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Ari Wintheiser"
- },
- "events": []
- },
- {
- "id": 832,
- "note": "Officiis sequi commodi pariatur totam fugiat voluptas corporis dignissimos.",
- "noteable_type": "MergeRequest",
- "author_id": 1,
- "created_at": "2016-06-14T15:03:00.902Z",
- "updated_at": "2016-06-14T15:03:00.902Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": null,
- "noteable_id": 9,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Administrator"
- },
- "events": []
- }
- ],
- "merge_request_diff": {
- "id": 9,
- "state": "collected",
- "merge_request_diff_commits": [
- {
- "merge_request_diff_id": 9,
- "relative_order": 0,
- "sha": "a4e5dfebf42e34596526acb8611bc7ed80e4eb3f",
- "message": "fixes #10\n",
- "authored_date": "2016-01-19T15:44:02.000+01:00",
- "author_name": "James Lopez",
- "author_email": "james@jameslopez.es",
- "committed_date": "2016-01-19T15:44:02.000+01:00",
- "committer_name": "James Lopez",
- "committer_email": "james@jameslopez.es"
- }
- ],
- "merge_request_diff_files": [
- {
- "merge_request_diff_id": 9,
- "relative_order": 0,
- "utf8_diff": "--- /dev/null\n+++ b/test\n",
- "new_path": "test",
- "old_path": "test",
- "a_mode": "0",
- "b_mode": "100644",
- "new_file": true,
- "renamed_file": false,
- "deleted_file": false,
- "too_large": false
- }
- ],
- "merge_request_id": 9,
- "created_at": "2016-06-14T15:02:22.829Z",
- "updated_at": "2016-06-14T15:02:22.900Z",
- "base_commit_sha": "be93687618e4b132087f430a4d8fc3a609c9b77c",
- "real_size": "1"
- },
- "events": [
- {
- "id": 229,
- "target_type": "MergeRequest",
- "target_id": 9,
- "project_id": 36,
- "created_at": "2016-06-14T15:02:22.927Z",
- "updated_at": "2016-06-14T15:02:22.927Z",
- "action": 1,
- "author_id": 16
- },
- {
- "id": 169,
- "target_type": "MergeRequest",
- "target_id": 9,
- "project_id": 5,
- "created_at": "2016-06-14T15:02:22.927Z",
- "updated_at": "2016-06-14T15:02:22.927Z",
- "action": 1,
- "author_id": 16
- }
- ]
- }
- ],
- "pipelines": [
- {
- "id": 36,
- "project_id": 5,
- "ref": "master",
- "sha": "sha-notes",
- "before_sha": null,
- "push_data": null,
- "created_at": "2016-03-22T15:20:35.755Z",
- "updated_at": "2016-03-22T15:20:35.755Z",
- "tag": null,
- "yaml_errors": null,
- "committed_at": null,
- "status": "failed",
- "started_at": null,
- "finished_at": null,
- "user_id": 9999,
- "duration": null,
- "notes": [
- {
- "id": 999,
- "note": "Natus rerum qui dolorem dolorum voluptas.",
- "noteable_type": "Commit",
- "author_id": 1,
- "created_at": "2016-03-22T15:19:59.469Z",
- "updated_at": "2016-03-22T15:19:59.469Z",
- "project_id": 5,
- "attachment": {
- "url": null
- },
- "line_code": null,
- "commit_id": "be93687618e4b132087f430a4d8fc3a609c9b77c",
- "noteable_id": 36,
- "system": false,
- "st_diff": null,
- "updated_by_id": null,
- "author": {
- "name": "Administrator"
- }
- }
- ],
- "stages": [
- {
- "id": 11,
- "project_id": 5,
- "pipeline_id": 36,
- "name": "test",
- "status": 1,
- "created_at": "2016-03-22T15:44:44.772Z",
- "updated_at": "2016-03-29T06:44:44.634Z",
- "statuses": [
- {
- "id": 71,
- "project_id": 5,
- "status": "failed",
- "finished_at": "2016-03-29T06:28:12.630Z",
- "trace": null,
- "created_at": "2016-03-22T15:20:35.772Z",
- "updated_at": "2016-03-29T06:28:12.634Z",
- "started_at": null,
- "runner_id": null,
- "coverage": null,
- "commit_id": 36,
- "commands": "$ build command",
- "job_id": null,
- "name": "test build 1",
- "deploy": false,
- "options": null,
- "allow_failure": false,
- "stage": "test",
- "trigger_request_id": null,
- "stage_idx": 1,
- "stage_id": 11,
- "tag": null,
- "ref": "master",
- "user_id": null,
- "target_url": null,
- "description": null,
- "erased_by_id": null,
- "erased_at": null,
- "type": "Ci::Build",
- "token": "abcd",
- "artifacts_file_store": 1,
- "artifacts_metadata_store": 1,
- "artifacts_size": 10
- },
- {
- "id": 72,
- "project_id": 5,
- "status": "success",
- "finished_at": null,
- "trace": "Porro ea qui ut dolores. Labore ab nemo explicabo aspernatur quis voluptates corporis. Et quasi delectus est sit aperiam perspiciatis asperiores. Repudiandae cum aut consectetur accusantium officia sunt.\n\nQuidem dolore iusto quaerat ut aut inventore et molestiae. Libero voluptates atque nemo qui. Nulla temporibus ipsa similique facere.\n\nAliquam ipsam perferendis qui fugit accusantium omnis id voluptatum. Dignissimos aliquid dicta eos voluptatem assumenda quia. Sed autem natus unde dolor et non nisi et. Consequuntur nihil consequatur rerum est.\n\nSimilique neque est iste ducimus qui fuga cupiditate. Libero autem est aut fuga. Consectetur natus quis non ducimus ut dolore. Magni voluptatibus eius et maxime aut.\n\nAd officiis tempore voluptate vitae corrupti explicabo labore est. Consequatur expedita et sunt nihil aut. Deleniti porro iusto molestiae et beatae.\n\nDeleniti modi nulla qui et labore sequi corrupti. Qui voluptatem assumenda eum cupiditate et. Nesciunt ipsam ut ea possimus eum. Consectetur quidem suscipit atque dolore itaque voluptatibus et cupiditate.",
- "created_at": "2016-03-22T15:20:35.777Z",
- "updated_at": "2016-03-22T15:20:35.777Z",
- "started_at": null,
- "runner_id": null,
- "coverage": null,
- "commit_id": 36,
- "commands": "$ deploy command",
- "job_id": null,
- "name": "test build 2",
- "deploy": false,
- "options": null,
- "allow_failure": false,
- "stage": "deploy",
- "trigger_request_id": null,
- "stage_idx": 1,
- "stage_id": 12,
- "tag": null,
- "ref": "master",
- "user_id": null,
- "target_url": null,
- "description": null,
- "erased_by_id": null,
- "erased_at": null
- }
- ]
- },
- {
- "id": 12,
- "project_id": 5,
- "pipeline_id": 36,
- "name": "deploy",
- "status": 2,
- "created_at": "2016-03-22T15:45:45.772Z",
- "updated_at": "2016-03-29T06:45:45.634Z"
- }
- ]
- },
- {
- "id": 37,
- "project_id": 5,
- "ref": null,
- "sha": "048721d90c449b244b7b4c53a9186b04330174ec",
- "before_sha": null,
- "push_data": null,
- "created_at": "2016-03-22T15:20:35.757Z",
- "updated_at": "2016-03-22T15:20:35.757Z",
- "tag": null,
- "yaml_errors": null,
- "committed_at": null,
- "status": "failed",
- "started_at": null,
- "finished_at": null,
- "duration": null,
- "stages": [
- {
- "id": 21,
- "project_id": 5,
- "pipeline_id": 37,
- "name": "test",
- "status": 1,
- "created_at": "2016-03-22T15:44:44.772Z",
- "updated_at": "2016-03-29T06:44:44.634Z",
- "statuses": [
- {
- "id": 74,
- "project_id": 5,
- "status": "success",
- "finished_at": null,
- "trace": "Ad ut quod repudiandae iste dolor doloribus. Adipisci consequuntur deserunt omnis quasi eveniet et sed fugit. Aut nemo omnis molestiae impedit ex consequatur ducimus. Voluptatum exercitationem quia aut est et hic dolorem.\n\nQuasi repellendus et eaque magni eum facilis. Dolorem aperiam nam nihil pariatur praesentium ad aliquam. Commodi enim et eos tenetur. Odio voluptatibus laboriosam mollitia rerum exercitationem magnam consequuntur. Tenetur ea vel eum corporis.\n\nVoluptatibus optio in aliquid est voluptates. Ad a ut ab placeat vero blanditiis. Earum aspernatur quia beatae expedita voluptatem dignissimos provident. Quis minima id nemo ut aut est veritatis provident.\n\nRerum voluptatem quidem eius maiores magnam veniam. Voluptatem aperiam aut voluptate et nulla deserunt voluptas. Quaerat aut accusantium laborum est dolorem architecto reiciendis. Aliquam asperiores doloribus omnis maxime enim nesciunt. Eum aut rerum repellendus debitis et ut eius.\n\nQuaerat assumenda ea sit consequatur autem in. Cum eligendi voluptatem quo sed. Ut fuga iusto cupiditate autem sint.\n\nOfficia totam officiis architecto corporis molestiae amet ut. Tempora sed dolorum rerum omnis voluptatem accusantium sit eum. Quia debitis ipsum quidem aliquam inventore sunt consequatur qui.",
- "created_at": "2016-03-22T15:20:35.846Z",
- "updated_at": "2016-03-22T15:20:35.846Z",
- "started_at": null,
- "runner_id": null,
- "coverage": null,
- "commit_id": 37,
- "commands": "$ build command",
- "job_id": null,
- "name": "test build 2",
- "deploy": false,
- "options": null,
- "allow_failure": false,
- "stage": "test",
- "trigger_request_id": null,
- "stage_idx": 1,
- "tag": null,
- "ref": "master",
- "user_id": null,
- "target_url": null,
- "description": null,
- "erased_by_id": null,
- "erased_at": null
- },
- {
- "id": 73,
- "project_id": 5,
- "status": "canceled",
- "finished_at": null,
- "trace": null,
- "created_at": "2016-03-22T15:20:35.842Z",
- "updated_at": "2016-03-22T15:20:35.842Z",
- "started_at": null,
- "runner_id": null,
- "coverage": null,
- "commit_id": 37,
- "commands": "$ build command",
- "job_id": null,
- "name": "test build 1",
- "deploy": false,
- "options": null,
- "allow_failure": false,
- "stage": "test",
- "trigger_request_id": null,
- "stage_idx": 1,
- "tag": null,
- "ref": "master",
- "user_id": null,
- "target_url": null,
- "description": null,
- "erased_by_id": null,
- "erased_at": null
- }
- ]
- }
- ]
- },
- {
- "id": 38,
- "iid": 1,
- "project_id": 5,
- "ref": "master",
- "sha": "5f923865dde3436854e9ceb9cdb7815618d4e849",
- "before_sha": null,
- "push_data": null,
- "created_at": "2016-03-22T15:20:35.759Z",
- "updated_at": "2016-03-22T15:20:35.759Z",
- "tag": null,
- "yaml_errors": null,
- "committed_at": null,
- "status": "failed",
- "started_at": null,
- "finished_at": null,
- "duration": null,
- "stages": [
- {
- "id": 22,
- "project_id": 5,
- "pipeline_id": 38,
- "name": "test",
- "status": 1,
- "created_at": "2016-03-22T15:44:44.772Z",
- "updated_at": "2016-03-29T06:44:44.634Z",
- "statuses": [
- {
- "id": 76,
- "project_id": 5,
- "status": "success",
- "finished_at": null,
- "trace": "Et rerum quia ea cumque ut modi non. Libero eaque ipsam architecto maiores expedita deleniti. Ratione quia qui est id.\n\nQuod sit officiis sed unde inventore veniam quisquam velit. Ea harum cum quibusdam quisquam minima quo possimus non. Temporibus itaque aliquam aut rerum veritatis at.\n\nMagnam ipsum eius recusandae qui quis sit maiores eum. Et animi iusto aut itaque. Doloribus harum deleniti nobis accusantium et libero.\n\nRerum fuga perferendis magni commodi officiis id repudiandae. Consequatur ratione consequatur suscipit facilis sunt iure est dicta. Qui unde quasi facilis et quae nesciunt. Magnam iste et nobis officiis tenetur. Aspernatur quo et temporibus non in.\n\nNisi rerum velit est ad enim sint molestiae consequuntur. Quaerat nisi nesciunt quasi officiis. Possimus non blanditiis laborum quos.\n\nRerum laudantium facere animi qui. Ipsa est iusto magnam nihil. Enim omnis occaecati non dignissimos ut recusandae eum quasi. Qui maxime dolor et nemo voluptates incidunt quia.",
- "created_at": "2016-03-22T15:20:35.882Z",
- "updated_at": "2016-03-22T15:20:35.882Z",
- "started_at": null,
- "runner_id": null,
- "coverage": null,
- "commit_id": 38,
- "commands": "$ build command",
- "job_id": null,
- "name": "test build 2",
- "deploy": false,
- "options": null,
- "allow_failure": false,
- "stage": "test",
- "trigger_request_id": null,
- "stage_idx": 1,
- "tag": null,
- "ref": "master",
- "user_id": null,
- "target_url": null,
- "description": null,
- "erased_by_id": null,
- "erased_at": null
- },
- {
- "id": 75,
- "project_id": 5,
- "status": "failed",
- "finished_at": null,
- "trace": "Sed et iste recusandae dicta corporis. Sunt alias porro fugit sunt. Fugiat omnis nihil dignissimos aperiam explicabo doloremque sit aut. Harum fugit expedita quia rerum ut consequatur laboriosam aliquam.\n\nNatus libero ut ut tenetur earum. Tempora omnis autem omnis et libero dolores illum autem. Deleniti eos sunt mollitia ipsam. Cum dolor repellendus dolorum sequi officia. Ullam sunt in aut pariatur excepturi.\n\nDolor nihil debitis et est eos. Cumque eos eum saepe ducimus autem. Alias architecto consequatur aut pariatur possimus. Aut quos aut incidunt quam velit et. Quas voluptatum ad dolorum dignissimos.\n\nUt voluptates consectetur illo et. Est commodi accusantium vel quo. Eos qui fugiat soluta porro.\n\nRatione possimus alias vel maxime sint totam est repellat. Ipsum corporis eos sint voluptatem eos odit. Temporibus libero nulla harum eligendi labore similique ratione magnam. Suscipit sequi in omnis neque.\n\nLaudantium dolor amet omnis placeat mollitia aut molestiae. Aut rerum similique ipsum quod illo quas unde. Sunt aut veritatis eos omnis porro. Rem veritatis mollitia praesentium dolorem. Consequatur sequi ad cumque earum omnis quia necessitatibus.",
- "created_at": "2016-03-22T15:20:35.864Z",
- "updated_at": "2016-03-22T15:20:35.864Z",
- "started_at": null,
- "runner_id": null,
- "coverage": null,
- "commit_id": 38,
- "commands": "$ build command",
- "job_id": null,
- "name": "test build 1",
- "deploy": false,
- "options": null,
- "allow_failure": false,
- "stage": "test",
- "trigger_request_id": null,
- "stage_idx": 1,
- "tag": null,
- "ref": "master",
- "user_id": null,
- "target_url": null,
- "description": null,
- "erased_by_id": null,
- "erased_at": null
- }
- ]
- }
- ]
- },
- {
- "id": 39,
- "project_id": 5,
- "ref": "master",
- "sha": "d2d430676773caa88cdaf7c55944073b2fd5561a",
- "before_sha": null,
- "push_data": null,
- "created_at": "2016-03-22T15:20:35.761Z",
- "updated_at": "2016-03-22T15:20:35.761Z",
- "tag": null,
- "yaml_errors": null,
- "committed_at": null,
- "status": "failed",
- "started_at": null,
- "finished_at": null,
- "duration": null,
- "stages": [
- {
- "id": 23,
- "project_id": 5,
- "pipeline_id": 39,
- "name": "test",
- "status": 1,
- "created_at": "2016-03-22T15:44:44.772Z",
- "updated_at": "2016-03-29T06:44:44.634Z",
- "statuses": [
- {
- "id": 78,
- "project_id": 5,
- "status": "success",
- "finished_at": null,
- "trace": "Dolorem deserunt quas quia error hic quo cum vel. Natus voluptatem cumque expedita numquam odit. Eos expedita nostrum corporis consequatur est recusandae.\n\nCulpa blanditiis rerum repudiandae alias voluptatem. Velit iusto est ullam consequatur doloribus porro. Corporis voluptas consectetur est veniam et quia quae.\n\nEt aut magni fuga nesciunt officiis molestias. Quaerat et nam necessitatibus qui rerum. Architecto quia officiis voluptatem laborum est recusandae. Quasi ducimus soluta odit necessitatibus labore numquam dignissimos. Quia facere sint temporibus inventore sunt nihil saepe dolorum.\n\nFacere dolores quis dolores a. Est minus nostrum nihil harum. Earum laborum et ipsum unde neque sit nemo. Corrupti est consequatur minima fugit. Illum voluptatem illo error ducimus officia qui debitis.\n\nDignissimos porro a autem harum aut. Aut id reprehenderit et exercitationem. Est et quisquam ipsa temporibus molestiae. Architecto natus dolore qui fugiat incidunt. Autem odit veniam excepturi et voluptatibus culpa ipsum eos.\n\nAmet quo quisquam dignissimos soluta modi dolores. Sint omnis eius optio corporis dolor. Eligendi animi porro quia placeat ut.",
- "created_at": "2016-03-22T15:20:35.927Z",
- "updated_at": "2016-03-22T15:20:35.927Z",
- "started_at": null,
- "runner_id": null,
- "coverage": null,
- "commit_id": 39,
- "commands": "$ build command",
- "job_id": null,
- "name": "test build 2",
- "deploy": false,
- "options": null,
- "allow_failure": false,
- "stage": "test",
- "trigger_request_id": null,
- "stage_idx": 1,
- "tag": null,
- "ref": "master",
- "user_id": null,
- "target_url": null,
- "description": null,
- "erased_by_id": null,
- "erased_at": null
- },
- {
- "id": 77,
- "project_id": 5,
- "status": "failed",
- "finished_at": null,
- "trace": "Rerum ut et suscipit est perspiciatis. Inventore debitis cum eius vitae. Ex incidunt id velit aut quo nisi. Laboriosam repellat deserunt eius reiciendis architecto et. Est harum quos nesciunt nisi consectetur.\n\nAlias esse omnis sint officia est consequatur in nobis. Dignissimos dolorum vel eligendi nesciunt dolores sit. Veniam mollitia ducimus et exercitationem molestiae libero sed. Atque omnis debitis laudantium voluptatibus qui. Repellendus tempore est commodi pariatur.\n\nExpedita voluptate illum est alias non. Modi nesciunt ab assumenda laborum nulla consequatur molestias doloremque. Magnam quod officia vel explicabo accusamus ut voluptatem incidunt. Rerum ut aliquid ullam saepe. Est eligendi debitis beatae blanditiis reiciendis.\n\nQui fuga sit dolores libero maiores et suscipit. Consectetur asperiores omnis minima impedit eos fugiat. Similique omnis nisi sed vero inventore ipsum aliquam exercitationem.\n\nBlanditiis magni iure dolorum omnis ratione delectus molestiae. Atque officia dolor voluptatem culpa quod. Incidunt suscipit quidem possimus veritatis non vel. Iusto aliquid et id quia quasi.\n\nVel facere velit blanditiis incidunt cupiditate sed maiores consequuntur. Quasi quia dicta consequuntur et quia voluptatem iste id. Incidunt et rerum fuga esse sint.",
- "created_at": "2016-03-22T15:20:35.905Z",
- "updated_at": "2016-03-22T15:20:35.905Z",
- "started_at": null,
- "runner_id": null,
- "coverage": null,
- "commit_id": 39,
- "commands": "$ build command",
- "job_id": null,
- "name": "test build 1",
- "deploy": false,
- "options": null,
- "allow_failure": false,
- "stage": "test",
- "trigger_request_id": null,
- "stage_idx": 1,
- "tag": null,
- "ref": "master",
- "user_id": null,
- "target_url": null,
- "description": null,
- "erased_by_id": null,
- "erased_at": null
- }
- ]
- }
- ]
- },
- {
- "id": 40,
- "project_id": 5,
- "ref": "master",
- "sha": "2ea1f3dec713d940208fb5ce4a38765ecb5d3f73",
- "before_sha": null,
- "push_data": null,
- "created_at": "2016-03-22T15:20:35.763Z",
- "updated_at": "2016-03-22T15:20:35.763Z",
- "tag": null,
- "yaml_errors": null,
- "committed_at": null,
- "status": "failed",
- "started_at": null,
- "finished_at": null,
- "duration": null,
- "stages": [
- {
- "id": 24,
- "project_id": 5,
- "pipeline_id": 40,
- "name": "test",
- "status": 1,
- "created_at": "2016-03-22T15:44:44.772Z",
- "updated_at": "2016-03-29T06:44:44.634Z",
- "statuses": [
- {
- "id": 79,
- "project_id": 5,
- "status": "failed",
- "finished_at": "2016-03-29T06:28:12.695Z",
- "trace": "Sed culpa est et facere saepe vel id ab. Quas temporibus aut similique dolorem consequatur corporis aut praesentium. Cum officia molestiae sit earum excepturi.\n\nSint possimus aut ratione quia. Quis nesciunt ratione itaque illo. Tenetur est dolor assumenda possimus voluptatem quia minima. Accusamus reprehenderit ut et itaque non reiciendis incidunt.\n\nRerum suscipit quibusdam dolore nam omnis. Consequatur ipsa nihil ut enim blanditiis delectus. Nulla quis hic occaecati mollitia qui placeat. Quo rerum sed perferendis a accusantium consequatur commodi ut. Sit quae et cumque vel eius tempora nostrum.\n\nUllam dolorem et itaque sint est. Ea molestias quia provident dolorem vitae error et et. Ea expedita officiis iste non. Qui vitae odit saepe illum. Dolores enim ratione deserunt tempore expedita amet non neque.\n\nEligendi asperiores voluptatibus omnis repudiandae expedita distinctio qui aliquid. Autem aut doloremque distinctio ab. Nostrum sapiente repudiandae aspernatur ea et quae voluptas. Officiis perspiciatis nisi laudantium asperiores error eligendi ab. Eius quia amet magni omnis exercitationem voluptatum et.\n\nVoluptatem ullam labore quas dicta est ex voluptas. Pariatur ea modi voluptas consequatur dolores perspiciatis similique. Numquam in distinctio perspiciatis ut qui earum. Quidem omnis mollitia facere aut beatae. Ea est iure et voluptatem.",
- "created_at": "2016-03-22T15:20:35.950Z",
- "updated_at": "2016-03-29T06:28:12.696Z",
- "started_at": null,
- "runner_id": null,
- "coverage": null,
- "commit_id": 40,
- "commands": "$ build command",
- "job_id": null,
- "name": "test build 1",
- "deploy": false,
- "options": null,
- "allow_failure": false,
- "stage": "test",
- "trigger_request_id": null,
- "stage_idx": 1,
- "tag": null,
- "ref": "master",
- "user_id": null,
- "target_url": null,
- "description": null,
- "erased_by_id": null,
- "erased_at": null
- },
- {
- "id": 80,
- "project_id": 5,
- "status": "success",
- "finished_at": null,
- "trace": "Impedit et optio nemo ipsa. Non ad non quis ut sequi laudantium omnis velit. Corporis a enim illo eos. Quia totam tempore inventore ad est.\n\nNihil recusandae cupiditate eaque voluptatem molestias sint. Consequatur id voluptatem cupiditate harum. Consequuntur iusto quaerat reiciendis aut autem libero est. Quisquam dolores veritatis rerum et sint maxime ullam libero. Id quas porro ut perspiciatis rem amet vitae.\n\nNemo inventore minus blanditiis magnam. Modi consequuntur nostrum aut voluptatem ex. Sunt rerum rem optio mollitia qui aliquam officiis officia. Aliquid eos et id aut minus beatae reiciendis.\n\nDolores non in temporibus dicta. Fugiat voluptatem est aspernatur expedita voluptatum nam qui. Quia et eligendi sit quae sint tempore exercitationem eos. Est sapiente corrupti quidem at. Qui magni odio repudiandae saepe tenetur optio dolore.\n\nEos placeat soluta at dolorem adipisci provident. Quo commodi id reprehenderit possimus quo tenetur. Ipsum et quae eligendi laborum. Et qui nesciunt at quasi quidem voluptatem cum rerum. Excepturi non facilis aut sunt vero sed.\n\nQui explicabo ratione ut eligendi recusandae. Quis quasi quas molestiae consequatur voluptatem et voluptatem. Ex repellat saepe occaecati aperiam ea eveniet dignissimos facilis.",
- "created_at": "2016-03-22T15:20:35.966Z",
- "updated_at": "2016-03-22T15:20:35.966Z",
- "started_at": null,
- "runner_id": null,
- "coverage": null,
- "commit_id": 40,
- "commands": "$ build command",
- "job_id": null,
- "name": "test build 2",
- "deploy": false,
- "options": null,
- "allow_failure": false,
- "stage": "test",
- "trigger_request_id": null,
- "stage_idx": 1,
- "tag": null,
- "ref": "master",
- "user_id": null,
- "target_url": null,
- "description": null,
- "erased_by_id": null,
- "erased_at": null
- }
- ]
- }
- ]
- }
- ],
- "triggers": [
- {
- "id": 123,
- "token": "cdbfasdf44a5958c83654733449e585",
- "project_id": 5,
- "owner_id": 1,
- "created_at": "2017-01-16T15:25:28.637Z",
- "updated_at": "2017-01-16T15:25:28.637Z"
- },
- {
- "id": 456,
- "token": "33a66349b5ad01fc00174af87804e40",
- "project_id": 5,
- "created_at": "2017-01-16T15:25:29.637Z",
- "updated_at": "2017-01-16T15:25:29.637Z"
- }
- ],
- "deploy_keys": [],
- "services": [
- {
- "id": 101,
- "title": "YouTrack",
- "project_id": 5,
- "created_at": "2016-06-14T15:01:51.327Z",
- "updated_at": "2016-06-14T15:01:51.327Z",
- "active": false,
- "properties": {},
- "template": false,
- "push_events": true,
- "issues_events": true,
- "merge_requests_events": true,
- "tag_push_events": true,
- "note_events": true,
- "job_events": true,
- "type": "YoutrackService",
- "category": "issue_tracker",
- "default": false,
- "wiki_page_events": true
- },
- {
- "id": 100,
- "title": "JetBrains TeamCity CI",
- "project_id": 5,
- "created_at": "2016-06-14T15:01:51.315Z",
- "updated_at": "2016-06-14T15:01:51.315Z",
- "active": false,
- "properties": {},
- "template": false,
- "push_events": true,
- "issues_events": true,
- "merge_requests_events": true,
- "tag_push_events": true,
- "note_events": true,
- "job_events": true,
- "type": "TeamcityService",
- "category": "ci",
- "default": false,
- "wiki_page_events": true
- },
- {
- "id": 99,
- "title": "Slack",
- "project_id": 5,
- "created_at": "2016-06-14T15:01:51.303Z",
- "updated_at": "2016-06-14T15:01:51.303Z",
- "active": false,
- "properties": {
- "notify_only_broken_pipelines": true
- },
- "template": false,
- "push_events": true,
- "issues_events": true,
- "merge_requests_events": true,
- "tag_push_events": true,
- "note_events": true,
- "pipeline_events": true,
- "type": "SlackService",
- "category": "common",
- "default": false,
- "wiki_page_events": true
- },
- {
- "id": 98,
- "title": "Redmine",
- "project_id": 5,
- "created_at": "2016-06-14T15:01:51.289Z",
- "updated_at": "2016-06-14T15:01:51.289Z",
- "active": false,
- "properties": {},
- "template": false,
- "push_events": true,
- "issues_events": true,
- "merge_requests_events": true,
- "tag_push_events": true,
- "note_events": true,
- "job_events": true,
- "type": "RedmineService",
- "category": "issue_tracker",
- "default": false,
- "wiki_page_events": true
- },
- {
- "id": 97,
- "title": "Pushover",
- "project_id": 5,
- "created_at": "2016-06-14T15:01:51.277Z",
- "updated_at": "2016-06-14T15:01:51.277Z",
- "active": false,
- "properties": {},
- "template": false,
- "push_events": true,
- "issues_events": true,
- "merge_requests_events": true,
- "tag_push_events": true,
- "note_events": true,
- "job_events": true,
- "type": "PushoverService",
- "category": "common",
- "default": false,
- "wiki_page_events": true
- },
- {
- "id": 96,
- "title": "PivotalTracker",
- "project_id": 5,
- "created_at": "2016-06-14T15:01:51.267Z",
- "updated_at": "2016-06-14T15:01:51.267Z",
- "active": false,
- "properties": {},
- "template": false,
- "push_events": true,
- "issues_events": true,
- "merge_requests_events": true,
- "tag_push_events": true,
- "note_events": true,
- "job_events": true,
- "type": "PivotalTrackerService",
- "category": "common",
- "default": false,
- "wiki_page_events": true
- },
- {
- "id": 95,
- "title": "Jira",
- "project_id": 5,
- "created_at": "2016-06-14T15:01:51.255Z",
- "updated_at": "2016-06-14T15:01:51.255Z",
- "active": false,
- "properties": {
- "api_url": "",
- "jira_issue_transition_id": "2"
- },
- "template": false,
- "push_events": true,
- "issues_events": true,
- "merge_requests_events": true,
- "tag_push_events": true,
- "note_events": true,
- "job_events": true,
- "type": "JiraService",
- "category": "issue_tracker",
- "default": false,
- "wiki_page_events": true
- },
- {
- "id": 94,
- "title": "Irker (IRC gateway)",
- "project_id": 5,
- "created_at": "2016-06-14T15:01:51.232Z",
- "updated_at": "2016-06-14T15:01:51.232Z",
- "active": true,
- "properties": {},
- "template": false,
- "push_events": true,
- "issues_events": true,
- "merge_requests_events": true,
- "tag_push_events": true,
- "note_events": true,
- "job_events": true,
- "type": "IrkerService",
- "category": "common",
- "default": false,
- "wiki_page_events": true
- },
- {
- "id": 93,
- "title": "HipChat",
- "project_id": 5,
- "created_at": "2016-06-14T15:01:51.219Z",
- "updated_at": "2016-06-14T15:01:51.219Z",
- "active": false,
- "properties": {
- "notify_only_broken_pipelines": true
- },
- "template": false,
- "push_events": true,
- "issues_events": true,
- "merge_requests_events": true,
- "tag_push_events": true,
- "note_events": true,
- "pipeline_events": true,
- "type": "HipchatService",
- "category": "common",
- "default": false,
- "wiki_page_events": true
- },
- {
- "id": 91,
- "title": "Flowdock",
- "project_id": 5,
- "created_at": "2016-06-14T15:01:51.182Z",
- "updated_at": "2016-06-14T15:01:51.182Z",
- "active": false,
- "properties": {},
- "template": false,
- "push_events": true,
- "issues_events": true,
- "merge_requests_events": true,
- "tag_push_events": true,
- "note_events": true,
- "job_events": true,
- "type": "FlowdockService",
- "category": "common",
- "default": false,
- "wiki_page_events": true
- },
- {
- "id": 90,
- "title": "External Wiki",
- "project_id": 5,
- "created_at": "2016-06-14T15:01:51.166Z",
- "updated_at": "2016-06-14T15:01:51.166Z",
- "active": false,
- "properties": {},
- "template": false,
- "push_events": true,
- "issues_events": true,
- "merge_requests_events": true,
- "tag_push_events": true,
- "note_events": true,
- "job_events": true,
- "type": "ExternalWikiService",
- "category": "common",
- "default": false,
- "wiki_page_events": true
- },
- {
- "id": 89,
- "title": "Emails on push",
- "project_id": 5,
- "created_at": "2016-06-14T15:01:51.153Z",
- "updated_at": "2016-06-14T15:01:51.153Z",
- "active": false,
- "properties": {},
- "template": false,
- "push_events": true,
- "issues_events": true,
- "merge_requests_events": true,
- "tag_push_events": true,
- "note_events": true,
- "job_events": true,
- "type": "EmailsOnPushService",
- "category": "common",
- "default": false,
- "wiki_page_events": true
- },
- {
- "id": 88,
- "title": "Drone CI",
- "project_id": 5,
- "created_at": "2016-06-14T15:01:51.139Z",
- "updated_at": "2016-06-14T15:01:51.139Z",
- "active": false,
- "properties": {},
- "template": false,
- "push_events": true,
- "issues_events": true,
- "merge_requests_events": true,
- "tag_push_events": true,
- "note_events": true,
- "job_events": true,
- "type": "DroneCiService",
- "category": "ci",
- "default": false,
- "wiki_page_events": true
- },
- {
- "id": 87,
- "title": "Custom Issue Tracker",
- "project_id": 5,
- "created_at": "2016-06-14T15:01:51.125Z",
- "updated_at": "2016-06-14T15:01:51.125Z",
- "active": false,
- "properties": {},
- "template": false,
- "push_events": true,
- "issues_events": true,
- "merge_requests_events": true,
- "tag_push_events": true,
- "note_events": true,
- "job_events": true,
- "type": "CustomIssueTrackerService",
- "category": "issue_tracker",
- "default": false,
- "wiki_page_events": true
- },
- {
- "id": 86,
- "title": "Campfire",
- "project_id": 5,
- "created_at": "2016-06-14T15:01:51.113Z",
- "updated_at": "2016-06-14T15:01:51.113Z",
- "active": false,
- "properties": {},
- "template": false,
- "push_events": true,
- "issues_events": true,
- "merge_requests_events": true,
- "tag_push_events": true,
- "note_events": true,
- "job_events": true,
- "type": "CampfireService",
- "category": "common",
- "default": false,
- "wiki_page_events": true
- },
- {
- "id": 84,
- "title": "Buildkite",
- "project_id": 5,
- "created_at": "2016-06-14T15:01:51.080Z",
- "updated_at": "2016-06-14T15:01:51.080Z",
- "active": false,
- "properties": {},
- "template": false,
- "push_events": true,
- "issues_events": true,
- "merge_requests_events": true,
- "tag_push_events": true,
- "note_events": true,
- "job_events": true,
- "type": "BuildkiteService",
- "category": "ci",
- "default": false,
- "wiki_page_events": true
- },
- {
- "id": 83,
- "title": "Atlassian Bamboo CI",
- "project_id": 5,
- "created_at": "2016-06-14T15:01:51.067Z",
- "updated_at": "2016-06-14T15:01:51.067Z",
- "active": false,
- "properties": {},
- "template": false,
- "push_events": true,
- "issues_events": true,
- "merge_requests_events": true,
- "tag_push_events": true,
- "note_events": true,
- "job_events": true,
- "type": "BambooService",
- "category": "ci",
- "default": false,
- "wiki_page_events": true
- },
- {
- "id": 82,
- "title": "Assembla",
- "project_id": 5,
- "created_at": "2016-06-14T15:01:51.047Z",
- "updated_at": "2016-06-14T15:01:51.047Z",
- "active": false,
- "properties": {},
- "template": false,
- "push_events": true,
- "issues_events": true,
- "merge_requests_events": true,
- "tag_push_events": true,
- "note_events": true,
- "job_events": true,
- "type": "AssemblaService",
- "category": "common",
- "default": false,
- "wiki_page_events": true
- },
- {
- "id": 81,
- "title": "Asana",
- "project_id": 5,
- "created_at": "2016-06-14T15:01:51.031Z",
- "updated_at": "2016-06-14T15:01:51.031Z",
- "active": false,
- "properties": {},
- "template": false,
- "push_events": true,
- "issues_events": true,
- "merge_requests_events": true,
- "tag_push_events": true,
- "note_events": true,
- "job_events": true,
- "type": "AssemblaService",
- "category": "common",
- "default": false,
- "wiki_page_events": true
- },
- {
- "id": 101,
- "title": "JenkinsDeprecated",
- "project_id": 5,
- "created_at": "2016-06-14T15:01:51.031Z",
- "updated_at": "2016-06-14T15:01:51.031Z",
- "active": false,
- "properties": {},
- "template": false,
- "push_events": true,
- "issues_events": true,
- "merge_requests_events": true,
- "tag_push_events": true,
- "note_events": true,
- "job_events": true,
- "category": "common",
- "default": false,
- "wiki_page_events": true,
- "type": "JenkinsDeprecatedService"
- }
- ],
- "hooks": [],
- "protected_branches": [
- {
- "id": 1,
- "project_id": 9,
- "name": "master",
- "created_at": "2016-08-30T07:32:52.426Z",
- "updated_at": "2016-08-30T07:32:52.426Z",
- "merge_access_levels": [
- {
- "id": 1,
- "protected_branch_id": 1,
- "access_level": 40,
- "created_at": "2016-08-30T07:32:52.458Z",
- "updated_at": "2016-08-30T07:32:52.458Z"
- }
- ],
- "push_access_levels": [
- {
- "id": 1,
- "protected_branch_id": 1,
- "access_level": 40,
- "created_at": "2016-08-30T07:32:52.490Z",
- "updated_at": "2016-08-30T07:32:52.490Z"
- }
- ]
- }
- ],
- "protected_tags": [
- {
- "id": 1,
- "project_id": 9,
- "name": "v*",
- "created_at": "2017-04-04T13:48:13.426Z",
- "updated_at": "2017-04-04T13:48:13.426Z",
- "create_access_levels": [
- {
- "id": 1,
- "protected_tag_id": 1,
- "access_level": 40,
- "created_at": "2017-04-04T13:48:13.458Z",
- "updated_at": "2017-04-04T13:48:13.458Z"
- }
- ]
- }
- ],
- "project_feature": {
- "builds_access_level": 0,
- "created_at": "2014-12-26T09:26:45.000Z",
- "id": 2,
- "issues_access_level": 0,
- "merge_requests_access_level": 20,
- "project_id": 4,
- "snippets_access_level": 20,
- "updated_at": "2016-09-23T11:58:28.000Z",
- "wiki_access_level": 20
- },
- "custom_attributes": [
- {
- "id": 1,
- "created_at": "2017-10-19T15:36:23.466Z",
- "updated_at": "2017-10-19T15:36:23.466Z",
- "project_id": 5,
- "key": "foo",
- "value": "foo"
- },
- {
- "id": 2,
- "created_at": "2017-10-19T15:37:21.904Z",
- "updated_at": "2017-10-19T15:37:21.904Z",
- "project_id": 5,
- "key": "bar",
- "value": "bar"
- }
- ],
- "project_badges": [
- {
- "id": 1,
- "created_at": "2017-10-19T15:36:23.466Z",
- "updated_at": "2017-10-19T15:36:23.466Z",
- "project_id": 5,
- "type": "ProjectBadge",
- "link_url": "http://www.example.com",
- "image_url": "http://www.example.com"
- },
- {
- "id": 2,
- "created_at": "2017-10-19T15:36:23.466Z",
- "updated_at": "2017-10-19T15:36:23.466Z",
- "project_id": 5,
- "type": "ProjectBadge",
- "link_url": "http://www.example.com",
- "image_url": "http://www.example.com"
- }
- ],
- "boards": [
- {
- "id": 29,
- "project_id": 49,
- "created_at": "2019-06-06T14:01:06.204Z",
- "updated_at": "2019-06-06T14:22:37.045Z",
- "name": "TestBoardABC",
- "milestone_id": null,
- "group_id": null,
- "weight": null,
- "lists": [
- {
- "id": 59,
- "board_id": 29,
- "label_id": null,
- "list_type": "backlog",
- "position": null,
- "created_at": "2019-06-06T14:01:06.214Z",
- "updated_at": "2019-06-06T14:01:06.214Z",
- "user_id": null,
- "milestone_id": null
- },
- {
- "id": 61,
- "board_id": 29,
- "label_id": 20,
- "list_type": "label",
- "position": 0,
- "created_at": "2019-06-06T14:01:43.197Z",
- "updated_at": "2019-06-06T14:01:43.197Z",
- "user_id": null,
- "milestone_id": null,
- "label": {
- "id": 20,
- "title": "testlabel",
- "color": "#0033CC",
- "project_id": 49,
- "created_at": "2019-06-06T14:01:19.698Z",
- "updated_at": "2019-06-06T14:01:19.698Z",
- "template": false,
- "description": null,
- "group_id": null,
- "type": "ProjectLabel",
- "priorities": []
- }
- },
- {
- "id": 60,
- "board_id": 29,
- "label_id": null,
- "list_type": "closed",
- "position": null,
- "created_at": "2019-06-06T14:01:06.221Z",
- "updated_at": "2019-06-06T14:01:06.221Z",
- "user_id": null,
- "milestone_id": null
- }
- ]
- }
- ]
-}
diff --git a/spec/lib/gitlab/import_export/project.light.json b/spec/lib/gitlab/import_export/project.light.json
deleted file mode 100644
index 2971ca0f0f8..00000000000
--- a/spec/lib/gitlab/import_export/project.light.json
+++ /dev/null
@@ -1,128 +0,0 @@
-{
- "description": "Nisi et repellendus ut enim quo accusamus vel magnam.",
- "import_type": "gitlab_project",
- "creator_id": 123,
- "visibility_level": 10,
- "archived": false,
- "milestones": [
- {
- "id": 1,
- "title": "A milestone",
- "project_id": 8,
- "description": "Project-level milestone",
- "due_date": null,
- "created_at": "2016-06-14T15:02:04.415Z",
- "updated_at": "2016-06-14T15:02:04.415Z",
- "state": "active",
- "iid": 1,
- "group_id": null
- }
- ],
- "labels": [
- {
- "id": 2,
- "title": "A project label",
- "color": "#428bca",
- "project_id": 8,
- "created_at": "2016-07-22T08:55:44.161Z",
- "updated_at": "2016-07-22T08:55:44.161Z",
- "template": false,
- "description": "",
- "type": "ProjectLabel",
- "priorities": [
- {
- "id": 1,
- "project_id": 5,
- "label_id": 1,
- "priority": 1,
- "created_at": "2016-10-18T09:35:43.338Z",
- "updated_at": "2016-10-18T09:35:43.338Z"
- }
- ]
- }
- ],
- "issues": [
- {
- "id": 1,
- "title": "Fugiat est minima quae maxime non similique.",
- "assignee_id": null,
- "project_id": 8,
- "author_id": 1,
- "created_at": "2017-07-07T18:13:01.138Z",
- "updated_at": "2017-08-15T18:37:40.807Z",
- "branch_name": null,
- "description": "Quam totam fuga numquam in eveniet.",
- "state": "opened",
- "iid": 20,
- "updated_by_id": 1,
- "confidential": false,
- "due_date": null,
- "moved_to_id": null,
- "lock_version": null,
- "time_estimate": 0,
- "closed_at": null,
- "last_edited_at": null,
- "last_edited_by_id": null,
- "group_milestone_id": null,
- "milestone": {
- "id": 1,
- "title": "A milestone",
- "group_id": 8,
- "description": "Project-level milestone",
- "due_date": null,
- "created_at": "2016-06-14T15:02:04.415Z",
- "updated_at": "2016-06-14T15:02:04.415Z",
- "state": "active",
- "iid": 1,
- "group_id": null
- },
- "label_links": [
- {
- "id": 11,
- "label_id": 2,
- "target_id": 1,
- "target_type": "Issue",
- "created_at": "2017-08-15T18:37:40.795Z",
- "updated_at": "2017-08-15T18:37:40.795Z",
- "label": {
- "id": 6,
- "title": "Another label",
- "color": "#A8D695",
- "project_id": null,
- "created_at": "2017-08-15T18:37:19.698Z",
- "updated_at": "2017-08-15T18:37:19.698Z",
- "template": false,
- "description": "",
- "group_id": null,
- "type": "ProjectLabel",
- "priorities": []
- }
- }
- ]
- }
- ],
- "services": [
- {
- "id": 100,
- "title": "JetBrains TeamCity CI",
- "project_id": 5,
- "created_at": "2016-06-14T15:01:51.315Z",
- "updated_at": "2016-06-14T15:01:51.315Z",
- "active": false,
- "properties": {},
- "template": true,
- "push_events": true,
- "issues_events": true,
- "merge_requests_events": true,
- "tag_push_events": true,
- "note_events": true,
- "job_events": true,
- "type": "TeamcityService",
- "category": "ci",
- "default": false,
- "wiki_page_events": true
- }
- ],
- "snippets": [],
- "hooks": []
-}
diff --git a/spec/lib/gitlab/import_export/project.milestone-iid.json b/spec/lib/gitlab/import_export/project.milestone-iid.json
deleted file mode 100644
index b028147b5eb..00000000000
--- a/spec/lib/gitlab/import_export/project.milestone-iid.json
+++ /dev/null
@@ -1,80 +0,0 @@
-{
- "description": "Nisi et repellendus ut enim quo accusamus vel magnam.",
- "import_type": "gitlab_project",
- "creator_id": 123,
- "visibility_level": 10,
- "archived": false,
- "issues": [
- {
- "id": 1,
- "title": "Fugiat est minima quae maxime non similique.",
- "assignee_id": null,
- "project_id": 8,
- "author_id": 1,
- "created_at": "2017-07-07T18:13:01.138Z",
- "updated_at": "2017-08-15T18:37:40.807Z",
- "branch_name": null,
- "description": "Quam totam fuga numquam in eveniet.",
- "state": "opened",
- "iid": 20,
- "updated_by_id": 1,
- "confidential": false,
- "due_date": null,
- "moved_to_id": null,
- "lock_version": null,
- "time_estimate": 0,
- "closed_at": null,
- "last_edited_at": null,
- "last_edited_by_id": null,
- "group_milestone_id": null,
- "milestone": {
- "id": 1,
- "title": "Group-level milestone",
- "description": "Group-level milestone",
- "due_date": null,
- "created_at": "2016-06-14T15:02:04.415Z",
- "updated_at": "2016-06-14T15:02:04.415Z",
- "state": "active",
- "iid": 1,
- "group_id": 8
- }
- },
- {
- "id": 2,
- "title": "est minima quae maxime non similique.",
- "assignee_id": null,
- "project_id": 8,
- "author_id": 1,
- "created_at": "2017-07-07T18:13:01.138Z",
- "updated_at": "2017-08-15T18:37:40.807Z",
- "branch_name": null,
- "description": "Quam totam fuga numquam in eveniet.",
- "state": "opened",
- "iid": 21,
- "updated_by_id": 1,
- "confidential": false,
- "due_date": null,
- "moved_to_id": null,
- "lock_version": null,
- "time_estimate": 0,
- "closed_at": null,
- "last_edited_at": null,
- "last_edited_by_id": null,
- "group_milestone_id": null,
- "milestone": {
- "id": 2,
- "title": "Another milestone",
- "project_id": 8,
- "description": "milestone",
- "due_date": null,
- "created_at": "2016-06-14T15:02:04.415Z",
- "updated_at": "2016-06-14T15:02:04.415Z",
- "state": "active",
- "iid": 1,
- "group_id": null
- }
- }
- ],
- "snippets": [],
- "hooks": []
-}
diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
index 87be7857e67..676973ff5e7 100644
--- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
@@ -2,6 +2,8 @@ require 'spec_helper'
include ImportExport::CommonUtil
describe Gitlab::ImportExport::ProjectTreeRestorer do
+ let(:shared) { project.import_export_shared }
+
describe 'restore project tree' do
before(:context) do
# Using an admin for import, so we can check assignment of existing members
@@ -14,7 +16,7 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
RSpec::Mocks.with_temporary_scope do
@project = create(:project, :builds_enabled, :issues_disabled, name: 'project', path: 'project')
@shared = @project.import_export_shared
- allow(@shared).to receive(:export_path).and_return('spec/lib/gitlab/import_export/')
+ allow(@shared).to receive(:export_path).and_return('spec/fixtures/lib/gitlab/import_export/')
allow_any_instance_of(Repository).to receive(:fetch_source_branch!).and_return(true)
allow_any_instance_of(Gitlab::Git::Repository).to receive(:branch_exists?).and_return(false)
@@ -94,6 +96,17 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
expect(Ci::Pipeline.where(ref: nil)).not_to be_empty
end
+ it 'restores pipeline for merge request' do
+ pipeline = Ci::Pipeline.find_by_sha('048721d90c449b244b7b4c53a9186b04330174ec')
+
+ expect(pipeline).to be_valid
+ expect(pipeline.tag).to be_falsey
+ expect(pipeline.source).to eq('merge_request_event')
+ expect(pipeline.merge_request.id).to be > 0
+ expect(pipeline.merge_request.target_branch).to eq('feature')
+ expect(pipeline.merge_request.source_branch).to eq('feature_conflict')
+ end
+
it 'preserves updated_at on issues' do
issue = Issue.where(description: 'Aliquam enim illo et possimus.').first
@@ -274,36 +287,6 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
end
end
- shared_examples 'restores project successfully' do
- it 'correctly restores project' do
- expect(shared.errors).to be_empty
- expect(restored_project_json).to be_truthy
- end
- end
-
- shared_examples 'restores project correctly' do |**results|
- it 'has labels' do
- expect(project.labels.size).to eq(results.fetch(:labels, 0))
- end
-
- it 'has label priorities' do
- expect(project.labels.find_by(title: 'A project label').priorities).not_to be_empty
- end
-
- it 'has milestones' do
- expect(project.milestones.size).to eq(results.fetch(:milestones, 0))
- end
-
- it 'has issues' do
- expect(project.issues.size).to eq(results.fetch(:issues, 0))
- end
-
- it 'does not set params that are excluded from import_export settings' do
- expect(project.import_type).to be_nil
- expect(project.creator_id).not_to eq 123
- end
- end
-
shared_examples 'restores group correctly' do |**results|
it 'has group label' do
expect(project.group.labels.size).to eq(results.fetch(:labels, 0))
@@ -322,18 +305,17 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
context 'Light JSON' do
let(:user) { create(:user) }
- let(:shared) { project.import_export_shared }
let!(:project) { create(:project, :builds_disabled, :issues_disabled, name: 'project', path: 'project') }
let(:project_tree_restorer) { described_class.new(user: user, shared: shared, project: project) }
let(:restored_project_json) { project_tree_restorer.restore }
before do
- allow(shared).to receive(:export_path).and_return('spec/lib/gitlab/import_export/')
+ allow(shared).to receive(:export_path).and_return('spec/fixtures/lib/gitlab/import_export/')
end
context 'with a simple project' do
before do
- project_tree_restorer.instance_variable_set(:@path, "spec/lib/gitlab/import_export/project.light.json")
+ project_tree_restorer.instance_variable_set(:@path, "spec/fixtures/lib/gitlab/import_export/project.light.json")
restored_project_json
end
@@ -341,6 +323,7 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
it_behaves_like 'restores project correctly',
issues: 1,
labels: 2,
+ label_with_priorities: 'A project label',
milestones: 1,
first_issue_labels: 1,
services: 1
@@ -363,7 +346,12 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
create(:ci_build, token: 'abcd')
end
- it_behaves_like 'restores project successfully'
+ it_behaves_like 'restores project correctly',
+ issues: 1,
+ labels: 2,
+ label_with_priorities: 'A project label',
+ milestones: 1,
+ first_issue_labels: 1
end
end
@@ -430,15 +418,15 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
end
before do
- project_tree_restorer.instance_variable_set(:@path, "spec/lib/gitlab/import_export/project.group.json")
+ project_tree_restorer.instance_variable_set(:@path, "spec/fixtures/lib/gitlab/import_export/project.group.json")
restored_project_json
end
- it_behaves_like 'restores project successfully'
it_behaves_like 'restores project correctly',
issues: 2,
labels: 2,
+ label_with_priorities: 'A project label',
milestones: 2,
first_issue_labels: 1
@@ -446,6 +434,11 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
labels: 0,
milestones: 0,
first_issue_labels: 1
+
+ it 'restores issue states' do
+ expect(project.issues.with_state(:closed).count).to eq(1)
+ expect(project.issues.with_state(:opened).count).to eq(1)
+ end
end
context 'with existing group models' do
@@ -459,7 +452,7 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
end
before do
- project_tree_restorer.instance_variable_set(:@path, "spec/lib/gitlab/import_export/project.light.json")
+ project_tree_restorer.instance_variable_set(:@path, "spec/fixtures/lib/gitlab/import_export/project.light.json")
end
it 'does not import any templated services' do
@@ -501,7 +494,7 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
end
it 'preserves the project milestone IID' do
- project_tree_restorer.instance_variable_set(:@path, "spec/lib/gitlab/import_export/project.milestone-iid.json")
+ project_tree_restorer.instance_variable_set(:@path, "spec/fixtures/lib/gitlab/import_export/project.milestone-iid.json")
expect_any_instance_of(Gitlab::ImportExport::Shared).not_to receive(:error)
@@ -532,21 +525,21 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
end
end
- describe '#restored_project' do
+ context 'Minimal JSON' do
let(:project) { create(:project) }
- let(:shared) { project.import_export_shared }
let(:tree_hash) { { 'visibility_level' => visibility } }
let(:restorer) { described_class.new(user: nil, shared: shared, project: project) }
before do
- restorer.instance_variable_set(:@tree_hash, tree_hash)
+ expect(restorer).to receive(:read_tree_hash) { tree_hash }
end
context 'no group visibility' do
let(:visibility) { Gitlab::VisibilityLevel::PRIVATE }
it 'uses the project visibility' do
- expect(restorer.restored_project.visibility_level).to eq(visibility)
+ expect(restorer.restore).to eq(true)
+ expect(restorer.project.visibility_level).to eq(visibility)
end
end
@@ -557,7 +550,8 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
it 'uses private visibility' do
stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::INTERNAL])
- expect(restorer.restored_project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE)
+ expect(restorer.restore).to eq(true)
+ expect(restorer.project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE)
end
end
end
@@ -574,7 +568,8 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
let(:visibility) { Gitlab::VisibilityLevel::PUBLIC }
it 'uses the group visibility' do
- expect(restorer.restored_project.visibility_level).to eq(group_visibility)
+ expect(restorer.restore).to eq(true)
+ expect(restorer.project.visibility_level).to eq(group_visibility)
end
end
@@ -583,7 +578,8 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
let(:visibility) { Gitlab::VisibilityLevel::PRIVATE }
it 'uses the project visibility' do
- expect(restorer.restored_project.visibility_level).to eq(visibility)
+ expect(restorer.restore).to eq(true)
+ expect(restorer.project.visibility_level).to eq(visibility)
end
end
@@ -592,14 +588,16 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
let(:visibility) { Gitlab::VisibilityLevel::PUBLIC }
it 'uses the group visibility' do
- expect(restorer.restored_project.visibility_level).to eq(group_visibility)
+ expect(restorer.restore).to eq(true)
+ expect(restorer.project.visibility_level).to eq(group_visibility)
end
context 'with restricted internal visibility' do
it 'sets private visibility' do
stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::INTERNAL])
- expect(restorer.restored_project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE)
+ expect(restorer.restore).to eq(true)
+ expect(restorer.project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE)
end
end
end
diff --git a/spec/lib/gitlab/import_export/relation_factory_spec.rb b/spec/lib/gitlab/import_export/relation_factory_spec.rb
index a31f77484d8..a23e68a8f00 100644
--- a/spec/lib/gitlab/import_export/relation_factory_spec.rb
+++ b/spec/lib/gitlab/import_export/relation_factory_spec.rb
@@ -3,12 +3,14 @@ require 'spec_helper'
describe Gitlab::ImportExport::RelationFactory do
let(:project) { create(:project) }
let(:members_mapper) { double('members_mapper').as_null_object }
+ let(:merge_requests_mapping) { {} }
let(:user) { create(:admin) }
let(:excluded_keys) { [] }
let(:created_object) do
described_class.create(relation_sym: relation_sym,
relation_hash: relation_hash,
members_mapper: members_mapper,
+ merge_requests_mapping: merge_requests_mapping,
user: user,
project: project,
excluded_keys: excluded_keys)
@@ -83,7 +85,7 @@ describe Gitlab::ImportExport::RelationFactory do
class FooModel
include ActiveModel::Model
- def initialize(params)
+ def initialize(params = {})
params.each { |key, value| send("#{key}=", value) }
end
diff --git a/spec/lib/gitlab/import_export/relation_rename_service_spec.rb b/spec/lib/gitlab/import_export/relation_rename_service_spec.rb
index 17bb5bcc155..472bf55d37e 100644
--- a/spec/lib/gitlab/import_export/relation_rename_service_spec.rb
+++ b/spec/lib/gitlab/import_export/relation_rename_service_spec.rb
@@ -21,7 +21,7 @@ describe Gitlab::ImportExport::RelationRenameService do
context 'when importing' do
let(:project_tree_restorer) { Gitlab::ImportExport::ProjectTreeRestorer.new(user: user, shared: shared, project: project) }
- let(:import_path) { 'spec/lib/gitlab/import_export' }
+ let(:import_path) { 'spec/fixtures/lib/gitlab/import_export' }
let(:file_content) { IO.read("#{import_path}/project.json") }
let!(:json_file) { ActiveSupport::JSON.decode(file_content) }
diff --git a/spec/lib/gitlab/import_export/repo_saver_spec.rb b/spec/lib/gitlab/import_export/repo_saver_spec.rb
index 5a646b4aac8..c3df371af43 100644
--- a/spec/lib/gitlab/import_export/repo_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/repo_saver_spec.rb
@@ -2,8 +2,8 @@ require 'spec_helper'
describe Gitlab::ImportExport::RepoSaver do
describe 'bundle a project Git repo' do
- let(:user) { create(:user) }
- let!(:project) { create(:project, :public, name: 'searchable_project') }
+ set(:user) { create(:user) }
+ let!(:project) { create(:project, :repository) }
let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
let(:shared) { project.import_export_shared }
let(:bundler) { described_class.new(project: project, shared: shared) }
@@ -20,5 +20,13 @@ describe Gitlab::ImportExport::RepoSaver do
it 'bundles the repo successfully' do
expect(bundler.save).to be true
end
+
+ context 'when the repo is empty' do
+ let!(:project) { create(:project) }
+
+ it 'bundles the repo successfully' do
+ expect(bundler.save).to be true
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index 2f178648838..8ae571a69ef 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -47,6 +47,7 @@ PushEventPayload:
- commit_to
- ref
- commit_title
+- ref_count
Note:
- id
- note
@@ -126,6 +127,12 @@ Release:
- created_at
- updated_at
- released_at
+Evidence:
+- id
+- release_id
+- summary
+- created_at
+- updated_at
Releases::Link:
- id
- release_id
@@ -717,6 +724,7 @@ List:
- updated_at
- milestone_id
- user_id
+- max_issue_count
ExternalPullRequest:
- id
- created_at
@@ -730,3 +738,18 @@ ExternalPullRequest:
- target_repository
- source_sha
- target_sha
+DesignManagement::Design:
+- id
+- project_id
+- issue_id
+- filename
+DesignManagement::Action:
+- design_id
+- event
+- version_id
+DesignManagement::Version:
+- id
+- created_at
+- sha
+- issue_id
+- user_id
diff --git a/spec/lib/gitlab/import_export/shared_spec.rb b/spec/lib/gitlab/import_export/shared_spec.rb
index 2c288cff6ef..62669836973 100644
--- a/spec/lib/gitlab/import_export/shared_spec.rb
+++ b/spec/lib/gitlab/import_export/shared_spec.rb
@@ -5,6 +5,35 @@ describe Gitlab::ImportExport::Shared do
let(:project) { build(:project) }
subject { project.import_export_shared }
+ context 'with a repository on disk' do
+ let(:project) { create(:project, :repository) }
+ let(:base_path) { %(/tmp/project_exports/#{project.disk_path}/) }
+
+ describe '#archive_path' do
+ it 'uses a random hash to avoid conflicts' do
+ expect(subject.archive_path).to match(/#{base_path}\h{32}/)
+ end
+
+ it 'memoizes the path' do
+ path = subject.archive_path
+
+ 2.times { expect(subject.archive_path).to eq(path) }
+ end
+ end
+
+ describe '#export_path' do
+ it 'uses a random hash relative to project path' do
+ expect(subject.export_path).to match(/#{base_path}\h{32}\/\h{32}/)
+ end
+
+ it 'memoizes the path' do
+ path = subject.export_path
+
+ 2.times { expect(subject.export_path).to eq(path) }
+ end
+ end
+ end
+
describe '#error' do
let(:error) { StandardError.new('Error importing into /my/folder Permission denied @ unlink_internal - /var/opt/gitlab/gitlab-rails/shared/a/b/c/uploads/file') }
@@ -24,16 +53,17 @@ describe Gitlab::ImportExport::Shared do
subject.error(error)
end
- it 'calls the error logger with the full message' do
- expect(subject).to receive(:log_error).with(hash_including(message: error.message))
+ it 'calls the error logger without a backtrace' do
+ expect(subject).to receive(:log_error).with(message: error.message)
subject.error(error)
end
- it 'calls the debug logger with a backtrace' do
- error.set_backtrace('backtrace')
+ it 'calls the error logger with the full message' do
+ backtrace = caller
+ allow(error).to receive(:backtrace).and_return(caller)
- expect(subject).to receive(:log_debug).with(hash_including(backtrace: 'backtrace'))
+ expect(subject).to receive(:log_error).with(message: error.message, error_backtrace: Gitlab::Profiler.clean_backtrace(backtrace))
subject.error(error)
end
diff --git a/spec/lib/gitlab/import_export/uploads_manager_spec.rb b/spec/lib/gitlab/import_export/uploads_manager_spec.rb
index 792117e1df1..f13f639d6b7 100644
--- a/spec/lib/gitlab/import_export/uploads_manager_spec.rb
+++ b/spec/lib/gitlab/import_export/uploads_manager_spec.rb
@@ -83,7 +83,7 @@ describe Gitlab::ImportExport::UploadsManager do
it 'restores the file' do
manager.restore
- expect(project.uploads.map { |u| u.build_uploader.filename }).to include('dummy.txt')
+ expect(project.uploads.map { |u| u.retrieve_uploader.filename }).to include('dummy.txt')
end
end
end
diff --git a/spec/lib/gitlab/import_export/uploads_restorer_spec.rb b/spec/lib/gitlab/import_export/uploads_restorer_spec.rb
index 6072f18b8c7..e2e8204b2fa 100644
--- a/spec/lib/gitlab/import_export/uploads_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/uploads_restorer_spec.rb
@@ -27,7 +27,7 @@ describe Gitlab::ImportExport::UploadsRestorer do
it 'copies the uploads to the project path' do
subject.restore
- expect(project.uploads.map { |u| u.build_uploader.filename }).to include('dummy.txt')
+ expect(project.uploads.map { |u| u.retrieve_uploader.filename }).to include('dummy.txt')
end
end
@@ -43,7 +43,7 @@ describe Gitlab::ImportExport::UploadsRestorer do
it 'copies the uploads to the project path' do
subject.restore
- expect(project.uploads.map { |u| u.build_uploader.filename }).to include('dummy.txt')
+ expect(project.uploads.map { |u| u.retrieve_uploader.filename }).to include('dummy.txt')
end
end
end
diff --git a/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb b/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb
index 441aa1defe6..249afbd23d1 100644
--- a/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb
@@ -2,8 +2,8 @@ require 'spec_helper'
describe Gitlab::ImportExport::WikiRepoSaver do
describe 'bundle a wiki Git repo' do
- let(:user) { create(:user) }
- let!(:project) { create(:project, :public, :wiki_repo, name: 'searchable_project') }
+ set(:user) { create(:user) }
+ let!(:project) { create(:project, :wiki_repo) }
let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
let(:shared) { project.import_export_shared }
let(:wiki_bundler) { described_class.new(project: project, shared: shared) }
@@ -23,5 +23,13 @@ describe Gitlab::ImportExport::WikiRepoSaver do
it 'bundles the repo successfully' do
expect(wiki_bundler.save).to be true
end
+
+ context 'when the repo is empty' do
+ let!(:project) { create(:project) }
+
+ it 'bundles the repo successfully' do
+ expect(wiki_bundler.save).to be true
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/kubernetes/helm/delete_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/delete_command_spec.rb
index 39a46f9bc6d..7e9853cf9ea 100644
--- a/spec/lib/gitlab/kubernetes/helm/delete_command_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/delete_command_spec.rb
@@ -14,7 +14,7 @@ describe Gitlab::Kubernetes::Helm::DeleteCommand do
let(:commands) do
<<~EOS
helm init --upgrade
- for i in $(seq 1 30); do helm version && break; sleep 1s; echo "Retrying ($i)..."; done
+ for i in $(seq 1 30); do helm version && s=0 && break || s=$?; sleep 1s; echo \"Retrying ($i)...\"; done; (exit $s)
helm delete --purge app-name
EOS
end
@@ -36,7 +36,7 @@ describe Gitlab::Kubernetes::Helm::DeleteCommand do
let(:commands) do
<<~EOS
helm init --upgrade
- for i in $(seq 1 30); do helm version #{tls_flags} && break; sleep 1s; echo "Retrying ($i)..."; done
+ for i in $(seq 1 30); do helm version #{tls_flags} && s=0 && break || s=$?; sleep 1s; echo \"Retrying ($i)...\"; done; (exit $s)
#{helm_delete_command}
EOS
end
diff --git a/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb
index f7f510f01db..9eb3322f1a6 100644
--- a/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb
@@ -36,7 +36,7 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do
let(:commands) do
<<~EOS
helm init --upgrade
- for i in $(seq 1 30); do helm version #{tls_flags} && break; sleep 1s; echo "Retrying ($i)..."; done
+ for i in $(seq 1 30); do helm version #{tls_flags} && s=0 && break || s=$?; sleep 1s; echo \"Retrying ($i)...\"; done; (exit $s)
helm repo add app-name https://repository.example.com
helm repo update
#{helm_install_comand}
@@ -64,7 +64,7 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do
let(:commands) do
<<~EOS
helm init --upgrade
- for i in $(seq 1 30); do helm version #{tls_flags} && break; sleep 1s; echo "Retrying ($i)..."; done
+ for i in $(seq 1 30); do helm version #{tls_flags} && s=0 && break || s=$?; sleep 1s; echo \"Retrying ($i)...\"; done; (exit $s)
helm repo add app-name https://repository.example.com
helm repo update
#{helm_install_command}
@@ -93,7 +93,7 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do
let(:commands) do
<<~EOS
helm init --upgrade
- for i in $(seq 1 30); do helm version #{tls_flags} && break; sleep 1s; echo "Retrying ($i)..."; done
+ for i in $(seq 1 30); do helm version #{tls_flags} && s=0 && break || s=$?; sleep 1s; echo \"Retrying ($i)...\"; done; (exit $s)
#{helm_install_command}
EOS
end
@@ -120,7 +120,7 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do
let(:commands) do
<<~EOS
helm init --upgrade
- for i in $(seq 1 30); do helm version #{tls_flags} && break; sleep 1s; echo "Retrying ($i)..."; done
+ for i in $(seq 1 30); do helm version #{tls_flags} && s=0 && break || s=$?; sleep 1s; echo \"Retrying ($i)...\"; done; (exit $s)
helm repo add app-name https://repository.example.com
helm repo update
/bin/date
@@ -151,7 +151,7 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do
let(:commands) do
<<~EOS
helm init --upgrade
- for i in $(seq 1 30); do helm version #{tls_flags} && break; sleep 1s; echo "Retrying ($i)..."; done
+ for i in $(seq 1 30); do helm version #{tls_flags} && s=0 && break || s=$?; sleep 1s; echo \"Retrying ($i)...\"; done; (exit $s)
helm repo add app-name https://repository.example.com
helm repo update
#{helm_install_command}
@@ -182,7 +182,7 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do
let(:commands) do
<<~EOS
helm init --upgrade
- for i in $(seq 1 30); do helm version && break; sleep 1s; echo "Retrying ($i)..."; done
+ for i in $(seq 1 30); do helm version && s=0 && break || s=$?; sleep 1s; echo \"Retrying ($i)...\"; done; (exit $s)
helm repo add app-name https://repository.example.com
helm repo update
#{helm_install_command}
@@ -210,7 +210,7 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do
let(:commands) do
<<~EOS
helm init --upgrade
- for i in $(seq 1 30); do helm version #{tls_flags} && break; sleep 1s; echo "Retrying ($i)..."; done
+ for i in $(seq 1 30); do helm version #{tls_flags} && s=0 && break || s=$?; sleep 1s; echo \"Retrying ($i)...\"; done; (exit $s)
helm repo add app-name https://repository.example.com
helm repo update
#{helm_install_command}
diff --git a/spec/lib/gitlab/kubernetes/helm/reset_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/reset_command_spec.rb
index d49d4779735..2a89b04723d 100644
--- a/spec/lib/gitlab/kubernetes/helm/reset_command_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/reset_command_spec.rb
@@ -15,6 +15,7 @@ describe Gitlab::Kubernetes::Helm::ResetCommand do
<<~EOS
helm reset
kubectl delete replicaset -n gitlab-managed-apps -l name\\=tiller
+ kubectl delete clusterrolebinding tiller-admin
EOS
end
end
@@ -32,6 +33,7 @@ describe Gitlab::Kubernetes::Helm::ResetCommand do
--tls-key /data/helm/helm/config/key.pem
EOS1
kubectl delete replicaset -n gitlab-managed-apps -l name\\=tiller
+ kubectl delete clusterrolebinding tiller-admin
EOS2
end
end
diff --git a/spec/lib/gitlab/kubernetes/kube_client_spec.rb b/spec/lib/gitlab/kubernetes/kube_client_spec.rb
index e5d688aa391..59e81d89a50 100644
--- a/spec/lib/gitlab/kubernetes/kube_client_spec.rb
+++ b/spec/lib/gitlab/kubernetes/kube_client_spec.rb
@@ -162,7 +162,9 @@ describe Gitlab::Kubernetes::KubeClient do
:get_secret,
:get_service,
:get_service_account,
+ :delete_namespace,
:delete_pod,
+ :delete_service_account,
:create_config_map,
:create_namespace,
:create_pod,
diff --git a/spec/lib/gitlab/legacy_github_import/release_formatter_spec.rb b/spec/lib/gitlab/legacy_github_import/release_formatter_spec.rb
index 2cf4b367c0b..554be57fbec 100644
--- a/spec/lib/gitlab/legacy_github_import/release_formatter_spec.rb
+++ b/spec/lib/gitlab/legacy_github_import/release_formatter_spec.rb
@@ -37,6 +37,14 @@ describe Gitlab::LegacyGithubImport::ReleaseFormatter do
expect(release.attributes).to eq(expected)
end
+
+ context 'with a nil published_at date' do
+ let(:published_at) { nil }
+
+ it 'inserts a timestamp for released_at' do
+ expect(release.attributes[:released_at]).to be_a(Time)
+ end
+ end
end
describe '#valid' do
diff --git a/spec/lib/gitlab/lets_encrypt_spec.rb b/spec/lib/gitlab/lets_encrypt_spec.rb
index 65aea0937f1..2229393fb32 100644
--- a/spec/lib/gitlab/lets_encrypt_spec.rb
+++ b/spec/lib/gitlab/lets_encrypt_spec.rb
@@ -24,4 +24,16 @@ describe ::Gitlab::LetsEncrypt do
it { is_expected.to eq(false) }
end
end
+
+ describe '.terms_of_service_url' do
+ before do
+ stub_lets_encrypt_client
+ end
+
+ subject { described_class.terms_of_service_url }
+
+ it 'returns the url' do
+ is_expected.to eq("https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf")
+ end
+ end
end
diff --git a/spec/lib/gitlab/lfs_token_spec.rb b/spec/lib/gitlab/lfs_token_spec.rb
index 701ed1f3a1b..b2fd7bdd307 100644
--- a/spec/lib/gitlab/lfs_token_spec.rb
+++ b/spec/lib/gitlab/lfs_token_spec.rb
@@ -115,6 +115,46 @@ describe Gitlab::LfsToken, :clean_gitlab_redis_shared_state do
expect(lfs_token.token_valid?(lfs_token.token)).to be_truthy
end
end
+
+ context 'when the actor is a regular user' do
+ context 'when the user is blocked' do
+ let(:actor) { create(:user, :blocked) }
+
+ it 'returns false' do
+ expect(lfs_token.token_valid?(lfs_token.token)).to be_falsey
+ end
+ end
+
+ context 'when the user password is expired' do
+ let(:actor) { create(:user, password_expires_at: 1.minute.ago) }
+
+ it 'returns false' do
+ expect(lfs_token.token_valid?(lfs_token.token)).to be_falsey
+ end
+ end
+ end
+
+ context 'when the actor is an ldap user' do
+ before do
+ allow(actor).to receive(:ldap_user?).and_return(true)
+ end
+
+ context 'when the user is blocked' do
+ let(:actor) { create(:user, :blocked) }
+
+ it 'returns false' do
+ expect(lfs_token.token_valid?(lfs_token.token)).to be_falsey
+ end
+ end
+
+ context 'when the user password is expired' do
+ let(:actor) { create(:user, password_expires_at: 1.minute.ago) }
+
+ it 'returns true' do
+ expect(lfs_token.token_valid?(lfs_token.token)).to be_truthy
+ end
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/metrics/exporter/base_exporter_spec.rb b/spec/lib/gitlab/metrics/exporter/base_exporter_spec.rb
new file mode 100644
index 00000000000..47ec69e2f45
--- /dev/null
+++ b/spec/lib/gitlab/metrics/exporter/base_exporter_spec.rb
@@ -0,0 +1,180 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Metrics::Exporter::BaseExporter do
+ let(:exporter) { described_class.new }
+ let(:log_filename) { File.join(Rails.root, 'log', 'sidekiq_exporter.log') }
+ let(:settings) { double('settings') }
+
+ before do
+ allow_any_instance_of(described_class).to receive(:log_filename).and_return(log_filename)
+ allow_any_instance_of(described_class).to receive(:settings).and_return(settings)
+ end
+
+ describe 'when exporter is enabled' do
+ before do
+ allow(::WEBrick::HTTPServer).to receive(:new).with(
+ Port: anything,
+ BindAddress: anything,
+ Logger: anything,
+ AccessLog: anything
+ ).and_call_original
+
+ allow(settings).to receive(:enabled).and_return(true)
+ allow(settings).to receive(:port).and_return(0)
+ allow(settings).to receive(:address).and_return('127.0.0.1')
+ end
+
+ after do
+ exporter.stop
+ end
+
+ describe 'when exporter is stopped' do
+ describe '#start' do
+ it 'starts the exporter' do
+ expect_any_instance_of(::WEBrick::HTTPServer).to receive(:start)
+
+ expect { exporter.start.join }.to change { exporter.thread? }.from(false).to(true)
+ end
+
+ describe 'with custom settings' do
+ let(:port) { 99999 }
+ let(:address) { 'sidekiq_exporter_address' }
+
+ before do
+ allow(settings).to receive(:port).and_return(port)
+ allow(settings).to receive(:address).and_return(address)
+ end
+
+ it 'starts server with port and address from settings' do
+ expect(::WEBrick::HTTPServer).to receive(:new).with(
+ Port: port,
+ BindAddress: address,
+ Logger: anything,
+ AccessLog: anything
+ ).and_wrap_original do |m, *args|
+ m.call(DoNotListen: true, Logger: args.first[:Logger])
+ end
+
+ allow_any_instance_of(::WEBrick::HTTPServer).to receive(:start)
+
+ exporter.start.join
+ end
+ end
+
+ describe 'when thread is not alive' do
+ it 'does close listeners' do
+ expect_any_instance_of(::WEBrick::HTTPServer).to receive(:start)
+ expect_any_instance_of(::WEBrick::HTTPServer).to receive(:listeners)
+ .and_call_original
+
+ expect { exporter.start.join }.to change { exporter.thread? }.from(false).to(true)
+
+ exporter.stop
+ end
+ end
+ end
+
+ describe '#stop' do
+ it "doesn't shutdown stopped server" do
+ expect_any_instance_of(::WEBrick::HTTPServer).not_to receive(:shutdown)
+
+ expect { exporter.stop }.not_to change { exporter.thread? }
+ end
+ end
+ end
+
+ describe 'when exporter is running' do
+ before do
+ exporter.start
+ end
+
+ describe '#start' do
+ it "doesn't start running server" do
+ expect_any_instance_of(::WEBrick::HTTPServer).not_to receive(:start)
+
+ expect { exporter.start }.not_to change { exporter.thread? }
+ end
+ end
+
+ describe '#stop' do
+ it 'shutdowns server' do
+ expect_any_instance_of(::WEBrick::HTTPServer).to receive(:shutdown)
+
+ expect { exporter.stop }.to change { exporter.thread? }.from(true).to(false)
+ end
+ end
+ end
+ end
+
+ describe 'request handling' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:method_class, :path, :http_status) do
+ Net::HTTP::Get | '/metrics' | 200
+ Net::HTTP::Get | '/liveness' | 200
+ Net::HTTP::Get | '/readiness' | 200
+ Net::HTTP::Get | '/' | 404
+ end
+
+ before do
+ allow(settings).to receive(:enabled).and_return(true)
+ allow(settings).to receive(:port).and_return(0)
+ allow(settings).to receive(:address).and_return('127.0.0.1')
+
+ # We want to wrap original method
+ # and run handling of requests
+ # in separate thread
+ allow_any_instance_of(::WEBrick::HTTPServer)
+ .to receive(:start).and_wrap_original do |m, *args|
+ Thread.new do
+ m.call(*args)
+ rescue IOError
+ # is raised as we close listeners
+ end
+ end
+
+ exporter.start.join
+ end
+
+ after do
+ exporter.stop
+ end
+
+ with_them do
+ let(:config) { exporter.server.config }
+ let(:request) { method_class.new(path) }
+
+ it 'responds with proper http_status' do
+ http = Net::HTTP.new(config[:BindAddress], config[:Port])
+ response = http.request(request)
+
+ expect(response.code).to eq(http_status.to_s)
+ end
+ end
+ end
+
+ describe 'when exporter is disabled' do
+ before do
+ allow(settings).to receive(:enabled).and_return(false)
+ end
+
+ describe '#start' do
+ it "doesn't start" do
+ expect_any_instance_of(::WEBrick::HTTPServer).not_to receive(:start)
+
+ expect(exporter.start).to be_nil
+ expect { exporter.start }.not_to change { exporter.thread? }
+ end
+ end
+
+ describe '#stop' do
+ it "doesn't shutdown" do
+ expect_any_instance_of(::WEBrick::HTTPServer).not_to receive(:shutdown)
+
+ expect { exporter.stop }.not_to change { exporter.thread? }
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/metrics/exporter/sidekiq_exporter_spec.rb b/spec/lib/gitlab/metrics/exporter/sidekiq_exporter_spec.rb
new file mode 100644
index 00000000000..a415b6407d5
--- /dev/null
+++ b/spec/lib/gitlab/metrics/exporter/sidekiq_exporter_spec.rb
@@ -0,0 +1,65 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Metrics::Exporter::SidekiqExporter do
+ let(:exporter) { described_class.new }
+
+ after do
+ exporter.stop
+ end
+
+ context 'with valid config' do
+ before do
+ stub_config(
+ monitoring: {
+ sidekiq_exporter: {
+ enabled: true,
+ port: 0,
+ address: '127.0.0.1'
+ }
+ }
+ )
+ end
+
+ it 'does start thread' do
+ expect(exporter.start).not_to be_nil
+ end
+ end
+
+ context 'when port is already taken' do
+ let(:first_exporter) { described_class.new }
+
+ before do
+ stub_config(
+ monitoring: {
+ sidekiq_exporter: {
+ enabled: true,
+ port: 9992,
+ address: '127.0.0.1'
+ }
+ }
+ )
+
+ first_exporter.start
+ end
+
+ after do
+ first_exporter.stop
+ end
+
+ it 'does print error message' do
+ expect(Sidekiq.logger).to receive(:error)
+ .with(
+ class: described_class.to_s,
+ message: 'Cannot start sidekiq_exporter',
+ exception: anything)
+
+ exporter.start
+ end
+
+ it 'does not start thread' do
+ expect(exporter.start).to be_nil
+ end
+ end
+end
diff --git a/spec/lib/gitlab/metrics/exporter/web_exporter_spec.rb b/spec/lib/gitlab/metrics/exporter/web_exporter_spec.rb
new file mode 100644
index 00000000000..99349934e63
--- /dev/null
+++ b/spec/lib/gitlab/metrics/exporter/web_exporter_spec.rb
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Metrics::Exporter::WebExporter do
+ let(:exporter) { described_class.new }
+
+ context 'when blackout seconds is used' do
+ let(:blackout_seconds) { 0 }
+ let(:readiness_probe) { exporter.send(:readiness_probe).execute }
+
+ before do
+ stub_config(
+ monitoring: {
+ web_exporter: {
+ enabled: true,
+ port: 0,
+ address: '127.0.0.1',
+ blackout_seconds: blackout_seconds
+ }
+ }
+ )
+
+ exporter.start
+ end
+
+ after do
+ exporter.stop
+ end
+
+ context 'when running server' do
+ it 'readiness probe returns succesful status' do
+ expect(readiness_probe.http_status).to eq(200)
+ expect(readiness_probe.json).to include(status: 'ok')
+ expect(readiness_probe.json).to include('web_exporter' => [{ 'status': 'ok' }])
+ end
+ end
+
+ context 'when blackout seconds is 10s' do
+ let(:blackout_seconds) { 10 }
+
+ it 'readiness probe returns a failure status' do
+ # during sleep we check the status of readiness probe
+ expect(exporter).to receive(:sleep).with(10) do
+ expect(readiness_probe.http_status).to eq(503)
+ expect(readiness_probe.json).to include(status: 'failed')
+ expect(readiness_probe.json).to include('web_exporter' => [{ 'status': 'failed' }])
+ end
+
+ exporter.stop
+ end
+ end
+
+ context 'when blackout is disabled' do
+ let(:blackout_seconds) { 0 }
+
+ it 'readiness probe returns a failure status' do
+ expect(exporter).not_to receive(:sleep)
+
+ exporter.stop
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb b/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb
index c29db3a93ec..66ea390a2bf 100644
--- a/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb
+++ b/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb
@@ -63,5 +63,19 @@ describe Gitlab::Metrics::RequestsRackMiddleware do
expect { subject.call(env) }.to raise_error(StandardError)
end
end
+
+ describe '.initialize_http_request_duration_seconds' do
+ it "sets labels" do
+ expected_labels = []
+ described_class::HTTP_METHODS.each do |method, statuses|
+ statuses.each do |status|
+ expected_labels << { method: method, status: status }
+ end
+ end
+
+ described_class.initialize_http_request_duration_seconds
+ expect(described_class.http_request_duration_seconds.values.keys).to include(*expected_labels)
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/metrics/samplers/puma_sampler_spec.rb b/spec/lib/gitlab/metrics/samplers/puma_sampler_spec.rb
index b8add3c1324..1097d26c320 100644
--- a/spec/lib/gitlab/metrics/samplers/puma_sampler_spec.rb
+++ b/spec/lib/gitlab/metrics/samplers/puma_sampler_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
describe Gitlab::Metrics::Samplers::PumaSampler do
subject { described_class.new(5) }
+
let(:null_metric) { double('null_metric', set: nil, observe: nil) }
before do
diff --git a/spec/lib/gitlab/metrics/sidekiq_metrics_exporter_spec.rb b/spec/lib/gitlab/metrics/sidekiq_metrics_exporter_spec.rb
deleted file mode 100644
index 9eea3eb79dc..00000000000
--- a/spec/lib/gitlab/metrics/sidekiq_metrics_exporter_spec.rb
+++ /dev/null
@@ -1,105 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe Gitlab::Metrics::SidekiqMetricsExporter do
- let(:exporter) { described_class.new }
- let(:server) { double('server') }
-
- before do
- allow(::WEBrick::HTTPServer).to receive(:new).and_return(server)
- allow(server).to receive(:mount)
- allow(server).to receive(:start)
- allow(server).to receive(:shutdown)
- end
-
- describe 'when exporter is enabled' do
- before do
- allow(Settings.monitoring.sidekiq_exporter).to receive(:enabled).and_return(true)
- end
-
- describe 'when exporter is stopped' do
- describe '#start' do
- it 'starts the exporter' do
- expect { exporter.start.join }.to change { exporter.thread? }.from(false).to(true)
-
- expect(server).to have_received(:start)
- end
-
- describe 'with custom settings' do
- let(:port) { 99999 }
- let(:address) { 'sidekiq_exporter_address' }
-
- before do
- allow(Settings.monitoring.sidekiq_exporter).to receive(:port).and_return(port)
- allow(Settings.monitoring.sidekiq_exporter).to receive(:address).and_return(address)
- end
-
- it 'starts server with port and address from settings' do
- exporter.start.join
-
- expect(::WEBrick::HTTPServer).to have_received(:new).with(
- Port: port,
- BindAddress: address,
- Logger: anything,
- AccessLog: anything
- )
- end
- end
- end
-
- describe '#stop' do
- it "doesn't shutdown stopped server" do
- expect { exporter.stop }.not_to change { exporter.thread? }
-
- expect(server).not_to have_received(:shutdown)
- end
- end
- end
-
- describe 'when exporter is running' do
- before do
- exporter.start.join
- end
-
- describe '#start' do
- it "doesn't start running server" do
- expect { exporter.start.join }.not_to change { exporter.thread? }
-
- expect(server).to have_received(:start).once
- end
- end
-
- describe '#stop' do
- it 'shutdowns server' do
- expect { exporter.stop }.to change { exporter.thread? }.from(true).to(false)
-
- expect(server).to have_received(:shutdown)
- end
- end
- end
- end
-
- describe 'when exporter is disabled' do
- before do
- allow(Settings.monitoring.sidekiq_exporter).to receive(:enabled).and_return(false)
- end
-
- describe '#start' do
- it "doesn't start" do
- expect(exporter.start).to be_nil
- expect { exporter.start }.not_to change { exporter.thread? }
-
- expect(server).not_to have_received(:start)
- end
- end
-
- describe '#stop' do
- it "doesn't shutdown" do
- expect { exporter.stop }.not_to change { exporter.thread? }
-
- expect(server).not_to have_received(:shutdown)
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/metrics/system_spec.rb b/spec/lib/gitlab/metrics/system_spec.rb
index 6d2764a06f2..a5aa80686fd 100644
--- a/spec/lib/gitlab/metrics/system_spec.rb
+++ b/spec/lib/gitlab/metrics/system_spec.rb
@@ -58,4 +58,44 @@ describe Gitlab::Metrics::System do
expect(described_class.monotonic_time).to be_an(Float)
end
end
+
+ describe '.thread_cpu_time' do
+ it 'returns cpu_time on supported platform' do
+ stub_const("Process::CLOCK_THREAD_CPUTIME_ID", 16)
+
+ expect(Process).to receive(:clock_gettime)
+ .with(16, kind_of(Symbol)) { 0.111222333 }
+
+ expect(described_class.thread_cpu_time).to eq(0.111222333)
+ end
+
+ it 'returns nil on unsupported platform' do
+ hide_const("Process::CLOCK_THREAD_CPUTIME_ID")
+
+ expect(described_class.thread_cpu_time).to be_nil
+ end
+ end
+
+ describe '.thread_cpu_duration' do
+ let(:start_time) { described_class.thread_cpu_time }
+
+ it 'returns difference between start and current time' do
+ stub_const("Process::CLOCK_THREAD_CPUTIME_ID", 16)
+
+ expect(Process).to receive(:clock_gettime)
+ .with(16, kind_of(Symbol))
+ .and_return(
+ 0.111222333,
+ 0.222333833
+ )
+
+ expect(described_class.thread_cpu_duration(start_time)).to eq(0.1111115)
+ end
+
+ it 'returns nil on unsupported platform' do
+ hide_const("Process::CLOCK_THREAD_CPUTIME_ID")
+
+ expect(described_class.thread_cpu_duration(start_time)).to be_nil
+ end
+ end
end
diff --git a/spec/lib/gitlab/metrics/transaction_spec.rb b/spec/lib/gitlab/metrics/transaction_spec.rb
index 45e74597a2e..08de2426c5a 100644
--- a/spec/lib/gitlab/metrics/transaction_spec.rb
+++ b/spec/lib/gitlab/metrics/transaction_spec.rb
@@ -27,6 +27,14 @@ describe Gitlab::Metrics::Transaction do
end
end
+ describe '#thread_cpu_duration' do
+ it 'returns the duration of a transaction in seconds' do
+ transaction.run { }
+
+ expect(transaction.thread_cpu_duration).to be > 0
+ end
+ end
+
describe '#allocated_memory' do
it 'returns the allocated memory in bytes' do
transaction.run { 'a' * 32 }
diff --git a/spec/lib/gitlab/middleware/read_only_spec.rb b/spec/lib/gitlab/middleware/read_only_spec.rb
index d2c8f4ab0bd..c7e9b38e3ca 100644
--- a/spec/lib/gitlab/middleware/read_only_spec.rb
+++ b/spec/lib/gitlab/middleware/read_only_spec.rb
@@ -103,6 +103,13 @@ describe Gitlab::Middleware::ReadOnly do
expect(subject).not_to disallow_request
end
+ it 'expects a graphql request to be allowed' do
+ response = request.post("/api/graphql")
+
+ expect(response).not_to be_redirect
+ expect(subject).not_to disallow_request
+ end
+
context 'sidekiq admin requests' do
where(:mounted_at) do
[
diff --git a/spec/lib/gitlab/pages_client_spec.rb b/spec/lib/gitlab/pages_client_spec.rb
deleted file mode 100644
index 84381843221..00000000000
--- a/spec/lib/gitlab/pages_client_spec.rb
+++ /dev/null
@@ -1,174 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe Gitlab::PagesClient do
- subject { described_class }
-
- describe '.token' do
- it 'returns the token as it is on disk' do
- pending 'add omnibus support for generating the secret file https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests/2466'
- expect(subject.token).to eq(File.read('.gitlab_pages_secret'))
- end
- end
-
- describe '.read_or_create_token' do
- subject { described_class.read_or_create_token }
- let(:token_path) { 'tmp/tests/gitlab-pages-secret' }
- before do
- allow(described_class).to receive(:token_path).and_return(token_path)
- FileUtils.rm_f(token_path)
- end
-
- it 'uses the existing token file if it exists' do
- secret = 'existing secret'
- File.write(token_path, secret)
-
- subject
- expect(described_class.token).to eq(secret)
- end
-
- it 'creates one if none exists' do
- pending 'add omnibus support for generating the secret file https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests/2466'
-
- old_token = described_class.token
- # sanity check
- expect(File.exist?(token_path)).to eq(false)
-
- subject
- expect(described_class.token.bytesize).to eq(64)
- expect(described_class.token).not_to eq(old_token)
- end
- end
-
- describe '.write_token' do
- let(:token_path) { 'tmp/tests/gitlab-pages-secret' }
- before do
- allow(described_class).to receive(:token_path).and_return(token_path)
- FileUtils.rm_f(token_path)
- end
-
- it 'writes the secret' do
- new_secret = 'hello new secret'
- expect(File.exist?(token_path)).to eq(false)
-
- described_class.send(:write_token, new_secret)
-
- expect(File.read(token_path)).to eq(new_secret)
- end
-
- it 'does nothing if the file already exists' do
- existing_secret = 'hello secret'
- File.write(token_path, existing_secret)
-
- described_class.send(:write_token, 'new secret')
-
- expect(File.read(token_path)).to eq(existing_secret)
- end
- end
-
- describe '.load_certificate' do
- subject { described_class.load_certificate }
- before do
- allow(described_class).to receive(:config).and_return(config)
- end
-
- context 'with no certificate in the config' do
- let(:config) { double(:config, certificate: '') }
-
- it 'does not set @certificate' do
- subject
-
- expect(described_class.certificate).to be_nil
- end
- end
-
- context 'with a certificate path in the config' do
- let(:certificate_path) { 'tmp/tests/fake-certificate' }
- let(:config) { double(:config, certificate: certificate_path) }
-
- it 'sets @certificate' do
- certificate_data = "--- BEGIN CERTIFICATE ---\nbla\n--- END CERTIFICATE ---\n"
- File.write(certificate_path, certificate_data)
- subject
-
- expect(described_class.certificate).to eq(certificate_data)
- end
- end
- end
-
- describe '.request_kwargs' do
- let(:token) { 'secret token' }
- let(:auth_header) { 'Bearer c2VjcmV0IHRva2Vu' }
- before do
- allow(described_class).to receive(:token).and_return(token)
- end
-
- context 'without timeout' do
- it { expect(subject.send(:request_kwargs, nil)[:metadata]['authorization']).to eq(auth_header) }
- end
-
- context 'with timeout' do
- let(:timeout) { 1.second }
-
- it 'still sets the authorization header' do
- expect(subject.send(:request_kwargs, timeout)[:metadata]['authorization']).to eq(auth_header)
- end
-
- it 'sets a deadline value' do
- now = Time.now
- deadline = subject.send(:request_kwargs, timeout)[:deadline]
-
- expect(deadline).to be_between(now, now + 2 * timeout)
- end
- end
- end
-
- describe '.stub' do
- before do
- allow(described_class).to receive(:address).and_return('unix:/foo/bar')
- end
-
- it { expect(subject.send(:stub, :health_check)).to be_a(Grpc::Health::V1::Health::Stub) }
- end
-
- describe '.address' do
- subject { described_class.send(:address) }
-
- before do
- allow(described_class).to receive(:config).and_return(config)
- end
-
- context 'with a unix: address' do
- let(:config) { double(:config, address: 'unix:/foo/bar') }
-
- it { expect(subject).to eq('unix:/foo/bar') }
- end
-
- context 'with a tcp:// address' do
- let(:config) { double(:config, address: 'tcp://localhost:1234') }
-
- it { expect(subject).to eq('localhost:1234') }
- end
- end
-
- describe '.grpc_creds' do
- subject { described_class.send(:grpc_creds) }
-
- before do
- allow(described_class).to receive(:config).and_return(config)
- end
-
- context 'with a unix: address' do
- let(:config) { double(:config, address: 'unix:/foo/bar') }
-
- it { expect(subject).to eq(:this_channel_is_insecure) }
- end
-
- context 'with a tcp:// address' do
- let(:config) { double(:config, address: 'tcp://localhost:1234') }
-
- it { expect(subject).to be_a(GRPC::Core::ChannelCredentials) }
- end
- end
-end
diff --git a/spec/lib/gitlab/patch/prependable_spec.rb b/spec/lib/gitlab/patch/prependable_spec.rb
index 725d733d176..255324f89d5 100644
--- a/spec/lib/gitlab/patch/prependable_spec.rb
+++ b/spec/lib/gitlab/patch/prependable_spec.rb
@@ -72,8 +72,8 @@ describe Gitlab::Patch::Prependable do
expect(subject.ancestors.take(3)).to eq([subject, ee, ce])
expect(subject.singleton_class.ancestors.take(3))
.to eq([subject.singleton_class,
- ee.const_get(:ClassMethods),
- ce.const_get(:ClassMethods)])
+ ee.const_get(:ClassMethods, false),
+ ce.const_get(:ClassMethods, false)])
end
it 'prepends only once even if called twice' do
@@ -115,8 +115,8 @@ describe Gitlab::Patch::Prependable do
it 'has the expected ancestors' do
expect(subject.ancestors.take(3)).to eq([ee, ce, subject])
expect(subject.singleton_class.ancestors.take(3))
- .to eq([ee.const_get(:ClassMethods),
- ce.const_get(:ClassMethods),
+ .to eq([ee.const_get(:ClassMethods, false),
+ ce.const_get(:ClassMethods, false),
subject.singleton_class])
end
@@ -152,7 +152,7 @@ describe Gitlab::Patch::Prependable do
it 'has the expected ancestors' do
expect(subject.ancestors.take(2)).to eq([ee, subject])
expect(subject.singleton_class.ancestors.take(2))
- .to eq([ee.const_get(:ClassMethods),
+ .to eq([ee.const_get(:ClassMethods, false),
subject.singleton_class])
end
diff --git a/spec/lib/gitlab/path_regex_spec.rb b/spec/lib/gitlab/path_regex_spec.rb
index 0829a2b4334..3cbcae4cdeb 100644
--- a/spec/lib/gitlab/path_regex_spec.rb
+++ b/spec/lib/gitlab/path_regex_spec.rb
@@ -108,7 +108,7 @@ describe Gitlab::PathRegex do
git = Gitlab.config.git.bin_path
tracked = `cd #{Rails.root} && #{git} ls-files public`
.split("\n")
- .map { |entry| entry.gsub('public/', '') }
+ .map { |entry| entry.start_with?('public/-/') ? '-' : entry.gsub('public/', '') }
.uniq
tracked + %w(assets uploads)
end
diff --git a/spec/lib/gitlab/phabricator_import/worker_state_spec.rb b/spec/lib/gitlab/phabricator_import/worker_state_spec.rb
index b6f2524a9d0..51514dd0ffd 100644
--- a/spec/lib/gitlab/phabricator_import/worker_state_spec.rb
+++ b/spec/lib/gitlab/phabricator_import/worker_state_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
describe Gitlab::PhabricatorImport::WorkerState, :clean_gitlab_redis_shared_state do
subject(:state) { described_class.new('weird-project-id') }
+
let(:key) { 'phabricator-import/jobs/project-weird-project-id/job-count' }
describe '#add_job' do
diff --git a/spec/lib/gitlab/reference_extractor_spec.rb b/spec/lib/gitlab/reference_extractor_spec.rb
index 7513dbeeb6f..6bc9b6365d1 100644
--- a/spec/lib/gitlab/reference_extractor_spec.rb
+++ b/spec/lib/gitlab/reference_extractor_spec.rb
@@ -259,13 +259,15 @@ describe Gitlab::ReferenceExtractor do
describe '.references_pattern' do
subject { described_class.references_pattern }
+
it { is_expected.to be_kind_of Regexp }
end
describe 'referables prefixes' do
def prefixes
described_class::REFERABLES.each_with_object({}) do |referable, result|
- klass = referable.to_s.camelize.constantize
+ class_name = referable.to_s.camelize
+ klass = class_name.constantize if Object.const_defined?(class_name)
next unless klass.respond_to?(:reference_prefix)
diff --git a/spec/lib/gitlab/regex_spec.rb b/spec/lib/gitlab/regex_spec.rb
index 3036e3a9754..b557baed258 100644
--- a/spec/lib/gitlab/regex_spec.rb
+++ b/spec/lib/gitlab/regex_spec.rb
@@ -64,4 +64,15 @@ describe Gitlab::Regex do
it { is_expected.not_to match('.my/image') }
it { is_expected.not_to match('my/image.') }
end
+
+ describe '.aws_account_id_regex' do
+ subject { described_class.aws_arn_regex }
+
+ it { is_expected.to match('arn:aws:iam::123456789012:role/role-name') }
+ it { is_expected.to match('arn:aws:s3:::bucket/key') }
+ it { is_expected.to match('arn:aws:ec2:us-east-1:123456789012:volume/vol-1') }
+ it { is_expected.to match('arn:aws:rds:us-east-1:123456789012:pg:prod') }
+ it { is_expected.not_to match('123456789012') }
+ it { is_expected.not_to match('role/role-name') }
+ end
end
diff --git a/spec/lib/gitlab/request_context_spec.rb b/spec/lib/gitlab/request_context_spec.rb
index a744f48da1f..cde12d4b310 100644
--- a/spec/lib/gitlab/request_context_spec.rb
+++ b/spec/lib/gitlab/request_context_spec.rb
@@ -5,6 +5,7 @@ require 'spec_helper'
describe Gitlab::RequestContext do
describe '#client_ip' do
subject { described_class.client_ip }
+
let(:app) { -> (env) {} }
let(:env) { Hash.new }
diff --git a/spec/lib/gitlab/sanitizers/exif_spec.rb b/spec/lib/gitlab/sanitizers/exif_spec.rb
index f882dbbdb5c..11e430e0be4 100644
--- a/spec/lib/gitlab/sanitizers/exif_spec.rb
+++ b/spec/lib/gitlab/sanitizers/exif_spec.rb
@@ -58,7 +58,7 @@ describe Gitlab::Sanitizers::Exif do
end
describe '#clean' do
- let(:uploader) { create(:upload, :with_file, :issuable_upload).build_uploader }
+ let(:uploader) { create(:upload, :with_file, :issuable_upload).retrieve_uploader }
context "no dry run" do
it "removes exif from the image" do
diff --git a/spec/lib/gitlab/search_results_spec.rb b/spec/lib/gitlab/search_results_spec.rb
index 26cba53502d..86dde15cc8a 100644
--- a/spec/lib/gitlab/search_results_spec.rb
+++ b/spec/lib/gitlab/search_results_spec.rb
@@ -57,8 +57,8 @@ describe Gitlab::SearchResults do
where(:count, :expected) do
23 | '23'
- 100 | '100'
- 101 | max_limited_count
+ 99 | '99'
+ 100 | max_limited_count
1234 | max_limited_count
end
diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb
index 55d8bac6c03..a17e9a31212 100644
--- a/spec/lib/gitlab/shell_spec.rb
+++ b/spec/lib/gitlab/shell_spec.rb
@@ -396,6 +396,7 @@ describe Gitlab::Shell do
describe 'namespace actions' do
subject { described_class.new }
+
let(:storage) { Gitlab.config.repositories.storages.keys.first }
describe '#add_namespace' do
@@ -422,6 +423,30 @@ describe Gitlab::Shell do
end
end
+ describe '#repository_exists?' do
+ context 'when the storage path does not exist' do
+ subject { described_class.new.repository_exists?(storage, "non-existing.git") }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when the repository does not exist' do
+ let(:project) { create(:project, :repository, :legacy_storage) }
+
+ subject { described_class.new.repository_exists?(storage, "#{project.repository.disk_path}-some-other-repo.git") }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when the repository exists' do
+ let(:project) { create(:project, :repository, :legacy_storage) }
+
+ subject { described_class.new.repository_exists?(storage, "#{project.repository.disk_path}.git") }
+
+ it { is_expected.to be_truthy }
+ end
+ end
+
describe '#remove' do
it 'removes the namespace' do
subject.add_namespace(storage, "mepmep")
diff --git a/spec/lib/gitlab/sidekiq_daemon/memory_killer_spec.rb b/spec/lib/gitlab/sidekiq_daemon/memory_killer_spec.rb
new file mode 100644
index 00000000000..45bcc71dfcb
--- /dev/null
+++ b/spec/lib/gitlab/sidekiq_daemon/memory_killer_spec.rb
@@ -0,0 +1,501 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::SidekiqDaemon::MemoryKiller do
+ let(:memory_killer) { described_class.new }
+ let(:pid) { 12345 }
+
+ before do
+ allow(Sidekiq.logger).to receive(:info)
+ allow(Sidekiq.logger).to receive(:warn)
+ allow(memory_killer).to receive(:pid).and_return(pid)
+
+ # make sleep no-op
+ allow(memory_killer).to receive(:sleep) {}
+ end
+
+ describe '#run_thread' do
+ subject { memory_killer.send(:run_thread) }
+
+ before do
+ # let enabled? return 3 times: true, true, false
+ allow(memory_killer).to receive(:enabled?).and_return(true, true, false)
+ end
+
+ context 'when structured logging is used' do
+ it 'logs start message once' do
+ expect(Sidekiq.logger).to receive(:info).once
+ .with(
+ class: described_class.to_s,
+ action: 'start',
+ pid: pid,
+ message: 'Starting Gitlab::SidekiqDaemon::MemoryKiller Daemon')
+
+ subject
+ end
+
+ it 'logs StandardError message twice' do
+ expect(Sidekiq.logger).to receive(:warn).twice
+ .with(
+ class: described_class.to_s,
+ pid: pid,
+ message: "Exception from run_thread: My Exception")
+
+ expect(memory_killer).to receive(:rss_within_range?)
+ .twice
+ .and_raise(StandardError, 'My Exception')
+
+ expect { subject }.not_to raise_exception
+ end
+
+ it 'logs exception message once and raise execption and log stop message' do
+ expect(Sidekiq.logger).to receive(:warn).once
+ .with(
+ class: described_class.to_s,
+ pid: pid,
+ message: "Exception from run_thread: My Exception")
+
+ expect(memory_killer).to receive(:rss_within_range?)
+ .once
+ .and_raise(Exception, 'My Exception')
+
+ expect(memory_killer).to receive(:sleep).with(Gitlab::SidekiqDaemon::MemoryKiller::CHECK_INTERVAL_SECONDS)
+ expect(Sidekiq.logger).to receive(:warn).once
+ .with(
+ class: described_class.to_s,
+ action: 'stop',
+ pid: pid,
+ message: 'Stopping Gitlab::SidekiqDaemon::MemoryKiller Daemon')
+
+ expect { subject }.to raise_exception
+ end
+
+ it 'logs stop message once' do
+ expect(Sidekiq.logger).to receive(:warn).once
+ .with(
+ class: described_class.to_s,
+ action: 'stop',
+ pid: pid,
+ message: 'Stopping Gitlab::SidekiqDaemon::MemoryKiller Daemon')
+
+ subject
+ end
+ end
+
+ it 'not invoke restart_sidekiq when rss in range' do
+ expect(memory_killer).to receive(:rss_within_range?)
+ .twice
+ .and_return(true)
+
+ expect(memory_killer).not_to receive(:restart_sidekiq)
+
+ subject
+ end
+
+ it 'invoke restart_sidekiq when rss not in range' do
+ expect(memory_killer).to receive(:rss_within_range?)
+ .at_least(:once)
+ .and_return(false)
+
+ expect(memory_killer).to receive(:restart_sidekiq)
+ .at_least(:once)
+
+ subject
+ end
+ end
+
+ describe '#stop_working' do
+ subject { memory_killer.send(:stop_working)}
+
+ it 'changes enable? to false' do
+ expect { subject }.to change { memory_killer.send(:enabled?) }
+ .from(true).to(false)
+ end
+ end
+
+ describe '#rss_within_range?' do
+ let(:shutdown_timeout_seconds) { 7 }
+ let(:check_interval_seconds) { 2 }
+ let(:grace_balloon_seconds) { 5 }
+
+ subject { memory_killer.send(:rss_within_range?) }
+
+ before do
+ stub_const("#{described_class}::SHUTDOWN_TIMEOUT_SECONDS", shutdown_timeout_seconds)
+ stub_const("#{described_class}::CHECK_INTERVAL_SECONDS", check_interval_seconds)
+ stub_const("#{described_class}::GRACE_BALLOON_SECONDS", grace_balloon_seconds)
+ allow(Process).to receive(:getpgrp).and_return(pid)
+ allow(Sidekiq).to receive(:options).and_return(timeout: 9)
+ end
+
+ it 'return true when everything is within limit' do
+ expect(memory_killer).to receive(:get_rss).and_return(100)
+ expect(memory_killer).to receive(:get_soft_limit_rss).and_return(200)
+ expect(memory_killer).to receive(:get_hard_limit_rss).and_return(300)
+
+ expect(memory_killer).to receive(:refresh_state)
+ .with(:running)
+ .and_call_original
+
+ expect(Gitlab::Metrics::System).to receive(:monotonic_time).and_call_original
+ expect(memory_killer).not_to receive(:log_rss_out_of_range)
+
+ expect(subject).to be true
+ end
+
+ it 'return false when rss exceeds hard_limit_rss' do
+ expect(memory_killer).to receive(:get_rss).at_least(:once).and_return(400)
+ expect(memory_killer).to receive(:get_soft_limit_rss).at_least(:once).and_return(200)
+ expect(memory_killer).to receive(:get_hard_limit_rss).at_least(:once).and_return(300)
+
+ expect(memory_killer).to receive(:refresh_state)
+ .with(:running)
+ .and_call_original
+
+ expect(memory_killer).to receive(:refresh_state)
+ .with(:above_soft_limit)
+ .and_call_original
+
+ expect(Gitlab::Metrics::System).to receive(:monotonic_time).and_call_original
+
+ expect(memory_killer).to receive(:log_rss_out_of_range).with(400, 300, 200)
+
+ expect(subject).to be false
+ end
+
+ it 'return false when rss exceed hard_limit_rss after a while' do
+ expect(memory_killer).to receive(:get_rss).and_return(250, 400, 400)
+ expect(memory_killer).to receive(:get_soft_limit_rss).at_least(:once).and_return(200)
+ expect(memory_killer).to receive(:get_hard_limit_rss).at_least(:once).and_return(300)
+
+ expect(memory_killer).to receive(:refresh_state)
+ .with(:running)
+ .and_call_original
+
+ expect(memory_killer).to receive(:refresh_state)
+ .at_least(:once)
+ .with(:above_soft_limit)
+ .and_call_original
+
+ expect(Gitlab::Metrics::System).to receive(:monotonic_time).twice.and_call_original
+ expect(memory_killer).to receive(:sleep).with(check_interval_seconds)
+ expect(memory_killer).to receive(:log_rss_out_of_range).with(400, 300, 200)
+
+ expect(subject).to be false
+ end
+
+ it 'return true when rss below soft_limit_rss after a while within GRACE_BALLOON_SECONDS' do
+ expect(memory_killer).to receive(:get_rss).and_return(250, 100)
+ expect(memory_killer).to receive(:get_soft_limit_rss).and_return(200, 200)
+ expect(memory_killer).to receive(:get_hard_limit_rss).and_return(300, 300)
+
+ expect(memory_killer).to receive(:refresh_state)
+ .with(:running)
+ .and_call_original
+
+ expect(memory_killer).to receive(:refresh_state)
+ .with(:above_soft_limit)
+ .and_call_original
+
+ expect(Gitlab::Metrics::System).to receive(:monotonic_time).twice.and_call_original
+ expect(memory_killer).to receive(:sleep).with(check_interval_seconds)
+
+ expect(memory_killer).not_to receive(:log_rss_out_of_range)
+
+ expect(subject).to be true
+ end
+
+ context 'when exceeding GRACE_BALLOON_SECONDS' do
+ let(:grace_balloon_seconds) { 0 }
+
+ it 'return false when rss exceed soft_limit_rss' do
+ allow(memory_killer).to receive(:get_rss).and_return(250)
+ allow(memory_killer).to receive(:get_soft_limit_rss).and_return(200)
+ allow(memory_killer).to receive(:get_hard_limit_rss).and_return(300)
+
+ expect(memory_killer).to receive(:refresh_state)
+ .with(:running)
+ .and_call_original
+
+ expect(memory_killer).to receive(:refresh_state)
+ .with(:above_soft_limit)
+ .and_call_original
+
+ expect(memory_killer).to receive(:log_rss_out_of_range)
+ .with(250, 300, 200)
+
+ expect(subject).to be false
+ end
+ end
+ end
+
+ describe '#restart_sidekiq' do
+ let(:shutdown_timeout_seconds) { 7 }
+
+ subject { memory_killer.send(:restart_sidekiq) }
+
+ before do
+ stub_const("#{described_class}::SHUTDOWN_TIMEOUT_SECONDS", shutdown_timeout_seconds)
+ allow(Sidekiq).to receive(:options).and_return(timeout: 9)
+ allow(memory_killer).to receive(:get_rss).and_return(100)
+ allow(memory_killer).to receive(:get_soft_limit_rss).and_return(200)
+ allow(memory_killer).to receive(:get_hard_limit_rss).and_return(300)
+ end
+
+ it 'send signal' do
+ expect(memory_killer).to receive(:refresh_state)
+ .with(:stop_fetching_new_jobs)
+ .ordered
+ .and_call_original
+ expect(memory_killer).to receive(:signal_and_wait)
+ .with(shutdown_timeout_seconds, 'SIGTSTP', 'stop fetching new jobs')
+ .ordered
+
+ expect(memory_killer).to receive(:refresh_state)
+ .with(:shutting_down)
+ .ordered
+ .and_call_original
+ expect(memory_killer).to receive(:signal_and_wait)
+ .with(11, 'SIGTERM', 'gracefully shut down')
+ .ordered
+
+ expect(memory_killer).to receive(:refresh_state)
+ .with(:killing_sidekiq)
+ .ordered
+ .and_call_original
+ expect(memory_killer).to receive(:signal_pgroup)
+ .with('SIGKILL', 'die')
+ .ordered
+
+ subject
+ end
+ end
+
+ describe '#signal_and_wait' do
+ let(:time) { 0 }
+ let(:signal) { 'my-signal' }
+ let(:explanation) { 'my-explanation' }
+ let(:check_interval_seconds) { 2 }
+
+ subject { memory_killer.send(:signal_and_wait, time, signal, explanation) }
+
+ before do
+ stub_const("#{described_class}::CHECK_INTERVAL_SECONDS", check_interval_seconds)
+ end
+
+ it 'send signal and return when all jobs finished' do
+ expect(Process).to receive(:kill).with(signal, pid).ordered
+ expect(Gitlab::Metrics::System).to receive(:monotonic_time).and_call_original
+
+ expect(memory_killer).to receive(:enabled?).and_return(true)
+ expect(memory_killer).to receive(:any_jobs?).and_return(false)
+
+ expect(memory_killer).not_to receive(:sleep)
+
+ subject
+ end
+
+ it 'send signal and wait till deadline if any job not finished' do
+ expect(Process).to receive(:kill)
+ .with(signal, pid)
+ .ordered
+
+ expect(Gitlab::Metrics::System).to receive(:monotonic_time)
+ .and_call_original
+ .at_least(:once)
+
+ expect(memory_killer).to receive(:enabled?).and_return(true).at_least(:once)
+ expect(memory_killer).to receive(:any_jobs?).and_return(true).at_least(:once)
+
+ subject
+ end
+ end
+
+ describe '#signal_pgroup' do
+ let(:signal) { 'my-signal' }
+ let(:explanation) { 'my-explanation' }
+
+ subject { memory_killer.send(:signal_pgroup, signal, explanation) }
+
+ it 'send signal to this proces if it is not group leader' do
+ expect(Process).to receive(:getpgrp).and_return(pid + 1)
+
+ expect(Sidekiq.logger).to receive(:warn).once
+ .with(
+ class: described_class.to_s,
+ signal: signal,
+ pid: pid,
+ message: "sending Sidekiq worker PID-#{pid} #{signal} (#{explanation})")
+ expect(Process).to receive(:kill).with(signal, pid).ordered
+
+ subject
+ end
+
+ it 'send signal to whole process group as group leader' do
+ expect(Process).to receive(:getpgrp).and_return(pid)
+
+ expect(Sidekiq.logger).to receive(:warn).once
+ .with(
+ class: described_class.to_s,
+ signal: signal,
+ pid: pid,
+ message: "sending Sidekiq worker PGRP-#{pid} #{signal} (#{explanation})")
+ expect(Process).to receive(:kill).with(signal, 0).ordered
+
+ subject
+ end
+ end
+
+ describe '#log_rss_out_of_range' do
+ let(:current_rss) { 100 }
+ let(:soft_limit_rss) { 200 }
+ let(:hard_limit_rss) { 300 }
+ let(:reason) { 'rss out of range reason description' }
+
+ subject { memory_killer.send(:log_rss_out_of_range, current_rss, hard_limit_rss, soft_limit_rss) }
+
+ it 'invoke sidekiq logger warn' do
+ expect(memory_killer).to receive(:out_of_range_description).with(current_rss, hard_limit_rss, soft_limit_rss).and_return(reason)
+ expect(Sidekiq.logger).to receive(:warn)
+ .with(
+ class: described_class.to_s,
+ pid: pid,
+ message: 'Sidekiq worker RSS out of range',
+ current_rss: current_rss,
+ hard_limit_rss: hard_limit_rss,
+ soft_limit_rss: soft_limit_rss,
+ reason: reason)
+
+ subject
+ end
+ end
+
+ describe '#out_of_range_description' do
+ let(:hard_limit) { 300 }
+ let(:soft_limit) { 200 }
+ let(:grace_balloon_seconds) { 12 }
+
+ subject { memory_killer.send(:out_of_range_description, rss, hard_limit, soft_limit) }
+
+ context 'when rss > hard_limit' do
+ let(:rss) { 400 }
+
+ it 'tells reason' do
+ expect(subject).to eq("current_rss(#{rss}) > hard_limit_rss(#{hard_limit})")
+ end
+ end
+
+ context 'when rss <= hard_limit' do
+ let(:rss) { 300 }
+
+ it 'tells reason' do
+ stub_const("#{described_class}::GRACE_BALLOON_SECONDS", grace_balloon_seconds)
+ expect(subject).to eq("current_rss(#{rss}) > soft_limit_rss(#{soft_limit}) longer than GRACE_BALLOON_SECONDS(#{grace_balloon_seconds})")
+ end
+ end
+ end
+
+ describe '#rss_increase_by_jobs' do
+ let(:running_jobs) { { id1: 'job1', id2: 'job2' } }
+
+ subject { memory_killer.send(:rss_increase_by_jobs) }
+
+ it 'adds up individual rss_increase_by_job' do
+ expect(Gitlab::SidekiqDaemon::Monitor).to receive_message_chain(:instance, :jobs).and_return(running_jobs)
+ expect(memory_killer).to receive(:rss_increase_by_job).and_return(11, 22)
+ expect(subject).to eq(33)
+ end
+
+ it 'return 0 if no job' do
+ expect(Gitlab::SidekiqDaemon::Monitor).to receive_message_chain(:instance, :jobs).and_return({})
+ expect(subject).to eq(0)
+ end
+ end
+
+ describe '#rss_increase_by_job' do
+ let(:worker_class) { Chaos::SleepWorker }
+ let(:job) { { worker_class: worker_class, started_at: 321 } }
+ let(:max_memory_kb) { 100000 }
+
+ subject { memory_killer.send(:rss_increase_by_job, job) }
+
+ before do
+ stub_const("#{described_class}::DEFAULT_MAX_MEMORY_GROWTH_KB", max_memory_kb)
+ end
+
+ it 'return 0 if memory_growth_kb return 0' do
+ expect(memory_killer).to receive(:get_job_options).with(job, 'memory_killer_memory_growth_kb', 0).and_return(0)
+ expect(memory_killer).to receive(:get_job_options).with(job, 'memory_killer_max_memory_growth_kb', max_memory_kb).and_return(0)
+
+ expect(Time).not_to receive(:now)
+ expect(subject).to eq(0)
+ end
+
+ it 'return time factored growth value when it does not exceed max growth limit for whilited job' do
+ expect(memory_killer).to receive(:get_job_options).with(job, 'memory_killer_memory_growth_kb', 0).and_return(10)
+ expect(memory_killer).to receive(:get_job_options).with(job, 'memory_killer_max_memory_growth_kb', max_memory_kb).and_return(100)
+
+ expect(Gitlab::Metrics::System).to receive(:monotonic_time).and_return(323)
+ expect(subject).to eq(20)
+ end
+
+ it 'return max growth limit when time factored growth value exceed max growth limit for whilited job' do
+ expect(memory_killer).to receive(:get_job_options).with(job, 'memory_killer_memory_growth_kb', 0).and_return(10)
+ expect(memory_killer).to receive(:get_job_options).with(job, 'memory_killer_max_memory_growth_kb', max_memory_kb).and_return(100)
+
+ expect(Gitlab::Metrics::System).to receive(:monotonic_time).and_return(332)
+ expect(subject).to eq(100)
+ end
+ end
+
+ describe '#get_job_options' do
+ let(:worker_class) { Chaos::SleepWorker }
+ let(:job) { { worker_class: worker_class, started_at: 321 } }
+ let(:key) { 'my-key' }
+ let(:default) { 'my-default' }
+
+ subject { memory_killer.send(:get_job_options, job, key, default) }
+
+ it 'return default if key is not defined' do
+ expect(worker_class).to receive(:sidekiq_options).and_return({ "retry" => 5 })
+
+ expect(subject).to eq(default)
+ end
+
+ it 'return default if get StandardError when retrieve sidekiq_options' do
+ expect(worker_class).to receive(:sidekiq_options).and_raise(StandardError)
+
+ expect(subject).to eq(default)
+ end
+
+ it 'return right value if sidekiq_options has the key' do
+ expect(worker_class).to receive(:sidekiq_options).and_return({ key => 10 })
+
+ expect(subject).to eq(10)
+ end
+ end
+
+ describe '#refresh_state' do
+ let(:metrics) { memory_killer.instance_variable_get(:@metrics) }
+
+ subject { memory_killer.send(:refresh_state, :shutting_down) }
+
+ it 'calls gitlab metrics gauge set methods' do
+ expect(memory_killer).to receive(:get_rss) { 1010 }
+ expect(memory_killer).to receive(:get_soft_limit_rss) { 1020 }
+ expect(memory_killer).to receive(:get_hard_limit_rss) { 1040 }
+
+ expect(metrics[:sidekiq_memory_killer_phase]).to receive(:set)
+ .with({}, described_class::PHASE[:shutting_down])
+ expect(metrics[:sidekiq_current_rss]).to receive(:set)
+ .with({}, 1010)
+ expect(metrics[:sidekiq_memory_killer_soft_limit_rss]).to receive(:set)
+ .with({}, 1020)
+ expect(metrics[:sidekiq_memory_killer_hard_limit_rss]).to receive(:set)
+ .with({}, 1040)
+
+ subject
+ end
+ end
+end
diff --git a/spec/lib/gitlab/sidekiq_daemon/monitor_spec.rb b/spec/lib/gitlab/sidekiq_daemon/monitor_spec.rb
index acbb09e3542..3f49ef0e9a7 100644
--- a/spec/lib/gitlab/sidekiq_daemon/monitor_spec.rb
+++ b/spec/lib/gitlab/sidekiq_daemon/monitor_spec.rb
@@ -8,12 +8,12 @@ describe Gitlab::SidekiqDaemon::Monitor do
describe '#within_job' do
it 'tracks thread' do
blk = proc do
- expect(monitor.jobs_thread['jid']).not_to be_nil
+ expect(monitor.jobs.dig('jid', :thread)).not_to be_nil
"OK"
end
- expect(monitor.within_job('jid', 'queue', &blk)).to eq("OK")
+ expect(monitor.within_job('worker_class', 'jid', 'queue', &blk)).to eq("OK")
end
context 'when job is canceled' do
@@ -25,26 +25,42 @@ describe Gitlab::SidekiqDaemon::Monitor do
it 'does not execute a block' do
expect do |blk|
- monitor.within_job(jid, 'queue', &blk)
+ monitor.within_job('worker_class', jid, 'queue', &blk)
rescue described_class::CancelledError
end.not_to yield_control
end
it 'raises exception' do
- expect { monitor.within_job(jid, 'queue') }.to raise_error(
+ expect { monitor.within_job('worker_class', jid, 'queue') }.to raise_error(
described_class::CancelledError)
end
end
end
- describe '#start_working' do
- subject { monitor.send(:start_working) }
+ describe '#run_thread when notification channel not enabled' do
+ subject { monitor.send(:run_thread) }
+
+ it 'return directly' do
+ allow(monitor).to receive(:notification_channel_enabled?).and_return(nil)
+
+ expect(Sidekiq.logger).not_to receive(:info)
+ expect(Sidekiq.logger).not_to receive(:warn)
+ expect(monitor).not_to receive(:enabled?)
+ expect(monitor).not_to receive(:process_messages)
+
+ subject
+ end
+ end
+
+ describe '#run_thread when notification channel enabled' do
+ subject { monitor.send(:run_thread) }
before do
# we want to run at most once cycle
# we toggle `enabled?` flag after the first call
stub_const('Gitlab::SidekiqDaemon::Monitor::RECONNECT_TIME', 0)
allow(monitor).to receive(:enabled?).and_return(true, false)
+ allow(monitor).to receive(:notification_channel_enabled?).and_return(1)
allow(Sidekiq.logger).to receive(:info)
allow(Sidekiq.logger).to receive(:warn)
@@ -204,7 +220,7 @@ describe Gitlab::SidekiqDaemon::Monitor do
let(:thread) { Thread.new { sleep 1000 } }
before do
- monitor.jobs_thread[jid] = thread
+ monitor.jobs[jid] = { worker_class: 'worker_class', thread: thread, started_at: Time.now.to_i }
end
after do
@@ -258,4 +274,24 @@ describe Gitlab::SidekiqDaemon::Monitor do
subject
end
end
+
+ describe '#notification_channel_enabled?' do
+ subject { monitor.send(:notification_channel_enabled?) }
+
+ it 'return nil when SIDEKIQ_MONITOR_WORKER is not set' do
+ expect(subject).to be nil
+ end
+
+ it 'return nil when SIDEKIQ_MONITOR_WORKER set to 0' do
+ allow(ENV).to receive(:fetch).with('SIDEKIQ_MONITOR_WORKER', 0).and_return("0")
+
+ expect(subject).to be nil
+ end
+
+ it 'return 1 when SIDEKIQ_MONITOR_WORKER set to 1' do
+ allow(ENV).to receive(:fetch).with('SIDEKIQ_MONITOR_WORKER', 0).and_return("1")
+
+ expect(subject).to be 1
+ end
+ end
end
diff --git a/spec/lib/gitlab/sidekiq_logging/exception_handler_spec.rb b/spec/lib/gitlab/sidekiq_logging/exception_handler_spec.rb
new file mode 100644
index 00000000000..24b6090cb19
--- /dev/null
+++ b/spec/lib/gitlab/sidekiq_logging/exception_handler_spec.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::SidekiqLogging::ExceptionHandler do
+ describe '#call' do
+ let(:job) do
+ {
+ "class" => "TestWorker",
+ "args" => [1234, 'hello'],
+ "retry" => false,
+ "queue" => "cronjob:test_queue",
+ "queue_namespace" => "cronjob",
+ "jid" => "da883554ee4fe414012f5f42",
+ "correlation_id" => 'cid'
+ }
+ end
+
+ let(:exception_message) { 'An error was thrown' }
+ let(:backtrace) { caller }
+ let(:exception) { RuntimeError.new(exception_message) }
+ let(:logger) { double }
+
+ before do
+ allow(Sidekiq).to receive(:logger).and_return(logger)
+ allow(exception).to receive(:backtrace).and_return(backtrace)
+ end
+
+ subject { described_class.new.call(exception, { context: 'Test', job: job }) }
+
+ it 'logs job data into root tree' do
+ expected_data = job.merge(
+ error_class: 'RuntimeError',
+ error_message: exception_message,
+ context: 'Test',
+ error_backtrace: Gitlab::Profiler.clean_backtrace(backtrace)
+ )
+
+ expect(logger).to receive(:warn).with(expected_data)
+
+ subject
+ end
+ end
+end
diff --git a/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb b/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb
index 1b89c094a6b..46fbc069efb 100644
--- a/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb
+++ b/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb
@@ -23,13 +23,15 @@ describe Gitlab::SidekiqLogging::StructuredLogger do
end
let(:logger) { double }
+ let(:clock_thread_cputime_start) { 0.222222299 }
+ let(:clock_thread_cputime_end) { 1.333333799 }
let(:start_payload) do
job.merge(
'message' => 'TestWorker JID-da883554ee4fe414012f5f42: start',
'job_status' => 'start',
'pid' => Process.pid,
- 'created_at' => created_at.iso8601(3),
- 'enqueued_at' => created_at.iso8601(3),
+ 'created_at' => created_at.iso8601(6),
+ 'enqueued_at' => created_at.iso8601(6),
'scheduling_latency_s' => scheduling_latency_s
)
end
@@ -38,16 +40,15 @@ describe Gitlab::SidekiqLogging::StructuredLogger do
'message' => 'TestWorker JID-da883554ee4fe414012f5f42: done: 0.0 sec',
'job_status' => 'done',
'duration' => 0.0,
- "completed_at" => timestamp.iso8601(3),
- "system_s" => 0.0,
- "user_s" => 0.0
+ "completed_at" => timestamp.iso8601(6),
+ "cpu_s" => 1.111112
)
end
let(:exception_payload) do
end_payload.merge(
'message' => 'TestWorker JID-da883554ee4fe414012f5f42: fail: 0.0 sec',
'job_status' => 'fail',
- 'error' => ArgumentError,
+ 'error_class' => 'ArgumentError',
'error_message' => 'some exception'
)
end
@@ -57,12 +58,7 @@ describe Gitlab::SidekiqLogging::StructuredLogger do
allow(subject).to receive(:current_time).and_return(timestamp.to_f)
- allow(Process).to receive(:times).and_return(
- stime: 0.0,
- utime: 0.0,
- cutime: 0.0,
- cstime: 0.0
- )
+ allow(Process).to receive(:clock_gettime).with(Process::CLOCK_THREAD_CPUTIME_ID).and_return(clock_thread_cputime_start, clock_thread_cputime_end)
end
subject { described_class.new }
@@ -86,7 +82,6 @@ describe Gitlab::SidekiqLogging::StructuredLogger do
it 'logs an exception in job' do
Timecop.freeze(timestamp) do
expect(logger).to receive(:info).with(start_payload)
- # This excludes the exception_backtrace
expect(logger).to receive(:warn).with(hash_including(exception_payload))
expect(subject).to receive(:log_job_start).and_call_original
expect(subject).to receive(:log_job_done).and_call_original
@@ -188,31 +183,22 @@ describe Gitlab::SidekiqLogging::StructuredLogger do
end
end
end
+ end
- def ctime(times)
- times[:cstime] + times[:cutime]
- end
+ describe '#add_time_keys!' do
+ let(:time) { { duration: 0.1231234, cputime: 1.2342345 } }
+ let(:payload) { { 'class' => 'my-class', 'message' => 'my-message', 'job_status' => 'my-job-status' } }
+ let(:current_utc_time) { '2019-09-23 10:00:58 UTC' }
+ let(:payload_with_time_keys) { { 'class' => 'my-class', 'message' => 'my-message', 'job_status' => 'my-job-status', 'duration' => 0.123123, 'cpu_s' => 1.234235, 'completed_at' => current_utc_time } }
- context 'with ctime value greater than 0' do
- let(:times_start) { { stime: 0.04999, utime: 0.0483, cstime: 0.0188, cutime: 0.0188 } }
- let(:times_end) { { stime: 0.0699, utime: 0.0699, cstime: 0.0399, cutime: 0.0399 } }
+ subject { described_class.new }
- before do
- end_payload['system_s'] = 0.02
- end_payload['user_s'] = 0.022
- end_payload['child_s'] = 0.042
+ it 'update payload correctly' do
+ expect(Time).to receive_message_chain(:now, :utc).and_return(current_utc_time)
- allow(Process).to receive(:times).and_return(times_start, times_end)
- end
+ subject.send(:add_time_keys!, time, payload)
- it 'logs with ctime data and other cpu data' do
- Timecop.freeze(timestamp) do
- expect(logger).to receive(:info).with(start_payload.except('args')).ordered
- expect(logger).to receive(:info).with(end_payload.except('args')).ordered
-
- subject.call(job, 'test_queue') { }
- end
- end
+ expect(payload).to eq(payload_with_time_keys)
end
end
end
diff --git a/spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb b/spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb
index bf3bc8e1add..b5be43ec96c 100644
--- a/spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
describe Gitlab::SidekiqMiddleware::MemoryKiller do
subject { described_class.new }
+
let(:pid) { 999 }
let(:worker) { double(:worker, class: ProjectCacheWorker) }
diff --git a/spec/lib/gitlab/sidekiq_middleware/metrics_spec.rb b/spec/lib/gitlab/sidekiq_middleware/metrics_spec.rb
index ac97a5ebd15..806112fcb16 100644
--- a/spec/lib/gitlab/sidekiq_middleware/metrics_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/metrics_spec.rb
@@ -8,12 +8,14 @@ describe Gitlab::SidekiqMiddleware::Metrics do
let(:worker) { double(:worker) }
let(:completion_seconds_metric) { double('completion seconds metric') }
+ let(:user_execution_seconds_metric) { double('user execution seconds metric') }
let(:failed_total_metric) { double('failed total metric') }
let(:retried_total_metric) { double('retried total metric') }
let(:running_jobs_metric) { double('running jobs metric') }
before do
allow(Gitlab::Metrics).to receive(:histogram).with(:sidekiq_jobs_completion_seconds, anything, anything, anything).and_return(completion_seconds_metric)
+ allow(Gitlab::Metrics).to receive(:histogram).with(:sidekiq_jobs_cpu_seconds, anything, anything, anything).and_return(user_execution_seconds_metric)
allow(Gitlab::Metrics).to receive(:counter).with(:sidekiq_jobs_failed_total, anything).and_return(failed_total_metric)
allow(Gitlab::Metrics).to receive(:counter).with(:sidekiq_jobs_retried_total, anything).and_return(retried_total_metric)
allow(Gitlab::Metrics).to receive(:gauge).with(:sidekiq_running_jobs, anything, {}, :livesum).and_return(running_jobs_metric)
@@ -23,13 +25,16 @@ describe Gitlab::SidekiqMiddleware::Metrics do
it 'yields block' do
allow(completion_seconds_metric).to receive(:observe)
+ allow(user_execution_seconds_metric).to receive(:observe)
expect { |b| middleware.call(worker, {}, :test, &b) }.to yield_control.once
end
it 'sets metrics' do
labels = { queue: :test }
+ allow(middleware).to receive(:get_thread_cputime).and_return(1, 3)
+ expect(user_execution_seconds_metric).to receive(:observe).with(labels, 2)
expect(running_jobs_metric).to receive(:increment).with(labels, 1)
expect(running_jobs_metric).to receive(:increment).with(labels, -1)
expect(completion_seconds_metric).to receive(:observe).with(labels, kind_of(Numeric))
@@ -37,9 +42,17 @@ describe Gitlab::SidekiqMiddleware::Metrics do
middleware.call(worker, {}, :test) { nil }
end
+ it 'ignore user execution when measured 0' do
+ allow(completion_seconds_metric).to receive(:observe)
+ allow(middleware).to receive(:get_thread_cputime).and_return(0, 0)
+
+ expect(user_execution_seconds_metric).not_to receive(:observe)
+ end
+
context 'when job is retried' do
it 'sets sidekiq_jobs_retried_total metric' do
allow(completion_seconds_metric).to receive(:observe)
+ expect(user_execution_seconds_metric).to receive(:observe)
expect(retried_total_metric).to receive(:increment)
diff --git a/spec/lib/gitlab/sidekiq_middleware/monitor_spec.rb b/spec/lib/gitlab/sidekiq_middleware/monitor_spec.rb
index 023df1a6391..398144025ea 100644
--- a/spec/lib/gitlab/sidekiq_middleware/monitor_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/monitor_spec.rb
@@ -12,7 +12,7 @@ describe Gitlab::SidekiqMiddleware::Monitor do
it 'calls Gitlab::SidekiqDaemon::Monitor' do
expect(Gitlab::SidekiqDaemon::Monitor.instance).to receive(:within_job)
- .with('job-id', 'my-queue')
+ .with(anything, 'job-id', 'my-queue')
.and_call_original
expect { |blk| monitor.call(worker, job, queue, &blk) }.to yield_control
diff --git a/spec/lib/gitlab/slash_commands/presenters/access_spec.rb b/spec/lib/gitlab/slash_commands/presenters/access_spec.rb
index f00039c634f..c7b83467660 100644
--- a/spec/lib/gitlab/slash_commands/presenters/access_spec.rb
+++ b/spec/lib/gitlab/slash_commands/presenters/access_spec.rb
@@ -3,6 +3,13 @@
require 'spec_helper'
describe Gitlab::SlashCommands::Presenters::Access do
+ shared_examples_for 'displays an error message' do
+ it do
+ expect(subject[:text]).to match(error_message)
+ expect(subject[:response_type]).to be(:ephemeral)
+ end
+ end
+
describe '#access_denied' do
let(:project) { build(:project) }
@@ -10,9 +17,18 @@ describe Gitlab::SlashCommands::Presenters::Access do
it { is_expected.to be_a(Hash) }
- it 'displays an error message' do
- expect(subject[:text]).to match('are not allowed')
- expect(subject[:response_type]).to be(:ephemeral)
+ it_behaves_like 'displays an error message' do
+ let(:error_message) { 'you do not have access to the GitLab project' }
+ end
+ end
+
+ describe '#deactivated' do
+ subject { described_class.new.deactivated }
+
+ it { is_expected.to be_a(Hash) }
+
+ it_behaves_like 'displays an error message' do
+ let(:error_message) { 'your account has been deactivated by your administrator' }
end
end
diff --git a/spec/lib/gitlab/snippet_search_results_spec.rb b/spec/lib/gitlab/snippet_search_results_spec.rb
index d3353b76c15..47f26fdebe2 100644
--- a/spec/lib/gitlab/snippet_search_results_spec.rb
+++ b/spec/lib/gitlab/snippet_search_results_spec.rb
@@ -6,18 +6,17 @@ describe Gitlab::SnippetSearchResults do
include SearchHelpers
let!(:snippet) { create(:snippet, content: 'foo', file_name: 'foo') }
-
- let(:results) { described_class.new(Snippet.all, 'foo') }
+ let(:results) { described_class.new(snippet.author, 'foo') }
describe '#snippet_titles_count' do
it 'returns the amount of matched snippet titles' do
- expect(results.snippet_titles_count).to eq(1)
+ expect(results.limited_snippet_titles_count).to eq(1)
end
end
describe '#snippet_blobs_count' do
it 'returns the amount of matched snippet blobs' do
- expect(results.snippet_blobs_count).to eq(1)
+ expect(results.limited_snippet_blobs_count).to eq(1)
end
end
@@ -25,10 +24,10 @@ describe Gitlab::SnippetSearchResults do
using RSpec::Parameterized::TableSyntax
where(:scope, :count_method, :expected) do
- 'snippet_titles' | :snippet_titles_count | '1234'
- 'snippet_blobs' | :snippet_blobs_count | '1234'
- 'projects' | :limited_projects_count | max_limited_count
- 'unknown' | nil | nil
+ 'snippet_titles' | :limited_snippet_titles_count | max_limited_count
+ 'snippet_blobs' | :limited_snippet_blobs_count | max_limited_count
+ 'projects' | :limited_projects_count | max_limited_count
+ 'unknown' | nil | nil
end
with_them do
diff --git a/spec/lib/gitlab/submodule_links_spec.rb b/spec/lib/gitlab/submodule_links_spec.rb
index d4420c5b513..f0c8825de74 100644
--- a/spec/lib/gitlab/submodule_links_spec.rb
+++ b/spec/lib/gitlab/submodule_links_spec.rb
@@ -8,7 +8,9 @@ describe Gitlab::SubmoduleLinks do
let(:links) { described_class.new(repo) }
describe '#for' do
- subject { links.for(submodule_item, 'ref') }
+ let(:ref) { 'ref' }
+
+ subject { links.for(submodule_item, ref) }
context 'when there is no .gitmodules file' do
before do
@@ -35,8 +37,20 @@ describe Gitlab::SubmoduleLinks do
stub_urls({ 'gitlab-foss' => 'git@gitlab.com:gitlab-org/gitlab-foss.git' })
end
- it 'returns links' do
+ it 'returns links and caches the by ref' do
expect(subject).to eq(['https://gitlab.com/gitlab-org/gitlab-foss', 'https://gitlab.com/gitlab-org/gitlab-foss/tree/hash'])
+
+ cache_store = links.instance_variable_get("@cache_store")
+
+ expect(cache_store[ref]).to eq({ "gitlab-foss" => "git@gitlab.com:gitlab-org/gitlab-foss.git" })
+ end
+
+ context 'when ref name contains a dash' do
+ let(:ref) { 'signed-commits' }
+
+ it 'returns links' do
+ expect(subject).to eq(['https://gitlab.com/gitlab-org/gitlab-foss', 'https://gitlab.com/gitlab-org/gitlab-foss/tree/hash'])
+ end
end
end
end
diff --git a/spec/lib/gitlab/tracking/incident_management_spec.rb b/spec/lib/gitlab/tracking/incident_management_spec.rb
new file mode 100644
index 00000000000..6f7e04b7c16
--- /dev/null
+++ b/spec/lib/gitlab/tracking/incident_management_spec.rb
@@ -0,0 +1,78 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Tracking::IncidentManagement do
+ describe '.track_from_params' do
+ shared_examples 'a tracked event' do |label, value = nil|
+ it 'creates the tracking event with the correct details' do
+ expect(::Gitlab::Tracking)
+ .to receive(:event)
+ .with(
+ 'IncidentManagement::Settings',
+ label,
+ value || kind_of(Hash)
+ )
+ end
+ end
+
+ after do
+ described_class.track_from_params(params)
+ end
+
+ context 'known params' do
+ known_params = described_class.tracking_keys
+
+ known_params.each do |key, values|
+ context "param #{key}" do
+ let(:params) { { key => '1' } }
+
+ it_behaves_like 'a tracked event', "enabled_#{known_params[key][:name]}"
+ end
+ end
+
+ context 'different input values' do
+ shared_examples 'the correct prefixed event name' do |input, enabled|
+ let(:params) { { issue_template_key: input } }
+
+ it 'matches' do
+ expect(::Gitlab::Tracking)
+ .to receive(:event)
+ .with(
+ anything,
+ "#{enabled}_issue_template_on_alerts",
+ anything
+ )
+ end
+ end
+
+ it_behaves_like 'the correct prefixed event name', 1, 'enabled'
+ it_behaves_like 'the correct prefixed event name', '1', 'enabled'
+ it_behaves_like 'the correct prefixed event name', 'template', 'enabled'
+ it_behaves_like 'the correct prefixed event name', '', 'disabled'
+ it_behaves_like 'the correct prefixed event name', nil, 'disabled'
+ end
+
+ context 'param with label' do
+ let(:params) { { issue_template_key: '1' } }
+
+ it_behaves_like 'a tracked event', "enabled_issue_template_on_alerts", { label: 'Template name', property: '1' }
+ end
+
+ context 'param without label' do
+ let(:params) { { create_issue: '1' } }
+
+ it_behaves_like 'a tracked event', "enabled_issue_auto_creation_on_alerts", {}
+ end
+ end
+
+ context 'unknown params' do
+ let(:params) { { 'unknown' => '1' } }
+
+ it 'does not create the tracking event' do
+ expect(::Gitlab::Tracking)
+ .not_to receive(:event)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/tracking_spec.rb b/spec/lib/gitlab/tracking_spec.rb
index 3cce82e522b..50488dba48c 100644
--- a/spec/lib/gitlab/tracking_spec.rb
+++ b/spec/lib/gitlab/tracking_spec.rb
@@ -12,10 +12,8 @@ describe Gitlab::Tracking do
end
describe '.snowplow_options' do
- subject(&method(:described_class))
-
it 'returns useful client options' do
- expect(subject.snowplow_options(nil)).to eq(
+ expect(described_class.snowplow_options(nil)).to eq(
namespace: 'gl',
hostname: 'gitfoo.com',
cookieDomain: '.gitfoo.com',
@@ -32,25 +30,37 @@ describe Gitlab::Tracking do
'_group_'
).and_return(false)
- expect(subject.snowplow_options('_group_')).to include(
+ expect(described_class.snowplow_options('_group_')).to include(
formTracking: false,
linkClickTracking: false
)
end
end
- describe '.event' do
- subject(&method(:described_class))
+ describe 'tracking events' do
+ shared_examples 'events not tracked' do
+ it 'does not track events' do
+ stub_application_setting(snowplow_enabled: false)
+ expect(SnowplowTracker::AsyncEmitter).not_to receive(:new)
+ expect(SnowplowTracker::Tracker).not_to receive(:new)
+
+ track_event
+ end
+ end
around do |example|
Timecop.freeze(timestamp) { example.run }
end
- it 'can track events' do
- tracker = double
+ before do
+ described_class.instance_variable_set("@snowplow", nil)
+ end
- expect(SnowplowTracker::Emitter).to receive(:new).with(
- 'gitfoo.com'
+ let(:tracker) { double }
+
+ def receive_events
+ expect(SnowplowTracker::AsyncEmitter).to receive(:new).with(
+ 'gitfoo.com', { protocol: 'https' }
).and_return('_emitter_')
expect(SnowplowTracker::Tracker).to receive(:new).with(
@@ -59,30 +69,67 @@ describe Gitlab::Tracking do
'gl',
'_abc123_'
).and_return(tracker)
+ end
- expect(tracker).to receive(:track_struct_event).with(
- 'category',
- 'action',
- '_label_',
- '_property_',
- '_value_',
- '_context_',
- timestamp.to_i
- )
+ describe '.event' do
+ let(:track_event) do
+ described_class.event('category', 'action',
+ label: '_label_',
+ property: '_property_',
+ value: '_value_',
+ context: nil
+ )
+ end
- subject.event('category', 'action',
- label: '_label_',
- property: '_property_',
- value: '_value_',
- context: '_context_'
- )
+ it_behaves_like 'events not tracked'
+
+ it 'can track events' do
+ receive_events
+ expect(tracker).to receive(:track_struct_event).with(
+ 'category',
+ 'action',
+ '_label_',
+ '_property_',
+ '_value_',
+ nil,
+ timestamp.to_i
+ )
+
+ track_event
+ end
end
- it 'does not track when not enabled' do
- stub_application_setting(snowplow_enabled: false)
- expect(SnowplowTracker::Tracker).not_to receive(:new)
+ describe '.self_describing_event' do
+ let(:track_event) do
+ described_class.self_describing_event('iglu:com.gitlab/example/jsonschema/1-0-2',
+ {
+ foo: 'bar',
+ foo_count: 42
+ },
+ context: nil
+ )
+ end
+
+ it_behaves_like 'events not tracked'
+
+ it 'can track self describing events' do
+ receive_events
+ expect(SnowplowTracker::SelfDescribingJson).to receive(:new).with(
+ 'iglu:com.gitlab/example/jsonschema/1-0-2',
+ {
+ foo: 'bar',
+ foo_count: 42
+ }
+ ).and_return('_event_json_')
+
+ expect(tracker).to receive(:track_self_describing_event).with(
+ '_event_json_',
+ nil,
+ timestamp.to_i
+ )
- subject.event('epics', 'action', property: 'what', value: 'doit')
+ track_event
+ end
end
end
end
diff --git a/spec/lib/gitlab/url_blocker_spec.rb b/spec/lib/gitlab/url_blocker_spec.rb
index 0e66e959b24..a68ba489986 100644
--- a/spec/lib/gitlab/url_blocker_spec.rb
+++ b/spec/lib/gitlab/url_blocker_spec.rb
@@ -62,6 +62,14 @@ describe Gitlab::UrlBlocker do
expect { subject }.to raise_error(described_class::BlockedUrlError)
end
end
+
+ context 'when domain is too long' do
+ let(:import_url) { 'https://example' + 'a' * 1024 + '.com' }
+
+ it 'raises an error' do
+ expect { subject }.to raise_error(described_class::BlockedUrlError)
+ end
+ end
end
context 'when the URL hostname is an IP address' do
diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb
index 62787c5abaf..f2e864472c5 100644
--- a/spec/lib/gitlab/usage_data_spec.rb
+++ b/spec/lib/gitlab/usage_data_spec.rb
@@ -38,7 +38,7 @@ describe Gitlab::UsageData do
subject { described_class.data }
- it 'gathers usage data' do
+ it 'gathers usage data', :aggregate_failures do
expect(subject.keys).to include(*%i(
active_user_count
counts
@@ -55,6 +55,7 @@ describe Gitlab::UsageData do
omniauth_enabled
reply_by_email_enabled
container_registry_enabled
+ dependency_proxy_enabled
gitlab_shared_runners_enabled
gitlab_pages
git
@@ -63,31 +64,29 @@ describe Gitlab::UsageData do
avg_cycle_analytics
influxdb_metrics_enabled
prometheus_metrics_enabled
- cycle_analytics_views
- productivity_analytics_views
))
-
- expect(subject).to include(
- snippet_create: a_kind_of(Integer),
- snippet_update: a_kind_of(Integer),
- snippet_comment: a_kind_of(Integer),
- merge_request_comment: a_kind_of(Integer),
- merge_request_create: a_kind_of(Integer),
- commit_comment: a_kind_of(Integer),
- wiki_pages_create: a_kind_of(Integer),
- wiki_pages_update: a_kind_of(Integer),
- wiki_pages_delete: a_kind_of(Integer),
- web_ide_views: a_kind_of(Integer),
- web_ide_commits: a_kind_of(Integer),
- web_ide_merge_requests: a_kind_of(Integer),
- navbar_searches: a_kind_of(Integer),
- cycle_analytics_views: a_kind_of(Integer),
- productivity_analytics_views: a_kind_of(Integer),
- source_code_pushes: a_kind_of(Integer)
- )
end
it 'gathers usage counts' do
+ smau_keys = %i(
+ snippet_create
+ snippet_update
+ snippet_comment
+ merge_request_comment
+ merge_request_create
+ commit_comment
+ wiki_pages_create
+ wiki_pages_update
+ wiki_pages_delete
+ web_ide_views
+ web_ide_commits
+ web_ide_merge_requests
+ navbar_searches
+ cycle_analytics_views
+ productivity_analytics_views
+ source_code_pushes
+ )
+
expected_keys = %i(
assignee_lists
boards
@@ -152,18 +151,18 @@ describe Gitlab::UsageData do
todos
uploads
web_hooks
- user_preferences
- )
+ ).push(*smau_keys)
count_data = subject[:counts]
expect(count_data[:boards]).to eq(1)
expect(count_data[:projects]).to eq(4)
+ expect(count_data.values_at(*smau_keys)).to all(be_an(Integer))
expect(count_data.keys).to include(*expected_keys)
expect(expected_keys - count_data.keys).to be_empty
end
- it 'gathers projects data correctly' do
+ it 'gathers projects data correctly', :aggregate_failures do
count_data = subject[:counts]
expect(count_data[:projects]).to eq(4)
@@ -209,11 +208,8 @@ describe Gitlab::UsageData do
describe 'the results of calling #totals on all objects in the array' do
subject { described_class.usage_data_counters.map(&:totals) }
- it do
- is_expected
- .to all(be_a Hash)
- .and all(have_attributes(keys: all(be_a Symbol), values: all(be_a Integer)))
- end
+ it { is_expected.to all(be_a Hash) }
+ it { is_expected.to all(have_attributes(keys: all(be_a Symbol), values: all(be_a Integer))) }
end
it 'does not have any conflicts' do
@@ -226,7 +222,7 @@ describe Gitlab::UsageData do
describe '#features_usage_data_ce' do
subject { described_class.features_usage_data_ce }
- it 'gathers feature usage data' do
+ it 'gathers feature usage data', :aggregate_failures do
expect(subject[:mattermost_enabled]).to eq(Gitlab.config.mattermost.enabled)
expect(subject[:signup_enabled]).to eq(Gitlab::CurrentSettings.allow_signup?)
expect(subject[:ldap_enabled]).to eq(Gitlab.config.ldap.enabled)
@@ -234,6 +230,7 @@ describe Gitlab::UsageData do
expect(subject[:omniauth_enabled]).to eq(Gitlab::Auth.omniauth_enabled?)
expect(subject[:reply_by_email_enabled]).to eq(Gitlab::IncomingEmail.enabled?)
expect(subject[:container_registry_enabled]).to eq(Gitlab.config.registry.enabled)
+ expect(subject[:dependency_proxy_enabled]).to eq(Gitlab.config.dependency_proxy.enabled)
expect(subject[:gitlab_shared_runners_enabled]).to eq(Gitlab.config.gitlab_ci.shared_runners_enabled)
end
end
@@ -241,7 +238,7 @@ describe Gitlab::UsageData do
describe '#components_usage_data' do
subject { described_class.components_usage_data }
- it 'gathers components usage data' do
+ it 'gathers components usage data', :aggregate_failures do
expect(subject[:gitlab_pages][:enabled]).to eq(Gitlab.config.pages.enabled)
expect(subject[:gitlab_pages][:version]).to eq(Gitlab::Pages::VERSION)
expect(subject[:git][:version]).to eq(Gitlab::Git.version)
@@ -257,7 +254,7 @@ describe Gitlab::UsageData do
describe '#license_usage_data' do
subject { described_class.license_usage_data }
- it 'gathers license data' do
+ it 'gathers license data', :aggregate_failures do
expect(subject[:uuid]).to eq(Gitlab::CurrentSettings.uuid)
expect(subject[:version]).to eq(Gitlab::VERSION)
expect(subject[:installation_type]).to eq('gitlab-development-kit')
@@ -289,11 +286,11 @@ describe Gitlab::UsageData do
end
describe '#approximate_counts' do
- it 'gets approximate counts for selected models' do
+ it 'gets approximate counts for selected models', :aggregate_failures do
create(:label)
expect(Gitlab::Database::Count).to receive(:approximate_counts)
- .with(described_class::APPROXIMATE_COUNT_MODELS).once.and_call_original
+ .with(described_class::APPROXIMATE_COUNT_MODELS).once.and_call_original
counts = described_class.approximate_counts.values
@@ -301,14 +298,12 @@ describe Gitlab::UsageData do
expect(counts.any? { |count| count < 0 }).to be_falsey
end
- it 'returns default values if counts can not be retrieved' do
+ it 'returns default values if counts can not be retrieved', :aggregate_failures do
described_class::APPROXIMATE_COUNT_MODELS.map do |model|
model.name.underscore.pluralize.to_sym
end
- expect(Gitlab::Database::Count).to receive(:approximate_counts)
- .and_return({})
-
+ expect(Gitlab::Database::Count).to receive(:approximate_counts).and_return({})
expect(described_class.approximate_counts.values.uniq).to eq([-1])
end
end
diff --git a/spec/lib/gitlab/utils/inline_hash_spec.rb b/spec/lib/gitlab/utils/inline_hash_spec.rb
new file mode 100644
index 00000000000..867db0b92a5
--- /dev/null
+++ b/spec/lib/gitlab/utils/inline_hash_spec.rb
@@ -0,0 +1,70 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+describe Gitlab::Utils::InlineHash do
+ describe '.merge_keys' do
+ subject { described_class.merge_keys(source) }
+
+ let(:source) do
+ {
+ nested_param: {
+ key: :Value
+ },
+ 'root_param' => 'Root',
+ unnested_symbol_key: :unnested_symbol_value,
+ 12 => 22,
+ 'very' => {
+ 'deep' => {
+ 'nested' => {
+ 'param' => 'Deep nested value'
+ }
+ }
+ }
+ }
+ end
+
+ it 'transforms a nested hash into a one-level hash' do
+ is_expected.to eq(
+ 'nested_param.key' => :Value,
+ 'root_param' => 'Root',
+ :unnested_symbol_key => :unnested_symbol_value,
+ 12 => 22,
+ 'very.deep.nested.param' => 'Deep nested value'
+ )
+ end
+
+ it 'retains key insertion order' do
+ expect(subject.keys)
+ .to eq(['nested_param.key', 'root_param', :unnested_symbol_key, 12, 'very.deep.nested.param'])
+ end
+
+ context 'with a custom connector' do
+ subject { described_class.merge_keys(source, connector: '::') }
+
+ it 'uses the connector to merge keys' do
+ is_expected.to eq(
+ 'nested_param::key' => :Value,
+ 'root_param' => 'Root',
+ :unnested_symbol_key => :unnested_symbol_value,
+ 12 => 22,
+ 'very::deep::nested::param' => 'Deep nested value'
+ )
+ end
+ end
+
+ context 'with a starter prefix' do
+ subject { described_class.merge_keys(source, prefix: 'options') }
+
+ it 'prefixes all the keys' do
+ is_expected.to eq(
+ 'options.nested_param.key' => :Value,
+ 'options.root_param' => 'Root',
+ 'options.unnested_symbol_key' => :unnested_symbol_value,
+ 'options.12' => 22,
+ 'options.very.deep.nested.param' => 'Deep nested value'
+ )
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/utils/override_spec.rb b/spec/lib/gitlab/utils/override_spec.rb
index 5855c4374a9..e2776efac85 100644
--- a/spec/lib/gitlab/utils/override_spec.rb
+++ b/spec/lib/gitlab/utils/override_spec.rb
@@ -151,6 +151,7 @@ describe Gitlab::Utils::Override do
context 'when subject is a module, and class is prepending it' do
subject { extension }
+
let(:klass) { prepending_class }
it_behaves_like 'checking as intended'
@@ -158,6 +159,7 @@ describe Gitlab::Utils::Override do
context 'when subject is a module, and class is including it' do
subject { extension }
+
let(:klass) { including_class }
it_behaves_like 'checking as intended, nothing was overridden'
@@ -177,6 +179,7 @@ describe Gitlab::Utils::Override do
context 'when subject is a module, and class is prepending it' do
subject { extension }
+
let(:klass) { prepending_class }
it_behaves_like 'nothing happened'
@@ -184,6 +187,7 @@ describe Gitlab::Utils::Override do
context 'when subject is a module, and class is including it' do
subject { extension }
+
let(:klass) { including_class }
it 'does not complain when it is overriding something' do
@@ -215,6 +219,7 @@ describe Gitlab::Utils::Override do
context 'when subject is a module, and class is prepending it' do
subject { extension }
+
let(:klass) { prepending_class_methods }
it_behaves_like 'checking as intended'
@@ -222,6 +227,7 @@ describe Gitlab::Utils::Override do
context 'when subject is a module, and class is extending it' do
subject { extension }
+
let(:klass) { extending_class_methods }
it_behaves_like 'checking as intended, nothing was overridden'
diff --git a/spec/lib/gitlab/utils/safe_inline_hash_spec.rb b/spec/lib/gitlab/utils/safe_inline_hash_spec.rb
new file mode 100644
index 00000000000..617845332bc
--- /dev/null
+++ b/spec/lib/gitlab/utils/safe_inline_hash_spec.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+describe Gitlab::Utils::SafeInlineHash do
+ describe '.merge_keys!' do
+ let(:source) { { 'foo' => { 'bar' => 'baz' } } }
+ let(:validator) { instance_double(Gitlab::Utils::DeepSize, valid?: valid) }
+
+ subject { described_class.merge_keys!(source, prefix: 'safe', connector: '::') }
+
+ before do
+ allow(Gitlab::Utils::DeepSize)
+ .to receive(:new)
+ .with(source)
+ .and_return(validator)
+ end
+
+ context 'when hash is too big' do
+ let(:valid) { false }
+
+ it 'raises an exception' do
+ expect { subject }.to raise_error ArgumentError, 'The Hash is too big'
+ end
+ end
+
+ context 'when hash has an acceptaable size' do
+ let(:valid) { true }
+
+ it 'returns a result of InlineHash' do
+ is_expected.to eq('safe::foo::bar' => 'baz')
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/utils/sanitize_node_link_spec.rb b/spec/lib/gitlab/utils/sanitize_node_link_spec.rb
index 80b0935a7ed..dd379f2fe1f 100644
--- a/spec/lib/gitlab/utils/sanitize_node_link_spec.rb
+++ b/spec/lib/gitlab/utils/sanitize_node_link_spec.rb
@@ -43,6 +43,11 @@ describe Gitlab::Utils::SanitizeNodeLink do
doc: HTML::Pipeline.parse("<video><source src='#{scheme}alert(1);'></video>"),
attr: "src",
node_to_check: -> (doc) { doc.children.first.children.filter("source").first }
+ },
+ audio: {
+ doc: HTML::Pipeline.parse("<audio><source src='#{scheme}alert(1);'></audio>"),
+ attr: "src",
+ node_to_check: -> (doc) { doc.children.first.children.filter("source").first }
}
}
diff --git a/spec/lib/gitlab_spec.rb b/spec/lib/gitlab_spec.rb
index ccb5cb3aa43..6bf837f1d3f 100644
--- a/spec/lib/gitlab_spec.rb
+++ b/spec/lib/gitlab_spec.rb
@@ -146,6 +146,7 @@ describe Gitlab do
describe '.ee?' do
before do
+ stub_env('FOSS_ONLY', nil) # Make sure the ENV is clean
described_class.instance_variable_set(:@is_ee, nil)
end
@@ -153,42 +154,66 @@ describe Gitlab do
described_class.instance_variable_set(:@is_ee, nil)
end
- it 'returns true when using Enterprise Edition' do
- root = Pathname.new('dummy')
- license_path = double(:path, exist?: true)
+ context 'for EE' do
+ before do
+ root = Pathname.new('dummy')
+ license_path = double(:path, exist?: true)
- allow(described_class)
- .to receive(:root)
- .and_return(root)
+ allow(described_class)
+ .to receive(:root)
+ .and_return(root)
- allow(root)
- .to receive(:join)
- .with('ee/app/models/license.rb')
- .and_return(license_path)
+ allow(root)
+ .to receive(:join)
+ .with('ee/app/models/license.rb')
+ .and_return(license_path)
+ end
- expect(described_class.ee?).to eq(true)
- end
+ context 'when using FOSS_ONLY=1' do
+ before do
+ stub_env('FOSS_ONLY', '1')
+ end
- it 'returns false when using Community Edition' do
- root = double(:path)
- license_path = double(:path, exists?: false)
+ it 'returns not to be EE' do
+ expect(described_class).not_to be_ee
+ end
+ end
- allow(described_class)
- .to receive(:root)
- .and_return(Pathname.new('dummy'))
+ context 'when using FOSS_ONLY=0' do
+ before do
+ stub_env('FOSS_ONLY', '0')
+ end
- allow(root)
- .to receive(:join)
- .with('ee/app/models/license.rb')
- .and_return(license_path)
+ it 'returns to be EE' do
+ expect(described_class).to be_ee
+ end
+ end
- expect(described_class.ee?).to eq(false)
+ context 'when using default FOSS_ONLY' do
+ it 'returns to be EE' do
+ expect(described_class).to be_ee
+ end
+ end
end
- it 'returns true when the IS_GITLAB_EE variable is not empty' do
- stub_env('IS_GITLAB_EE', '1')
+ context 'for CE' do
+ before do
+ root = double(:path)
+ license_path = double(:path, exists?: false)
- expect(described_class.ee?).to eq(true)
+ allow(described_class)
+ .to receive(:root)
+ .and_return(Pathname.new('dummy'))
+
+ allow(root)
+ .to receive(:join)
+ .with('ee/app/models/license.rb')
+ .and_return(license_path)
+ end
+
+ it 'returns not to be EE' do
+ expect(described_class).not_to be_ee
+ end
end
end
diff --git a/spec/lib/google_api/cloud_platform/client_spec.rb b/spec/lib/google_api/cloud_platform/client_spec.rb
index c24998d32f8..0f7f57095df 100644
--- a/spec/lib/google_api/cloud_platform/client_spec.rb
+++ b/spec/lib/google_api/cloud_platform/client_spec.rb
@@ -54,6 +54,7 @@ describe GoogleApi::CloudPlatform::Client do
describe '#projects_zones_clusters_get' do
subject { client.projects_zones_clusters_get(spy, spy, spy) }
+
let(:gke_cluster) { double }
before do
@@ -68,7 +69,7 @@ describe GoogleApi::CloudPlatform::Client do
describe '#projects_zones_clusters_create' do
subject do
client.projects_zones_clusters_create(
- project_id, zone, cluster_name, cluster_size, machine_type: machine_type, legacy_abac: legacy_abac)
+ project_id, zone, cluster_name, cluster_size, machine_type: machine_type, legacy_abac: legacy_abac, enable_addons: enable_addons)
end
let(:project_id) { 'project-123' }
@@ -77,39 +78,54 @@ describe GoogleApi::CloudPlatform::Client do
let(:cluster_size) { 1 }
let(:machine_type) { 'n1-standard-2' }
let(:legacy_abac) { true }
- let(:create_cluster_request_body) { double('Google::Apis::ContainerV1::CreateClusterRequest') }
+ let(:enable_addons) { [] }
+
+ let(:addons_config) do
+ enable_addons.each_with_object({}) do |addon, hash|
+ hash[addon] = { disabled: false }
+ end
+ end
+
+ let(:cluster_options) do
+ {
+ cluster: {
+ name: cluster_name,
+ initial_node_count: cluster_size,
+ node_config: {
+ machine_type: machine_type
+ },
+ master_auth: {
+ username: 'admin',
+ client_certificate_config: {
+ issue_client_certificate: true
+ }
+ },
+ legacy_abac: {
+ enabled: legacy_abac
+ },
+ ip_allocation_policy: {
+ use_ip_aliases: true
+ },
+ addons_config: addons_config
+ }
+ }
+ end
+
+ let(:create_cluster_request_body) { double('Google::Apis::ContainerV1beta1::CreateClusterRequest') }
let(:operation) { double }
before do
- allow_any_instance_of(Google::Apis::ContainerV1::ContainerService)
+ allow_any_instance_of(Google::Apis::ContainerV1beta1::ContainerService)
.to receive(:create_cluster).with(any_args)
.and_return(operation)
end
it 'sets corresponded parameters' do
- expect_any_instance_of(Google::Apis::ContainerV1::ContainerService)
+ expect_any_instance_of(Google::Apis::ContainerV1beta1::ContainerService)
.to receive(:create_cluster).with(project_id, zone, create_cluster_request_body, options: user_agent_options)
- expect(Google::Apis::ContainerV1::CreateClusterRequest)
- .to receive(:new).with(
- {
- "cluster": {
- "name": cluster_name,
- "initial_node_count": cluster_size,
- "node_config": {
- "machine_type": machine_type
- },
- "master_auth": {
- "username": "admin",
- "client_certificate_config": {
- issue_client_certificate: true
- }
- },
- "legacy_abac": {
- "enabled": true
- }
- }
- } ).and_return(create_cluster_request_body)
+ expect(Google::Apis::ContainerV1beta1::CreateClusterRequest)
+ .to receive(:new).with(cluster_options).and_return(create_cluster_request_body)
expect(subject).to eq operation
end
@@ -118,29 +134,25 @@ describe GoogleApi::CloudPlatform::Client do
let(:legacy_abac) { false }
it 'sets corresponded parameters' do
- expect_any_instance_of(Google::Apis::ContainerV1::ContainerService)
+ expect_any_instance_of(Google::Apis::ContainerV1beta1::ContainerService)
+ .to receive(:create_cluster).with(project_id, zone, create_cluster_request_body, options: user_agent_options)
+
+ expect(Google::Apis::ContainerV1beta1::CreateClusterRequest)
+ .to receive(:new).with(cluster_options).and_return(create_cluster_request_body)
+
+ expect(subject).to eq operation
+ end
+ end
+
+ context 'create with enable_addons for cloud_run' do
+ let(:enable_addons) { [:http_load_balancing, :istio_config, :cloud_run_config] }
+
+ it 'sets corresponded parameters' do
+ expect_any_instance_of(Google::Apis::ContainerV1beta1::ContainerService)
.to receive(:create_cluster).with(project_id, zone, create_cluster_request_body, options: user_agent_options)
- expect(Google::Apis::ContainerV1::CreateClusterRequest)
- .to receive(:new).with(
- {
- "cluster": {
- "name": cluster_name,
- "initial_node_count": cluster_size,
- "node_config": {
- "machine_type": machine_type
- },
- "master_auth": {
- "username": "admin",
- "client_certificate_config": {
- issue_client_certificate: true
- }
- },
- "legacy_abac": {
- "enabled": false
- }
- }
- } ).and_return(create_cluster_request_body)
+ expect(Google::Apis::ContainerV1beta1::CreateClusterRequest)
+ .to receive(:new).with(cluster_options).and_return(create_cluster_request_body)
expect(subject).to eq operation
end
@@ -149,6 +161,7 @@ describe GoogleApi::CloudPlatform::Client do
describe '#projects_zones_operations' do
subject { client.projects_zones_operations(spy, spy, spy) }
+
let(:operation) { double }
before do
diff --git a/spec/lib/grafana/client_spec.rb b/spec/lib/grafana/client_spec.rb
new file mode 100644
index 00000000000..bd93a3c59a2
--- /dev/null
+++ b/spec/lib/grafana/client_spec.rb
@@ -0,0 +1,107 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Grafana::Client do
+ let(:grafana_url) { 'https://grafanatest.com/-/grafana-project' }
+ let(:token) { 'test-token' }
+
+ subject(:client) { described_class.new(api_url: grafana_url, token: token) }
+
+ shared_examples 'calls grafana api' do
+ let!(:grafana_api_request) { stub_grafana_request(grafana_api_url) }
+
+ it 'calls grafana api' do
+ subject
+
+ expect(grafana_api_request).to have_been_requested
+ end
+ end
+
+ shared_examples 'no redirects' do
+ let(:redirect_to) { 'https://redirected.example.com' }
+ let(:other_url) { 'https://grafana.example.org' }
+
+ let!(:redirected_req_stub) { stub_grafana_request(other_url) }
+
+ let!(:redirect_req_stub) do
+ stub_grafana_request(
+ grafana_api_url,
+ status: 302,
+ headers: { location: redirect_to }
+ )
+ end
+
+ it 'does not follow redirects' do
+ expect { subject }.to raise_exception(
+ Grafana::Client::Error,
+ 'Grafana response status code: 302'
+ )
+
+ expect(redirect_req_stub).to have_been_requested
+ expect(redirected_req_stub).not_to have_been_requested
+ end
+ end
+
+ shared_examples 'handles exceptions' do
+ exceptions = {
+ Gitlab::HTTP::Error => 'Error when connecting to Grafana',
+ Net::OpenTimeout => 'Connection to Grafana timed out',
+ SocketError => 'Received SocketError when trying to connect to Grafana',
+ OpenSSL::SSL::SSLError => 'Grafana returned invalid SSL data',
+ Errno::ECONNREFUSED => 'Connection refused',
+ StandardError => 'Grafana request failed due to StandardError'
+ }
+
+ exceptions.each do |exception, message|
+ context "#{exception}" do
+ before do
+ stub_request(:get, grafana_api_url).to_raise(exception)
+ end
+
+ it do
+ expect { subject }
+ .to raise_exception(Grafana::Client::Error, message)
+ end
+ end
+ end
+ end
+
+ describe '#proxy_datasource' do
+ let(:grafana_api_url) do
+ 'https://grafanatest.com/-/grafana-project/' \
+ 'api/datasources/proxy/' \
+ '1/api/v1/query_range' \
+ '?query=rate(relevant_metric)' \
+ '&start=1570441248&end=1570444848&step=900'
+ end
+
+ subject do
+ client.proxy_datasource(
+ datasource_id: '1',
+ proxy_path: 'api/v1/query_range',
+ query: {
+ query: 'rate(relevant_metric)',
+ start: 1570441248,
+ end: 1570444848,
+ step: 900
+ }
+ )
+ end
+
+ it_behaves_like 'calls grafana api'
+ it_behaves_like 'no redirects'
+ it_behaves_like 'handles exceptions'
+ end
+
+ private
+
+ def stub_grafana_request(url, body: {}, status: 200, headers: {})
+ stub_request(:get, url)
+ .to_return(
+ status: status,
+ headers: { 'Content-Type' => 'application/json' }.merge(headers),
+ body: body.to_json
+ )
+ end
+end
diff --git a/spec/lib/json_web_token/token_spec.rb b/spec/lib/json_web_token/token_spec.rb
index 916d11ce0ed..ca587a6ebcd 100644
--- a/spec/lib/json_web_token/token_spec.rb
+++ b/spec/lib/json_web_token/token_spec.rb
@@ -16,6 +16,7 @@ describe JSONWebToken::Token do
context 'embeds default payload' do
subject { token.payload }
+
let(:default) { token.send(:default_payload) }
it { is_expected.to include(default) }
diff --git a/spec/lib/omni_auth/strategies/jwt_spec.rb b/spec/lib/omni_auth/strategies/jwt_spec.rb
index bdf3ea6be98..a8c565aa705 100644
--- a/spec/lib/omni_auth/strategies/jwt_spec.rb
+++ b/spec/lib/omni_auth/strategies/jwt_spec.rb
@@ -8,6 +8,7 @@ describe OmniAuth::Strategies::Jwt do
context '#decoded' do
subject { described_class.new({}) }
+
let(:timestamp) { Time.now.to_i }
let(:jwt_config) { Devise.omniauth_configs[:jwt] }
let(:claims) do
diff --git a/spec/lib/quality/test_level_spec.rb b/spec/lib/quality/test_level_spec.rb
index 59870ce44a7..4db188bd8f2 100644
--- a/spec/lib/quality/test_level_spec.rb
+++ b/spec/lib/quality/test_level_spec.rb
@@ -4,6 +4,20 @@ require 'fast_spec_helper'
RSpec.describe Quality::TestLevel do
describe '#pattern' do
+ context 'when level is all' do
+ it 'returns a pattern' do
+ expect(subject.pattern(:all))
+ .to eq("spec/**{,/**/}*_spec.rb")
+ end
+ end
+
+ context 'when level is geo' do
+ it 'returns a pattern' do
+ expect(subject.pattern(:geo))
+ .to eq("spec/**{,/**/}*_spec.rb")
+ end
+ end
+
context 'when level is unit' do
it 'returns a pattern' do
expect(subject.pattern(:unit))
@@ -44,6 +58,20 @@ RSpec.describe Quality::TestLevel do
end
describe '#regexp' do
+ context 'when level is all' do
+ it 'returns a regexp' do
+ expect(subject.regexp(:all))
+ .to eq(%r{spec/})
+ end
+ end
+
+ context 'when level is geo' do
+ it 'returns a regexp' do
+ expect(subject.regexp(:geo))
+ .to eq(%r{spec/})
+ end
+ end
+
context 'when level is unit' do
it 'returns a regexp' do
expect(subject.regexp(:unit))
diff --git a/spec/lib/uploaded_file_spec.rb b/spec/lib/uploaded_file_spec.rb
index 2cb4727bd4b..2bbbd67b13c 100644
--- a/spec/lib/uploaded_file_spec.rb
+++ b/spec/lib/uploaded_file_spec.rb
@@ -72,16 +72,6 @@ describe UploadedFile do
end
end
- context 'when only remote id is specified' do
- let(:params) do
- { 'file.remote_id' => 'remote_id' }
- end
-
- it "raises an error" do
- expect { subject }.to raise_error(UploadedFile::InvalidPathError, /file is invalid/)
- end
- end
-
context 'when verifying allowed paths' do
let(:params) do
{ 'file.path' => temp_file.path }
@@ -120,6 +110,52 @@ describe UploadedFile do
end
end
+ describe '.initialize' do
+ context 'when no size is provided' do
+ it 'determine size from local path' do
+ file = described_class.new(temp_file.path)
+
+ expect(file.size).to eq(temp_file.size)
+ end
+
+ it 'raises an exception if is a remote file' do
+ expect do
+ described_class.new(nil, remote_id: 'id')
+ end.to raise_error(UploadedFile::UnknownSizeError, 'Unable to determine file size')
+ end
+ end
+
+ context 'when size is a number' do
+ let_it_be(:size) { 1.gigabyte }
+
+ it 'is overridden by the size of the local file' do
+ file = described_class.new(temp_file.path, size: size)
+
+ expect(file.size).to eq(temp_file.size)
+ end
+
+ it 'is respected if is a remote file' do
+ file = described_class.new(nil, remote_id: 'id', size: size)
+
+ expect(file.size).to eq(size)
+ end
+ end
+
+ context 'when size is a string' do
+ it 'is converted to a number' do
+ file = described_class.new(nil, remote_id: 'id', size: '1')
+
+ expect(file.size).to eq(1)
+ end
+
+ it 'raises an exception if does not represent a number' do
+ expect do
+ described_class.new(nil, remote_id: 'id', size: 'not a number')
+ end.to raise_error(UploadedFile::UnknownSizeError, 'Unable to determine file size')
+ end
+ end
+ end
+
describe '#sanitize_filename' do
it { expect(described_class.new(temp_file.path).sanitize_filename('spaced name')).to eq('spaced_name') }
it { expect(described_class.new(temp_file.path).sanitize_filename('#$%^&')).to eq('_____') }