summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAchilleas Pipinellis <axilleas@axilleas.me>2017-08-15 11:53:50 +0300
committerAchilleas Pipinellis <axilleas@axilleas.me>2017-08-15 11:53:50 +0300
commitb38b7e68f45535234ef5a7f09f0616d4eb18e210 (patch)
treeb9168daee1bdb203193f522c885659407324f812
parent9e71d42761f1164bc90de041ed6813248fa1ec7d (diff)
parentde0d0e4e47dffdab3f507ba93dcdff27da4d7421 (diff)
downloadgitlab-ce-b38b7e68f45535234ef5a7f09f0616d4eb18e210.tar.gz
Merge branch 'master' into 33329-tech-article-deploying-maven-artifacts
-rw-r--r--.eslintrc3
-rw-r--r--.flayignore1
-rw-r--r--.gitlab-ci.yml230
-rw-r--r--.gitlab/merge_request_templates/Database Changes.md73
-rw-r--r--.haml-lint.yml18
-rw-r--r--.rubocop.yml50
-rw-r--r--.rubocop_todo.yml13
-rw-r--r--CHANGELOG.md77
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--GITLAB_SHELL_VERSION2
-rw-r--r--Gemfile26
-rw-r--r--Gemfile.lock130
-rw-r--r--PROCESS.md37
-rw-r--r--app/assets/images/new_repo.pngbin0 -> 19292 bytes
-rw-r--r--app/assets/images/old_repo.pngbin0 -> 20668 bytes
-rw-r--r--app/assets/javascripts/ajax_loading_spinner.js2
-rw-r--r--app/assets/javascripts/api.js16
-rw-r--r--app/assets/javascripts/awards_handler.js2
-rw-r--r--app/assets/javascripts/behaviors/requires_input.js9
-rw-r--r--app/assets/javascripts/behaviors/toggler_behavior.js1
-rw-r--r--app/assets/javascripts/blob/blob_file_dropzone.js30
-rw-r--r--app/assets/javascripts/boards/boards_bundle.js1
-rw-r--r--app/assets/javascripts/boards/components/board_blank_state.js1
-rw-r--r--app/assets/javascripts/boards/components/issue_card_inner.js5
-rw-r--r--app/assets/javascripts/boards/components/modal/index.js4
-rw-r--r--app/assets/javascripts/boards/components/modal/list.js2
-rw-r--r--app/assets/javascripts/boards/components/new_list_dropdown.js1
-rw-r--r--app/assets/javascripts/boards/stores/boards_store.js2
-rw-r--r--app/assets/javascripts/breakpoints.js77
-rw-r--r--app/assets/javascripts/build.js8
-rw-r--r--app/assets/javascripts/build_variables.js2
-rw-r--r--app/assets/javascripts/commons/bootstrap.js1
-rw-r--r--app/assets/javascripts/commons/index.js1
-rw-r--r--app/assets/javascripts/commons/jquery.js1
-rw-r--r--app/assets/javascripts/copy_as_gfm.js2
-rw-r--r--app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js2
-rw-r--r--app/assets/javascripts/diff_notes/components/jump_to_discussion.js10
-rw-r--r--app/assets/javascripts/dispatcher.js36
-rw-r--r--app/assets/javascripts/dropzone_input.js5
-rw-r--r--app/assets/javascripts/due_date_select.js2
-rw-r--r--app/assets/javascripts/emoji/index.js1
-rw-r--r--app/assets/javascripts/extensions/array.js11
-rw-r--r--app/assets/javascripts/filterable_list.js2
-rw-r--r--app/assets/javascripts/filtered_search/dropdown_utils.js5
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js2
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_manager.js2
-rw-r--r--app/assets/javascripts/fly_out_nav.js171
-rw-r--r--app/assets/javascripts/gfm_auto_complete.js1
-rw-r--r--app/assets/javascripts/gl_dropdown.js80
-rw-r--r--app/assets/javascripts/graphs/graphs_charts.js34
-rw-r--r--app/assets/javascripts/graphs/stat_graph_contributors.js1
-rw-r--r--app/assets/javascripts/graphs/stat_graph_contributors_graph.js2
-rw-r--r--app/assets/javascripts/graphs/stat_graph_contributors_util.js1
-rw-r--r--app/assets/javascripts/helpers/user_feature_helper.js11
-rw-r--r--app/assets/javascripts/init_changes_dropdown.js10
-rw-r--r--app/assets/javascripts/issuable_bulk_update_actions.js1
-rw-r--r--app/assets/javascripts/issuable_context.js3
-rw-r--r--app/assets/javascripts/issuable_form.js2
-rw-r--r--app/assets/javascripts/issuable_index.js2
-rw-r--r--app/assets/javascripts/jobs/components/header.vue2
-rw-r--r--app/assets/javascripts/labels_select.js2
-rw-r--r--app/assets/javascripts/layout_nav.js3
-rw-r--r--app/assets/javascripts/lib/utils/common_utils.js10
-rw-r--r--app/assets/javascripts/lib/utils/constants.js1
-rw-r--r--app/assets/javascripts/lib/utils/pretty_time.js2
-rw-r--r--app/assets/javascripts/lib/utils/sticky.js23
-rw-r--r--app/assets/javascripts/main.js19
-rw-r--r--app/assets/javascripts/member_expiration_date.js4
-rw-r--r--app/assets/javascripts/merge_request_tabs.js12
-rw-r--r--app/assets/javascripts/milestone_select.js1
-rw-r--r--app/assets/javascripts/monitoring/components/monitoring_column.vue5
-rw-r--r--app/assets/javascripts/new_sidebar.js41
-rw-r--r--app/assets/javascripts/notes.js1
-rw-r--r--app/assets/javascripts/pdf/index.vue4
-rw-r--r--app/assets/javascripts/pipeline_schedules/components/interval_pattern_input.vue2
-rw-r--r--app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue21
-rw-r--r--app/assets/javascripts/pipelines/components/graph/graph_component.vue4
-rw-r--r--app/assets/javascripts/profile/gl_crop.js1
-rw-r--r--app/assets/javascripts/project.js17
-rw-r--r--app/assets/javascripts/project_edit.js2
-rw-r--r--app/assets/javascripts/project_select.js17
-rw-r--r--app/assets/javascripts/project_select_combo_button.js85
-rw-r--r--app/assets/javascripts/projects/project_import_gitlab_project.js14
-rw-r--r--app/assets/javascripts/projects/project_new.js22
-rw-r--r--app/assets/javascripts/protected_branches/protected_branch_dropdown.js2
-rw-r--r--app/assets/javascripts/protected_tags/protected_tag_dropdown.js2
-rw-r--r--app/assets/javascripts/repo/components/repo.vue63
-rw-r--r--app/assets/javascripts/repo/components/repo_commit_section.vue100
-rw-r--r--app/assets/javascripts/repo/components/repo_edit_button.vue47
-rw-r--r--app/assets/javascripts/repo/components/repo_editor.vue135
-rw-r--r--app/assets/javascripts/repo/components/repo_file.vue66
-rw-r--r--app/assets/javascripts/repo/components/repo_file_buttons.vue42
-rw-r--r--app/assets/javascripts/repo/components/repo_file_options.vue25
-rw-r--r--app/assets/javascripts/repo/components/repo_loading_file.vue51
-rw-r--r--app/assets/javascripts/repo/components/repo_prev_directory.vue26
-rw-r--r--app/assets/javascripts/repo/components/repo_preview.vue52
-rw-r--r--app/assets/javascripts/repo/components/repo_sidebar.vue102
-rw-r--r--app/assets/javascripts/repo/components/repo_tab.vue63
-rw-r--r--app/assets/javascripts/repo/components/repo_tabs.vue32
-rw-r--r--app/assets/javascripts/repo/helpers/monaco_loader_helper.js24
-rw-r--r--app/assets/javascripts/repo/helpers/repo_helper.js300
-rw-r--r--app/assets/javascripts/repo/index.js73
-rw-r--r--app/assets/javascripts/repo/mixins/repo_mixin.js17
-rw-r--r--app/assets/javascripts/repo/monaco_loader.js13
-rw-r--r--app/assets/javascripts/repo/services/repo_service.js84
-rw-r--r--app/assets/javascripts/repo/stores/repo_store.js225
-rw-r--r--app/assets/javascripts/right_sidebar.js1
-rw-r--r--app/assets/javascripts/shortcuts_issuable.js3
-rw-r--r--app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue82
-rw-r--r--app/assets/javascripts/sidebar/components/confidential/edit_form.vue47
-rw-r--r--app/assets/javascripts/sidebar/components/confidential/edit_form_buttons.vue45
-rw-r--r--app/assets/javascripts/sidebar/components/time_tracking/sidebar_time_tracking.js2
-rw-r--r--app/assets/javascripts/sidebar/sidebar_bundle.js18
-rw-r--r--app/assets/javascripts/sidebar_height_manager.js6
-rw-r--r--app/assets/javascripts/test_utils/index.js2
-rw-r--r--app/assets/javascripts/todos.js3
-rw-r--r--app/assets/javascripts/u2f/authenticate.js2
-rw-r--r--app/assets/javascripts/u2f/register.js2
-rw-r--r--app/assets/javascripts/username_validator.js2
-rw-r--r--app/assets/javascripts/users/activity_calendar.js13
-rw-r--r--app/assets/javascripts/users/user_tabs.js8
-rw-r--r--app/assets/javascripts/users_select.js1
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.js9
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js100
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.js81
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_memory_usage.js7
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.js2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.js79
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.js27
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.js36
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_archived.js30
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.js25
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.js25
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.js37
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.js30
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.js64
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_locked.js24
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.js95
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.js154
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merging.js29
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.js39
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_not_allowed.js24
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_nothing_to_merge.js16
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.js22
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_failed.js22
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js178
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_sha_mismatch.js22
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_unresolved_discussions.js36
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_wip.js36
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/dependencies.js4
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js35
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js9
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/stores/state_maps.js4
-rw-r--r--app/assets/javascripts/vue_shared/components/header_ci_component.vue4
-rw-r--r--app/assets/javascripts/vue_shared/components/popup_dialog.vue67
-rw-r--r--app/assets/javascripts/wikis.js13
-rw-r--r--app/assets/stylesheets/framework.scss1
-rw-r--r--app/assets/stylesheets/framework/avatar.scss2
-rw-r--r--app/assets/stylesheets/framework/calendar.scss1
-rw-r--r--app/assets/stylesheets/framework/common.scss4
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss55
-rw-r--r--app/assets/stylesheets/framework/header.scss27
-rw-r--r--app/assets/stylesheets/framework/highlight.scss2
-rw-r--r--app/assets/stylesheets/framework/layout.scss22
-rw-r--r--app/assets/stylesheets/framework/media_object.scss8
-rw-r--r--app/assets/stylesheets/framework/nav.scss26
-rw-r--r--app/assets/stylesheets/framework/sidebar.scss5
-rw-r--r--app/assets/stylesheets/framework/tw_bootstrap_variables.scss2
-rw-r--r--app/assets/stylesheets/framework/typography.scss2
-rw-r--r--app/assets/stylesheets/framework/variables.scss23
-rw-r--r--app/assets/stylesheets/new_nav.scss4
-rw-r--r--app/assets/stylesheets/new_sidebar.scss265
-rw-r--r--app/assets/stylesheets/pages/boards.scss2
-rw-r--r--app/assets/stylesheets/pages/builds.scss19
-rw-r--r--app/assets/stylesheets/pages/cycle_analytics.scss24
-rw-r--r--app/assets/stylesheets/pages/diff.scss72
-rw-r--r--app/assets/stylesheets/pages/issuable.scss34
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss319
-rw-r--r--app/assets/stylesheets/pages/note_form.scss45
-rw-r--r--app/assets/stylesheets/pages/notes.scss56
-rw-r--r--app/assets/stylesheets/pages/pipelines.scss7
-rw-r--r--app/assets/stylesheets/pages/projects.scss137
-rw-r--r--app/assets/stylesheets/pages/repo.scss413
-rw-r--r--app/assets/stylesheets/pages/settings.scss20
-rw-r--r--app/assets/stylesheets/pages/tree.scss32
-rw-r--r--app/assets/stylesheets/pages/wiki.scss12
-rw-r--r--app/controllers/admin/appearances_controller.rb2
-rw-r--r--app/controllers/admin/health_check_controller.rb7
-rw-r--r--app/controllers/application_controller.rb24
-rw-r--r--app/controllers/concerns/authenticates_with_two_factor.rb2
-rw-r--r--app/controllers/concerns/cycle_analytics_params.rb9
-rw-r--r--app/controllers/concerns/renders_blob.rb14
-rw-r--r--app/controllers/dashboard/projects_controller.rb14
-rw-r--r--app/controllers/dashboard/todos_controller.rb4
-rw-r--r--app/controllers/dashboard_controller.rb6
-rw-r--r--app/controllers/explore/projects_controller.rb11
-rw-r--r--app/controllers/groups_controller.rb6
-rw-r--r--app/controllers/import/github_controller.rb6
-rw-r--r--app/controllers/import/gitlab_controller.rb2
-rw-r--r--app/controllers/import/gitlab_projects_controller.rb10
-rw-r--r--app/controllers/omniauth_callbacks_controller.rb21
-rw-r--r--app/controllers/projects/blob_controller.rb39
-rw-r--r--app/controllers/projects/issues_controller.rb14
-rw-r--r--app/controllers/projects/merge_requests_controller.rb5
-rw-r--r--app/controllers/projects/tree_controller.rb17
-rw-r--r--app/controllers/projects_controller.rb44
-rw-r--r--app/controllers/unicorn_test_controller.rb4
-rw-r--r--app/finders/admin/projects_finder.rb2
-rw-r--r--app/finders/todos_finder.rb13
-rw-r--r--app/helpers/appearances_helper.rb2
-rw-r--r--app/helpers/application_helper.rb8
-rw-r--r--app/helpers/application_settings_helper.rb1
-rw-r--r--app/helpers/avatars_helper.rb3
-rw-r--r--app/helpers/blob_helper.rb6
-rw-r--r--app/helpers/defer_script_tag_helper.rb6
-rw-r--r--app/helpers/diff_helper.rb26
-rw-r--r--app/helpers/dropdowns_helper.rb19
-rw-r--r--app/helpers/gitlab_routing_helper.rb16
-rw-r--r--app/helpers/graph_helper.rb2
-rw-r--r--app/helpers/groups_helper.rb2
-rw-r--r--app/helpers/icons_helper.rb1
-rw-r--r--app/helpers/issuables_helper.rb6
-rw-r--r--app/helpers/labels_helper.rb4
-rw-r--r--app/helpers/milestones_routing_helper.rb17
-rw-r--r--app/helpers/nav_helper.rb1
-rw-r--r--app/helpers/pagination_helper.rb21
-rw-r--r--app/helpers/projects_helper.rb22
-rw-r--r--app/helpers/storage_health_helper.rb37
-rw-r--r--app/helpers/submodule_helper.rb4
-rw-r--r--app/models/appearance.rb20
-rw-r--r--app/models/application_setting.rb1
-rw-r--r--app/models/blob_viewer/base.rb2
-rw-r--r--app/models/blob_viewer/server_side.rb2
-rw-r--r--app/models/broadcast_message.rb14
-rw-r--r--app/models/commit.rb17
-rw-r--r--app/models/concerns/issuable.rb1
-rw-r--r--app/models/concerns/referable.rb12
-rw-r--r--app/models/concerns/spammable.rb6
-rw-r--r--app/models/concerns/token_authenticatable.rb3
-rw-r--r--app/models/conversational_development_index/metric.rb4
-rw-r--r--app/models/event.rb59
-rw-r--r--app/models/event_collection.rb98
-rw-r--r--app/models/event_for_migration.rb5
-rw-r--r--app/models/key.rb3
-rw-r--r--app/models/member.rb11
-rw-r--r--app/models/members/group_member.rb4
-rw-r--r--app/models/members/project_member.rb4
-rw-r--r--app/models/merge_request.rb47
-rw-r--r--app/models/merge_request_diff.rb10
-rw-r--r--app/models/merge_request_diff_commit.rb2
-rw-r--r--app/models/milestone.rb12
-rw-r--r--app/models/network/graph.rb2
-rw-r--r--app/models/note.rb20
-rw-r--r--app/models/notification_recipient.rb142
-rw-r--r--app/models/notification_setting.rb2
-rw-r--r--app/models/project.rb31
-rw-r--r--app/models/project_feature.rb4
-rw-r--r--app/models/project_import_data.rb2
-rw-r--r--app/models/project_services/jira_service.rb8
-rw-r--r--app/models/project_statistics.rb2
-rw-r--r--app/models/project_wiki.rb4
-rw-r--r--app/models/push_event.rb126
-rw-r--r--app/models/push_event_payload.rb22
-rw-r--r--app/models/redirect_route.rb10
-rw-r--r--app/models/repository.rb127
-rw-r--r--app/models/user.rb52
-rw-r--r--app/models/wiki_page.rb78
-rw-r--r--app/serializers/analytics_build_entity.rb2
-rw-r--r--app/serializers/analytics_issue_entity.rb2
-rw-r--r--app/serializers/blob_entity.rb17
-rw-r--r--app/serializers/job_entity.rb2
-rw-r--r--app/serializers/merge_request_entity.rb2
-rw-r--r--app/serializers/submodule_entity.rb23
-rw-r--r--app/serializers/tree_entity.rb17
-rw-r--r--app/serializers/tree_root_entity.rb8
-rw-r--r--app/serializers/tree_serializer.rb3
-rw-r--r--app/services/auth/container_registry_authentication_service.rb7
-rw-r--r--app/services/ci/register_job_service.rb24
-rw-r--r--app/services/event_create_service.rb9
-rw-r--r--app/services/issuable_base_service.rb8
-rw-r--r--app/services/issues/create_service.rb8
-rw-r--r--app/services/labels/transfer_service.rb2
-rw-r--r--app/services/merge_requests/create_service.rb16
-rw-r--r--app/services/notification_recipient_service.rb471
-rw-r--r--app/services/notification_service.rb114
-rw-r--r--app/services/projects/autocomplete_service.rb10
-rw-r--r--app/services/projects/create_from_template_service.rb15
-rw-r--r--app/services/projects/create_service.rb4
-rw-r--r--app/services/projects/gitlab_projects_import_service.rb36
-rw-r--r--app/services/projects/import_service.rb8
-rw-r--r--app/services/projects/update_pages_service.rb18
-rw-r--r--app/services/projects/update_service.rb2
-rw-r--r--app/services/push_event_payload_service.rb120
-rw-r--r--app/services/quick_actions/interpret_service.rb7
-rw-r--r--app/services/submit_usage_ping_service.rb20
-rw-r--r--app/services/system_note_service.rb3
-rw-r--r--app/services/todo_service.rb16
-rw-r--r--app/services/wiki_pages/update_service.rb2
-rw-r--r--app/uploaders/personal_file_uploader.rb2
-rw-r--r--app/views/admin/application_settings/_form.html.haml8
-rw-r--r--app/views/admin/health_check/_failing_storages.html.haml15
-rw-r--r--app/views/admin/health_check/show.html.haml27
-rw-r--r--app/views/ci/lints/show.html.haml2
-rw-r--r--app/views/dashboard/projects/_blank_state_admin_welcome.html.haml2
-rw-r--r--app/views/dashboard/projects/index.html.haml4
-rw-r--r--app/views/dashboard/projects/starred.html.haml2
-rw-r--r--app/views/events/_commit.html.haml4
-rw-r--r--app/views/events/_event_push.atom.haml19
-rw-r--r--app/views/events/event/_push.html.haml13
-rw-r--r--app/views/help/_shortcuts.html.haml2
-rw-r--r--app/views/import/fogbugz/new_user_map.html.haml2
-rw-r--r--app/views/import/gitlab_projects/new.html.haml50
-rw-r--r--app/views/kaminari/gitlab/_without_count.html.haml8
-rw-r--r--app/views/layouts/_bootlint.haml7
-rw-r--r--app/views/layouts/_head.html.haml3
-rw-r--r--app/views/layouts/_init_auto_complete.html.haml1
-rw-r--r--app/views/layouts/header/_default.html.haml2
-rw-r--r--app/views/layouts/header/_new.html.haml2
-rw-r--r--app/views/layouts/nav/_group.html.haml2
-rw-r--r--app/views/layouts/nav/_new_admin_sidebar.html.haml9
-rw-r--r--app/views/layouts/nav/_new_group_sidebar.html.haml15
-rw-r--r--app/views/layouts/nav/_new_profile_sidebar.html.haml7
-rw-r--r--app/views/layouts/nav/_new_project_sidebar.html.haml66
-rw-r--r--app/views/profiles/preferences/show.html.haml4
-rw-r--r--app/views/projects/_export.html.haml41
-rw-r--r--app/views/projects/_files.html.haml11
-rw-r--r--app/views/projects/_md_preview.html.haml10
-rw-r--r--app/views/projects/_merge_request_settings.html.haml7
-rw-r--r--app/views/projects/_project_templates.html.haml10
-rw-r--r--app/views/projects/artifacts/file.html.haml2
-rw-r--r--app/views/projects/blob/_blob.html.haml2
-rw-r--r--app/views/projects/blob/_upload.html.haml4
-rw-r--r--app/views/projects/blob/_viewer.html.haml1
-rw-r--r--app/views/projects/blob/show.html.haml21
-rw-r--r--app/views/projects/blob/viewers/_balsamiq.html.haml2
-rw-r--r--app/views/projects/blob/viewers/_download.html.haml2
-rw-r--r--app/views/projects/blob/viewers/_image.html.haml2
-rw-r--r--app/views/projects/blob/viewers/_notebook.html.haml2
-rw-r--r--app/views/projects/blob/viewers/_pdf.html.haml2
-rw-r--r--app/views/projects/blob/viewers/_sketch.html.haml2
-rw-r--r--app/views/projects/blob/viewers/_stl.html.haml2
-rw-r--r--app/views/projects/blob/viewers/_video.html.haml2
-rw-r--r--app/views/projects/boards/components/_board.html.haml12
-rw-r--r--app/views/projects/cycle_analytics/show.html.haml3
-rw-r--r--app/views/projects/diffs/_diffs.html.haml32
-rw-r--r--app/views/projects/diffs/_stats.html.haml66
-rw-r--r--app/views/projects/edit.html.haml430
-rw-r--r--app/views/projects/graphs/charts.html.haml6
-rw-r--r--app/views/projects/issues/show.html.haml3
-rw-r--r--app/views/projects/jobs/_sidebar.html.haml168
-rw-r--r--app/views/projects/merge_requests/diffs/_diffs.html.haml2
-rw-r--r--app/views/projects/merge_requests/show.html.haml1
-rw-r--r--app/views/projects/new.html.haml109
-rw-r--r--app/views/projects/notes/_actions.html.haml38
-rw-r--r--app/views/projects/notes/_more_actions_dropdown.html.haml9
-rw-r--r--app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml2
-rw-r--r--app/views/projects/runners/edit.html.haml2
-rw-r--r--app/views/projects/tree/_old_tree_content.html.haml24
-rw-r--r--app/views/projects/tree/_old_tree_header.html.haml70
-rw-r--r--app/views/projects/tree/_tree_content.html.haml29
-rw-r--r--app/views/projects/tree/_tree_header.html.haml83
-rw-r--r--app/views/projects/tree/show.html.haml8
-rw-r--r--app/views/projects/wikis/_form.html.haml5
-rw-r--r--app/views/projects/wikis/_sidebar.html.haml31
-rw-r--r--app/views/shared/_clone_panel.html.haml2
-rw-r--r--app/views/shared/_commit_well.html.haml4
-rw-r--r--app/views/shared/_import_form.html.haml26
-rw-r--r--app/views/shared/_new_project_item_select.html.haml9
-rw-r--r--app/views/shared/_sidebar_toggle_button.html.haml8
-rw-r--r--app/views/shared/_target_switcher.html.haml20
-rw-r--r--app/views/shared/icons/_container_registry.svg1
-rw-r--r--app/views/shared/icons/_ellipsis_v.svg1
-rw-r--r--app/views/shared/icons/_java_spring.svg6
-rw-r--r--app/views/shared/icons/_node_express.svg6
-rw-r--r--app/views/shared/icons/_rails.svg6
-rw-r--r--app/views/shared/icons/_spam_logs.svg1
-rw-r--r--app/views/shared/issuable/_label_page_create.html.haml2
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml6
-rw-r--r--app/views/shared/issuable/_user_dropdown_item.html.haml3
-rw-r--r--app/views/shared/milestones/_sidebar.html.haml2
-rw-r--r--app/views/shared/projects/_list.html.haml6
-rw-r--r--app/views/shared/repo/_editable_mode.html.haml2
-rw-r--r--app/views/shared/repo/_repo.html.haml2
-rw-r--r--app/views/snippets/notes/_actions.html.haml17
-rw-r--r--app/views/users/calendar_activities.html.haml6
-rw-r--r--app/views/users/show.html.haml2
-rw-r--r--app/workers/concerns/new_issuable.rb26
-rw-r--r--app/workers/email_receiver_worker.rb2
-rw-r--r--app/workers/merge_worker.rb2
-rw-r--r--app/workers/new_issue_worker.rb17
-rw-r--r--app/workers/new_merge_request_worker.rb17
-rw-r--r--app/workers/pages_worker.rb2
-rw-r--r--app/workers/stuck_merge_jobs_worker.rb34
-rwxr-xr-xbin/changelog134
-rwxr-xr-xbin/rspec-stackprof3
-rw-r--r--changelogs/unreleased/13265-project_events_noteable_iid.yml4
-rw-r--r--changelogs/unreleased/13325-bugfix-silence-on-disabled-notifications.yml6
-rw-r--r--changelogs/unreleased/21949-add-type-to-changelog.yml4
-rw-r--r--changelogs/unreleased/27616-fix-contributions-graph-utc-offset-mysql.yml4
-rw-r--r--changelogs/unreleased/28472-ignore-auto-generated-mails.yml4
-rw-r--r--changelogs/unreleased/29811-fix-line-number-alignment.yml4
-rw-r--r--changelogs/unreleased/31207-clean-locked-merge-requests.yml4
-rw-r--r--changelogs/unreleased/32844-issuables-performance.yml4
-rw-r--r--changelogs/unreleased/33095-mr-widget-ui.yml4
-rw-r--r--changelogs/unreleased/33874_confi.yml5
-rw-r--r--changelogs/unreleased/34028-collapse-sidebar.yml4
-rw-r--r--changelogs/unreleased/34339-user_avatar-url-in-push-event-webhook-json-payload-is-relative-should-be-absolute.yml4
-rw-r--r--changelogs/unreleased/34492-firefox-job.yml4
-rw-r--r--changelogs/unreleased/34527-make-edit-comment-button-always-available-outside-of-dropdown.yml4
-rw-r--r--changelogs/unreleased/34764-rename-to-overview.yml4
-rw-r--r--changelogs/unreleased/35098-raise-encoding-confidence-threshold.yml4
-rw-r--r--changelogs/unreleased/35136-barchart-not-display-label-at-0-hour.yml4
-rw-r--r--changelogs/unreleased/35483-improve-mobile-sidebar.yml4
-rw-r--r--changelogs/unreleased/35659-rename-pipeline.yml4
-rw-r--r--changelogs/unreleased/35697-allow-logged-in-user-to-read-user-list.yml4
-rw-r--r--changelogs/unreleased/35761-convdev-perc.yml4
-rw-r--r--changelogs/unreleased/36010-api-v4-allows-setting-a-branch-that-doesn-t-exist-as-the-default-one.yml4
-rw-r--r--changelogs/unreleased/36119-issuable-workers.yml4
-rw-r--r--changelogs/unreleased/36185-or-separator.yml4
-rw-r--r--changelogs/unreleased/36213-return-is_admin-in-users-api-when-current_user-is-admin.yml6
-rw-r--r--changelogs/unreleased/36385-pipeline-graph-dropdown.yml5
-rw-r--r--changelogs/unreleased/3686_make_tarball_download_url.yml4
-rw-r--r--changelogs/unreleased/add-star-for-action-scope.yml4
-rw-r--r--changelogs/unreleased/appearances-caching-and-schema.yml4
-rw-r--r--changelogs/unreleased/broadcast-messages-cache.yml4
-rw-r--r--changelogs/unreleased/bump-omniauth-ldap-gem-version-2-0-4.yml4
-rw-r--r--changelogs/unreleased/bvl-nfs-circuitbreaker.yml4
-rw-r--r--changelogs/unreleased/bvl-rollback-renamed-system-namespace.yml4
-rw-r--r--changelogs/unreleased/diff-changed-files-dropdown.yml4
-rw-r--r--changelogs/unreleased/disable-project-export.yml4
-rw-r--r--changelogs/unreleased/dont-use-limit-offset-when-counting-projects.yml4
-rw-r--r--changelogs/unreleased/eager-load-project-creators-for-project-dashboards.yml4
-rw-r--r--changelogs/unreleased/fix-group-milestone-link-in-issuable-sidebar.yml4
-rw-r--r--changelogs/unreleased/fix-import-symbolink-links.yml4
-rw-r--r--changelogs/unreleased/fix-thread-safe-gpgme-tmp-directory.yml4
-rw-r--r--changelogs/unreleased/github.yml4
-rw-r--r--changelogs/unreleased/group-milestone-references-system-notes.yml4
-rw-r--r--changelogs/unreleased/group-new-issue.yml4
-rw-r--r--changelogs/unreleased/memoize-user-personal-projects-count.yml4
-rw-r--r--changelogs/unreleased/migrate-events-into-a-new-format.yml4
-rw-r--r--changelogs/unreleased/mk-fix-deploy-key-deletion.yml4
-rw-r--r--changelogs/unreleased/pagination-projects-explore.yml4
-rw-r--r--changelogs/unreleased/pawel-add-sidekiq-metrics-endpoint-32145.yml4
-rw-r--r--changelogs/unreleased/project-foreign-keys-without-errors.yml4
-rw-r--r--changelogs/unreleased/rc-fix-branches-api-endpoint.yml2
-rw-r--r--changelogs/unreleased/rc-fix-commits-api.yml5
-rw-r--r--changelogs/unreleased/rc-fix-tags-api.yml5
-rw-r--r--changelogs/unreleased/remove-redundant-query-when-retrieving-recent-pushes.yml4
-rw-r--r--changelogs/unreleased/reorganise-issues-indexes-for-sorting.yml4
-rw-r--r--changelogs/unreleased/restrict-haml-javascript.yml4
-rw-r--r--changelogs/unreleased/rs-alphanumeric-ssh-params.yml5
-rw-r--r--changelogs/unreleased/search-flickering.yml4
-rw-r--r--changelogs/unreleased/seven-days-cycle-analytics.yml5
-rw-r--r--changelogs/unreleased/tc-fix-wildcard-protected-delete-merged.yml4
-rw-r--r--changelogs/unreleased/tc-no-todo-service-select.yml4
-rw-r--r--changelogs/unreleased/use-a-specialized-class-for-querying-events.yml4
-rw-r--r--changelogs/unreleased/wiki_title.yml4
-rw-r--r--changelogs/unreleased/zj-project-templates.yml4
-rw-r--r--config/application.rb10
-rw-r--r--config/dependency_decisions.yml6
-rw-r--r--config/gitlab.yml.example15
-rw-r--r--config/initializers/1_settings.rb21
-rw-r--r--config/initializers/6_validations.rb16
-rw-r--r--config/initializers/7_prometheus_metrics.rb6
-rw-r--r--config/initializers/active_record_locking.rb4
-rw-r--r--config/prometheus/additional_metrics.yml38
-rw-r--r--config/routes/admin.rb4
-rw-r--r--config/routes/repository.rb5
-rw-r--r--config/routes/uploads.rb4
-rw-r--r--config/sidekiq_queues.yml2
-rw-r--r--config/webpack.config.js61
-rw-r--r--db/fixtures/development/10_merge_requests.rb2
-rw-r--r--db/migrate/20160713205315_add_domain_blacklist_to_application_settings.rb1
-rw-r--r--db/migrate/20160817133006_add_koding_to_application_settings.rb1
-rw-r--r--db/migrate/20161017125927_add_unique_index_to_labels.rb2
-rw-r--r--db/migrate/20161103191444_add_sidekiq_throttling_to_application_settings.rb1
-rw-r--r--db/migrate/20161128161412_add_html_emails_enabled_to_application_settings.rb1
-rw-r--r--db/migrate/20161206003819_add_plant_uml_enabled_to_application_settings.rb1
-rw-r--r--db/migrate/20170316163800_rename_system_namespaces.rb231
-rw-r--r--db/migrate/20170316163845_move_uploads_to_system_dir.rb2
-rw-r--r--db/migrate/20170602154736_add_help_page_hide_commercial_content_to_application_settings.rb1
-rw-r--r--db/migrate/20170608152747_prepare_events_table_for_push_events_migration.rb51
-rw-r--r--db/migrate/20170608152748_create_push_event_payloads_tables.rb46
-rw-r--r--db/migrate/20170717074009_move_system_upload_folder.rb10
-rw-r--r--db/migrate/20170727123534_add_index_on_events_project_id_id.rb37
-rw-r--r--db/migrate/20170731175128_add_percentages_to_conv_dev.rb32
-rw-r--r--db/migrate/20170731183033_add_merge_jid_to_merge_requests.rb7
-rw-r--r--db/migrate/20170803130232_reorganise_issues_indexes_for_faster_sorting.rb43
-rw-r--r--db/migrate/20170809133343_add_broadcast_messages_index.rb21
-rw-r--r--db/migrate/20170809134534_add_broadcast_message_not_null_constraints.rb17
-rw-r--r--db/migrate/20170809142252_cleanup_appearances_schema.rb33
-rw-r--r--db/migrate/20170809161910_add_project_export_enabled_to_application_settings.rb14
-rw-r--r--db/post_migrate/20170317162059_update_upload_paths_to_system.rb2
-rw-r--r--db/post_migrate/20170406111121_clean_upload_symlinks.rb2
-rw-r--r--db/post_migrate/20170502101023_cleanup_namespaceless_pending_delete_projects.rb2
-rw-r--r--db/post_migrate/20170606202615_move_appearance_to_system_dir.rb2
-rw-r--r--db/post_migrate/20170612071012_move_personal_snippets_files.rb4
-rw-r--r--db/post_migrate/20170627101016_schedule_event_migrations.rb40
-rw-r--r--db/post_migrate/20170703130158_schedule_merge_request_diff_migrations.rb33
-rw-r--r--db/post_migrate/20170803090603_calculate_conv_dev_index_percentages.rb30
-rw-r--r--db/post_migrate/20170807160457_remove_locked_at_column_from_merge_requests.rb11
-rw-r--r--db/post_migrate/20170807190736_move_personal_snippet_files_into_correct_folder.rb29
-rw-r--r--db/schema.rb71
-rw-r--r--doc/README.md10
-rw-r--r--doc/administration/high_availability/nfs.md4
-rw-r--r--doc/administration/img/failing_storage.pngbin0 -> 48281 bytes
-rw-r--r--doc/administration/logs.md27
-rw-r--r--doc/administration/reply_by_email_postfix_setup.md14
-rw-r--r--doc/administration/repository_storage_paths.md77
-rw-r--r--doc/api/README.md63
-rw-r--r--doc/api/commits.md3
-rw-r--r--doc/api/events.md81
-rw-r--r--doc/api/group_milestones.md4
-rw-r--r--doc/api/issues.md6
-rw-r--r--doc/api/merge_requests.md2
-rw-r--r--doc/api/milestones.md4
-rw-r--r--doc/api/notes.md9
-rw-r--r--doc/api/repository_files.md9
-rw-r--r--doc/api/repository_storage_health.md74
-rw-r--r--doc/api/tags.md41
-rw-r--r--doc/articles/index.md2
-rw-r--r--doc/articles/openshift_and_gitlab/img/add-gitlab-to-project.pngbin0 -> 37386 bytes
-rw-r--r--doc/articles/openshift_and_gitlab/img/add-to-project.pngbin0 -> 21672 bytes
-rw-r--r--doc/articles/openshift_and_gitlab/img/create-project-ui.pngbin0 -> 22290 bytes
-rw-r--r--doc/articles/openshift_and_gitlab/img/gitlab-logs.pngbin0 -> 70858 bytes
-rw-r--r--doc/articles/openshift_and_gitlab/img/gitlab-overview.pngbin0 -> 106432 bytes
-rw-r--r--doc/articles/openshift_and_gitlab/img/gitlab-running.pngbin0 -> 107993 bytes
-rw-r--r--doc/articles/openshift_and_gitlab/img/gitlab-scale.pngbin0 -> 36628 bytes
-rw-r--r--doc/articles/openshift_and_gitlab/img/gitlab-settings.pngbin0 -> 111366 bytes
-rw-r--r--doc/articles/openshift_and_gitlab/img/no-resources.pngbin0 -> 34669 bytes
-rw-r--r--doc/articles/openshift_and_gitlab/img/openshift-infra-project.pngbin0 -> 95725 bytes
-rw-r--r--doc/articles/openshift_and_gitlab/img/pods-overview.pngbin0 -> 106861 bytes
-rw-r--r--doc/articles/openshift_and_gitlab/img/rc-name.pngbin0 -> 51390 bytes
-rw-r--r--doc/articles/openshift_and_gitlab/img/running-pods.pngbin0 -> 29818 bytes
-rw-r--r--doc/articles/openshift_and_gitlab/img/storage-volumes.pngbin0 -> 49584 bytes
-rw-r--r--doc/articles/openshift_and_gitlab/img/web-console.pngbin0 -> 34774 bytes
-rw-r--r--doc/articles/openshift_and_gitlab/index.md510
-rw-r--r--doc/ci/environments.md5
-rw-r--r--doc/development/background_migrations.md9
-rw-r--r--doc/development/changelog.md54
-rw-r--r--doc/development/fe_guide/style_guide_js.md71
-rw-r--r--doc/development/migration_style_guide.md15
-rw-r--r--doc/development/rake_tasks.md17
-rw-r--r--doc/development/testing.md75
-rw-r--r--doc/install/installation.md23
-rw-r--r--doc/install/kubernetes/gitlab_chart.md6
-rw-r--r--doc/install/kubernetes/gitlab_omnibus.md171
-rw-r--r--doc/install/kubernetes/gitlab_runner_chart.md4
-rw-r--r--doc/install/kubernetes/index.md13
-rw-r--r--doc/install/requirements.md4
-rw-r--r--doc/system_hooks/system_hooks.md14
-rw-r--r--doc/update/8.17-to-9.0.md15
-rw-r--r--doc/update/9.0-to-9.1.md15
-rw-r--r--doc/update/9.1-to-9.2.md21
-rw-r--r--doc/update/9.2-to-9.3.md20
-rw-r--r--doc/update/9.3-to-9.4.md20
-rw-r--r--doc/update/9.4-to-9.5.md356
-rw-r--r--doc/update/README.md68
-rw-r--r--doc/update/patch_versions.md8
-rw-r--r--doc/user/group/index.md10
-rw-r--r--doc/user/index.md88
-rw-r--r--doc/user/markdown.md6
-rw-r--r--doc/user/permissions.md4
-rw-r--r--doc/user/project/index.md6
-rw-r--r--doc/user/project/integrations/prometheus.md6
-rw-r--r--doc/user/project/integrations/prometheus_library/metrics.md6
-rw-r--r--doc/user/project/integrations/prometheus_library/nginx.md4
-rw-r--r--doc/user/project/integrations/prometheus_library/nginx_ingress.md25
-rw-r--r--doc/user/project/integrations/webhooks.md1
-rw-r--r--doc/user/project/members/img/access_requests_management.png (renamed from doc/workflow/add-user/img/access_requests_management.png)bin11018 -> 11018 bytes
-rw-r--r--doc/user/project/members/img/add_new_user_to_project_settings.png (renamed from doc/workflow/add-user/img/add_new_user_to_project_settings.png)bin11046 -> 11046 bytes
-rw-r--r--doc/user/project/members/img/add_user_email_accept.png (renamed from doc/workflow/add-user/img/add_user_email_accept.png)bin16890 -> 16890 bytes
-rw-r--r--doc/user/project/members/img/add_user_email_ready.png (renamed from doc/workflow/add-user/img/add_user_email_ready.png)bin28171 -> 28171 bytes
-rw-r--r--doc/user/project/members/img/add_user_email_search.png (renamed from doc/workflow/add-user/img/add_user_email_search.png)bin29628 -> 29628 bytes
-rw-r--r--doc/user/project/members/img/add_user_give_permissions.png (renamed from doc/workflow/add-user/img/add_user_give_permissions.png)bin36619 -> 36619 bytes
-rw-r--r--doc/user/project/members/img/add_user_import_members_from_another_project.png (renamed from doc/workflow/add-user/img/add_user_import_members_from_another_project.png)bin25343 -> 25343 bytes
-rw-r--r--doc/user/project/members/img/add_user_imported_members.png (renamed from doc/workflow/add-user/img/add_user_imported_members.png)bin25398 -> 25398 bytes
-rw-r--r--doc/user/project/members/img/add_user_list_members.png (renamed from doc/workflow/add-user/img/add_user_list_members.png)bin16916 -> 16916 bytes
-rw-r--r--doc/user/project/members/img/add_user_members_menu.png (renamed from doc/workflow/add-user/img/add_user_members_menu.png)bin28994 -> 28994 bytes
-rw-r--r--doc/user/project/members/img/add_user_search_people.png (renamed from doc/workflow/add-user/img/add_user_search_people.png)bin25368 -> 25368 bytes
-rw-r--r--doc/user/project/members/img/max_access_level.png (renamed from doc/workflow/groups/max_access_level.png)bin34718 -> 34718 bytes
-rw-r--r--doc/user/project/members/img/other_group_sees_shared_project.png (renamed from doc/workflow/groups/other_group_sees_shared_project.png)bin30182 -> 30182 bytes
-rw-r--r--doc/user/project/members/img/request_access_button.png (renamed from doc/workflow/add-user/img/request_access_button.png)bin25281 -> 25281 bytes
-rw-r--r--doc/user/project/members/img/share_project_with_groups.png (renamed from doc/workflow/groups/share_project_with_groups.png)bin30307 -> 30307 bytes
-rw-r--r--doc/user/project/members/img/withdraw_access_request_button.png (renamed from doc/workflow/add-user/img/withdraw_access_request_button.png)bin26135 -> 26135 bytes
-rw-r--r--doc/user/project/members/index.md116
-rw-r--r--doc/user/project/members/share_project_with_groups.md41
-rw-r--r--doc/user/project/milestones/index.md3
-rw-r--r--doc/user/project/repository/index.md6
-rw-r--r--doc/user/project/settings/import_export.md3
-rw-r--r--doc/user/snippets.md2
-rw-r--r--doc/workflow/README.md7
-rw-r--r--doc/workflow/add-user/add-user.md115
-rw-r--r--doc/workflow/project_features.md46
-rw-r--r--doc/workflow/share_projects_with_other_groups.md33
-rw-r--r--doc/workflow/share_with_group.md14
-rw-r--r--features/steps/project/merge_requests.rb3
-rw-r--r--features/steps/project/project.rb16
-rw-r--r--features/steps/project/wiki.rb2
-rw-r--r--features/steps/shared/note.rb7
-rw-r--r--features/steps/shared/project.rb30
-rw-r--r--lib/api/api.rb3
-rw-r--r--lib/api/circuit_breakers.rb50
-rw-r--r--lib/api/commits.rb40
-rw-r--r--lib/api/entities.rb53
-rw-r--r--lib/api/files.rb7
-rw-r--r--lib/api/helpers.rb12
-rw-r--r--lib/api/helpers/members_helpers.rb4
-rw-r--r--lib/api/notes.rb2
-rw-r--r--lib/api/settings.rb1
-rw-r--r--lib/api/tags.rb14
-rw-r--r--lib/api/todos.rb6
-rw-r--r--lib/api/users.rb13
-rw-r--r--lib/api/v3/entities.rb14
-rw-r--r--lib/api/v3/todos.rb6
-rw-r--r--lib/backup/manager.rb4
-rw-r--r--lib/banzai/filter/abstract_reference_filter.rb81
-rw-r--r--lib/banzai/filter/milestone_reference_filter.rb34
-rw-r--r--lib/banzai/renderer.rb2
-rw-r--r--lib/ci/ansi2html.rb4
-rw-r--r--lib/ci/charts.rb2
-rw-r--r--lib/constraints/project_url_constrainer.rb2
-rw-r--r--lib/declarative_policy.rb14
-rw-r--r--lib/declarative_policy/runner.rb12
-rw-r--r--lib/file_streamer.rb16
-rw-r--r--lib/github/client.rb36
-rw-r--r--lib/github/import.rb34
-rw-r--r--lib/gitlab/auth.rb3
-rw-r--r--lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits.rb109
-rw-r--r--lib/gitlab/background_migration/migrate_events_to_push_event_payloads.rb176
-rw-r--r--lib/gitlab/background_migration/move_personal_snippet_files.rb79
-rw-r--r--lib/gitlab/checks/force_push.rb19
-rw-r--r--lib/gitlab/conflict/file_collection.rb4
-rw-r--r--lib/gitlab/contributions_calendar.rb14
-rw-r--r--lib/gitlab/cycle_analytics/plan_event_fetcher.rb2
-rw-r--r--lib/gitlab/daemon.rb62
-rw-r--r--lib/gitlab/database.rb4
-rw-r--r--lib/gitlab/diff/line.rb2
-rw-r--r--lib/gitlab/ee_compat_check.rb5
-rw-r--r--lib/gitlab/email/handler/create_note_handler.rb1
-rw-r--r--lib/gitlab/email/receiver.rb13
-rw-r--r--lib/gitlab/encoding_helper.rb2
-rw-r--r--lib/gitlab/environment.rb7
-rw-r--r--lib/gitlab/git/blame.rb26
-rw-r--r--lib/gitlab/git/blob.rb136
-rw-r--r--lib/gitlab/git/commit.rb128
-rw-r--r--lib/gitlab/git/commit_stats.rb2
-rw-r--r--lib/gitlab/git/diff.rb4
-rw-r--r--lib/gitlab/git/diff_collection.rb3
-rw-r--r--lib/gitlab/git/repository.rb282
-rw-r--r--lib/gitlab/git/storage.rb22
-rw-r--r--lib/gitlab/git/storage/circuit_breaker.rb144
-rw-r--r--lib/gitlab/git/storage/forked_storage_check.rb55
-rw-r--r--lib/gitlab/git/storage/health.rb91
-rw-r--r--lib/gitlab/gitaly_client.rb4
-rw-r--r--lib/gitlab/gitaly_client/commit.rb14
-rw-r--r--lib/gitlab/gitaly_client/commit_service.rb80
-rw-r--r--lib/gitlab/gitaly_client/diff.rb4
-rw-r--r--lib/gitlab/gitaly_client/ref_service.rb8
-rw-r--r--lib/gitlab/gitaly_client/repository_service.rb7
-rw-r--r--lib/gitlab/gitaly_client/util.rb4
-rw-r--r--lib/gitlab/gitlab_import/client.rb2
-rw-r--r--lib/gitlab/gpg.rb40
-rw-r--r--lib/gitlab/health_checks/fs_shards_check.rb21
-rw-r--r--lib/gitlab/i18n.rb3
-rw-r--r--lib/gitlab/import_export/file_importer.rb6
-rw-r--r--lib/gitlab/import_export/import_export.yml27
-rw-r--r--lib/gitlab/import_sources.rb2
-rw-r--r--lib/gitlab/key_fingerprint.rb71
-rw-r--r--lib/gitlab/metrics/base_sampler.rb75
-rw-r--r--lib/gitlab/metrics/sidekiq_metrics_exporter.rb39
-rw-r--r--lib/gitlab/middleware/webpack_proxy.rb2
-rw-r--r--lib/gitlab/o_auth/session.rb2
-rw-r--r--lib/gitlab/project_template.rb45
-rw-r--r--lib/gitlab/prometheus/queries/additional_metrics_environment_query.rb2
-rw-r--r--lib/gitlab/prometheus/queries/environment_query.rb2
-rw-r--r--lib/gitlab/reference_extractor.rb2
-rw-r--r--lib/gitlab/seeder.rb2
-rw-r--r--lib/gitlab/shell.rb24
-rw-r--r--lib/gitlab/url_blocker.rb8
-rw-r--r--lib/gitlab/usage_data.rb4
-rw-r--r--lib/haml_lint/inline_javascript.rb16
-rw-r--r--lib/mattermost/session.rb26
-rw-r--r--lib/rspec_flaky/example.rb46
-rw-r--r--lib/rspec_flaky/flaky_example.rb39
-rw-r--r--lib/rspec_flaky/listener.rb75
-rw-r--r--lib/static_model.rb2
-rw-r--r--lib/support/nginx/gitlab35
-rw-r--r--lib/support/nginx/gitlab-pages5
-rw-r--r--lib/support/nginx/gitlab-pages-ssl5
-rw-r--r--lib/support/nginx/gitlab-ssl39
-rw-r--r--lib/tasks/gitlab/check.rake12
-rw-r--r--lib/tasks/gitlab/gitaly.rake9
-rw-r--r--lib/tasks/gitlab/helpers.rake2
-rw-r--r--lib/tasks/gitlab/task_helpers.rb2
-rw-r--r--lib/tasks/gitlab/update_templates.rake49
-rw-r--r--lib/tasks/haml-lint.rake1
-rw-r--r--lib/tasks/import.rake3
-rw-r--r--locale/bg/gitlab.po26
-rw-r--r--locale/en/gitlab.po123
-rw-r--r--locale/eo/gitlab.po35
-rw-r--r--locale/es/gitlab.po20
-rw-r--r--locale/fr/gitlab.po30
-rw-r--r--locale/gitlab.pot10
-rw-r--r--locale/it/gitlab.po34
-rw-r--r--locale/ja/gitlab.po30
-rw-r--r--locale/ko/gitlab.po1200
-rw-r--r--locale/ko/gitlab.po.time_stamp0
-rw-r--r--locale/pt_BR/gitlab.po37
-rw-r--r--locale/ru/gitlab.po45
-rw-r--r--locale/uk/gitlab.po73
-rw-r--r--locale/zh_CN/gitlab.po17
-rw-r--r--locale/zh_HK/gitlab.po17
-rw-r--r--locale/zh_TW/gitlab.po21
-rw-r--r--package.json19
-rw-r--r--rubocop/cop/migration/safer_boolean_column.rb94
-rw-r--r--rubocop/rubocop.rb1
-rwxr-xr-xscripts/detect-new-flaky-examples21
-rwxr-xr-xscripts/gitaly-test-build11
-rwxr-xr-xscripts/merge-reports2
-rw-r--r--spec/bin/changelog_spec.rb98
-rw-r--r--spec/config/mail_room_spec.rb4
-rw-r--r--spec/controllers/admin/health_check_controller_spec.rb25
-rw-r--r--spec/controllers/admin/projects_controller_spec.rb12
-rw-r--r--spec/controllers/admin/users_controller_spec.rb2
-rw-r--r--spec/controllers/application_controller_spec.rb24
-rw-r--r--spec/controllers/autocomplete_controller_spec.rb135
-rw-r--r--spec/controllers/invites_controller_spec.rb2
-rw-r--r--spec/controllers/projects/blob_controller_spec.rb20
-rw-r--r--spec/controllers/projects/issues_controller_spec.rb12
-rw-r--r--spec/controllers/projects/repositories_controller_spec.rb2
-rw-r--r--spec/controllers/projects/todos_controller_spec.rb4
-rw-r--r--spec/controllers/projects_controller_spec.rb126
-rw-r--r--spec/controllers/registrations_controller_spec.rb4
-rw-r--r--spec/controllers/snippets/notes_controller_spec.rb6
-rw-r--r--spec/controllers/snippets_controller_spec.rb8
-rw-r--r--spec/controllers/uploads_controller_spec.rb4
-rw-r--r--spec/controllers/users_controller_spec.rb10
-rw-r--r--spec/factories/conversational_development_index_metrics.rb10
-rw-r--r--spec/factories/deployments.rb4
-rw-r--r--spec/factories/events.rb16
-rw-r--r--spec/factories/merge_requests.rb11
-rw-r--r--spec/factories/projects.rb105
-rw-r--r--spec/factories/users.rb6
-rw-r--r--spec/features/admin/admin_health_check_spec.rb24
-rw-r--r--spec/features/admin/admin_settings_spec.rb10
-rw-r--r--spec/features/boards/boards_spec.rb4
-rw-r--r--spec/features/boards/sidebar_spec.rb18
-rw-r--r--spec/features/calendar_spec.rb16
-rw-r--r--spec/features/cycle_analytics_spec.rb38
-rw-r--r--spec/features/dashboard/activity_spec.rb28
-rw-r--r--spec/features/dashboard/issues_spec.rb15
-rw-r--r--spec/features/groups/empty_states_spec.rb4
-rw-r--r--spec/features/groups/group_settings_spec.rb2
-rw-r--r--spec/features/groups/milestone_spec.rb10
-rw-r--r--spec/features/issues/create_branch_merge_request_spec.rb14
-rw-r--r--spec/features/issues/issue_sidebar_spec.rb8
-rw-r--r--spec/features/issues/note_polling_spec.rb2
-rw-r--r--spec/features/issues_spec.rb25
-rw-r--r--spec/features/merge_requests/closes_issues_spec.rb22
-rw-r--r--spec/features/merge_requests/diff_notes_avatars_spec.rb2
-rw-r--r--spec/features/merge_requests/diffs_spec.rb2
-rw-r--r--spec/features/merge_requests/discussion_spec.rb4
-rw-r--r--spec/features/merge_requests/filter_by_labels_spec.rb10
-rw-r--r--spec/features/merge_requests/filter_merge_requests_spec.rb12
-rw-r--r--spec/features/merge_requests/form_spec.rb2
-rw-r--r--spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb10
-rw-r--r--spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb8
-rw-r--r--spec/features/merge_requests/pipelines_spec.rb97
-rw-r--r--spec/features/merge_requests/reset_filters_spec.rb6
-rw-r--r--spec/features/merge_requests/user_lists_merge_requests_spec.rb16
-rw-r--r--spec/features/merge_requests/user_posts_diff_notes_spec.rb4
-rw-r--r--spec/features/merge_requests/user_posts_notes_spec.rb3
-rw-r--r--spec/features/merge_requests/user_uses_slash_commands_spec.rb10
-rw-r--r--spec/features/merge_requests/widget_spec.rb13
-rw-r--r--spec/features/password_reset_spec.rb4
-rw-r--r--spec/features/profiles/account_spec.rb2
-rw-r--r--spec/features/profiles/preferences_spec.rb28
-rw-r--r--spec/features/projects/features_visibility_spec.rb32
-rw-r--r--spec/features/projects/import_export/import_file_spec.rb16
-rw-r--r--spec/features/projects/pipeline_schedules_spec.rb19
-rw-r--r--spec/features/projects/project_settings_spec.rb14
-rw-r--r--spec/features/projects/settings/merge_requests_settings_spec.rb13
-rw-r--r--spec/features/projects/user_edits_files_spec.rb26
-rw-r--r--spec/features/projects/wiki/user_updates_wiki_page_spec.rb2
-rw-r--r--spec/features/projects_spec.rb36
-rw-r--r--spec/features/snippets/notes_on_personal_snippets_spec.rb6
-rw-r--r--spec/features/snippets/user_creates_snippet_spec.rb6
-rw-r--r--spec/features/snippets/user_edits_snippet_spec.rb2
-rw-r--r--spec/features/task_lists_spec.rb4
-rw-r--r--spec/features/triggers_spec.rb4
-rw-r--r--spec/features/users_spec.rb4
-rw-r--r--spec/finders/admin/projects_finder_spec.rb6
-rw-r--r--spec/finders/contributed_projects_finder_spec.rb4
-rw-r--r--spec/finders/environments_finder_spec.rb2
-rw-r--r--spec/fixtures/api/schemas/entities/merge_request.json4
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/comment.json21
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/commit/detail.json16
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/commit_note.json19
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/commit_notes.json4
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/commit_stats.json14
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/commits.json4
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/release.json12
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/tag.json21
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/tags.json4
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/user/basic.json15
-rw-r--r--spec/fixtures/encoding/Japanese.md42
-rw-r--r--spec/fixtures/markdown.md.erb5
-rw-r--r--spec/helpers/defer_script_tag_helper_spec.rb13
-rw-r--r--spec/helpers/gitlab_routing_helper_spec.rb40
-rw-r--r--spec/helpers/milestones_routing_helper_spec.rb46
-rw-r--r--spec/helpers/pagination_helper_spec.rb23
-rw-r--r--spec/helpers/projects_helper_spec.rb44
-rw-r--r--spec/helpers/storage_health_helper_spec.rb20
-rw-r--r--spec/initializers/6_validations_spec.rb21
-rw-r--r--spec/initializers/settings_spec.rb11
-rw-r--r--spec/javascripts/abuse_reports_spec.js6
-rw-r--r--spec/javascripts/ajax_loading_spinner_spec.js1
-rw-r--r--spec/javascripts/blob/blob_file_dropzone_spec.js42
-rw-r--r--spec/javascripts/blob/viewer/index_spec.js4
-rw-r--r--spec/javascripts/boards/issue_card_spec.js28
-rw-r--r--spec/javascripts/breakpoints_spec.js15
-rw-r--r--spec/javascripts/build_spec.js1
-rw-r--r--spec/javascripts/extensions/array_spec.js22
-rw-r--r--spec/javascripts/filtered_search/dropdown_utils_spec.js1
-rw-r--r--spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js1
-rw-r--r--spec/javascripts/filtered_search/filtered_search_token_keys_spec.js1
-rw-r--r--spec/javascripts/filtered_search/filtered_search_tokenizer_spec.js1
-rw-r--r--spec/javascripts/fixtures/abuse_reports.rb2
-rw-r--r--spec/javascripts/fixtures/blob.rb2
-rw-r--r--spec/javascripts/fixtures/boards.rb2
-rw-r--r--spec/javascripts/fixtures/branches.rb2
-rw-r--r--spec/javascripts/fixtures/dashboard.rb2
-rw-r--r--spec/javascripts/fixtures/deploy_keys.rb2
-rw-r--r--spec/javascripts/fixtures/environments.rb2
-rw-r--r--spec/javascripts/fixtures/issues.rb2
-rw-r--r--spec/javascripts/fixtures/jobs.rb2
-rw-r--r--spec/javascripts/fixtures/labels.rb4
-rw-r--r--spec/javascripts/fixtures/merge_requests.rb2
-rw-r--r--spec/javascripts/fixtures/merge_requests_diffs.rb2
-rw-r--r--spec/javascripts/fixtures/pipelines.rb2
-rw-r--r--spec/javascripts/fixtures/project_select_combo_button.html.haml6
-rw-r--r--spec/javascripts/fixtures/projects.rb2
-rw-r--r--spec/javascripts/fixtures/prometheus_service.rb2
-rw-r--r--spec/javascripts/fixtures/services.rb2
-rw-r--r--spec/javascripts/fixtures/snippet.rb27
-rw-r--r--spec/javascripts/fixtures/todos.rb4
-rw-r--r--spec/javascripts/fly_out_nav_spec.js329
-rw-r--r--spec/javascripts/labels_issue_sidebar_spec.js1
-rw-r--r--spec/javascripts/lib/utils/sticky_spec.js52
-rw-r--r--spec/javascripts/pdf/index_spec.js4
-rw-r--r--spec/javascripts/pdf/page_spec.js4
-rw-r--r--spec/javascripts/project_select_combo_button_spec.js105
-rw-r--r--spec/javascripts/projects/project_import_gitlab_project_spec.js25
-rw-r--r--spec/javascripts/repo/components/repo_commit_section_spec.js158
-rw-r--r--spec/javascripts/repo/components/repo_edit_button_spec.js51
-rw-r--r--spec/javascripts/repo/components/repo_editor_spec.js26
-rw-r--r--spec/javascripts/repo/components/repo_file_buttons_spec.js82
-rw-r--r--spec/javascripts/repo/components/repo_file_options_spec.js33
-rw-r--r--spec/javascripts/repo/components/repo_file_spec.js136
-rw-r--r--spec/javascripts/repo/components/repo_loading_file_spec.js79
-rw-r--r--spec/javascripts/repo/components/repo_prev_directory_spec.js43
-rw-r--r--spec/javascripts/repo/components/repo_preview_spec.js23
-rw-r--r--spec/javascripts/repo/components/repo_sidebar_spec.js110
-rw-r--r--spec/javascripts/repo/components/repo_tab_spec.js70
-rw-r--r--spec/javascripts/repo/components/repo_tabs_spec.js53
-rw-r--r--spec/javascripts/repo/monaco_loader_spec.js17
-rw-r--r--spec/javascripts/repo/services/repo_service_spec.js121
-rw-r--r--spec/javascripts/sidebar/confidential_edit_buttons_spec.js39
-rw-r--r--spec/javascripts/sidebar/confidential_edit_form_buttons_spec.js39
-rw-r--r--spec/javascripts/sidebar/confidential_issue_sidebar_spec.js65
-rw-r--r--spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js10
-rw-r--r--spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js6
-rw-r--r--spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js4
-rw-r--r--spec/javascripts/vue_mr_widget/components/mr_widget_related_links_spec.js73
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_archived_spec.js2
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js4
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_checking_spec.js2
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_conflicts_spec.js13
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_failed_to_merge_spec.js4
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_locked_spec.js10
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_merge_when_pipeline_succeeds_spec.js8
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_merged_spec.js16
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_missing_branch_spec.js2
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_not_allowed_spec.js2
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_pipeline_blocked_spec.js2
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_pipeline_failed_spec.js2
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js2
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_sha_mismatch_spec.js2
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_wip_spec.js2
-rw-r--r--spec/javascripts/vue_mr_widget/mock_data.js2
-rw-r--r--spec/javascripts/vue_mr_widget/mr_widget_options_spec.js2
-rw-r--r--spec/lib/banzai/filter/milestone_reference_filter_spec.rb196
-rw-r--r--spec/lib/bitbucket/paginator_spec.rb2
-rw-r--r--spec/lib/ci/gitlab_ci_yaml_processor_spec.rb4
-rw-r--r--spec/lib/event_filter_spec.rb2
-rw-r--r--spec/lib/extracts_path_spec.rb2
-rw-r--r--spec/lib/gitlab/auth_spec.rb3
-rw-r--r--spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb199
-rw-r--r--spec/lib/gitlab/background_migration/migrate_events_to_push_event_payloads_spec.rb423
-rw-r--r--spec/lib/gitlab/background_migration/move_personal_snippet_files_spec.rb72
-rw-r--r--spec/lib/gitlab/bitbucket_import/importer_spec.rb2
-rw-r--r--spec/lib/gitlab/bitbucket_import/project_creator_spec.rb2
-rw-r--r--spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb14
-rw-r--r--spec/lib/gitlab/checks/force_push_spec.rb2
-rw-r--r--spec/lib/gitlab/contributions_calendar_spec.rb38
-rw-r--r--spec/lib/gitlab/daemon_spec.rb103
-rw-r--r--spec/lib/gitlab/data_builder/note_spec.rb2
-rw-r--r--spec/lib/gitlab/database_spec.rb22
-rw-r--r--spec/lib/gitlab/email/handler/create_note_handler_spec.rb9
-rw-r--r--spec/lib/gitlab/email/receiver_spec.rb16
-rw-r--r--spec/lib/gitlab/encoding_helper_spec.rb47
-rw-r--r--spec/lib/gitlab/git/blame_spec.rb102
-rw-r--r--spec/lib/gitlab/git/blob_spec.rb71
-rw-r--r--spec/lib/gitlab/git/commit_spec.rb59
-rw-r--r--spec/lib/gitlab/git/diff_collection_spec.rb6
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb112
-rw-r--r--spec/lib/gitlab/git/storage/circuit_breaker_spec.rb290
-rw-r--r--spec/lib/gitlab/git/storage/forked_storage_check_spec.rb58
-rw-r--r--spec/lib/gitlab/git/storage/health_spec.rb87
-rw-r--r--spec/lib/gitlab/gitaly_client/commit_service_spec.rb28
-rw-r--r--spec/lib/gitlab/gitaly_client/repository_service_spec.rb19
-rw-r--r--spec/lib/gitlab/gitlab_import/project_creator_spec.rb2
-rw-r--r--spec/lib/gitlab/google_code_import/project_creator_spec.rb2
-rw-r--r--spec/lib/gitlab/gpg_spec.rb52
-rw-r--r--spec/lib/gitlab/health_checks/fs_shards_check_spec.rb21
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml3
-rw-r--r--spec/lib/gitlab/import_export/attribute_cleaner_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/file_importer_spec.rb5
-rw-r--r--spec/lib/gitlab/import_export/project.json9
-rw-r--r--spec/lib/gitlab/import_export/safe_model_attributes.yml9
-rw-r--r--spec/lib/gitlab/import_sources_spec.rb2
-rw-r--r--spec/lib/gitlab/key_fingerprint_spec.rb84
-rw-r--r--spec/lib/gitlab/ldap/config_spec.rb4
-rw-r--r--spec/lib/gitlab/ldap/user_spec.rb10
-rw-r--r--spec/lib/gitlab/metrics/influx_sampler_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb18
-rw-r--r--spec/lib/gitlab/metrics/sidekiq_metrics_exporter_spec.rb101
-rw-r--r--spec/lib/gitlab/o_auth/user_spec.rb4
-rw-r--r--spec/lib/gitlab/project_template_spec.rb63
-rw-r--r--spec/lib/gitlab/request_context_spec.rb2
-rw-r--r--spec/lib/gitlab/saml/user_spec.rb4
-rw-r--r--spec/lib/gitlab/shell_spec.rb90
-rw-r--r--spec/lib/gitlab/sql/union_spec.rb2
-rw-r--r--spec/lib/gitlab/template/issue_template_spec.rb44
-rw-r--r--spec/lib/gitlab/template/merge_request_template_spec.rb44
-rw-r--r--spec/lib/gitlab/url_blocker_spec.rb34
-rw-r--r--spec/lib/gitlab/version_info_spec.rb4
-rw-r--r--spec/lib/gitlab/workhorse_spec.rb4
-rw-r--r--spec/lib/json_web_token/rsa_token_spec.rb4
-rw-r--r--spec/lib/mattermost/session_spec.rb7
-rw-r--r--spec/lib/rspec_flaky/example_spec.rb89
-rw-r--r--spec/lib/rspec_flaky/flaky_example_spec.rb104
-rw-r--r--spec/lib/rspec_flaky/listener_spec.rb178
-rw-r--r--spec/lib/system_check/simple_executor_spec.rb2
-rw-r--r--spec/mailers/notify_spec.rb6
-rw-r--r--spec/migrations/calculate_conv_dev_index_percentages_spec.rb41
-rw-r--r--spec/migrations/clean_upload_symlinks_spec.rb2
-rw-r--r--spec/migrations/migrate_process_commit_worker_jobs_spec.rb2
-rw-r--r--spec/migrations/move_personal_snippets_files_spec.rb10
-rw-r--r--spec/migrations/move_system_upload_folder_spec.rb18
-rw-r--r--spec/migrations/move_uploads_to_system_dir_spec.rb2
-rw-r--r--spec/migrations/rename_system_namespaces_spec.rb254
-rw-r--r--spec/migrations/schedule_merge_request_diff_migrations_spec.rb59
-rw-r--r--spec/migrations/update_upload_paths_to_system_spec.rb8
-rw-r--r--spec/models/appearance_spec.rb35
-rw-r--r--spec/models/broadcast_message_spec.rb20
-rw-r--r--spec/models/ci/pipeline_spec.rb6
-rw-r--r--spec/models/commit_spec.rb4
-rw-r--r--spec/models/commit_status_spec.rb2
-rw-r--r--spec/models/concerns/issuable_spec.rb4
-rw-r--r--spec/models/concerns/reactive_caching_spec.rb2
-rw-r--r--spec/models/conversational_development_index/metric_spec.rb11
-rw-r--r--spec/models/deploy_key_spec.rb4
-rw-r--r--spec/models/event_collection_spec.rb51
-rw-r--r--spec/models/event_spec.rb32
-rw-r--r--spec/models/gpg_key_spec.rb4
-rw-r--r--spec/models/issue_spec.rb16
-rw-r--r--spec/models/key_spec.rb20
-rw-r--r--spec/models/members/project_member_spec.rb2
-rw-r--r--spec/models/merge_request_spec.rb32
-rw-r--r--spec/models/milestone_spec.rb38
-rw-r--r--spec/models/project_services/drone_ci_service_spec.rb2
-rw-r--r--spec/models/project_services/hipchat_service_spec.rb2
-rw-r--r--spec/models/project_services/jira_service_spec.rb9
-rw-r--r--spec/models/project_services/pipelines_email_service_spec.rb8
-rw-r--r--spec/models/project_spec.rb13
-rw-r--r--spec/models/project_wiki_spec.rb14
-rw-r--r--spec/models/push_event_payload_spec.rb16
-rw-r--r--spec/models/push_event_spec.rb202
-rw-r--r--spec/models/redirect_route_spec.rb12
-rw-r--r--spec/models/repository_spec.rb163
-rw-r--r--spec/models/route_spec.rb74
-rw-r--r--spec/models/user_spec.rb60
-rw-r--r--spec/models/wiki_page_spec.rb35
-rw-r--r--spec/presenters/conversational_development_index/metric_presenter_spec.rb6
-rw-r--r--spec/requests/api/circuit_breakers_spec.rb57
-rw-r--r--spec/requests/api/commit_statuses_spec.rb4
-rw-r--r--spec/requests/api/commits_spec.rb634
-rw-r--r--spec/requests/api/deploy_keys_spec.rb2
-rw-r--r--spec/requests/api/environments_spec.rb9
-rw-r--r--spec/requests/api/events_spec.rb58
-rw-r--r--spec/requests/api/group_variables_spec.rb8
-rw-r--r--spec/requests/api/groups_spec.rb2
-rw-r--r--spec/requests/api/helpers_spec.rb4
-rw-r--r--spec/requests/api/internal_spec.rb9
-rw-r--r--spec/requests/api/issues_spec.rb4
-rw-r--r--spec/requests/api/merge_requests_spec.rb226
-rw-r--r--spec/requests/api/pipeline_schedules_spec.rb2
-rw-r--r--spec/requests/api/projects_spec.rb14
-rw-r--r--spec/requests/api/runner_spec.rb4
-rw-r--r--spec/requests/api/runners_spec.rb30
-rw-r--r--spec/requests/api/settings_spec.rb5
-rw-r--r--spec/requests/api/snippets_spec.rb4
-rw-r--r--spec/requests/api/tags_spec.rb471
-rw-r--r--spec/requests/api/triggers_spec.rb4
-rw-r--r--spec/requests/api/users_spec.rb10
-rw-r--r--spec/requests/api/v3/deploy_keys_spec.rb4
-rw-r--r--spec/requests/api/v3/groups_spec.rb2
-rw-r--r--spec/requests/api/v3/issues_spec.rb4
-rw-r--r--spec/requests/api/v3/merge_requests_spec.rb32
-rw-r--r--spec/requests/api/v3/project_snippets_spec.rb2
-rw-r--r--spec/requests/api/v3/projects_spec.rb10
-rw-r--r--spec/requests/api/v3/runners_spec.rb12
-rw-r--r--spec/requests/api/v3/snippets_spec.rb4
-rw-r--r--spec/requests/api/v3/triggers_spec.rb4
-rw-r--r--spec/requests/api/v3/users_spec.rb25
-rw-r--r--spec/requests/api/variables_spec.rb8
-rw-r--r--spec/requests/ci/api/builds_spec.rb4
-rw-r--r--spec/routing/project_routing_spec.rb10
-rw-r--r--spec/rubocop/cop/migration/safer_boolean_column_spec.rb85
-rw-r--r--spec/serializers/analytics_build_entity_spec.rb14
-rw-r--r--spec/serializers/merge_request_entity_spec.rb2
-rw-r--r--spec/serializers/pipeline_serializer_spec.rb2
-rw-r--r--spec/services/auth/container_registry_authentication_service_spec.rb138
-rw-r--r--spec/services/ci/create_pipeline_service_spec.rb2
-rw-r--r--spec/services/create_deployment_service_spec.rb4
-rw-r--r--spec/services/event_create_service_spec.rb44
-rw-r--r--spec/services/git_push_service_spec.rb9
-rw-r--r--spec/services/git_tag_push_service_spec.rb2
-rw-r--r--spec/services/issuable/bulk_update_service_spec.rb2
-rw-r--r--spec/services/issues/create_service_spec.rb6
-rw-r--r--spec/services/issues/resolve_discussions_spec.rb2
-rw-r--r--spec/services/issues/update_service_spec.rb6
-rw-r--r--spec/services/labels/update_service_spec.rb2
-rw-r--r--spec/services/merge_requests/get_urls_service_spec.rb8
-rw-r--r--spec/services/merge_requests/update_service_spec.rb6
-rw-r--r--spec/services/notification_recipient_service_spec.rb34
-rw-r--r--spec/services/notification_service_spec.rb126
-rw-r--r--spec/services/projects/autocomplete_service_spec.rb27
-rw-r--r--spec/services/projects/create_from_template_service_spec.rb26
-rw-r--r--spec/services/projects/destroy_service_spec.rb4
-rw-r--r--spec/services/projects/housekeeping_service_spec.rb2
-rw-r--r--spec/services/projects/import_service_spec.rb32
-rw-r--r--spec/services/projects/update_service_spec.rb7
-rw-r--r--spec/services/protected_branches/update_service_spec.rb2
-rw-r--r--spec/services/protected_tags/update_service_spec.rb2
-rw-r--r--spec/services/push_event_payload_service_spec.rb218
-rw-r--r--spec/services/quick_actions/interpret_service_spec.rb20
-rw-r--r--spec/services/submit_usage_ping_service_spec.rb7
-rw-r--r--spec/services/system_note_service_spec.rb57
-rw-r--r--spec/services/todo_service_spec.rb37
-rw-r--r--spec/services/web_hook_service_spec.rb2
-rw-r--r--spec/services/wiki_pages/update_service_spec.rb4
-rw-r--r--spec/simplecov_env.rb25
-rw-r--r--spec/spec_helper.rb20
-rw-r--r--spec/support/api/milestones_shared_examples.rb2
-rw-r--r--spec/support/api/schema_matcher.rb18
-rw-r--r--spec/support/cycle_analytics_helpers.rb4
-rw-r--r--spec/support/features/issuable_slash_commands_shared_examples.rb11
-rw-r--r--spec/support/features/reportable_note_shared_examples.rb7
-rw-r--r--spec/support/issuable_shared_examples.rb6
-rw-r--r--spec/support/issuables_list_metadata_shared_examples.rb4
-rw-r--r--spec/support/login_helpers.rb2
-rw-r--r--spec/support/markdown_feature.rb6
-rw-r--r--spec/support/matchers/markdown_matchers.rb2
-rw-r--r--spec/support/matchers/query_matcher.rb2
-rw-r--r--spec/support/migrations_helpers.rb10
-rw-r--r--spec/support/notify_shared_examples.rb1
-rw-r--r--spec/support/rake_helpers.rb10
-rw-r--r--spec/support/redis/redis_shared_examples.rb4
-rw-r--r--spec/support/stored_repositories.rb12
-rw-r--r--spec/support/stub_configuration.rb11
-rw-r--r--spec/support/test_env.rb40
-rw-r--r--spec/support/unique_ip_check_shared_examples.rb2
-rw-r--r--spec/support/updating_mentions_shared_examples.rb2
-rw-r--r--spec/tasks/config_lint_spec.rb8
-rw-r--r--spec/tasks/gitlab/backup_rake_spec.rb4
-rw-r--r--spec/tasks/gitlab/gitaly_rake_spec.rb33
-rw-r--r--spec/tasks/gitlab/shell_rake_spec.rb3
-rw-r--r--spec/tasks/gitlab/workhorse_rake_spec.rb16
-rw-r--r--spec/uploaders/file_mover_spec.rb14
-rw-r--r--spec/uploaders/personal_file_uploader_spec.rb4
-rw-r--r--spec/views/projects/edit.html.haml_spec.rb14
-rw-r--r--spec/views/projects/notes/_more_actions_dropdown.html.haml_spec.rb6
-rw-r--r--spec/workers/email_receiver_worker_spec.rb26
-rw-r--r--spec/workers/emails_on_push_worker_spec.rb6
-rw-r--r--spec/workers/merge_worker_spec.rb11
-rw-r--r--spec/workers/new_issue_worker_spec.rb54
-rw-r--r--spec/workers/new_merge_request_worker_spec.rb56
-rw-r--r--spec/workers/pipeline_notification_worker_spec.rb4
-rw-r--r--spec/workers/pipeline_schedule_worker_spec.rb2
-rw-r--r--spec/workers/post_receive_spec.rb4
-rw-r--r--spec/workers/prune_old_events_worker_spec.rb8
-rw-r--r--spec/workers/stuck_merge_jobs_worker_spec.rb50
-rw-r--r--vendor/assets/javascripts/jquery.nicescroll.js3634
-rwxr-xr-x[-rw-r--r--]vendor/assets/javascripts/pdf.js (renamed from vendor/assets/javascripts/pdflab.js)3133
-rwxr-xr-xvendor/assets/javascripts/pdf.min.js6
-rwxr-xr-x[-rw-r--r--]vendor/assets/javascripts/pdf.worker.js305
-rwxr-xr-xvendor/assets/javascripts/pdf.worker.min.js19
-rw-r--r--vendor/project_templates/rails.tar.gzbin0 -> 23749 bytes
-rw-r--r--yarn.lock752
1112 files changed, 24155 insertions, 13960 deletions
diff --git a/.eslintrc b/.eslintrc
index c72a5e0335b..3e07edbccfe 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -30,6 +30,7 @@
"filenames/match-regex": [2, "^[a-z0-9_]+$"],
"import/no-commonjs": "error",
"no-multiple-empty-lines": ["error", { "max": 1 }],
- "promise/catch-or-return": "error"
+ "promise/catch-or-return": "error",
+ "no-underscore-dangle": ["error", { "allow": ["__"]}]
}
}
diff --git a/.flayignore b/.flayignore
index e2d0a2e50c5..b63ce4c4df0 100644
--- a/.flayignore
+++ b/.flayignore
@@ -3,4 +3,5 @@ lib/gitlab/sanitizers/svg/whitelist.rb
lib/gitlab/diff/position_tracer.rb
app/policies/project_policy.rb
app/models/concerns/relative_positioning.rb
+app/workers/stuck_merge_jobs_worker.rb
lib/gitlab/redis/*.rb
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 27fdf6ca0b5..cd19b6f47ff 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -27,6 +27,7 @@ variables:
GET_SOURCES_ATTEMPTS: "3"
KNAPSACK_RSPEC_SUITE_REPORT_PATH: knapsack/${CI_PROJECT_NAME}/rspec_report-master.json
KNAPSACK_SPINACH_SUITE_REPORT_PATH: knapsack/${CI_PROJECT_NAME}/spinach_report-master.json
+ FLAKY_RSPEC_SUITE_REPORT_PATH: rspec_flaky/${CI_PROJECT_NAME}/report-master.json
before_script:
- bundle --version
@@ -45,16 +46,17 @@ stages:
tags:
- gitlab-org
-.knapsack-state: &knapsack-state
+.tests-metadata-state: &tests-metadata-state
services: []
variables:
SETUP_DB: "false"
USE_BUNDLE_INSTALL: "false"
- KNAPSACK_S3_BUCKET: "gitlab-ce-cache"
+ TESTS_METADATA_S3_BUCKET: "gitlab-ce-cache"
artifacts:
expire_in: 31d
paths:
- knapsack/
+ - rspec_flaky/
.use-pg: &use-pg
services:
@@ -66,16 +68,18 @@ stages:
- mysql:latest
- redis:alpine
-.only-master-and-ee-or-mysql: &only-master-and-ee-or-mysql
+.only-if-want-mysql: &only-if-want-mysql
only:
- /mysql/
- /-stable/
- master@gitlab-org/gitlab-ce
+ - master@gitlab-org/gitlab-ee
- master@gitlab/gitlabhq
+ - master@gitlab/gitlab-ee
- tags@gitlab-org/gitlab-ce
+ - tags@gitlab-org/gitlab-ee
- tags@gitlab/gitlabhq
- - //@gitlab-org/gitlab-ee
- - //@gitlab/gitlab-ee
+ - tags@gitlab/gitlab-ee
# Skip all jobs except the ones that begin with 'docs/'.
# Used for commits including ONLY documentation changes.
@@ -84,7 +88,7 @@ stages:
except:
- /(^docs[\/-].*|.*-docs$)/
-.rspec-knapsack: &rspec-knapsack
+.rspec-metadata: &rspec-metadata
<<: *dedicated-runner
<<: *pull-cache
stage: test
@@ -94,8 +98,13 @@ stages:
- export CI_NODE_TOTAL=${JOB_NAME[-1]}
- export KNAPSACK_REPORT_PATH=knapsack/${CI_PROJECT_NAME}/${JOB_NAME[0]}_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json
- export KNAPSACK_GENERATE_REPORT=true
+ - export ALL_FLAKY_RSPEC_REPORT_PATH=rspec_flaky/${CI_PROJECT_NAME}/all_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json
+ - export NEW_FLAKY_RSPEC_REPORT_PATH=rspec_flaky/${CI_PROJECT_NAME}/new_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json
+ - export FLAKY_RSPEC_GENERATE_REPORT=true
- export CACHE_CLASSES=true
- cp ${KNAPSACK_RSPEC_SUITE_REPORT_PATH} ${KNAPSACK_REPORT_PATH}
+ - cp ${FLAKY_RSPEC_SUITE_REPORT_PATH} ${ALL_FLAKY_RSPEC_REPORT_PATH}
+ - '[[ -f $NEW_FLAKY_RSPEC_REPORT_PATH ]] || echo "{}" > ${NEW_FLAKY_RSPEC_REPORT_PATH}'
- scripts/gitaly-test-spawn
- knapsack rspec "--color --format documentation"
artifacts:
@@ -104,20 +113,21 @@ stages:
paths:
- coverage/
- knapsack/
+ - rspec_flaky/
- tmp/capybara/
-.rspec-knapsack-pg: &rspec-knapsack-pg
- <<: *rspec-knapsack
+.rspec-metadata-pg: &rspec-metadata-pg
+ <<: *rspec-metadata
<<: *use-pg
<<: *except-docs
-.rspec-knapsack-mysql: &rspec-knapsack-mysql
- <<: *rspec-knapsack
+.rspec-metadata-mysql: &rspec-metadata-mysql
+ <<: *rspec-metadata
<<: *use-mysql
- <<: *only-master-and-ee-or-mysql
+ <<: *only-if-want-mysql
<<: *except-docs
-.spinach-knapsack: &spinach-knapsack
+.spinach-metadata: &spinach-metadata
<<: *dedicated-runner
<<: *pull-cache
stage: test
@@ -138,15 +148,15 @@ stages:
- knapsack/
- tmp/capybara/
-.spinach-knapsack-pg: &spinach-knapsack-pg
- <<: *spinach-knapsack
+.spinach-metadata-pg: &spinach-metadata-pg
+ <<: *spinach-metadata
<<: *use-pg
<<: *except-docs
-.spinach-knapsack-mysql: &spinach-knapsack-mysql
- <<: *spinach-knapsack
+.spinach-metadata-mysql: &spinach-metadata-mysql
+ <<: *spinach-metadata
<<: *use-mysql
- <<: *only-master-and-ee-or-mysql
+ <<: *only-if-want-mysql
<<: *except-docs
.only-canonical-masters: &only-canonical-masters
@@ -174,40 +184,71 @@ build-package:
- //@gitlab-org/gitlab-ce
- //@gitlab-org/gitlab-ee
-# Prepare and merge knapsack tests
-knapsack:
- <<: *knapsack-state
+# Retrieve knapsack and rspec_flaky reports
+retrieve-tests-metadata:
+ <<: *tests-metadata-state
<<: *dedicated-runner
<<: *except-docs
stage: prepare
cache:
- key: knapsack
- paths:
- - knapsack/
+ key: tests_metadata
policy: pull
script:
- mkdir -p knapsack/${CI_PROJECT_NAME}/
- - wget -O $KNAPSACK_RSPEC_SUITE_REPORT_PATH http://${KNAPSACK_S3_BUCKET}.s3.amazonaws.com/$KNAPSACK_RSPEC_SUITE_REPORT_PATH || rm $KNAPSACK_RSPEC_SUITE_REPORT_PATH
- - wget -O $KNAPSACK_SPINACH_SUITE_REPORT_PATH http://${KNAPSACK_S3_BUCKET}.s3.amazonaws.com/$KNAPSACK_SPINACH_SUITE_REPORT_PATH || rm $KNAPSACK_SPINACH_SUITE_REPORT_PATH
+ - wget -O $KNAPSACK_RSPEC_SUITE_REPORT_PATH http://${TESTS_METADATA_S3_BUCKET}.s3.amazonaws.com/$KNAPSACK_RSPEC_SUITE_REPORT_PATH || rm $KNAPSACK_RSPEC_SUITE_REPORT_PATH
+ - wget -O $KNAPSACK_SPINACH_SUITE_REPORT_PATH http://${TESTS_METADATA_S3_BUCKET}.s3.amazonaws.com/$KNAPSACK_SPINACH_SUITE_REPORT_PATH || rm $KNAPSACK_SPINACH_SUITE_REPORT_PATH
- '[[ -f $KNAPSACK_RSPEC_SUITE_REPORT_PATH ]] || echo "{}" > ${KNAPSACK_RSPEC_SUITE_REPORT_PATH}'
- '[[ -f $KNAPSACK_SPINACH_SUITE_REPORT_PATH ]] || echo "{}" > ${KNAPSACK_SPINACH_SUITE_REPORT_PATH}'
+ - mkdir -p rspec_flaky/${CI_PROJECT_NAME}/
+ - wget -O $FLAKY_RSPEC_SUITE_REPORT_PATH http://${TESTS_METADATA_S3_BUCKET}.s3.amazonaws.com/$FLAKY_RSPEC_SUITE_REPORT_PATH || rm $FLAKY_RSPEC_SUITE_REPORT_PATH
+ - '[[ -f $FLAKY_RSPEC_SUITE_REPORT_PATH ]] || echo "{}" > ${FLAKY_RSPEC_SUITE_REPORT_PATH}'
-update-knapsack:
- <<: *knapsack-state
+update-tests-metadata:
+ <<: *tests-metadata-state
<<: *dedicated-runner
<<: *only-canonical-masters
stage: post-test
cache:
- key: knapsack
+ key: tests_metadata
paths:
- knapsack/
+ - rspec_flaky/
policy: push
script:
- retry gem install fog-aws mime-types
- scripts/merge-reports ${KNAPSACK_RSPEC_SUITE_REPORT_PATH} knapsack/${CI_PROJECT_NAME}/rspec-pg_node_*.json
- scripts/merge-reports ${KNAPSACK_SPINACH_SUITE_REPORT_PATH} knapsack/${CI_PROJECT_NAME}/spinach-pg_node_*.json
- - '[[ -z ${KNAPSACK_S3_BUCKET} ]] || scripts/sync-reports put $KNAPSACK_S3_BUCKET $KNAPSACK_RSPEC_SUITE_REPORT_PATH $KNAPSACK_SPINACH_SUITE_REPORT_PATH'
+ - scripts/merge-reports ${FLAKY_RSPEC_SUITE_REPORT_PATH} rspec_flaky/${CI_PROJECT_NAME}/all_node_*.json
+ - '[[ -z ${TESTS_METADATA_S3_BUCKET} ]] || scripts/sync-reports put $TESTS_METADATA_S3_BUCKET $KNAPSACK_RSPEC_SUITE_REPORT_PATH $KNAPSACK_SPINACH_SUITE_REPORT_PATH'
+ - '[[ -z ${TESTS_METADATA_S3_BUCKET} ]] || scripts/sync-reports put $TESTS_METADATA_S3_BUCKET $FLAKY_RSPEC_SUITE_REPORT_PATH'
- rm -f knapsack/${CI_PROJECT_NAME}/*_node_*.json
+ - rm -f rspec_flaky/${CI_PROJECT_NAME}/all_node_*.json
+
+flaky-examples-check:
+ <<: *dedicated-runner
+ <<: *except-docs
+ image: ruby:2.3-alpine
+ services: []
+ before_script: []
+ cache: {}
+ variables:
+ SETUP_DB: "false"
+ USE_BUNDLE_INSTALL: "false"
+ NEW_FLAKY_SPECS_REPORT: rspec_flaky/${CI_PROJECT_NAME}/new_rspec_flaky_examples.json
+ stage: post-test
+ allow_failure: yes
+ only:
+ - branches
+ except:
+ - master
+ artifacts:
+ expire_in: 30d
+ paths:
+ - rspec_flaky/
+ script:
+ - '[[ -f $NEW_FLAKY_SPECS_REPORT ]] || echo "{}" > ${NEW_FLAKY_SPECS_REPORT}'
+ - scripts/merge-reports $NEW_FLAKY_SPECS_REPORT rspec_flaky/${CI_PROJECT_NAME}/new_node_*.json
+ - scripts/detect-new-flaky-examples $NEW_FLAKY_SPECS_REPORT
setup-test-env:
<<: *use-pg
@@ -230,69 +271,69 @@ setup-test-env:
- public/assets
- tmp/tests
-rspec-pg 0 25: *rspec-knapsack-pg
-rspec-pg 1 25: *rspec-knapsack-pg
-rspec-pg 2 25: *rspec-knapsack-pg
-rspec-pg 3 25: *rspec-knapsack-pg
-rspec-pg 4 25: *rspec-knapsack-pg
-rspec-pg 5 25: *rspec-knapsack-pg
-rspec-pg 6 25: *rspec-knapsack-pg
-rspec-pg 7 25: *rspec-knapsack-pg
-rspec-pg 8 25: *rspec-knapsack-pg
-rspec-pg 9 25: *rspec-knapsack-pg
-rspec-pg 10 25: *rspec-knapsack-pg
-rspec-pg 11 25: *rspec-knapsack-pg
-rspec-pg 12 25: *rspec-knapsack-pg
-rspec-pg 13 25: *rspec-knapsack-pg
-rspec-pg 14 25: *rspec-knapsack-pg
-rspec-pg 15 25: *rspec-knapsack-pg
-rspec-pg 16 25: *rspec-knapsack-pg
-rspec-pg 17 25: *rspec-knapsack-pg
-rspec-pg 18 25: *rspec-knapsack-pg
-rspec-pg 19 25: *rspec-knapsack-pg
-rspec-pg 20 25: *rspec-knapsack-pg
-rspec-pg 21 25: *rspec-knapsack-pg
-rspec-pg 22 25: *rspec-knapsack-pg
-rspec-pg 23 25: *rspec-knapsack-pg
-rspec-pg 24 25: *rspec-knapsack-pg
-
-rspec-mysql 0 25: *rspec-knapsack-mysql
-rspec-mysql 1 25: *rspec-knapsack-mysql
-rspec-mysql 2 25: *rspec-knapsack-mysql
-rspec-mysql 3 25: *rspec-knapsack-mysql
-rspec-mysql 4 25: *rspec-knapsack-mysql
-rspec-mysql 5 25: *rspec-knapsack-mysql
-rspec-mysql 6 25: *rspec-knapsack-mysql
-rspec-mysql 7 25: *rspec-knapsack-mysql
-rspec-mysql 8 25: *rspec-knapsack-mysql
-rspec-mysql 9 25: *rspec-knapsack-mysql
-rspec-mysql 10 25: *rspec-knapsack-mysql
-rspec-mysql 11 25: *rspec-knapsack-mysql
-rspec-mysql 12 25: *rspec-knapsack-mysql
-rspec-mysql 13 25: *rspec-knapsack-mysql
-rspec-mysql 14 25: *rspec-knapsack-mysql
-rspec-mysql 15 25: *rspec-knapsack-mysql
-rspec-mysql 16 25: *rspec-knapsack-mysql
-rspec-mysql 17 25: *rspec-knapsack-mysql
-rspec-mysql 18 25: *rspec-knapsack-mysql
-rspec-mysql 19 25: *rspec-knapsack-mysql
-rspec-mysql 20 25: *rspec-knapsack-mysql
-rspec-mysql 21 25: *rspec-knapsack-mysql
-rspec-mysql 22 25: *rspec-knapsack-mysql
-rspec-mysql 23 25: *rspec-knapsack-mysql
-rspec-mysql 24 25: *rspec-knapsack-mysql
-
-spinach-pg 0 5: *spinach-knapsack-pg
-spinach-pg 1 5: *spinach-knapsack-pg
-spinach-pg 2 5: *spinach-knapsack-pg
-spinach-pg 3 5: *spinach-knapsack-pg
-spinach-pg 4 5: *spinach-knapsack-pg
-
-spinach-mysql 0 5: *spinach-knapsack-mysql
-spinach-mysql 1 5: *spinach-knapsack-mysql
-spinach-mysql 2 5: *spinach-knapsack-mysql
-spinach-mysql 3 5: *spinach-knapsack-mysql
-spinach-mysql 4 5: *spinach-knapsack-mysql
+rspec-pg 0 25: *rspec-metadata-pg
+rspec-pg 1 25: *rspec-metadata-pg
+rspec-pg 2 25: *rspec-metadata-pg
+rspec-pg 3 25: *rspec-metadata-pg
+rspec-pg 4 25: *rspec-metadata-pg
+rspec-pg 5 25: *rspec-metadata-pg
+rspec-pg 6 25: *rspec-metadata-pg
+rspec-pg 7 25: *rspec-metadata-pg
+rspec-pg 8 25: *rspec-metadata-pg
+rspec-pg 9 25: *rspec-metadata-pg
+rspec-pg 10 25: *rspec-metadata-pg
+rspec-pg 11 25: *rspec-metadata-pg
+rspec-pg 12 25: *rspec-metadata-pg
+rspec-pg 13 25: *rspec-metadata-pg
+rspec-pg 14 25: *rspec-metadata-pg
+rspec-pg 15 25: *rspec-metadata-pg
+rspec-pg 16 25: *rspec-metadata-pg
+rspec-pg 17 25: *rspec-metadata-pg
+rspec-pg 18 25: *rspec-metadata-pg
+rspec-pg 19 25: *rspec-metadata-pg
+rspec-pg 20 25: *rspec-metadata-pg
+rspec-pg 21 25: *rspec-metadata-pg
+rspec-pg 22 25: *rspec-metadata-pg
+rspec-pg 23 25: *rspec-metadata-pg
+rspec-pg 24 25: *rspec-metadata-pg
+
+rspec-mysql 0 25: *rspec-metadata-mysql
+rspec-mysql 1 25: *rspec-metadata-mysql
+rspec-mysql 2 25: *rspec-metadata-mysql
+rspec-mysql 3 25: *rspec-metadata-mysql
+rspec-mysql 4 25: *rspec-metadata-mysql
+rspec-mysql 5 25: *rspec-metadata-mysql
+rspec-mysql 6 25: *rspec-metadata-mysql
+rspec-mysql 7 25: *rspec-metadata-mysql
+rspec-mysql 8 25: *rspec-metadata-mysql
+rspec-mysql 9 25: *rspec-metadata-mysql
+rspec-mysql 10 25: *rspec-metadata-mysql
+rspec-mysql 11 25: *rspec-metadata-mysql
+rspec-mysql 12 25: *rspec-metadata-mysql
+rspec-mysql 13 25: *rspec-metadata-mysql
+rspec-mysql 14 25: *rspec-metadata-mysql
+rspec-mysql 15 25: *rspec-metadata-mysql
+rspec-mysql 16 25: *rspec-metadata-mysql
+rspec-mysql 17 25: *rspec-metadata-mysql
+rspec-mysql 18 25: *rspec-metadata-mysql
+rspec-mysql 19 25: *rspec-metadata-mysql
+rspec-mysql 20 25: *rspec-metadata-mysql
+rspec-mysql 21 25: *rspec-metadata-mysql
+rspec-mysql 22 25: *rspec-metadata-mysql
+rspec-mysql 23 25: *rspec-metadata-mysql
+rspec-mysql 24 25: *rspec-metadata-mysql
+
+spinach-pg 0 5: *spinach-metadata-pg
+spinach-pg 1 5: *spinach-metadata-pg
+spinach-pg 2 5: *spinach-metadata-pg
+spinach-pg 3 5: *spinach-metadata-pg
+spinach-pg 4 5: *spinach-metadata-pg
+
+spinach-mysql 0 5: *spinach-metadata-mysql
+spinach-mysql 1 5: *spinach-metadata-mysql
+spinach-mysql 2 5: *spinach-metadata-mysql
+spinach-mysql 3 5: *spinach-metadata-mysql
+spinach-mysql 4 5: *spinach-metadata-mysql
# Static analysis jobs
.ruby-static-analysis: &ruby-static-analysis
@@ -352,7 +393,7 @@ ee_compat_check:
except:
- master
- tags
- - /^[\d-]+-stable(-ee)?$/
+ - /^[\d-]+-stable(-ee)?/
allow_failure: yes
cache:
key: "ee_compat_check_repo"
@@ -512,8 +553,11 @@ codeclimate:
services:
- docker:dind
script:
+ - cp .rubocop.yml .rubocop.yml.bak
+ - grep -v "rubocop-gitlab-security" .rubocop.yml.bak > .rubocop.yml
- docker run --env CODECLIMATE_CODE="$PWD" --volume "$PWD":/code --volume /var/run/docker.sock:/var/run/docker.sock --volume /tmp/cc:/tmp/cc codeclimate/codeclimate analyze -f json > raw_codeclimate.json
- cat raw_codeclimate.json | docker run -i stedolan/jq -c 'map({check_name,fingerprint,location})' > codeclimate.json
+ - mv .rubocop.yml.bak .rubocop.yml
artifacts:
paths: [codeclimate.json]
diff --git a/.gitlab/merge_request_templates/Database Changes.md b/.gitlab/merge_request_templates/Database Changes.md
new file mode 100644
index 00000000000..2a5c8267872
--- /dev/null
+++ b/.gitlab/merge_request_templates/Database Changes.md
@@ -0,0 +1,73 @@
+Remove this section and replace it with a description of your MR. Also follow the
+checklist below and check off any tasks that are done. If a certain task can not
+be done you should explain so in the MR body. You are free to remove any
+sections that do not apply to your MR.
+
+When gathering statistics (e.g. the output of `EXPLAIN ANALYZE`) you should make
+sure your database has enough data. Having around 10 000 rows in the tables
+being queries should provide a reasonable estimate of how a query will behave.
+Also make sure that PostgreSQL uses the following settings:
+
+* `random_page_cost`: `1`
+* `work_mem`: `16MB`
+* `maintenance_work_mem`: at least `64MB`
+* `shared_buffers`: at least `256MB`
+
+If you have access to GitLab.com's staging environment you should also run your
+measurements there, and include the results in this MR.
+
+## Database Checklist
+
+When adding migrations:
+
+- [ ] Updated `db/schema.rb`
+- [ ] Added a `down` method so the migration can be reverted
+- [ ] Added the output of the migration(s) to the MR body
+- [ ] Added the execution time of the migration(s) to the MR body
+- [ ] Added tests for the migration in `spec/migrations` if necessary (e.g. when
+ migrating data)
+- [ ] Made sure the migration won't interfere with a running GitLab cluster,
+ for example by disabling transactions for long running migrations
+
+When adding or modifying queries to improve performance:
+
+- [ ] Included the raw SQL queries of the relevant queries
+- [ ] Included the output of `EXPLAIN ANALYZE` and execution timings of the
+ relevant queries
+- [ ] Added tests for the relevant changes
+
+When adding indexes:
+
+- [ ] Described the need for these indexes in the MR body
+- [ ] Made sure existing indexes can not be reused instead
+
+When adding foreign keys to existing tables:
+
+- [ ] Included a migration to remove orphaned rows in the source table
+- [ ] Removed any instances of `dependent: ...` that may no longer be necessary
+
+When adding tables:
+
+- [ ] Ordered columns based on their type sizes in descending order
+- [ ] Added foreign keys if necessary
+- [ ] Added indexes if necessary
+
+When removing columns, tables, indexes or other structures:
+
+- [ ] Removed these in a post-deployment migration
+- [ ] Made sure the application no longer uses (or ignores) these structures
+
+## General Checklist
+
+- [ ] [Changelog entry](https://docs.gitlab.com/ce/development/changelog.html) added, if necessary
+- [ ] [Documentation created/updated](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/development/doc_styleguide.md)
+- [ ] API support added
+- [ ] Tests added for this feature/bug
+- Review
+ - [ ] Has been reviewed by UX
+ - [ ] Has been reviewed by Frontend
+ - [ ] Has been reviewed by Backend
+ - [ ] Has been reviewed by Database
+- [ ] Conform by the [merge request performance guides](http://docs.gitlab.com/ce/development/merge_request_performance_guidelines.html)
+- [ ] Conform by the [style guides](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#style-guides)
+- [ ] [Squashed related commits together](https://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits)
diff --git a/.haml-lint.yml b/.haml-lint.yml
index 528f99d08d2..32c7de0fb78 100644
--- a/.haml-lint.yml
+++ b/.haml-lint.yml
@@ -35,9 +35,21 @@ linters:
HtmlAttributes:
enabled: true
+ IdNames:
+ enabled: false
+
ImplicitDiv:
enabled: true
+ InlineJavaScript:
+ enabled: true
+
+ InlineStyles:
+ enabled: false
+
+ InstanceVariables:
+ enabled: false
+
LeadingCommentSpace:
enabled: false
@@ -54,6 +66,9 @@ linters:
ObjectReferenceAttributes:
enabled: true
+ RepeatedId:
+ enabled: false
+
RuboCop:
enabled: false
# These cops are incredibly noisy when it comes to HAML templates, so we
@@ -101,3 +116,6 @@ linters:
UnnecessaryStringOutput:
enabled: true
+
+ ViewLength:
+ enabled: false
diff --git a/.rubocop.yml b/.rubocop.yml
index a5ccec0437b..d25b4ac39c9 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -1,5 +1,6 @@
require:
- rubocop-rspec
+ - rubocop-gitlab-security
- ./rubocop/rubocop
inherit_from: .rubocop_todo.yml
@@ -206,6 +207,13 @@ Layout/SpaceAroundKeyword:
Layout/SpaceAroundOperators:
Enabled: true
+# Checks that block braces have or don't have a space before the opening
+# brace depending on configuration.
+# Configuration parameters: EnforcedStyle, SupportedStyles.
+# SupportedStyles: space, no_space
+Layout/SpaceBeforeBlockBraces:
+ Enabled: true
+
# No spaces before commas.
Layout/SpaceBeforeComma:
Enabled: true
@@ -1037,7 +1045,7 @@ RSpec/BeforeAfterAll:
RSpec/DescribeClass:
Enabled: false
-# Use `described_class` for tested class / module.
+# Checks that the second argument to `describe` specifies a method.
RSpec/DescribeMethod:
Enabled: false
@@ -1045,8 +1053,7 @@ RSpec/DescribeMethod:
RSpec/DescribeSymbol:
Enabled: true
-# Checks that the second argument to top level describe is the tested method
-# name.
+# Checks that tests use `described_class`.
RSpec/DescribedClass:
Enabled: true
@@ -1091,6 +1098,11 @@ RSpec/FilePath:
RSpec/Focus:
Enabled: true
+# Checks the arguments passed to `before`, `around`, and `after`.
+RSpec/HookArgument:
+ Enabled: true
+ EnforcedStyle: implicit
+
# Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: is_expected, should
RSpec/ImplicitExpect:
@@ -1156,3 +1168,35 @@ RSpec/SubjectStub:
# Prefer using verifying doubles over normal doubles.
RSpec/VerifiedDoubles:
Enabled: false
+
+# GitlabSecurity ##############################################################
+
+GitlabSecurity/DeepMunge:
+ Enabled: true
+ Exclude:
+ - 'spec/**/*'
+ - 'lib/**/*.rake'
+
+GitlabSecurity/PublicSend:
+ Enabled: true
+ Exclude:
+ - 'spec/**/*'
+ - 'lib/**/*.rake'
+
+GitlabSecurity/RedirectToParamsUpdate:
+ Enabled: true
+ Exclude:
+ - 'spec/**/*'
+ - 'lib/**/*.rake'
+
+GitlabSecurity/SqlInjection:
+ Enabled: true
+ Exclude:
+ - 'spec/**/*'
+ - 'lib/**/*.rake'
+
+GitlabSecurity/SystemCommandInjection:
+ Enabled: true
+ Exclude:
+ - 'spec/**/*'
+ - 'lib/**/*.rake'
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index 9caef3bde08..cf14285ec2a 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -26,13 +26,6 @@ Layout/IndentArray:
Layout/IndentHash:
Enabled: false
-# Offense count: 174
-# Cop supports --auto-correct.
-# Configuration parameters: EnforcedStyle, SupportedStyles.
-# SupportedStyles: space, no_space
-Layout/SpaceBeforeBlockBraces:
- Enabled: false
-
# Offense count: 8
# Cop supports --auto-correct.
# Configuration parameters: AllowForAlignment.
@@ -77,12 +70,6 @@ RSpec/EmptyLineAfterFinalLet:
RSpec/EmptyLineAfterSubject:
Enabled: false
-# Offense count: 78
-# Configuration parameters: EnforcedStyle, SupportedStyles.
-# SupportedStyles: implicit, each, example
-RSpec/HookArgument:
- Enabled: false
-
# Offense count: 9
# Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: it_behaves_like, it_should_behave_like
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 706823ed693..3ecedd44c89 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,32 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 9.4.5 (2017-08-14)
+
+- Fix deletion of deploy keys linked to other projects. !13162
+- Allow any logged in users to read_users_list even if it's restricted. !13201
+- Make Delete Merged Branches handle wildcard protected branches correctly. !13251
+- Fix an order of operations for CI connection error message in merge request widget. !13252
+- Fix pipeline_schedules pages when active schedule has an abnormal state. !13286
+- Add missing validation error for username change with container registry tags. !13356
+- Fix destroy of case-insensitive conflicting redirects. !13357
+- Project pending delete no longer return 500 error in admins projects view. !13389
+- Fix search box losing focus when typing.
+- Use jQuery to control scroll behavior in job log for cross browser consistency.
+- Use project_ref_path to create the link to a branch to fix links that 404.
+- improve file upload/replace experience.
+- fix jump to next discussion button.
+- Fixes new issue button for failed job returning 404.
+- Fix links to group milestones from issue and merge request sidebar.
+- Fixed sign-in restrictions buttons not toggling active state.
+- Fix Mattermost integration.
+- Change project FK migration to skip existing FKs.
+
+## 9.4.4 (2017-08-09)
+
+- Remove hidden symlinks from project import files.
+- Disallow Git URLs that include a username or hostname beginning with a non-alphanumeric character.
+
## 9.4.3 (2017-07-31)
- Fix Prometheus client PID reuse bug. !13130
@@ -226,6 +252,11 @@ entry.
- Log rescued exceptions to Sentry.
- Remove remaining N+1 queries in merge requests API with emojis and labels.
+## 9.3.10 (2017-08-09)
+
+- Remove hidden symlinks from project import files.
+- Disallow Git URLs that include a username or hostname beginning with a non-alphanumeric character.
+
## 9.3.9 (2017-07-20)
- Fix an infinite loop when handling user-supplied regular expressions.
@@ -498,6 +529,11 @@ entry.
- Remove foreigh key on ci_trigger_schedules only if it exists.
- Allow translation of Pipeline Schedules.
+## 9.2.10 (2017-08-09)
+
+- Remove hidden symlinks from project import files.
+- Disallow Git URLs that include a username or hostname beginning with a non-alphanumeric character.
+
## 9.2.9 (2017-07-20)
- Fix an infinite loop when handling user-supplied regular expressions.
@@ -753,6 +789,11 @@ entry.
- Fix preemptive scroll bar on user activity calendar.
- Pipeline chat notifications convert seconds to minutes and hours.
+## 9.1.10 (2017-08-09)
+
+- Remove hidden symlinks from project import files.
+- Disallow Git URLs that include a username or hostname beginning with a non-alphanumeric character.
+
## 9.1.9 (2017-07-20)
- Fix an infinite loop when handling user-supplied regular expressions.
@@ -1076,6 +1117,11 @@ entry.
- Only send chat notifications for the default branch.
- Don't fill in the default kubernetes namespace.
+## 9.0.13 (2017-08-09)
+
+- Remove hidden symlinks from project import files.
+- Disallow Git URLs that include a username or hostname beginning with a non-alphanumeric character.
+
## 9.0.12 (2017-07-20)
- Fix an infinite loop when handling user-supplied regular expressions.
@@ -1456,6 +1502,11 @@ entry.
- Change development tanuki favicon colors to match logo color order.
- API issues - support filtering by iids.
+## 8.17.8 (2017-08-09)
+
+- Remove hidden symlinks from project import files.
+- Disallow Git URLs that include a username or hostname beginning with a non-alphanumeric character.
+
## 8.17.7 (2017-07-19)
- Renders 404 if given project is not readable by the user on Todos dashboard.
@@ -1708,8 +1759,6 @@ entry.
## 8.16.7 (2017-02-27)
-- No changes.
-- No changes.
- Fix MR changes tab size count when there are over 100 files in the diff.
## 8.16.6 (2017-02-17)
@@ -1923,6 +1972,14 @@ entry.
- Prevent the GitHub importer from assigning labels and comments to merge requests or issues belonging to other projects.
- Patch XSS vulnerability in RDOC support.
+## 8.15.5 (2017-01-20)
+
+- Ensure export files are removed after a namespace is deleted.
+- Don't allow project guests to subscribe to merge requests through the API. (Robert Schilling)
+- Prevent users from creating notes on resources they can't access.
+- Prevent users from deleting system deploy keys via the project deploy key API.
+- Upgrade omniauth gem to 1.3.2.
+
## 8.15.4 (2017-01-09)
- Make successful pipeline emails off for watchers. !8176
@@ -2205,6 +2262,14 @@ entry.
- Speed up group milestone index by passing group_id to IssuesFinder. !8363
- Ensure issuable state changes only fire webhooks once.
+## 8.14.7 (2017-01-21)
+
+- Ensure export files are removed after a namespace is deleted.
+- Don't allow project guests to subscribe to merge requests through the API. (Robert Schilling)
+- Prevent users from creating notes on resources they can't access.
+- Prevent users from deleting system deploy keys via the project deploy key API.
+- Upgrade omniauth gem to 1.3.2.
+
## 8.14.6 (2017-01-10)
- Update the gitlab-markup gem to the version 1.5.1. !8509
@@ -2487,6 +2552,14 @@ entry.
- Fix "Without projects" filter. !6611 (Ben Bodenmiller)
- Fix 404 when visit /projects page
+## 8.13.12 (2017-01-21)
+
+- Ensure export files are removed after a namespace is deleted.
+- Don't allow project guests to subscribe to merge requests through the API. (Robert Schilling)
+- Prevent users from creating notes on resources they can't access.
+- Prevent users from deleting system deploy keys via the project deploy key API.
+- Upgrade omniauth gem to 1.3.2.
+
## 8.13.11 (2017-01-10)
- Update the gitlab-markup gem to the version 1.5.1. !8509
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 4e8f395fa5e..c25c8e5b741 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-0.26.0
+0.30.0
diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION
index 8a30e8f94a3..11d9efa3d5a 100644
--- a/GITLAB_SHELL_VERSION
+++ b/GITLAB_SHELL_VERSION
@@ -1 +1 @@
-5.4.0
+5.8.0
diff --git a/Gemfile b/Gemfile
index a9a1cbc144d..a484cefb9a4 100644
--- a/Gemfile
+++ b/Gemfile
@@ -64,7 +64,7 @@ gem 'gpgme'
# LDAP Auth
# GitLab fork with several improvements to original library. For full list of changes
# see https://github.com/intridea/omniauth-ldap/compare/master...gitlabhq:master
-gem 'gitlab_omniauth-ldap', '~> 2.0.3', require: 'omniauth-ldap'
+gem 'gitlab_omniauth-ldap', '~> 2.0.4', require: 'omniauth-ldap'
gem 'net-ldap'
# Git Wiki
@@ -84,7 +84,7 @@ gem 'rack-cors', '~> 0.4.0', require: 'rack/cors'
gem 'hashie-forbidden_attributes'
# Pagination
-gem 'kaminari', '~> 0.17.0'
+gem 'kaminari', '~> 1.0'
# HAML
gem 'hamlit', '~> 2.6.1'
@@ -314,16 +314,17 @@ group :development, :test do
gem 'pry-rails', '~> 0.3.4'
gem 'awesome_print', '~> 1.2.0', require: false
- gem 'fuubar', '~> 2.0.0'
+ gem 'fuubar', '~> 2.2.0'
gem 'database_cleaner', '~> 1.5.0'
gem 'factory_girl_rails', '~> 4.7.0'
- gem 'rspec-rails', '~> 3.5.0'
+ gem 'rspec-rails', '~> 3.6.0'
gem 'rspec-retry', '~> 0.4.5'
gem 'spinach-rails', '~> 0.2.1'
gem 'spinach-rerun-reporter', '~> 0.0.2'
gem 'rspec_profiling', '~> 0.0.5'
gem 'rspec-set', '~> 0.1.3'
+ gem 'rspec-parameterized'
# Prevent occasions where minitest is not bundled in packaged versions of ruby (see #3826)
gem 'minitest', '~> 5.7.0'
@@ -341,8 +342,9 @@ group :development, :test do
gem 'rubocop', '~> 0.49.1', require: false
gem 'rubocop-rspec', '~> 1.15.1', require: false
+ gem 'rubocop-gitlab-security', '~> 0.0.6', require: false
gem 'scss_lint', '~> 0.54.0', require: false
- gem 'haml_lint', '~> 0.21.0', require: false
+ gem 'haml_lint', '~> 0.26.0', require: false
gem 'simplecov', '~> 0.14.0', require: false
gem 'flay', '~> 2.8.0', require: false
gem 'bundler-audit', '~> 0.5.0', require: false
@@ -354,7 +356,7 @@ group :development, :test do
gem 'activerecord_sane_schema_dumper', '0.2'
- gem 'stackprof', '~> 0.2.10'
+ gem 'stackprof', '~> 0.2.10', require: false
end
group :test do
@@ -390,8 +392,18 @@ gem 'health_check', '~> 2.6.0'
gem 'vmstat', '~> 2.3.0'
gem 'sys-filesystem', '~> 1.1.6'
+# SSH host key support
+gem 'net-ssh', '~> 4.1.0'
+
+# Required for ED25519 SSH host key support
+group :ed25519 do
+ gem 'rbnacl-libsodium'
+ gem 'rbnacl', '~> 3.2'
+ gem 'bcrypt_pbkdf', '~> 1.0'
+end
+
# Gitaly GRPC client
-gem 'gitaly', '~> 0.21.0'
+gem 'gitaly', '~> 0.27.0'
gem 'toml-rb', '~> 0.3.15', require: false
diff --git a/Gemfile.lock b/Gemfile.lock
index 5a327a14c4a..a93caba2393 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -2,6 +2,7 @@ GEM
remote: https://rubygems.org/
specs:
RedCloth (4.3.2)
+ abstract_type (0.0.7)
ace-rails-ap (4.1.2)
actionmailer (4.2.8)
actionpack (= 4.2.8)
@@ -41,6 +42,9 @@ GEM
tzinfo (~> 1.1)
acts-as-taggable-on (4.0.0)
activerecord (>= 4.0)
+ adamantium (0.2.0)
+ ice_nine (~> 0.11.0)
+ memoizable (~> 0.4.0)
addressable (2.3.8)
after_commit_queue (1.3.0)
activerecord (>= 3.0)
@@ -75,6 +79,7 @@ GEM
babosa (1.0.2)
base32 (0.3.2)
bcrypt (3.1.11)
+ bcrypt_pbkdf (1.0.0)
benchmark-ips (2.3.0)
better_errors (2.1.1)
coderay (>= 1.0.0)
@@ -123,6 +128,9 @@ GEM
coercible (1.0.0)
descendants_tracker (~> 0.0.1)
colorize (0.7.7)
+ concord (0.1.5)
+ adamantium (~> 0.2.0)
+ equalizer (~> 0.0.9)
concurrent-ruby (1.0.5)
concurrent-ruby-ext (1.0.5)
concurrent-ruby (= 1.0.5)
@@ -156,7 +164,7 @@ GEM
devise (~> 4.0)
railties
rotp (~> 2.0)
- diff-lcs (1.2.5)
+ diff-lcs (1.3)
diffy (3.1.0)
docile (1.1.5)
domain_name (0.5.20161021)
@@ -250,8 +258,8 @@ GEM
foreman (0.78.0)
thor (~> 0.19.1)
formatador (0.2.5)
- fuubar (2.0.0)
- rspec (~> 3.0)
+ fuubar (2.2.0)
+ rspec-core (~> 3.0)
ruby-progressbar (~> 1.4)
gemnasium-gitlab-service (0.2.6)
rugged (~> 0.21)
@@ -269,7 +277,7 @@ GEM
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
gherkin-ruby (0.3.2)
- gitaly (0.21.0)
+ gitaly (0.27.0)
google-protobuf (~> 3.1)
grpc (~> 1.0)
github-linguist (4.7.6)
@@ -288,7 +296,7 @@ GEM
mime-types (>= 1.16, < 3)
posix-spawn (~> 0.3)
gitlab-markup (1.5.1)
- gitlab_omniauth-ldap (2.0.3)
+ gitlab_omniauth-ldap (2.0.4)
net-ldap (~> 0.16)
omniauth (~> 1.3)
pyu-ruby-sasl (>= 0.0.3.3, < 0.1)
@@ -356,10 +364,11 @@ GEM
googleauth (~> 0.5.1)
haml (4.0.7)
tilt
- haml_lint (0.21.0)
- haml (~> 4.0)
+ haml_lint (0.26.0)
+ haml (>= 4.0, < 5.1)
+ rainbow
rake (>= 10, < 13)
- rubocop (>= 0.47.0)
+ rubocop (>= 0.49.0)
sysexits (~> 1.1)
hamlit (2.6.1)
temple (~> 0.7.6)
@@ -393,7 +402,7 @@ GEM
json (~> 1.8)
multi_xml (>= 0.5.2)
httpclient (2.8.2)
- i18n (0.8.1)
+ i18n (0.8.6)
ice_nine (0.11.2)
influxdb (0.2.3)
cause
@@ -417,9 +426,18 @@ GEM
json-schema (2.6.2)
addressable (~> 2.3.8)
jwt (1.5.6)
- kaminari (0.17.0)
- actionpack (>= 3.0.0)
- activesupport (>= 3.0.0)
+ kaminari (1.0.1)
+ activesupport (>= 4.1.0)
+ kaminari-actionview (= 1.0.1)
+ kaminari-activerecord (= 1.0.1)
+ kaminari-core (= 1.0.1)
+ kaminari-actionview (1.0.1)
+ actionview
+ kaminari-core (= 1.0.1)
+ kaminari-activerecord (1.0.1)
+ activerecord
+ kaminari-core (= 1.0.1)
+ kaminari-core (1.0.1)
kgio (2.10.0)
knapsack (1.11.0)
rake
@@ -459,6 +477,8 @@ GEM
mime-types (>= 1.16, < 4)
mail_room (0.9.1)
memoist (0.15.0)
+ memoizable (0.4.2)
+ thread_safe (~> 0.3, >= 0.3.1)
method_source (0.8.2)
mime-types (2.99.3)
mimemagic (0.3.0)
@@ -474,6 +494,7 @@ GEM
mustermann (~> 1.0.0)
mysql2 (0.4.5)
net-ldap (0.16.0)
+ net-ssh (4.1.0)
netrc (0.11.0)
nokogiri (1.6.8.1)
mini_portile2 (~> 2.1.0)
@@ -545,7 +566,7 @@ GEM
rubypants (~> 0.2)
orm_adapter (0.5.0)
os (0.9.6)
- parallel (1.11.2)
+ parallel (1.12.0)
paranoia (2.3.1)
activerecord (>= 4.0, < 5.2)
parser (2.4.0.0)
@@ -598,6 +619,11 @@ GEM
premailer-rails (1.9.7)
actionmailer (>= 3, < 6)
premailer (~> 1.7, >= 1.7.9)
+ proc_to_ast (0.1.0)
+ coderay
+ parser
+ unparser
+ procto (0.0.3)
prometheus-client-mmap (0.7.0.beta11)
mmap2 (~> 2.2, >= 2.2.7)
pry (0.10.4)
@@ -610,7 +636,7 @@ GEM
pry-rails (0.3.5)
pry (>= 0.9.10)
pyu-ruby-sasl (0.0.3.3)
- rack (1.6.5)
+ rack (1.6.8)
rack-accept (0.4.5)
rack (>= 0.4)
rack-attack (4.4.1)
@@ -658,9 +684,13 @@ GEM
rainbow (2.2.2)
rake
raindrops (0.18.0)
- rake (10.5.0)
+ rake (12.0.0)
rblineprof (0.3.6)
debugger-ruby_core_source (~> 1.3)
+ rbnacl (3.4.0)
+ ffi
+ rbnacl-libsodium (1.0.11)
+ rbnacl (>= 3.0.1)
rdoc (4.2.2)
json (~> 1.4)
re2 (1.1.1)
@@ -702,30 +732,36 @@ GEM
chunky_png
rqrcode-rails3 (0.1.7)
rqrcode (>= 0.4.2)
- rspec (3.5.0)
- rspec-core (~> 3.5.0)
- rspec-expectations (~> 3.5.0)
- rspec-mocks (~> 3.5.0)
- rspec-core (3.5.0)
- rspec-support (~> 3.5.0)
- rspec-expectations (3.5.0)
+ rspec (3.6.0)
+ rspec-core (~> 3.6.0)
+ rspec-expectations (~> 3.6.0)
+ rspec-mocks (~> 3.6.0)
+ rspec-core (3.6.0)
+ rspec-support (~> 3.6.0)
+ rspec-expectations (3.6.0)
diff-lcs (>= 1.2.0, < 2.0)
- rspec-support (~> 3.5.0)
- rspec-mocks (3.5.0)
+ rspec-support (~> 3.6.0)
+ rspec-mocks (3.6.0)
diff-lcs (>= 1.2.0, < 2.0)
- rspec-support (~> 3.5.0)
- rspec-rails (3.5.0)
+ rspec-support (~> 3.6.0)
+ rspec-parameterized (0.4.0)
+ binding_of_caller
+ parser
+ proc_to_ast
+ rspec (>= 2.13, < 4)
+ unparser
+ rspec-rails (3.6.0)
actionpack (>= 3.0)
activesupport (>= 3.0)
railties (>= 3.0)
- rspec-core (~> 3.5.0)
- rspec-expectations (~> 3.5.0)
- rspec-mocks (~> 3.5.0)
- rspec-support (~> 3.5.0)
+ rspec-core (~> 3.6.0)
+ rspec-expectations (~> 3.6.0)
+ rspec-mocks (~> 3.6.0)
+ rspec-support (~> 3.6.0)
rspec-retry (0.4.5)
rspec-core
rspec-set (0.1.3)
- rspec-support (3.5.0)
+ rspec-support (3.6.0)
rspec_profiling (0.0.5)
activerecord
pg
@@ -738,6 +774,8 @@ GEM
rainbow (>= 1.99.1, < 3.0)
ruby-progressbar (~> 1.7)
unicode-display_width (~> 1.0, >= 1.0.1)
+ rubocop-gitlab-security (0.0.6)
+ rubocop (>= 0.47.1)
rubocop-rspec (1.15.1)
rubocop (>= 0.42.0)
ruby-fogbugz (0.2.1)
@@ -860,7 +898,7 @@ GEM
truncato (0.7.8)
htmlentities (~> 4.3.1)
nokogiri (~> 1.6.1)
- tzinfo (1.2.2)
+ tzinfo (1.2.3)
thread_safe (~> 0.1)
u2f (0.2.1)
uglifier (2.7.2)
@@ -878,6 +916,14 @@ GEM
get_process_mem (~> 0)
unicorn (>= 4, < 6)
uniform_notifier (1.10.0)
+ unparser (0.2.6)
+ abstract_type (~> 0.0.7)
+ adamantium (~> 0.2.0)
+ concord (~> 0.1.5)
+ diff-lcs (~> 1.3)
+ equalizer (~> 0.0.9)
+ parser (>= 2.3.1.2, < 2.5)
+ procto (~> 0.0.2)
url_safe_base64 (0.2.2)
validates_hostname (1.0.6)
activerecord (>= 3.0)
@@ -927,6 +973,7 @@ DEPENDENCIES
awesome_print (~> 1.2.0)
babosa (~> 1.0.2)
base32 (~> 0.3.0)
+ bcrypt_pbkdf (~> 1.0)
benchmark-ips (~> 2.3.0)
better_errors (~> 2.1.0)
binding_of_caller (~> 0.7.2)
@@ -972,17 +1019,17 @@ DEPENDENCIES
fog-rackspace (~> 0.1.1)
font-awesome-rails (~> 4.7)
foreman (~> 0.78.0)
- fuubar (~> 2.0.0)
+ fuubar (~> 2.2.0)
gemnasium-gitlab-service (~> 0.2)
gemojione (~> 3.0)
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.2.0)
- gitaly (~> 0.21.0)
+ gitaly (~> 0.27.0)
github-linguist (~> 4.7.0)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-markup (~> 1.5.1)
- gitlab_omniauth-ldap (~> 2.0.3)
+ gitlab_omniauth-ldap (~> 2.0.4)
gollum-lib (~> 4.2)
gollum-rugged_adapter (~> 0.4.4)
gon (~> 6.1.0)
@@ -991,7 +1038,7 @@ DEPENDENCIES
grape (~> 0.19.2)
grape-entity (~> 0.6.0)
grape-route-helpers (~> 2.0.0)
- haml_lint (~> 0.21.0)
+ haml_lint (~> 0.26.0)
hamlit (~> 2.6.1)
hashie-forbidden_attributes
health_check (~> 2.6.0)
@@ -1005,7 +1052,7 @@ DEPENDENCIES
jquery-rails (~> 4.1.0)
json-schema (~> 2.6.2)
jwt (~> 1.5.6)
- kaminari (~> 0.17.0)
+ kaminari (~> 1.0)
knapsack (~> 1.11.0)
kubeclient (~> 2.2.0)
letter_opener_web (~> 1.3.0)
@@ -1019,6 +1066,7 @@ DEPENDENCIES
mousetrap-rails (~> 1.4.6)
mysql2 (~> 0.4.5)
net-ldap
+ net-ssh (~> 4.1.0)
nokogiri (~> 1.6.7, >= 1.6.7.2)
oauth2 (~> 1.4)
octokit (~> 4.6.2)
@@ -1065,6 +1113,8 @@ DEPENDENCIES
rainbow (~> 2.2)
raindrops (~> 0.18)
rblineprof (~> 0.3.6)
+ rbnacl (~> 3.2)
+ rbnacl-libsodium
rdoc (~> 4.2)
re2 (~> 1.1.1)
recaptcha (~> 3.0)
@@ -1076,11 +1126,13 @@ DEPENDENCIES
responders (~> 2.0)
rouge (~> 2.0)
rqrcode-rails3 (~> 0.1.7)
- rspec-rails (~> 3.5.0)
+ rspec-parameterized
+ rspec-rails (~> 3.6.0)
rspec-retry (~> 0.4.5)
rspec-set (~> 0.1.3)
rspec_profiling (~> 0.0.5)
rubocop (~> 0.49.1)
+ rubocop-gitlab-security (~> 0.0.6)
rubocop-rspec (~> 1.15.1)
ruby-fogbugz (~> 0.2.1)
ruby-prof (~> 0.16.2)
@@ -1130,4 +1182,4 @@ DEPENDENCIES
wikicloth (= 0.8.1)
BUNDLED WITH
- 1.15.1
+ 1.15.3
diff --git a/PROCESS.md b/PROCESS.md
index 2b3d142bf77..e5b17784d20 100644
--- a/PROCESS.md
+++ b/PROCESS.md
@@ -119,6 +119,12 @@ only be left until after the freeze if:
are aware of it.
* It is in the correct milestone, with the ~Deliverable label.
+If a merge request is not ready, but the developers and Product Manager
+responsible for the feature think it is essential that it is in the release,
+they can [ask for an exception](#asking-for-an-exception) in advance. This is
+preferable to merging something that we are not confident in, but should still
+be a rare case: most features can be allowed to slip a release.
+
All Community Edition merge requests from GitLab team members merged on the
freeze date (the 7th) should have a corresponding Enterprise Edition merge
request, even if there are no conflicts. This is to reduce the size of the
@@ -128,11 +134,26 @@ information, see
### After the 7th
-Once the stable branch is frozen, only fixes for [regressions](#regressions)
-and security issues will be cherry-picked into the stable branch.
-Any merge requests cherry-picked into the stable branch for a previous release will also be picked into the latest stable branch.
-These fixes will be shipped in the next RC for that release if it is before the 22nd.
-If the fixes are are completed on or after the 22nd, they will be shipped in a patch for that release.
+Once the stable branch is frozen, the only MRs that can be cherry-picked into
+the stable branch are:
+
+* Fixes for [regressions](#regressions)
+* Fixes for security issues
+* New or updated translations (as long as they do not touch application code)
+
+Any merge requests cherry-picked into the stable branch for a previous release
+will also be picked into the latest stable branch. These fixes will be shipped
+in the next RC for that release if it is before the 22nd. If the fixes are are
+completed on or after the 22nd, they will be shipped in a patch for that
+release.
+
+During the feature freeze all merge requests that are meant to go into the upcoming
+release should have the correct milestone assigned _and_ have the label
+~"Pick into Stable" set, so that release managers can find and pick them.
+Merge requests without a milestone and this label will
+not be merged into any stable branches.
+
+### Asking for an exception
If you think a merge request should go into an RC or patch even though it does not meet these requirements,
you can ask for an exception to be made. Exceptions require sign-off from 3 people besides the developer:
@@ -152,11 +173,7 @@ When in doubt, we err on the side of _not_ cherry-picking.
For example, it is likely that an exception will be made for a trivial 1-5 line performance improvement
(e.g. adding a database index or adding `includes` to a query), but not for a new feature, no matter how relatively small or thoroughly tested.
-During the feature freeze all merge requests that are meant to go into the upcoming
-release should have the correct milestone assigned _and_ have the label
-~"Pick into Stable" set, so that release managers can find and pick them.
-Merge requests without a milestone and this label will
-not be merged into any stable branches.
+All MRs which have had exceptions granted must be merged by the 15th.
### Regressions
diff --git a/app/assets/images/new_repo.png b/app/assets/images/new_repo.png
new file mode 100644
index 00000000000..ed3af06ab1d
--- /dev/null
+++ b/app/assets/images/new_repo.png
Binary files differ
diff --git a/app/assets/images/old_repo.png b/app/assets/images/old_repo.png
new file mode 100644
index 00000000000..c3c3b791ad9
--- /dev/null
+++ b/app/assets/images/old_repo.png
Binary files differ
diff --git a/app/assets/javascripts/ajax_loading_spinner.js b/app/assets/javascripts/ajax_loading_spinner.js
index 38a8317dbd7..8f5e2e545ec 100644
--- a/app/assets/javascripts/ajax_loading_spinner.js
+++ b/app/assets/javascripts/ajax_loading_spinner.js
@@ -10,7 +10,7 @@ class AjaxLoadingSpinner {
e.target.setAttribute('disabled', '');
const iconElement = e.target.querySelector('i');
// get first fa- icon
- const originalIcon = iconElement.className.match(/(fa-)([^\s]+)/g).first();
+ const originalIcon = iconElement.className.match(/(fa-)([^\s]+)/g)[0];
iconElement.dataset.icon = originalIcon;
AjaxLoadingSpinner.toggleLoadingIcon(iconElement);
$(e.target).off('ajax:beforeSend', AjaxLoadingSpinner.ajaxBeforeSend);
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js
index 56fa0d71a9a..76b724e1bcb 100644
--- a/app/assets/javascripts/api.js
+++ b/app/assets/javascripts/api.js
@@ -13,6 +13,7 @@ const Api = {
dockerfilePath: '/api/:version/templates/dockerfiles/:key',
issuableTemplatePath: '/:namespace_path/:project_path/templates/:type/:key',
usersPath: '/api/:version/users.json',
+ commitPath: '/api/:version/projects/:id/repository/commits',
group(groupId, callback) {
const url = Api.buildUrl(Api.groupPath)
@@ -95,6 +96,21 @@ const Api = {
.done(projects => callback(projects));
},
+ commitMultiple(id, data, callback) {
+ // see https://docs.gitlab.com/ce/api/commits.html#create-a-commit-with-multiple-files-and-actions
+ const url = Api.buildUrl(Api.commitPath)
+ .replace(':id', id);
+ return $.ajax({
+ url,
+ type: 'POST',
+ contentType: 'application/json; charset=utf-8',
+ data: JSON.stringify(data),
+ dataType: 'json',
+ })
+ .done(commitData => callback(commitData))
+ .fail(message => callback(message.responseJSON));
+ },
+
// Return text for a specific license
licenseText(key, data, callback) {
const url = Api.buildUrl(Api.licensePath)
diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js
index 18cd04b176a..097f79a250a 100644
--- a/app/assets/javascripts/awards_handler.js
+++ b/app/assets/javascripts/awards_handler.js
@@ -1,6 +1,6 @@
/* eslint-disable class-methods-use-this */
/* global Flash */
-
+import _ from 'underscore';
import Cookies from 'js-cookie';
const animationEndEventString = 'animationend webkitAnimationEnd MSAnimationEnd oAnimationEnd';
diff --git a/app/assets/javascripts/behaviors/requires_input.js b/app/assets/javascripts/behaviors/requires_input.js
index b20d108aa25..035a7e5c431 100644
--- a/app/assets/javascripts/behaviors/requires_input.js
+++ b/app/assets/javascripts/behaviors/requires_input.js
@@ -1,3 +1,4 @@
+import _ from 'underscore';
import '../commons/bootstrap';
// Requires Input behavior
@@ -48,7 +49,9 @@ function hideOrShowHelpBlock(form) {
$(() => {
const $form = $('form.js-requires-input');
- $form.requiresInput();
- hideOrShowHelpBlock($form);
- $('.select2.js-select-namespace').change(() => hideOrShowHelpBlock($form));
+ if ($form) {
+ $form.requiresInput();
+ hideOrShowHelpBlock($form);
+ $('.select2.js-select-namespace').change(() => hideOrShowHelpBlock($form));
+ }
});
diff --git a/app/assets/javascripts/behaviors/toggler_behavior.js b/app/assets/javascripts/behaviors/toggler_behavior.js
index 77e92ff8caf..b70b0a9bbf8 100644
--- a/app/assets/javascripts/behaviors/toggler_behavior.js
+++ b/app/assets/javascripts/behaviors/toggler_behavior.js
@@ -1,4 +1,3 @@
-
// Toggle button. Show/hide content inside parent container.
// Button does not change visibility. If button has icon - it changes chevron style.
//
diff --git a/app/assets/javascripts/blob/blob_file_dropzone.js b/app/assets/javascripts/blob/blob_file_dropzone.js
index dc636050221..26d3419a162 100644
--- a/app/assets/javascripts/blob/blob_file_dropzone.js
+++ b/app/assets/javascripts/blob/blob_file_dropzone.js
@@ -1,9 +1,24 @@
/* eslint-disable func-names, object-shorthand, prefer-arrow-callback */
/* global Dropzone */
+import '../lib/utils/url_utility';
+import { HIDDEN_CLASS } from '../lib/utils/constants';
+
+function toggleLoading($el, $icon, loading) {
+ if (loading) {
+ $el.disable();
+ $icon.removeClass(HIDDEN_CLASS);
+ } else {
+ $el.enable();
+ $icon.addClass(HIDDEN_CLASS);
+ }
+}
export default class BlobFileDropzone {
constructor(form, method) {
const formDropzone = form.find('.dropzone');
+ const submitButton = form.find('#submit-all');
+ const submitButtonLoadingIcon = submitButton.find('.js-loading-icon');
+ const dropzoneMessage = form.find('.dz-message');
Dropzone.autoDiscover = false;
const dropzone = formDropzone.dropzone({
@@ -26,12 +41,20 @@ export default class BlobFileDropzone {
},
init: function () {
this.on('addedfile', function () {
+ toggleLoading(submitButton, submitButtonLoadingIcon, false);
+ dropzoneMessage.addClass(HIDDEN_CLASS);
$('.dropzone-alerts').html('').hide();
});
+ this.on('removedfile', function () {
+ toggleLoading(submitButton, submitButtonLoadingIcon, false);
+ dropzoneMessage.removeClass(HIDDEN_CLASS);
+ });
this.on('success', function (header, response) {
- window.location.href = response.filePath;
+ $('#modal-upload-blob').modal('hide');
+ window.gl.utils.visitUrl(response.filePath);
});
this.on('maxfilesexceeded', function (file) {
+ dropzoneMessage.addClass(HIDDEN_CLASS);
this.removeFile(file);
});
this.on('sending', function (file, xhr, formData) {
@@ -48,14 +71,15 @@ export default class BlobFileDropzone {
},
});
- const submitButton = form.find('#submit-all')[0];
- submitButton.addEventListener('click', function (e) {
+ submitButton.on('click', (e) => {
e.preventDefault();
e.stopPropagation();
if (dropzone[0].dropzone.getQueuedFiles().length === 0) {
// eslint-disable-next-line no-alert
alert('Please select a file');
+ return false;
}
+ toggleLoading(submitButton, submitButtonLoadingIcon, true);
dropzone[0].dropzone.processQueue();
return false;
});
diff --git a/app/assets/javascripts/boards/boards_bundle.js b/app/assets/javascripts/boards/boards_bundle.js
index 88b054b76e6..89c14180149 100644
--- a/app/assets/javascripts/boards/boards_bundle.js
+++ b/app/assets/javascripts/boards/boards_bundle.js
@@ -2,6 +2,7 @@
/* global BoardService */
/* global Flash */
+import _ from 'underscore';
import Vue from 'vue';
import VueResource from 'vue-resource';
import FilteredSearchBoards from './filtered_search_boards';
diff --git a/app/assets/javascripts/boards/components/board_blank_state.js b/app/assets/javascripts/boards/components/board_blank_state.js
index e7f16899362..edfe7c326db 100644
--- a/app/assets/javascripts/boards/components/board_blank_state.js
+++ b/app/assets/javascripts/boards/components/board_blank_state.js
@@ -1,5 +1,6 @@
/* global ListLabel */
+import _ from 'underscore';
import Cookies from 'js-cookie';
const Store = gl.issueBoards.BoardsStore;
diff --git a/app/assets/javascripts/boards/components/issue_card_inner.js b/app/assets/javascripts/boards/components/issue_card_inner.js
index daef01bc93d..d3de1830895 100644
--- a/app/assets/javascripts/boards/components/issue_card_inner.js
+++ b/app/assets/javascripts/boards/components/issue_card_inner.js
@@ -97,9 +97,8 @@ gl.issueBoards.IssueCardInner = Vue.extend({
return `Avatar for ${assignee.name}`;
},
showLabel(label) {
- if (!this.list) return true;
-
- return !this.list.label || label.id !== this.list.label.id;
+ if (!this.list || !label) return true;
+ return true;
},
filterByLabel(label, e) {
if (!this.updateFilters) return;
diff --git a/app/assets/javascripts/boards/components/modal/index.js b/app/assets/javascripts/boards/components/modal/index.js
index 1d36519c75c..96af69e7a36 100644
--- a/app/assets/javascripts/boards/components/modal/index.js
+++ b/app/assets/javascripts/boards/components/modal/index.js
@@ -1,8 +1,8 @@
/* global ListIssue */
import Vue from 'vue';
-import queryData from '../../utils/query_data';
-import loadingIcon from '../../../vue_shared/components/loading_icon.vue';
+import queryData from '~/boards/utils/query_data';
+import loadingIcon from '~/vue_shared/components/loading_icon.vue';
import './header';
import './list';
import './footer';
diff --git a/app/assets/javascripts/boards/components/modal/list.js b/app/assets/javascripts/boards/components/modal/list.js
index 363269c0d5d..b4a45feee4d 100644
--- a/app/assets/javascripts/boards/components/modal/list.js
+++ b/app/assets/javascripts/boards/components/modal/list.js
@@ -1,7 +1,7 @@
/* global ListIssue */
-/* global bp */
import Vue from 'vue';
+import bp from '../../../breakpoints';
const ModalStore = gl.issueBoards.ModalStore;
diff --git a/app/assets/javascripts/boards/components/new_list_dropdown.js b/app/assets/javascripts/boards/components/new_list_dropdown.js
index f29b6caa1ac..72bb9e10fbc 100644
--- a/app/assets/javascripts/boards/components/new_list_dropdown.js
+++ b/app/assets/javascripts/boards/components/new_list_dropdown.js
@@ -1,5 +1,6 @@
/* eslint-disable comma-dangle, func-names, no-new, space-before-function-paren, one-var,
promise/catch-or-return */
+import _ from 'underscore';
window.gl = window.gl || {};
window.gl.issueBoards = window.gl.issueBoards || {};
diff --git a/app/assets/javascripts/boards/stores/boards_store.js b/app/assets/javascripts/boards/stores/boards_store.js
index 1e12d4ca415..43928e602d6 100644
--- a/app/assets/javascripts/boards/stores/boards_store.js
+++ b/app/assets/javascripts/boards/stores/boards_store.js
@@ -1,6 +1,6 @@
/* eslint-disable comma-dangle, space-before-function-paren, one-var, no-shadow, dot-notation, max-len */
/* global List */
-
+import _ from 'underscore';
import Cookies from 'js-cookie';
window.gl = window.gl || {};
diff --git a/app/assets/javascripts/breakpoints.js b/app/assets/javascripts/breakpoints.js
index 2c1f988d987..7951348d8b2 100644
--- a/app/assets/javascripts/breakpoints.js
+++ b/app/assets/javascripts/breakpoints.js
@@ -1,66 +1,19 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, one-var, no-var, one-var-declaration-per-line, quotes, no-shadow, prefer-arrow-callback, prefer-template, consistent-return, no-return-assign, new-parens, no-param-reassign, max-len */
+export const breakpoints = {
+ lg: 1200,
+ md: 992,
+ sm: 768,
+ xs: 0,
+};
-var Breakpoints = (function() {
- var BreakpointInstance, instance;
+const BreakpointInstance = {
+ windowWidth: () => window.innerWidth,
+ getBreakpointSize() {
+ const windowWidth = this.windowWidth();
- function Breakpoints() {}
+ const breakpoint = Object.keys(breakpoints).find(key => windowWidth > breakpoints[key]);
- instance = null;
+ return breakpoint;
+ },
+};
- BreakpointInstance = (function() {
- var BREAKPOINTS;
-
- BREAKPOINTS = ["xs", "sm", "md", "lg"];
-
- function BreakpointInstance() {
- this.setup();
- }
-
- BreakpointInstance.prototype.setup = function() {
- var allDeviceSelector, els;
- allDeviceSelector = BREAKPOINTS.map(function(breakpoint) {
- return ".device-" + breakpoint;
- });
- if ($(allDeviceSelector.join(",")).length) {
- return;
- }
- // Create all the elements
- els = $.map(BREAKPOINTS, function(breakpoint) {
- return "<div class='device-" + breakpoint + " visible-" + breakpoint + "'></div>";
- });
- return $("body").append(els.join(''));
- };
-
- BreakpointInstance.prototype.visibleDevice = function() {
- var allDeviceSelector;
- allDeviceSelector = BREAKPOINTS.map(function(breakpoint) {
- return ".device-" + breakpoint;
- });
- return $(allDeviceSelector.join(",")).filter(":visible");
- };
-
- BreakpointInstance.prototype.getBreakpointSize = function() {
- var $visibleDevice;
- $visibleDevice = this.visibleDevice;
- // TODO: Consider refactoring in light of turbolinks removal.
- // the page refreshed via turbolinks
- if (!$visibleDevice().length) {
- this.setup();
- }
- $visibleDevice = this.visibleDevice();
- return $visibleDevice.attr("class").split("visible-")[1];
- };
-
- return BreakpointInstance;
- })();
-
- Breakpoints.get = function() {
- return instance != null ? instance : instance = new BreakpointInstance;
- };
-
- return Breakpoints;
-})();
-
-$(() => { window.bp = Breakpoints.get(); });
-
-window.Breakpoints = Breakpoints;
+export default BreakpointInstance;
diff --git a/app/assets/javascripts/build.js b/app/assets/javascripts/build.js
index b3d3bbcf84f..ae1a23132a7 100644
--- a/app/assets/javascripts/build.js
+++ b/app/assets/javascripts/build.js
@@ -1,8 +1,7 @@
/* eslint-disable func-names, wrap-iife, no-use-before-define,
consistent-return, prefer-rest-params */
-/* global Breakpoints */
-
import _ from 'underscore';
+import bp from './breakpoints';
import { bytesToKiB } from './lib/utils/number_utils';
window.Build = (function () {
@@ -34,8 +33,6 @@ window.Build = (function () {
this.$scrollBottomBtn = $('.js-scroll-down');
clearTimeout(Build.timeout);
- // Init breakpoint checker
- this.bp = Breakpoints.get();
this.initSidebar();
this.populateJobs(this.buildStage);
@@ -164,7 +161,6 @@ window.Build = (function () {
Build.prototype.initSidebar = function () {
this.$sidebar = $('.js-build-sidebar');
- this.$sidebar.niceScroll();
};
Build.prototype.getBuildTrace = function () {
@@ -231,7 +227,7 @@ window.Build = (function () {
};
Build.prototype.shouldHideSidebarForViewport = function () {
- const bootstrapBreakpoint = this.bp.getBreakpointSize();
+ const bootstrapBreakpoint = bp.getBreakpointSize();
return bootstrapBreakpoint === 'xs' || bootstrapBreakpoint === 'sm';
};
diff --git a/app/assets/javascripts/build_variables.js b/app/assets/javascripts/build_variables.js
index 99082b412e2..c955a9ac2ea 100644
--- a/app/assets/javascripts/build_variables.js
+++ b/app/assets/javascripts/build_variables.js
@@ -2,7 +2,7 @@
$(function() {
$('.reveal-variables').off('click').on('click', function() {
- $('.js-build').toggle().niceScroll();
+ $('.js-build-variables').toggle();
$(this).hide();
});
});
diff --git a/app/assets/javascripts/commons/bootstrap.js b/app/assets/javascripts/commons/bootstrap.js
index 510bedbf641..c11b7d5f340 100644
--- a/app/assets/javascripts/commons/bootstrap.js
+++ b/app/assets/javascripts/commons/bootstrap.js
@@ -3,6 +3,7 @@ import $ from 'jquery';
// bootstrap jQuery plugins
import 'bootstrap-sass/assets/javascripts/bootstrap/affix';
import 'bootstrap-sass/assets/javascripts/bootstrap/alert';
+import 'bootstrap-sass/assets/javascripts/bootstrap/button';
import 'bootstrap-sass/assets/javascripts/bootstrap/dropdown';
import 'bootstrap-sass/assets/javascripts/bootstrap/modal';
import 'bootstrap-sass/assets/javascripts/bootstrap/tab';
diff --git a/app/assets/javascripts/commons/index.js b/app/assets/javascripts/commons/index.js
index 7063f59d446..6db8b3afbef 100644
--- a/app/assets/javascripts/commons/index.js
+++ b/app/assets/javascripts/commons/index.js
@@ -1,3 +1,4 @@
+import 'underscore';
import './polyfills';
import './jquery';
import './bootstrap';
diff --git a/app/assets/javascripts/commons/jquery.js b/app/assets/javascripts/commons/jquery.js
index b53f6284afc..b93e94a3c97 100644
--- a/app/assets/javascripts/commons/jquery.js
+++ b/app/assets/javascripts/commons/jquery.js
@@ -6,6 +6,5 @@ import 'vendor/jquery.endless-scroll';
import 'vendor/jquery.caret';
import 'vendor/jquery.atwho';
import 'vendor/jquery.scrollTo';
-import 'vendor/jquery.nicescroll';
import 'vendor/jquery.waitforimages';
import 'select2/select2';
diff --git a/app/assets/javascripts/copy_as_gfm.js b/app/assets/javascripts/copy_as_gfm.js
index 54257531284..13ba4a57293 100644
--- a/app/assets/javascripts/copy_as_gfm.js
+++ b/app/assets/javascripts/copy_as_gfm.js
@@ -1,5 +1,5 @@
/* eslint-disable class-methods-use-this, object-shorthand, no-unused-vars, no-use-before-define, no-new, max-len, no-restricted-syntax, guard-for-in, no-continue */
-
+import _ from 'underscore';
import './lib/utils/common_utils';
import { placeholderImage } from './lazy_loader';
diff --git a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js
index 44791a93936..6583e471a48 100644
--- a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js
+++ b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js
@@ -92,7 +92,7 @@ $(() => {
});
},
selectDefaultStage() {
- const stage = this.state.stages.first();
+ const stage = this.state.stages[0];
this.selectStage(stage);
},
selectStage(stage) {
diff --git a/app/assets/javascripts/diff_notes/components/jump_to_discussion.js b/app/assets/javascripts/diff_notes/components/jump_to_discussion.js
index 37ddca29e71..298f737a2bc 100644
--- a/app/assets/javascripts/diff_notes/components/jump_to_discussion.js
+++ b/app/assets/javascripts/diff_notes/components/jump_to_discussion.js
@@ -94,7 +94,7 @@ const JumpToDiscussion = Vue.extend({
hasDiscussionsToJumpTo = false;
}
}
- } else if (activeTab !== 'notes') {
+ } else if (activeTab !== 'show') {
// If we are on the commits or builds tabs,
// there are no discussions to jump to.
hasDiscussionsToJumpTo = false;
@@ -103,12 +103,12 @@ const JumpToDiscussion = Vue.extend({
if (!hasDiscussionsToJumpTo) {
// If there are no discussions to jump to on the current page,
// switch to the notes tab and jump to the first disucssion there.
- window.mrTabs.activateTab('notes');
- activeTab = 'notes';
+ window.mrTabs.activateTab('show');
+ activeTab = 'show';
jumpToFirstDiscussion = true;
}
- if (activeTab === 'notes') {
+ if (activeTab === 'show') {
discussionsSelector = '.discussion[data-discussion-id]';
discussionIdsInScope = discussionIdsForElements($(discussionsSelector));
}
@@ -156,7 +156,7 @@ const JumpToDiscussion = Vue.extend({
let $target = $(`${discussionsSelector}[data-discussion-id="${nextUnresolvedDiscussionId}"]`);
- if (activeTab === 'notes') {
+ if (activeTab === 'show') {
$target = $target.closest('.note-discussion');
// If the next discussion is closed, toggle it open.
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index 178e72a1127..de47485c9f2 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -75,14 +75,12 @@ import initNotes from './init_notes';
import initLegacyFilters from './init_legacy_filters';
import initIssuableSidebar from './init_issuable_sidebar';
import GpgBadges from './gpg_badges';
+import UserFeatureHelper from './helpers/user_feature_helper';
+import initChangesDropdown from './init_changes_dropdown';
(function() {
var Dispatcher;
- $(function() {
- return new Dispatcher();
- });
-
Dispatcher = (function() {
function Dispatcher() {
this.initSearch();
@@ -96,6 +94,7 @@ import GpgBadges from './gpg_badges';
if (!page) {
return false;
}
+
path = page.split(':');
shortcut_handler = null;
@@ -230,6 +229,7 @@ import GpgBadges from './gpg_badges';
break;
case 'projects:compare:show':
new gl.Diff();
+ initChangesDropdown();
break;
case 'projects:branches:new':
case 'projects:branches:create':
@@ -322,6 +322,7 @@ import GpgBadges from './gpg_badges';
container: '.js-commit-pipeline-graph',
}).bindEvents();
initNotes();
+ initChangesDropdown();
$('.commit-info.branches').load(document.querySelector('.js-commit-box').dataset.commitPath);
break;
case 'projects:commit:pipelines':
@@ -336,22 +337,24 @@ import GpgBadges from './gpg_badges';
break;
case 'projects:commits:show':
CommitsList.init(document.querySelector('.js-project-commits-show').dataset.commitsLimit);
- new gl.Activities();
shortcut_handler = new ShortcutsNavigation();
GpgBadges.fetch();
break;
case 'projects:show':
shortcut_handler = new ShortcutsNavigation();
new NotificationsForm();
- if ($('#tree-slider').length) {
- new TreeView();
- }
- if ($('.blob-viewer').length) {
- new BlobViewer();
- }
+
+ if ($('#tree-slider').length) new TreeView();
+ if ($('.blob-viewer').length) new BlobViewer();
+ if ($('.project-show-activity').length) new gl.Activities();
+ $('#tree-slider').waitForImages(function() {
+ gl.utils.ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath);
+ });
break;
case 'projects:edit':
setupProjectEdit();
+ // Initialize expandable settings panels
+ initSettingsPanels();
break;
case 'projects:imports:show':
new ProjectImport();
@@ -410,6 +413,9 @@ import GpgBadges from './gpg_badges';
break;
case 'projects:tree:show':
shortcut_handler = new ShortcutsNavigation();
+
+ if (UserFeatureHelper.isNewRepo()) break;
+
new TreeView();
new BlobViewer();
new NewCommitForm($('.js-create-dir-form'));
@@ -428,6 +434,7 @@ import GpgBadges from './gpg_badges';
shortcut_handler = true;
break;
case 'projects:blob:show':
+ if (UserFeatureHelper.isNewRepo()) break;
new BlobViewer();
initBlob();
break;
@@ -508,7 +515,7 @@ import GpgBadges from './gpg_badges';
new gl.DueDateSelectors();
break;
}
- switch (path.first()) {
+ switch (path[0]) {
case 'sessions':
case 'omniauth_callbacks':
if (!gon.u2f) break;
@@ -580,7 +587,6 @@ import GpgBadges from './gpg_badges';
shortcut_handler = new ShortcutsWiki();
new ZenMode();
new gl.GLForm($('.wiki-form'), true);
- new Sidebar();
break;
case 'snippets':
shortcut_handler = new ShortcutsNavigation();
@@ -637,4 +643,8 @@ import GpgBadges from './gpg_badges';
return Dispatcher;
})();
+
+ $(function() {
+ new Dispatcher();
+ });
}).call(window);
diff --git a/app/assets/javascripts/dropzone_input.js b/app/assets/javascripts/dropzone_input.js
index 9ebbb22e807..6d19a6d9b3a 100644
--- a/app/assets/javascripts/dropzone_input.js
+++ b/app/assets/javascripts/dropzone_input.js
@@ -1,11 +1,10 @@
/* eslint-disable func-names, space-before-function-paren, wrap-iife, max-len, one-var, no-var, one-var-declaration-per-line, no-unused-vars, camelcase, quotes, no-useless-concat, prefer-template, quote-props, comma-dangle, object-shorthand, consistent-return, prefer-arrow-callback */
/* global Dropzone */
-
+import _ from 'underscore';
import './preview_markdown';
window.DropzoneInput = (function() {
function DropzoneInput(form) {
- Dropzone.autoDiscover = false;
const divHover = '<div class="div-dropzone-hover"></div>';
const iconPaperclip = '<i class="fa fa-paperclip div-dropzone-icon"></i>';
const $attachButton = form.find('.button-attach-file');
@@ -218,7 +217,7 @@ window.DropzoneInput = (function() {
value = e.clipboardData.getData('text/plain');
}
value = value.split("\r");
- return value.first();
+ return value[0];
};
const showSpinner = function(e) {
diff --git a/app/assets/javascripts/due_date_select.js b/app/assets/javascripts/due_date_select.js
index 2856c8e2862..ee71728184f 100644
--- a/app/assets/javascripts/due_date_select.js
+++ b/app/assets/javascripts/due_date_select.js
@@ -1,7 +1,7 @@
/* eslint-disable wrap-iife, func-names, space-before-function-paren, comma-dangle, prefer-template, consistent-return, class-methods-use-this, arrow-body-style, no-unused-vars, no-underscore-dangle, no-new, max-len, no-sequences, no-unused-expressions, no-param-reassign */
/* global dateFormat */
-/* global Pikaday */
+import Pikaday from 'pikaday';
import DateFix from './lib/utils/datefix';
class DueDateSelect {
diff --git a/app/assets/javascripts/emoji/index.js b/app/assets/javascripts/emoji/index.js
index cac35d6eed5..dc7672560ea 100644
--- a/app/assets/javascripts/emoji/index.js
+++ b/app/assets/javascripts/emoji/index.js
@@ -1,3 +1,4 @@
+import _ from 'underscore';
import emojiMap from 'emojis/digests.json';
import emojiAliases from 'emojis/aliases.json';
diff --git a/app/assets/javascripts/extensions/array.js b/app/assets/javascripts/extensions/array.js
deleted file mode 100644
index 027222f804d..00000000000
--- a/app/assets/javascripts/extensions/array.js
+++ /dev/null
@@ -1,11 +0,0 @@
-// TODO: remove this
-
-// eslint-disable-next-line no-extend-native
-Array.prototype.first = function first() {
- return this[0];
-};
-
-// eslint-disable-next-line no-extend-native
-Array.prototype.last = function last() {
- return this[this.length - 1];
-};
diff --git a/app/assets/javascripts/filterable_list.js b/app/assets/javascripts/filterable_list.js
index 139206cc185..6d516a253bb 100644
--- a/app/assets/javascripts/filterable_list.js
+++ b/app/assets/javascripts/filterable_list.js
@@ -1,3 +1,5 @@
+import _ from 'underscore';
+
/**
* Makes search request for content when user types a value in the search input.
* Updates the html content of the page with the received one.
diff --git a/app/assets/javascripts/filtered_search/dropdown_utils.js b/app/assets/javascripts/filtered_search/dropdown_utils.js
index 9c7a4d9f6ad..8d711e3213c 100644
--- a/app/assets/javascripts/filtered_search/dropdown_utils.js
+++ b/app/assets/javascripts/filtered_search/dropdown_utils.js
@@ -1,3 +1,4 @@
+import _ from 'underscore';
import FilteredSearchContainer from './container';
class DropdownUtils {
@@ -122,11 +123,11 @@ class DropdownUtils {
if (!allowMultiple && itemInExistingTokens) {
updatedItem.droplab_hidden = true;
- } else if (!lastKey || searchInput.split('').last() === ' ') {
+ } else if (!lastKey || _.last(searchInput.split('')) === ' ') {
updatedItem.droplab_hidden = false;
} else if (lastKey) {
const split = lastKey.split(':');
- const tokenName = split[0].split(' ').last();
+ const tokenName = _.last(split[0].split(' '));
const match = updatedItem.hint.indexOf(tokenName.toLowerCase()) === -1;
updatedItem.droplab_hidden = tokenName ? match : false;
diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
index 47cecd5b5f7..dd1c067df87 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
@@ -167,7 +167,7 @@ class FilteredSearchDropdownManager {
// Eg. token = 'label:'
const split = lastToken.split(':');
- const dropdownName = split[0].split(' ').last();
+ const dropdownName = _.last(split[0].split(' '));
this.loadDropdown(split.length > 1 ? dropdownName : '');
} else if (lastToken) {
// Token has been initialized into an object because it has a value
diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js b/app/assets/javascripts/filtered_search/filtered_search_manager.js
index 3ce8b8607ad..a31be2b0bc7 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_manager.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js
@@ -367,7 +367,7 @@ class FilteredSearchManager {
const fragments = searchToken.split(':');
if (fragments.length > 1) {
const inputValues = fragments[0].split(' ');
- const tokenKey = inputValues.last();
+ const tokenKey = _.last(inputValues);
if (inputValues.length > 1) {
inputValues.pop();
diff --git a/app/assets/javascripts/fly_out_nav.js b/app/assets/javascripts/fly_out_nav.js
new file mode 100644
index 00000000000..cbc3ad23990
--- /dev/null
+++ b/app/assets/javascripts/fly_out_nav.js
@@ -0,0 +1,171 @@
+import Cookies from 'js-cookie';
+import bp from './breakpoints';
+
+const HIDE_INTERVAL_TIMEOUT = 300;
+const IS_OVER_CLASS = 'is-over';
+const IS_ABOVE_CLASS = 'is-above';
+const IS_SHOWING_FLY_OUT_CLASS = 'is-showing-fly-out';
+let currentOpenMenu = null;
+let menuCornerLocs;
+let timeoutId;
+
+export const mousePos = [];
+
+export const setOpenMenu = (menu = null) => { currentOpenMenu = menu; };
+
+export const slope = (a, b) => (b.y - a.y) / (b.x - a.x);
+
+export const canShowActiveSubItems = (el) => {
+ const isHiddenByMedia = bp.getBreakpointSize() === 'sm' || bp.getBreakpointSize() === 'md';
+
+ if (el.classList.contains('active') && !isHiddenByMedia) {
+ return Cookies.get('sidebar_collapsed') === 'true';
+ }
+
+ return true;
+};
+
+export const canShowSubItems = () => bp.getBreakpointSize() === 'sm' || bp.getBreakpointSize() === 'md' || bp.getBreakpointSize() === 'lg';
+
+export const getHideSubItemsInterval = () => {
+ if (!currentOpenMenu) return 0;
+
+ const currentMousePos = mousePos[mousePos.length - 1];
+ const prevMousePos = mousePos[0];
+ const currentMousePosY = currentMousePos.y;
+ const [menuTop, menuBottom] = menuCornerLocs;
+
+ if (currentMousePosY < menuTop.y ||
+ currentMousePosY > menuBottom.y) return 0;
+
+ if (slope(prevMousePos, menuBottom) < slope(currentMousePos, menuBottom) &&
+ slope(prevMousePos, menuTop) > slope(currentMousePos, menuTop)) {
+ return HIDE_INTERVAL_TIMEOUT;
+ }
+
+ return 0;
+};
+
+export const calculateTop = (boundingRect, outerHeight) => {
+ const windowHeight = window.innerHeight;
+ const bottomOverflow = windowHeight - (boundingRect.top + outerHeight);
+
+ return bottomOverflow < 0 ? (boundingRect.top - outerHeight) + boundingRect.height :
+ boundingRect.top;
+};
+
+export const hideMenu = (el) => {
+ if (!el) return;
+
+ const parentEl = el.parentNode;
+
+ el.style.display = ''; // eslint-disable-line no-param-reassign
+ el.style.transform = ''; // eslint-disable-line no-param-reassign
+ el.classList.remove(IS_ABOVE_CLASS);
+ parentEl.classList.remove(IS_OVER_CLASS);
+ parentEl.classList.remove(IS_SHOWING_FLY_OUT_CLASS);
+
+ setOpenMenu();
+};
+
+export const moveSubItemsToPosition = (el, subItems) => {
+ const boundingRect = el.getBoundingClientRect();
+ const top = calculateTop(boundingRect, subItems.offsetHeight);
+ const isAbove = top < boundingRect.top;
+
+ subItems.classList.add('fly-out-list');
+ subItems.style.transform = `translate3d(0, ${Math.floor(top)}px, 0)`; // eslint-disable-line no-param-reassign
+
+ const subItemsRect = subItems.getBoundingClientRect();
+
+ menuCornerLocs = [
+ {
+ x: subItemsRect.left, // left position of the sub items
+ y: subItemsRect.top, // top position of the sub items
+ },
+ {
+ x: subItemsRect.left, // left position of the sub items
+ y: subItemsRect.top + subItemsRect.height, // bottom position of the sub items
+ },
+ ];
+
+ if (isAbove) {
+ subItems.classList.add(IS_ABOVE_CLASS);
+ }
+};
+
+export const showSubLevelItems = (el) => {
+ const subItems = el.querySelector('.sidebar-sub-level-items');
+
+ if (!canShowSubItems() || !canShowActiveSubItems(el)) return;
+
+ el.classList.add(IS_OVER_CLASS);
+
+ if (!subItems) return;
+
+ subItems.style.display = 'block';
+ el.classList.add(IS_SHOWING_FLY_OUT_CLASS);
+
+ setOpenMenu(subItems);
+ moveSubItemsToPosition(el, subItems);
+};
+
+export const mouseEnterTopItems = (el) => {
+ clearTimeout(timeoutId);
+
+ timeoutId = setTimeout(() => {
+ if (currentOpenMenu) hideMenu(currentOpenMenu);
+
+ showSubLevelItems(el);
+ }, getHideSubItemsInterval());
+};
+
+export const mouseLeaveTopItem = (el) => {
+ const subItems = el.querySelector('.sidebar-sub-level-items');
+
+ if (!canShowSubItems() || !canShowActiveSubItems(el) ||
+ (subItems && subItems === currentOpenMenu)) return;
+
+ el.classList.remove(IS_OVER_CLASS);
+};
+
+export const documentMouseMove = (e) => {
+ mousePos.push({
+ x: e.clientX,
+ y: e.clientY,
+ });
+
+ if (mousePos.length > 6) mousePos.shift();
+};
+
+export default () => {
+ const sidebar = document.querySelector('.sidebar-top-level-items');
+
+ if (!sidebar) return;
+
+ const items = [...sidebar.querySelectorAll('.sidebar-top-level-items > li')];
+
+ sidebar.addEventListener('mouseleave', () => {
+ clearTimeout(timeoutId);
+
+ timeoutId = setTimeout(() => {
+ if (currentOpenMenu) hideMenu(currentOpenMenu);
+ }, getHideSubItemsInterval());
+ });
+
+ items.forEach((el) => {
+ const subItems = el.querySelector('.sidebar-sub-level-items');
+
+ if (subItems) {
+ subItems.addEventListener('mouseleave', () => {
+ clearTimeout(timeoutId);
+ hideMenu(currentOpenMenu);
+ });
+ }
+
+ el.addEventListener('mouseenter', e => mouseEnterTopItems(e.currentTarget));
+ el.addEventListener('mouseleave', e => mouseLeaveTopItem(e.currentTarget));
+ });
+
+ document.addEventListener('mousemove', documentMouseMove);
+};
diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js
index 6cb9cfe1382..5c624b79d45 100644
--- a/app/assets/javascripts/gfm_auto_complete.js
+++ b/app/assets/javascripts/gfm_auto_complete.js
@@ -1,3 +1,4 @@
+import _ from 'underscore';
import glRegexp from './lib/utils/regexp';
import AjaxCache from './lib/utils/ajax_cache';
diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js
index 9475498e176..b62acfcd445 100644
--- a/app/assets/javascripts/gl_dropdown.js
+++ b/app/assets/javascripts/gl_dropdown.js
@@ -1,8 +1,53 @@
-/* eslint-disable func-names, space-before-function-paren, no-var, one-var, one-var-declaration-per-line, prefer-rest-params, max-len, vars-on-top, wrap-iife, no-unused-vars, quotes, no-shadow, no-cond-assign, prefer-arrow-callback, no-return-assign, no-else-return, camelcase, comma-dangle, no-lonely-if, guard-for-in, no-restricted-syntax, consistent-return, prefer-template, no-param-reassign, no-loop-func, no-mixed-operators */
+/* eslint-disable func-names, no-underscore-dangle, space-before-function-paren, no-var, one-var, one-var-declaration-per-line, prefer-rest-params, max-len, vars-on-top, wrap-iife, no-unused-vars, quotes, no-shadow, no-cond-assign, prefer-arrow-callback, no-return-assign, no-else-return, camelcase, comma-dangle, no-lonely-if, guard-for-in, no-restricted-syntax, consistent-return, prefer-template, no-param-reassign, no-loop-func, no-mixed-operators */
/* global fuzzaldrinPlus */
+import _ from 'underscore';
import { isObject } from './lib/utils/type_utility';
-var GitLabDropdown, GitLabDropdownFilter, GitLabDropdownRemote;
+var GitLabDropdown, GitLabDropdownFilter, GitLabDropdownRemote, GitLabDropdownInput;
+
+GitLabDropdownInput = (function() {
+ function GitLabDropdownInput(input, options) {
+ var $inputContainer, $clearButton;
+ var _this = this;
+ this.input = input;
+ this.options = options;
+ this.fieldName = this.options.fieldName || 'field-name';
+ $inputContainer = this.input.parent();
+ $clearButton = $inputContainer.find('.js-dropdown-input-clear');
+ $clearButton.on('click', (function(_this) {
+ // Clear click
+ return function(e) {
+ e.preventDefault();
+ e.stopPropagation();
+ return _this.input.val('').trigger('input').focus();
+ };
+ })(this));
+
+ this.input
+ .on('keydown', function (e) {
+ var keyCode = e.which;
+ if (keyCode === 13 && !options.elIsInput) {
+ e.preventDefault();
+ }
+ })
+ .on('input', function(e) {
+ var val = e.currentTarget.value || _this.options.inputFieldName;
+ val = val.split(' ').join('-') // replaces space with dash
+ .replace(/[^a-zA-Z0-9 -]/g, '').toLowerCase() // replace non alphanumeric
+ .replace(/(-)\1+/g, '-'); // replace repeated dashes
+ _this.cb(_this.options.fieldName, val, {}, true);
+ _this.input.closest('.dropdown')
+ .find('.dropdown-toggle-text')
+ .text(val);
+ });
+ }
+
+ GitLabDropdownInput.prototype.onInput = function(cb) {
+ this.cb = cb;
+ };
+
+ return GitLabDropdownInput;
+})();
GitLabDropdownFilter = (function() {
var ARROW_KEY_CODES, BLUR_KEYCODES, HAS_VALUE_CLASS;
@@ -114,7 +159,7 @@ GitLabDropdownFilter = (function() {
} else {
elements = this.options.elements();
if (search_text) {
- return elements.each(function() {
+ elements.each(function() {
var $el, matches;
$el = $(this);
matches = fuzzaldrinPlus.match($el.text().trim(), search_text);
@@ -127,8 +172,10 @@ GitLabDropdownFilter = (function() {
}
});
} else {
- return elements.show().removeClass('option-hidden');
+ elements.show().removeClass('option-hidden');
}
+
+ elements.parent().find('.dropdown-menu-empty-link').toggleClass('hidden', elements.is(':visible'));
}
};
@@ -188,7 +235,7 @@ GitLabDropdownRemote = (function() {
})();
GitLabDropdown = (function() {
- var ACTIVE_CLASS, FILTER_INPUT, INDETERMINATE_CLASS, LOADING_CLASS, PAGE_TWO_CLASS, NON_SELECTABLE_CLASSES, SELECTABLE_CLASSES, CURSOR_SELECT_SCROLL_PADDING, currentIndex;
+ var ACTIVE_CLASS, FILTER_INPUT, NO_FILTER_INPUT, INDETERMINATE_CLASS, LOADING_CLASS, PAGE_TWO_CLASS, NON_SELECTABLE_CLASSES, SELECTABLE_CLASSES, CURSOR_SELECT_SCROLL_PADDING, currentIndex;
LOADING_CLASS = "is-loading";
@@ -206,7 +253,9 @@ GitLabDropdown = (function() {
CURSOR_SELECT_SCROLL_PADDING = 5;
- FILTER_INPUT = '.dropdown-input .dropdown-input-field';
+ FILTER_INPUT = '.dropdown-input .dropdown-input-field:not(.dropdown-no-filter)';
+
+ NO_FILTER_INPUT = '.dropdown-input .dropdown-input-field.dropdown-no-filter';
function GitLabDropdown(el1, options) {
var searchFields, selector, self;
@@ -221,6 +270,7 @@ GitLabDropdown = (function() {
this.dropdown = selector != null ? $(selector) : $(this.el).parent();
// Set Defaults
this.filterInput = this.options.filterInput || this.getElement(FILTER_INPUT);
+ this.noFilterInput = this.options.noFilterInput || this.getElement(NO_FILTER_INPUT);
this.highlight = !!this.options.highlight;
this.filterInputBlur = this.options.filterInputBlur != null
? this.options.filterInputBlur
@@ -259,6 +309,10 @@ GitLabDropdown = (function() {
});
}
}
+ if (this.noFilterInput.length) {
+ this.plainInput = new GitLabDropdownInput(this.noFilterInput, this.options);
+ this.plainInput.onInput(this.addInput.bind(this));
+ }
// Init filterable
if (this.options.filterable) {
this.filter = new GitLabDropdownFilter(this.filterInput, {
@@ -731,9 +785,15 @@ GitLabDropdown = (function() {
GitLabDropdown.prototype.focusTextInput = function(triggerFocus = false) {
if (this.options.filterable) {
this.dropdown.one('transitionend', () => {
+ const initialScrollTop = $(window).scrollTop();
+
if (this.dropdown.is('.open')) {
this.filterInput.focus();
}
+
+ if ($(window).scrollTop() < initialScrollTop) {
+ $(window).scrollTop(initialScrollTop);
+ }
});
if (triggerFocus) {
@@ -744,9 +804,13 @@ GitLabDropdown = (function() {
}
};
- GitLabDropdown.prototype.addInput = function(fieldName, value, selectedObject) {
+ GitLabDropdown.prototype.addInput = function(fieldName, value, selectedObject, single) {
var $input;
// Create hidden input for form
+ if (single) {
+ $('input[name="' + fieldName + '"]').remove();
+ }
+
$input = $('<input>').attr('type', 'hidden').attr('name', fieldName).val(value);
if (this.options.inputId != null) {
$input.attr('id', this.options.inputId);
@@ -762,7 +826,7 @@ GitLabDropdown = (function() {
$input.attr('data-meta', selectedObject[this.options.inputMeta]);
}
- return this.dropdown.before($input);
+ this.dropdown.before($input).trigger('change');
};
GitLabDropdown.prototype.selectRowAtIndex = function(index) {
diff --git a/app/assets/javascripts/graphs/graphs_charts.js b/app/assets/javascripts/graphs/graphs_charts.js
index 279ffef770f..ec6eab34989 100644
--- a/app/assets/javascripts/graphs/graphs_charts.js
+++ b/app/assets/javascripts/graphs/graphs_charts.js
@@ -1,4 +1,5 @@
import Chart from 'vendor/Chart';
+import _ from 'underscore';
document.addEventListener('DOMContentLoaded', () => {
const projectChartData = JSON.parse(document.getElementById('projectChartData').innerHTML);
@@ -27,28 +28,25 @@ document.addEventListener('DOMContentLoaded', () => {
return generateChart();
};
- const chartData = (keys, values) => {
- const data = {
- labels: keys,
- datasets: [{
- fillColor: 'rgba(220,220,220,0.5)',
- strokeColor: 'rgba(220,220,220,1)',
- barStrokeWidth: 1,
- barValueSpacing: 1,
- barDatasetSpacing: 1,
- data: values,
- }],
- };
- return data;
- };
-
- const hourData = chartData(projectChartData.hour.keys, projectChartData.hour.values);
+ const chartData = data => ({
+ labels: Object.keys(data),
+ datasets: [{
+ fillColor: 'rgba(220,220,220,0.5)',
+ strokeColor: 'rgba(220,220,220,1)',
+ barStrokeWidth: 1,
+ barValueSpacing: 1,
+ barDatasetSpacing: 1,
+ data: _.values(data),
+ }],
+ });
+
+ const hourData = chartData(projectChartData.hour);
responsiveChart($('#hour-chart'), hourData);
- const dayData = chartData(projectChartData.weekDays.keys, projectChartData.weekDays.values);
+ const dayData = chartData(projectChartData.weekDays);
responsiveChart($('#weekday-chart'), dayData);
- const monthData = chartData(projectChartData.month.keys, projectChartData.month.values);
+ const monthData = chartData(projectChartData.month);
responsiveChart($('#month-chart'), monthData);
const data = projectChartData.languages;
diff --git a/app/assets/javascripts/graphs/stat_graph_contributors.js b/app/assets/javascripts/graphs/stat_graph_contributors.js
index c6be4c9e8fe..cdc4fcf6573 100644
--- a/app/assets/javascripts/graphs/stat_graph_contributors.js
+++ b/app/assets/javascripts/graphs/stat_graph_contributors.js
@@ -1,5 +1,6 @@
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, one-var, camelcase, one-var-declaration-per-line, quotes, no-param-reassign, quote-props, comma-dangle, prefer-template, max-len, no-return-assign, no-shadow */
+import _ from 'underscore';
import d3 from 'd3';
import { ContributorsGraph, ContributorsAuthorGraph, ContributorsMasterGraph } from './stat_graph_contributors_graph';
import ContributorsStatGraphUtil from './stat_graph_contributors_util';
diff --git a/app/assets/javascripts/graphs/stat_graph_contributors_graph.js b/app/assets/javascripts/graphs/stat_graph_contributors_graph.js
index 0deb27e522b..f64b4638485 100644
--- a/app/assets/javascripts/graphs/stat_graph_contributors_graph.js
+++ b/app/assets/javascripts/graphs/stat_graph_contributors_graph.js
@@ -1,5 +1,5 @@
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, max-len, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, comma-dangle, no-return-assign, prefer-arrow-callback, quotes, prefer-template, newline-per-chained-call, no-else-return, no-shadow */
-
+import _ from 'underscore';
import d3 from 'd3';
const extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
diff --git a/app/assets/javascripts/graphs/stat_graph_contributors_util.js b/app/assets/javascripts/graphs/stat_graph_contributors_util.js
index c583757f3f2..77135ad1f0e 100644
--- a/app/assets/javascripts/graphs/stat_graph_contributors_util.js
+++ b/app/assets/javascripts/graphs/stat_graph_contributors_util.js
@@ -1,4 +1,5 @@
/* eslint-disable func-names, space-before-function-paren, object-shorthand, no-var, one-var, camelcase, one-var-declaration-per-line, comma-dangle, no-param-reassign, no-return-assign, quotes, prefer-arrow-callback, wrap-iife, consistent-return, no-unused-vars, max-len, no-cond-assign, no-else-return, max-len */
+import _ from 'underscore';
export default {
parse_log: function(log) {
diff --git a/app/assets/javascripts/helpers/user_feature_helper.js b/app/assets/javascripts/helpers/user_feature_helper.js
new file mode 100644
index 00000000000..fcd8569819c
--- /dev/null
+++ b/app/assets/javascripts/helpers/user_feature_helper.js
@@ -0,0 +1,11 @@
+import Cookies from 'js-cookie';
+
+function isNewRepo() {
+ return Cookies.get('new_repo') === 'true';
+}
+
+const UserFeatureHelper = {
+ isNewRepo,
+};
+
+export default UserFeatureHelper;
diff --git a/app/assets/javascripts/init_changes_dropdown.js b/app/assets/javascripts/init_changes_dropdown.js
new file mode 100644
index 00000000000..f785ed29e6c
--- /dev/null
+++ b/app/assets/javascripts/init_changes_dropdown.js
@@ -0,0 +1,10 @@
+import stickyMonitor from './lib/utils/sticky';
+
+export default () => {
+ stickyMonitor(document.querySelector('.js-diff-files-changed'));
+
+ $('.js-diff-stats-dropdown').glDropdown({
+ filterable: true,
+ remoteFilter: false,
+ });
+};
diff --git a/app/assets/javascripts/issuable_bulk_update_actions.js b/app/assets/javascripts/issuable_bulk_update_actions.js
index e46c0e90255..c39ffdb2e0f 100644
--- a/app/assets/javascripts/issuable_bulk_update_actions.js
+++ b/app/assets/javascripts/issuable_bulk_update_actions.js
@@ -1,6 +1,7 @@
/* eslint-disable comma-dangle, quotes, consistent-return, func-names, array-callback-return, space-before-function-paren, prefer-arrow-callback, max-len, no-unused-expressions, no-sequences, no-underscore-dangle, no-unused-vars, no-param-reassign */
/* global IssuableIndex */
/* global Flash */
+import _ from 'underscore';
export default {
init({ container, form, issues, prefixId } = {}) {
diff --git a/app/assets/javascripts/issuable_context.js b/app/assets/javascripts/issuable_context.js
index 26392db4b5b..70c364e51fe 100644
--- a/app/assets/javascripts/issuable_context.js
+++ b/app/assets/javascripts/issuable_context.js
@@ -1,7 +1,6 @@
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-new, comma-dangle, quotes, prefer-arrow-callback, consistent-return, one-var, no-var, one-var-declaration-per-line, no-underscore-dangle, max-len */
-/* global bp */
-
import Cookies from 'js-cookie';
+import bp from './breakpoints';
import UsersSelect from './users_select';
const PARTICIPANTS_ROW_COUNT = 7;
diff --git a/app/assets/javascripts/issuable_form.js b/app/assets/javascripts/issuable_form.js
index 9ac1325fc95..3f848e0859b 100644
--- a/app/assets/javascripts/issuable_form.js
+++ b/app/assets/javascripts/issuable_form.js
@@ -2,8 +2,8 @@
/* global GitLab */
/* global Autosave */
/* global dateFormat */
-/* global Pikaday */
+import Pikaday from 'pikaday';
import UsersSelect from './users_select';
import GfmAutoComplete from './gfm_auto_complete';
import ZenMode from './zen_mode';
diff --git a/app/assets/javascripts/issuable_index.js b/app/assets/javascripts/issuable_index.js
index 5c96646def8..ece0220c927 100644
--- a/app/assets/javascripts/issuable_index.js
+++ b/app/assets/javascripts/issuable_index.js
@@ -1,6 +1,6 @@
/* eslint-disable no-param-reassign, func-names, no-var, camelcase, no-unused-vars, object-shorthand, space-before-function-paren, no-return-assign, comma-dangle, consistent-return, one-var, one-var-declaration-per-line, quotes, prefer-template, prefer-arrow-callback, wrap-iife, max-len */
/* global IssuableIndex */
-
+import _ from 'underscore';
import IssuableBulkUpdateSidebar from './issuable_bulk_update_sidebar';
import IssuableBulkUpdateActions from './issuable_bulk_update_actions';
diff --git a/app/assets/javascripts/jobs/components/header.vue b/app/assets/javascripts/jobs/components/header.vue
index 5b9cf577189..3f6f40d47ba 100644
--- a/app/assets/javascripts/jobs/components/header.vue
+++ b/app/assets/javascripts/jobs/components/header.vue
@@ -40,7 +40,7 @@
label: 'New issue',
path: this.job.new_issue_path,
cssClass: 'js-new-issue btn btn-new btn-inverted visible-md-block visible-lg-block',
- type: 'ujs-link',
+ type: 'link',
});
}
diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js
index f0e02ca0fb2..7d7f91227f9 100644
--- a/app/assets/javascripts/labels_select.js
+++ b/app/assets/javascripts/labels_select.js
@@ -1,7 +1,7 @@
/* eslint-disable no-useless-return, func-names, space-before-function-paren, wrap-iife, no-var, no-underscore-dangle, prefer-arrow-callback, max-len, one-var, no-unused-vars, one-var-declaration-per-line, prefer-template, no-new, consistent-return, object-shorthand, comma-dangle, no-shadow, no-param-reassign, brace-style, vars-on-top, quotes, no-lonely-if, no-else-return, dot-notation, no-empty, no-return-assign, camelcase, prefer-spread */
/* global Issuable */
/* global ListLabel */
-
+import _ from 'underscore';
import IssuableBulkUpdateActions from './issuable_bulk_update_actions';
import DropdownUtils from './filtered_search/dropdown_utils';
diff --git a/app/assets/javascripts/layout_nav.js b/app/assets/javascripts/layout_nav.js
index 6186ffe20b3..5c1ba416a03 100644
--- a/app/assets/javascripts/layout_nav.js
+++ b/app/assets/javascripts/layout_nav.js
@@ -2,6 +2,7 @@
import _ from 'underscore';
import Cookies from 'js-cookie';
import NewNavSidebar from './new_sidebar';
+import initFlyOutNav from './fly_out_nav';
(function() {
var hideEndFade;
@@ -58,6 +59,8 @@ import NewNavSidebar from './new_sidebar';
if (Cookies.get('new_nav') === 'true') {
const newNavSidebar = new NewNavSidebar();
newNavSidebar.bindEvents();
+
+ initFlyOutNav();
}
$(window).on('scroll', _.throttle(applyScrollNavClass, 100));
diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js
index 122ec138c59..e916724b666 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js
+++ b/app/assets/javascripts/lib/utils/common_utils.js
@@ -86,8 +86,9 @@
// This is required to handle non-unicode characters in hash
hash = decodeURIComponent(hash);
- var fixedTabs = document.querySelector('.js-tabs-affix');
- var fixedNav = document.querySelector('.navbar-gitlab');
+ const fixedTabs = document.querySelector('.js-tabs-affix');
+ const fixedDiffStats = document.querySelector('.js-diff-files-changed.is-stuck');
+ const fixedNav = document.querySelector('.navbar-gitlab');
var adjustment = 0;
if (fixedNav) adjustment -= fixedNav.offsetHeight;
@@ -104,6 +105,11 @@
if (fixedTabs) {
adjustment -= fixedTabs.offsetHeight;
}
+
+ if (fixedDiffStats) {
+ adjustment -= fixedDiffStats.offsetHeight;
+ }
+
window.scrollBy(0, adjustment);
}
};
diff --git a/app/assets/javascripts/lib/utils/constants.js b/app/assets/javascripts/lib/utils/constants.js
index 1e96c7ab5cd..7a72509d234 100644
--- a/app/assets/javascripts/lib/utils/constants.js
+++ b/app/assets/javascripts/lib/utils/constants.js
@@ -1,2 +1,3 @@
/* eslint-disable import/prefer-default-export */
export const BYTES_IN_KIB = 1024;
+export const HIDDEN_CLASS = 'hidden';
diff --git a/app/assets/javascripts/lib/utils/pretty_time.js b/app/assets/javascripts/lib/utils/pretty_time.js
index ae397212e55..716aefbfcb7 100644
--- a/app/assets/javascripts/lib/utils/pretty_time.js
+++ b/app/assets/javascripts/lib/utils/pretty_time.js
@@ -1,3 +1,5 @@
+import _ from 'underscore';
+
(() => {
/*
* TODO: Make these methods more configurable (e.g. parseSeconds timePeriodContstraints,
diff --git a/app/assets/javascripts/lib/utils/sticky.js b/app/assets/javascripts/lib/utils/sticky.js
new file mode 100644
index 00000000000..43a808b6ab3
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/sticky.js
@@ -0,0 +1,23 @@
+export const isSticky = (el, scrollY, stickyTop) => {
+ const top = el.offsetTop - scrollY;
+
+ if (top === stickyTop) {
+ el.classList.add('is-stuck');
+ } else {
+ el.classList.remove('is-stuck');
+ }
+};
+
+export default (el) => {
+ if (!el) return;
+
+ const computedStyle = window.getComputedStyle(el);
+
+ if (!/sticky/.test(computedStyle.position)) return;
+
+ const stickyTop = parseInt(computedStyle.top, 10);
+
+ document.addEventListener('scroll', () => isSticky(el, window.scrollY, stickyTop), {
+ passive: true,
+ });
+};
diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js
index cd45091c211..37f531c78f4 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -1,5 +1,4 @@
/* eslint-disable func-names, space-before-function-paren, no-var, quotes, consistent-return, prefer-arrow-callback, comma-dangle, object-shorthand, no-new, max-len, no-multi-spaces, import/newline-after-import, import/first */
-/* global bp */
/* global Flash */
/* global ConfirmDangerModal */
/* global Aside */
@@ -7,7 +6,6 @@
import jQuery from 'jquery';
import _ from 'underscore';
import Cookies from 'js-cookie';
-import Pikaday from 'pikaday';
import Dropzone from 'dropzone';
import Sortable from 'vendor/Sortable';
@@ -16,14 +14,10 @@ import 'mousetrap';
import 'mousetrap/plugins/pause/mousetrap-pause';
import 'vendor/fuzzaldrin-plus';
-// extensions
-import './extensions/array';
-
// expose common libraries as globals (TODO: remove these)
window.jQuery = jQuery;
window.$ = jQuery;
window._ = _;
-window.Pikaday = Pikaday;
window.Dropzone = Dropzone;
window.Sortable = Sortable;
@@ -36,9 +30,6 @@ import './shortcuts_find_file';
import './shortcuts_issuable';
import './shortcuts_network';
-// behaviors
-import './behaviors/';
-
// templates
import './templates/issuable_template_selector';
import './templates/issuable_template_selectors';
@@ -56,6 +47,9 @@ import './lib/utils/pretty_time';
import './lib/utils/text_utility';
import './lib/utils/url_utility';
+// behaviors
+import './behaviors/';
+
// u2f
import './u2f/authenticate';
import './u2f/error';
@@ -71,7 +65,7 @@ import './api';
import './aside';
import './autosave';
import loadAwardsHandler from './awards_handler';
-import './breakpoints';
+import bp from './breakpoints';
import './broadcast_message';
import './build';
import './build_artifacts';
@@ -86,7 +80,6 @@ import './copy_as_gfm';
import './copy_to_clipboard';
import './create_label';
import './diff';
-import './dispatcher';
import './dropzone_input';
import './due_date_select';
import './files_comment_button';
@@ -150,9 +143,13 @@ import './subscription';
import './subscription_select';
import './syntax_highlight';
+import './dispatcher';
+
// eslint-disable-next-line global-require, import/no-commonjs
if (process.env.NODE_ENV !== 'production') require('./test_utils/');
+Dropzone.autoDiscover = false;
+
document.addEventListener('beforeunload', function () {
// Unbind scroll events
$(document).off('scroll');
diff --git a/app/assets/javascripts/member_expiration_date.js b/app/assets/javascripts/member_expiration_date.js
index e034729bd39..cc9016e74da 100644
--- a/app/assets/javascripts/member_expiration_date.js
+++ b/app/assets/javascripts/member_expiration_date.js
@@ -1,5 +1,7 @@
-/* global Pikaday */
/* global dateFormat */
+
+import Pikaday from 'pikaday';
+
(() => {
// Add datepickers to all `js-access-expiration-date` elements. If those elements are
// children of an element with the `clearable-input` class, and have a sibling
diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js
index 7840f05a8ae..5a9b3d19f84 100644
--- a/app/assets/javascripts/merge_request_tabs.js
+++ b/app/assets/javascripts/merge_request_tabs.js
@@ -1,12 +1,12 @@
/* eslint-disable no-new, class-methods-use-this */
-/* global Breakpoints */
/* global Flash */
/* global notes */
import Cookies from 'js-cookie';
-import './breakpoints';
import './flash';
import BlobForkSuggestion from './blob/blob_fork_suggestion';
+import initChangesDropdown from './init_changes_dropdown';
+import bp from './breakpoints';
/* eslint-disable max-len */
// MergeRequestTabs
@@ -133,7 +133,7 @@ import BlobForkSuggestion from './blob/blob_fork_suggestion';
this.destroyPipelinesView();
} else if (this.isDiffAction(action)) {
this.loadDiff($target.attr('href'));
- if (Breakpoints.get().getBreakpointSize() !== 'lg') {
+ if (bp.getBreakpointSize() !== 'lg') {
this.shrinkView();
}
if (this.diffViewType() === 'parallel') {
@@ -144,7 +144,7 @@ import BlobForkSuggestion from './blob/blob_fork_suggestion';
this.resetViewContainer();
this.mountPipelinesView();
} else {
- if (Breakpoints.get().getBreakpointSize() !== 'xs') {
+ if (bp.getBreakpointSize() !== 'xs') {
this.expandView();
}
this.resetViewContainer();
@@ -266,6 +266,8 @@ import BlobForkSuggestion from './blob/blob_fork_suggestion';
const $container = $('#diffs');
$container.html(data.html);
+ initChangesDropdown();
+
if (typeof gl.diffNotesCompileComponents !== 'undefined') {
gl.diffNotesCompileComponents();
}
@@ -389,7 +391,7 @@ import BlobForkSuggestion from './blob/blob_fork_suggestion';
// Screen space on small screens is usually very sparse
// So we dont affix the tabs on these
- if (Breakpoints.get().getBreakpointSize() === 'xs' || !$tabs.length) return;
+ if (bp.getBreakpointSize() === 'xs' || !$tabs.length) return;
/**
If the browser does not support position sticky, it returns the position as static.
diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js
index 6756ab0b3aa..04579058688 100644
--- a/app/assets/javascripts/milestone_select.js
+++ b/app/assets/javascripts/milestone_select.js
@@ -1,6 +1,7 @@
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-underscore-dangle, prefer-arrow-callback, max-len, one-var, one-var-declaration-per-line, no-unused-vars, object-shorthand, comma-dangle, no-else-return, no-self-compare, consistent-return, no-param-reassign, no-shadow */
/* global Issuable */
/* global ListMilestone */
+import _ from 'underscore';
(function() {
this.MilestoneSelect = (function() {
diff --git a/app/assets/javascripts/monitoring/components/monitoring_column.vue b/app/assets/javascripts/monitoring/components/monitoring_column.vue
index c376baea79c..407af51cb7a 100644
--- a/app/assets/javascripts/monitoring/components/monitoring_column.vue
+++ b/app/assets/javascripts/monitoring/components/monitoring_column.vue
@@ -1,5 +1,4 @@
<script>
- /* global Breakpoints */
import d3 from 'd3';
import monitoringLegends from './monitoring_legends.vue';
import monitoringFlag from './monitoring_flag.vue';
@@ -8,6 +7,7 @@
import eventHub from '../event_hub';
import measurements from '../utils/measurements';
import { formatRelevantDigits } from '../../lib/utils/number_utils';
+ import bp from '../../breakpoints';
const bisectDate = d3.bisector(d => d.time).left;
@@ -42,7 +42,6 @@
yScale: {},
margin: {},
data: [],
- breakpointHandler: Breakpoints.get(),
unitOfDisplay: '',
areaColorRgb: '#8fbce8',
lineColorRgb: '#1f78d1',
@@ -96,7 +95,7 @@
methods: {
draw() {
- const breakpointSize = this.breakpointHandler.getBreakpointSize();
+ const breakpointSize = bp.getBreakpointSize();
const query = this.columnData.queries[0];
this.margin = measurements.large.margin;
if (breakpointSize === 'xs' || breakpointSize === 'sm') {
diff --git a/app/assets/javascripts/new_sidebar.js b/app/assets/javascripts/new_sidebar.js
index 5f98aff8ced..b10b074f5ac 100644
--- a/app/assets/javascripts/new_sidebar.js
+++ b/app/assets/javascripts/new_sidebar.js
@@ -1,23 +1,64 @@
+import Cookies from 'js-cookie';
+import _ from 'underscore';
+import bp from './breakpoints';
+
export default class NewNavSidebar {
constructor() {
this.initDomElements();
+ this.render();
}
initDomElements() {
+ this.$page = $('.page-with-sidebar');
this.$sidebar = $('.nav-sidebar');
this.$overlay = $('.mobile-overlay');
this.$openSidebar = $('.toggle-mobile-nav');
this.$closeSidebar = $('.close-nav-button');
+ this.$sidebarToggle = $('.js-toggle-sidebar');
}
bindEvents() {
this.$openSidebar.on('click', () => this.toggleSidebarNav(true));
this.$closeSidebar.on('click', () => this.toggleSidebarNav(false));
this.$overlay.on('click', () => this.toggleSidebarNav(false));
+ this.$sidebarToggle.on('click', () => {
+ const value = !this.$sidebar.hasClass('sidebar-icons-only');
+ this.toggleCollapsedSidebar(value);
+ });
+
+ $(window).on('resize', () => _.debounce(this.render(), 100));
+ }
+
+ static setCollapsedCookie(value) {
+ if (bp.getBreakpointSize() !== 'lg') {
+ return;
+ }
+ Cookies.set('sidebar_collapsed', value, { expires: 365 * 10 });
}
toggleSidebarNav(show) {
this.$sidebar.toggleClass('nav-sidebar-expanded', show);
this.$overlay.toggleClass('mobile-nav-open', show);
+ this.$sidebar.removeClass('sidebar-icons-only');
+ }
+
+ toggleCollapsedSidebar(collapsed) {
+ this.$sidebar.toggleClass('sidebar-icons-only', collapsed);
+ if (this.$sidebar.length) {
+ this.$page.toggleClass('page-with-new-sidebar', !collapsed);
+ this.$page.toggleClass('page-with-icon-sidebar', collapsed);
+ }
+ NewNavSidebar.setCollapsedCookie(collapsed);
+ }
+
+ render() {
+ const breakpoint = bp.getBreakpointSize();
+
+ if (breakpoint === 'sm' || breakpoint === 'md') {
+ this.toggleCollapsedSidebar(true);
+ } else if (breakpoint === 'lg') {
+ const collapse = Cookies.get('sidebar_collapsed') === 'true';
+ this.toggleCollapsedSidebar(collapse);
+ }
}
}
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index dfa07a2def4..b38a6abc8d1 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -11,6 +11,7 @@ newline-per-chained-call, no-useless-escape, class-methods-use-this */
/* global mrRefreshWidgetUrl */
import $ from 'jquery';
+import _ from 'underscore';
import Cookies from 'js-cookie';
import autosize from 'vendor/autosize';
import Dropzone from 'dropzone';
diff --git a/app/assets/javascripts/pdf/index.vue b/app/assets/javascripts/pdf/index.vue
index 4603859d7b0..b874e484d45 100644
--- a/app/assets/javascripts/pdf/index.vue
+++ b/app/assets/javascripts/pdf/index.vue
@@ -9,8 +9,8 @@
</template>
<script>
- import pdfjsLib from 'pdfjs-dist';
- import workerSrc from 'vendor/pdf.worker';
+ import pdfjsLib from 'vendor/pdf';
+ import workerSrc from 'vendor/pdf.worker.min';
import page from './page/index.vue';
diff --git a/app/assets/javascripts/pipeline_schedules/components/interval_pattern_input.vue b/app/assets/javascripts/pipeline_schedules/components/interval_pattern_input.vue
index ce46b3fa3fa..b5d85299cf8 100644
--- a/app/assets/javascripts/pipeline_schedules/components/interval_pattern_input.vue
+++ b/app/assets/javascripts/pipeline_schedules/components/interval_pattern_input.vue
@@ -1,4 +1,6 @@
<script>
+ import _ from 'underscore';
+
export default {
props: {
initialCronInterval: {
diff --git a/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue b/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue
index 2944689a5a7..7695b04db74 100644
--- a/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue
@@ -48,6 +48,27 @@
return `${this.job.name} - ${this.job.status.label}`;
},
},
+
+ methods: {
+ /**
+ * When the user right clicks or cmd/ctrl + click in the job name
+ * the dropdown should not be closed and the link should open in another tab,
+ * so we stop propagation of the click event inside the dropdown.
+ *
+ * Since this component is rendered multiple times per page we need to guarantee we only
+ * target the click event of this component.
+ */
+ stopDropdownClickPropagation() {
+ $(this.$el.querySelectorAll('.js-grouped-pipeline-dropdown a.mini-pipeline-graph-dropdown-item'))
+ .on('click', (e) => {
+ e.stopPropagation();
+ });
+ },
+ },
+
+ mounted() {
+ this.stopDropdownClickPropagation();
+ },
};
</script>
<template>
diff --git a/app/assets/javascripts/pipelines/components/graph/graph_component.vue b/app/assets/javascripts/pipelines/components/graph/graph_component.vue
index 77cbaeb43ef..66bc1d1979c 100644
--- a/app/assets/javascripts/pipelines/components/graph/graph_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/graph_component.vue
@@ -1,7 +1,7 @@
<script>
+ import loadingIcon from '~/vue_shared/components/loading_icon.vue';
+ import '~/flash';
import stageColumnComponent from './stage_column_component.vue';
- import loadingIcon from '../../../vue_shared/components/loading_icon.vue';
- import '../../../flash';
export default {
props: {
diff --git a/app/assets/javascripts/profile/gl_crop.js b/app/assets/javascripts/profile/gl_crop.js
index f32b2413725..291ae24aa68 100644
--- a/app/assets/javascripts/profile/gl_crop.js
+++ b/app/assets/javascripts/profile/gl_crop.js
@@ -1,6 +1,7 @@
/* eslint-disable no-useless-escape, max-len, quotes, no-var, no-underscore-dangle, func-names, space-before-function-paren, no-unused-vars, no-return-assign, object-shorthand, one-var, one-var-declaration-per-line, comma-dangle, consistent-return, class-methods-use-this, new-parens */
import 'cropper';
+import _ from 'underscore';
((global) => {
// Matches everything but the file name
diff --git a/app/assets/javascripts/project.js b/app/assets/javascripts/project.js
index a3f7d69b98d..1c2100a1c25 100644
--- a/app/assets/javascripts/project.js
+++ b/app/assets/javascripts/project.js
@@ -10,14 +10,19 @@ import Cookies from 'js-cookie';
const $projectCloneField = $('#project_clone');
const $cloneBtnText = $('a.clone-dropdown-btn span');
+ const selectedCloneOption = $cloneBtnText.text().trim();
+ if (selectedCloneOption.length > 0) {
+ $(`a:contains('${selectedCloneOption}')`, $cloneOptions).addClass('is-active');
+ }
+
$('a', $cloneOptions).on('click', (e) => {
const $this = $(e.currentTarget);
const url = $this.attr('href');
e.preventDefault();
- $('.active', $cloneOptions).not($this).removeClass('active');
- $this.toggleClass('active');
+ $('.is-active', $cloneOptions).not($this).removeClass('is-active');
+ $this.toggleClass('is-active');
$projectCloneField.val(url);
$cloneBtnText.text($this.text());
@@ -85,6 +90,7 @@ import Cookies from 'js-cookie';
filterable: true,
filterRemote: true,
filterByText: true,
+ inputFieldName: $dropdown.data('input-field-name'),
fieldName: $dropdown.data('field-name'),
renderRow: function(ref) {
var li = refListItem.cloneNode(false);
@@ -118,9 +124,14 @@ import Cookies from 'js-cookie';
e.preventDefault();
if ($('input[name="ref"]').length) {
var $form = $dropdown.closest('form');
+
+ var $visit = $dropdown.data('visit');
+ var shouldVisit = typeof $visit === 'undefined' ? true : $visit;
var action = $form.attr('action');
var divider = action.indexOf('?') === -1 ? '?' : '&';
- gl.utils.visitUrl(action + '' + divider + '' + $form.serialize());
+ if (shouldVisit) {
+ gl.utils.visitUrl(action + '' + divider + '' + $form.serialize());
+ }
}
}
});
diff --git a/app/assets/javascripts/project_edit.js b/app/assets/javascripts/project_edit.js
index d7d284b6c86..7572fec15e0 100644
--- a/app/assets/javascripts/project_edit.js
+++ b/app/assets/javascripts/project_edit.js
@@ -1,6 +1,6 @@
export default function setupProjectEdit() {
const $transferForm = $('.js-project-transfer-form');
- const $selectNamespace = $transferForm.find('.select2');
+ const $selectNamespace = $transferForm.find('select.select2');
$selectNamespace.on('change', () => {
$transferForm.find(':submit').prop('disabled', !$selectNamespace.val());
diff --git a/app/assets/javascripts/project_select.js b/app/assets/javascripts/project_select.js
index ebcefc819f5..1b4ed6be90a 100644
--- a/app/assets/javascripts/project_select.js
+++ b/app/assets/javascripts/project_select.js
@@ -1,5 +1,6 @@
/* eslint-disable func-names, space-before-function-paren, wrap-iife, prefer-arrow-callback, no-var, comma-dangle, object-shorthand, one-var, one-var-declaration-per-line, no-else-return, quotes, max-len */
import Api from './api';
+import ProjectSelectComboButton from './project_select_combo_button';
(function() {
this.ProjectSelect = (function() {
@@ -58,7 +59,8 @@ import Api from './api';
if (this.includeGroups) {
placeholder += " or group";
}
- return $(select).select2({
+
+ $(select).select2({
placeholder: placeholder,
minimumInputLength: 0,
query: (function(_this) {
@@ -96,21 +98,18 @@ import Api from './api';
};
})(this),
id: function(project) {
- return project.web_url;
+ return JSON.stringify({
+ name: project.name,
+ url: project.web_url,
+ });
},
text: function(project) {
return project.name_with_namespace || project.name;
},
dropdownCssClass: "ajax-project-dropdown"
});
- });
-
- $('.new-project-item-select-button').on('click', function() {
- $('.project-item-select', this.parentNode).select2('open');
- });
- $('.project-item-select').on('click', function() {
- window.location = `${$(this).val()}/${this.dataset.relativePath}`;
+ return new ProjectSelectComboButton(select);
});
}
diff --git a/app/assets/javascripts/project_select_combo_button.js b/app/assets/javascripts/project_select_combo_button.js
new file mode 100644
index 00000000000..f799d9d619a
--- /dev/null
+++ b/app/assets/javascripts/project_select_combo_button.js
@@ -0,0 +1,85 @@
+import AccessorUtilities from './lib/utils/accessor';
+
+export default class ProjectSelectComboButton {
+ constructor(select) {
+ this.projectSelectInput = $(select);
+ this.newItemBtn = $('.new-project-item-link');
+ this.newItemBtnBaseText = this.newItemBtn.data('label');
+ this.itemType = this.deriveItemTypeFromLabel();
+ this.groupId = this.projectSelectInput.data('groupId');
+
+ this.bindEvents();
+ this.initLocalStorage();
+ }
+
+ bindEvents() {
+ this.projectSelectInput.siblings('.new-project-item-select-button')
+ .on('click', this.openDropdown);
+
+ this.projectSelectInput.on('change', () => this.selectProject());
+ }
+
+ initLocalStorage() {
+ const localStorageIsSafe = AccessorUtilities.isLocalStorageAccessSafe();
+
+ if (localStorageIsSafe) {
+ const itemTypeKebabed = this.newItemBtnBaseText.toLowerCase().split(' ').join('-');
+
+ this.localStorageKey = ['group', this.groupId, itemTypeKebabed, 'recent-project'].join('-');
+ this.setBtnTextFromLocalStorage();
+ }
+ }
+
+ openDropdown() {
+ $(this).siblings('.project-item-select').select2('open');
+ }
+
+ selectProject() {
+ const selectedProjectData = JSON.parse(this.projectSelectInput.val());
+ const projectUrl = `${selectedProjectData.url}/${this.projectSelectInput.data('relativePath')}`;
+ const projectName = selectedProjectData.name;
+
+ const projectMeta = {
+ url: projectUrl,
+ name: projectName,
+ };
+
+ this.setNewItemBtnAttributes(projectMeta);
+ this.setProjectInLocalStorage(projectMeta);
+ }
+
+ setBtnTextFromLocalStorage() {
+ const cachedProjectData = this.getProjectFromLocalStorage();
+
+ this.setNewItemBtnAttributes(cachedProjectData);
+ }
+
+ setNewItemBtnAttributes(project) {
+ if (project) {
+ this.newItemBtn.attr('href', project.url);
+ this.newItemBtn.text(`${this.newItemBtnBaseText} in ${project.name}`);
+ this.newItemBtn.enable();
+ } else {
+ this.newItemBtn.text(`Select project to create ${this.itemType}`);
+ this.newItemBtn.disable();
+ }
+ }
+
+ deriveItemTypeFromLabel() {
+ // label is either 'New issue' or 'New merge request'
+ return this.newItemBtnBaseText.split(' ').slice(1).join(' ');
+ }
+
+ getProjectFromLocalStorage() {
+ const projectString = localStorage.getItem(this.localStorageKey);
+
+ return JSON.parse(projectString);
+ }
+
+ setProjectInLocalStorage(projectMeta) {
+ const projectString = JSON.stringify(projectMeta);
+
+ localStorage.setItem(this.localStorageKey, projectString);
+ }
+}
+
diff --git a/app/assets/javascripts/projects/project_import_gitlab_project.js b/app/assets/javascripts/projects/project_import_gitlab_project.js
new file mode 100644
index 00000000000..c34927499fc
--- /dev/null
+++ b/app/assets/javascripts/projects/project_import_gitlab_project.js
@@ -0,0 +1,14 @@
+import '../lib/utils/url_utility';
+
+const bindEvents = () => {
+ const path = gl.utils.getParameterValues('path')[0];
+
+ // get the path url and append it in the inputS
+ $('.js-path-name').val(path);
+};
+
+document.addEventListener('DOMContentLoaded', bindEvents);
+
+export default {
+ bindEvents,
+};
diff --git a/app/assets/javascripts/projects/project_new.js b/app/assets/javascripts/projects/project_new.js
index 1dc1dbf356d..7f972b6f6ee 100644
--- a/app/assets/javascripts/projects/project_new.js
+++ b/app/assets/javascripts/projects/project_new.js
@@ -1,7 +1,7 @@
let hasUserDefinedProjectPath = false;
const deriveProjectPathFromUrl = ($projectImportUrl, $projectPath) => {
- if ($projectImportUrl.attr('disabled') || hasUserDefinedProjectPath) {
+ if (hasUserDefinedProjectPath) {
return;
}
@@ -27,8 +27,6 @@ const deriveProjectPathFromUrl = ($projectImportUrl, $projectPath) => {
const bindEvents = () => {
const $newProjectForm = $('#new_project');
- const importBtnTooltip = 'Please enter a valid project name.';
- const $importBtnWrapper = $('.import_gitlab_project');
const $projectImportUrl = $('#project_import_url');
const $projectPath = $('#project_path');
@@ -38,7 +36,7 @@ const bindEvents = () => {
$('.how_to_import_link').on('click', (e) => {
e.preventDefault();
- $('.how_to_import_link').next('.modal').show();
+ $(e.currentTarget).next('.modal').show();
});
$('.modal-header .close').on('click', () => {
@@ -50,31 +48,15 @@ const bindEvents = () => {
$('.btn_import_gitlab_project').attr('href', `${importHref}?namespace_id=${$('#project_namespace_id').val()}&path=${$projectPath.val()}`);
});
- $('.btn_import_gitlab_project').attr('disabled', !$projectPath.val().trim().length);
- $importBtnWrapper.attr('title', importBtnTooltip);
-
$newProjectForm.on('submit', () => {
$projectPath.val($projectPath.val().trim());
});
$projectPath.on('keyup', () => {
hasUserDefinedProjectPath = $projectPath.val().trim().length > 0;
- if (hasUserDefinedProjectPath) {
- $('.btn_import_gitlab_project').attr('disabled', false);
- $importBtnWrapper.attr('title', '');
- $importBtnWrapper.removeClass('has-tooltip');
- } else {
- $('.btn_import_gitlab_project').attr('disabled', true);
- $importBtnWrapper.addClass('has-tooltip');
- }
});
- $projectImportUrl.disable();
$projectImportUrl.keyup(() => deriveProjectPathFromUrl($projectImportUrl, $projectPath));
-
- $('.import_git').on('click', () => {
- $projectImportUrl.attr('disabled', !$projectImportUrl.attr('disabled'));
- });
};
document.addEventListener('DOMContentLoaded', bindEvents);
diff --git a/app/assets/javascripts/protected_branches/protected_branch_dropdown.js b/app/assets/javascripts/protected_branches/protected_branch_dropdown.js
index cc0b2ebe071..678882a8d2c 100644
--- a/app/assets/javascripts/protected_branches/protected_branch_dropdown.js
+++ b/app/assets/javascripts/protected_branches/protected_branch_dropdown.js
@@ -1,3 +1,5 @@
+import _ from 'underscore';
+
export default class ProtectedBranchDropdown {
/**
* @param {Object} options containing
diff --git a/app/assets/javascripts/protected_tags/protected_tag_dropdown.js b/app/assets/javascripts/protected_tags/protected_tag_dropdown.js
index 9d045886262..a0224213aa0 100644
--- a/app/assets/javascripts/protected_tags/protected_tag_dropdown.js
+++ b/app/assets/javascripts/protected_tags/protected_tag_dropdown.js
@@ -1,3 +1,5 @@
+import _ from 'underscore';
+
export default class ProtectedTagDropdown {
/**
* @param {Object} options containing
diff --git a/app/assets/javascripts/repo/components/repo.vue b/app/assets/javascripts/repo/components/repo.vue
new file mode 100644
index 00000000000..703da749ad3
--- /dev/null
+++ b/app/assets/javascripts/repo/components/repo.vue
@@ -0,0 +1,63 @@
+<script>
+import RepoSidebar from './repo_sidebar.vue';
+import RepoCommitSection from './repo_commit_section.vue';
+import RepoTabs from './repo_tabs.vue';
+import RepoFileButtons from './repo_file_buttons.vue';
+import RepoPreview from './repo_preview.vue';
+import RepoMixin from '../mixins/repo_mixin';
+import PopupDialog from '../../vue_shared/components/popup_dialog.vue';
+import Store from '../stores/repo_store';
+import Helper from '../helpers/repo_helper';
+import MonacoLoaderHelper from '../helpers/monaco_loader_helper';
+
+export default {
+ data: () => Store,
+ mixins: [RepoMixin],
+ components: {
+ 'repo-sidebar': RepoSidebar,
+ 'repo-tabs': RepoTabs,
+ 'repo-file-buttons': RepoFileButtons,
+ 'repo-editor': MonacoLoaderHelper.repoEditorLoader,
+ 'repo-commit-section': RepoCommitSection,
+ 'popup-dialog': PopupDialog,
+ 'repo-preview': RepoPreview,
+ },
+
+ mounted() {
+ Helper.getContent().catch(Helper.loadingError);
+ },
+
+ methods: {
+ dialogToggled(toggle) {
+ this.dialog.open = toggle;
+ },
+
+ dialogSubmitted(status) {
+ this.dialog.open = false;
+ this.dialog.status = status;
+ },
+
+ toggleBlobView: Store.toggleBlobView,
+ },
+};
+</script>
+
+<template>
+<div class="repository-view tree-content-holder">
+ <repo-sidebar/><div class="panel-right" :class="{'edit-mode': editMode}">
+ <repo-tabs/>
+ <component :is="currentBlobView" class="blob-viewer-container"></component>
+ <repo-file-buttons/>
+ </div>
+ <repo-commit-section/>
+ <popup-dialog
+ :primary-button-label="__('Discard changes')"
+ :open="dialog.open"
+ kind="warning"
+ :title="__('Are you sure?')"
+ :body="__('Are you sure you want to discard your changes?')"
+ @toggle="dialogToggled"
+ @submit="dialogSubmitted"
+ />
+</div>
+</template>
diff --git a/app/assets/javascripts/repo/components/repo_commit_section.vue b/app/assets/javascripts/repo/components/repo_commit_section.vue
new file mode 100644
index 00000000000..bd83f80c928
--- /dev/null
+++ b/app/assets/javascripts/repo/components/repo_commit_section.vue
@@ -0,0 +1,100 @@
+<script>
+/* global Flash */
+import Store from '../stores/repo_store';
+import RepoMixin from '../mixins/repo_mixin';
+import Helper from '../helpers/repo_helper';
+import Service from '../services/repo_service';
+
+const RepoCommitSection = {
+ data: () => Store,
+
+ mixins: [RepoMixin],
+
+ computed: {
+ branchPaths() {
+ const branch = Helper.getBranch();
+ return this.changedFiles.map(f => Helper.getFilePathFromFullPath(f.url, branch));
+ },
+
+ cantCommitYet() {
+ return !this.commitMessage || this.submitCommitsLoading;
+ },
+
+ filePluralize() {
+ return this.changedFiles.length > 1 ? 'files' : 'file';
+ },
+ },
+
+ methods: {
+ makeCommit() {
+ // see https://docs.gitlab.com/ce/api/commits.html#create-a-commit-with-multiple-files-and-actions
+ const branch = Helper.getBranch();
+ const commitMessage = this.commitMessage;
+ const actions = this.changedFiles.map(f => ({
+ action: 'update',
+ file_path: Helper.getFilePathFromFullPath(f.url, branch),
+ content: f.newContent,
+ }));
+ const payload = {
+ branch: Store.targetBranch,
+ commit_message: commitMessage,
+ actions,
+ };
+ Store.submitCommitsLoading = true;
+ Service.commitFiles(payload, this.resetCommitState);
+ },
+
+ resetCommitState() {
+ this.submitCommitsLoading = false;
+ this.changedFiles = [];
+ this.openedFiles = [];
+ this.commitMessage = '';
+ this.editMode = false;
+ $('html, body').animate({ scrollTop: 0 }, 'fast');
+ },
+ },
+};
+
+export default RepoCommitSection;
+</script>
+
+<template>
+<div id="commit-area" v-if="isCommitable && changedFiles.length" >
+ <form class="form-horizontal">
+ <fieldset>
+ <div class="form-group">
+ <label class="col-md-4 control-label staged-files">Staged files ({{changedFiles.length}})</label>
+ <div class="col-md-4">
+ <ul class="list-unstyled changed-files">
+ <li v-for="file in branchPaths" :key="file.id">
+ <span class="help-block">{{file}}</span>
+ </li>
+ </ul>
+ </div>
+ </div>
+ <!-- Textarea
+ -->
+ <div class="form-group">
+ <label class="col-md-4 control-label" for="commit-message">Commit message</label>
+ <div class="col-md-4">
+ <textarea class="form-control" id="commit-message" name="commit-message" v-model="commitMessage"></textarea>
+ </div>
+ </div>
+ <!-- Button Drop Down
+ -->
+ <div class="form-group target-branch">
+ <label class="col-md-4 control-label" for="target-branch">Target branch</label>
+ <div class="col-md-4">
+ <span class="help-block">{{targetBranch}}</span>
+ </div>
+ </div>
+ <div class="col-md-offset-4 col-md-4">
+ <button type="submit" :disabled="cantCommitYet" class="btn btn-success submit-commit" @click.prevent="makeCommit">
+ <i class="fa fa-spinner fa-spin" v-if="submitCommitsLoading"></i>
+ <span class="commit-summary">Commit {{changedFiles.length}} {{filePluralize}}</span>
+ </button>
+ </div>
+ </fieldset>
+ </form>
+</div>
+</template>
diff --git a/app/assets/javascripts/repo/components/repo_edit_button.vue b/app/assets/javascripts/repo/components/repo_edit_button.vue
new file mode 100644
index 00000000000..f47b6c33fa2
--- /dev/null
+++ b/app/assets/javascripts/repo/components/repo_edit_button.vue
@@ -0,0 +1,47 @@
+<script>
+import Store from '../stores/repo_store';
+import RepoMixin from '../mixins/repo_mixin';
+
+export default {
+ data: () => Store,
+ mixins: [RepoMixin],
+ computed: {
+ buttonLabel() {
+ return this.editMode ? this.__('Cancel edit') : this.__('Edit');
+ },
+
+ buttonIcon() {
+ return this.editMode ? [] : ['fa', 'fa-pencil'];
+ },
+ },
+ methods: {
+ editClicked() {
+ if (this.changedFiles.length) {
+ this.dialog.open = true;
+ return;
+ }
+ this.editMode = !this.editMode;
+ Store.toggleBlobView();
+ },
+ },
+
+ watch: {
+ editMode() {
+ if (this.editMode) {
+ $('.project-refs-form').addClass('disabled');
+ $('.js-tree-ref-target-holder').show();
+ } else {
+ $('.project-refs-form').removeClass('disabled');
+ $('.js-tree-ref-target-holder').hide();
+ }
+ },
+ },
+};
+</script>
+
+<template>
+<button class="btn btn-default" @click.prevent="editClicked" v-cloak v-if="isCommitable && !activeFile.render_error" :disabled="binary">
+ <i :class="buttonIcon"></i>
+ <span>{{buttonLabel}}</span>
+</button>
+</template>
diff --git a/app/assets/javascripts/repo/components/repo_editor.vue b/app/assets/javascripts/repo/components/repo_editor.vue
new file mode 100644
index 00000000000..fd1a21e15b4
--- /dev/null
+++ b/app/assets/javascripts/repo/components/repo_editor.vue
@@ -0,0 +1,135 @@
+<script>
+/* global monaco */
+import Store from '../stores/repo_store';
+import Service from '../services/repo_service';
+import Helper from '../helpers/repo_helper';
+
+const RepoEditor = {
+ data: () => Store,
+
+ destroyed() {
+ // this.monacoInstance.getModels().forEach((m) => {
+ // m.dispose();
+ // });
+ this.monacoInstance.destroy();
+ },
+
+ mounted() {
+ Service.getRaw(this.activeFile.raw_path)
+ .then((rawResponse) => {
+ Store.blobRaw = rawResponse.data;
+ Helper.findOpenedFileFromActive().plain = rawResponse.data;
+
+ const monacoInstance = this.monaco.editor.create(this.$el, {
+ model: null,
+ readOnly: false,
+ contextmenu: false,
+ });
+
+ Store.monacoInstance = monacoInstance;
+
+ this.addMonacoEvents();
+
+ const languages = this.monaco.languages.getLanguages();
+ const languageID = Helper.getLanguageIDForFile(this.activeFile, languages);
+ this.showHide();
+ const newModel = this.monaco.editor.createModel(this.blobRaw, languageID);
+
+ this.monacoInstance.setModel(newModel);
+ }).catch(Helper.loadingError);
+ },
+
+ methods: {
+ showHide() {
+ if (!this.openedFiles.length || (this.binary && !this.activeFile.raw)) {
+ this.$el.style.display = 'none';
+ } else {
+ this.$el.style.display = 'inline-block';
+ }
+ },
+
+ addMonacoEvents() {
+ this.monacoInstance.onMouseUp(this.onMonacoEditorMouseUp);
+ this.monacoInstance.onKeyUp(this.onMonacoEditorKeysPressed.bind(this));
+ },
+
+ onMonacoEditorKeysPressed() {
+ Store.setActiveFileContents(this.monacoInstance.getValue());
+ },
+
+ onMonacoEditorMouseUp(e) {
+ const lineNumber = e.target.position.lineNumber;
+ if (e.target.element.className === 'line-numbers') {
+ location.hash = `L${lineNumber}`;
+ Store.activeLine = lineNumber;
+ }
+ },
+ },
+
+ watch: {
+ activeLine() {
+ this.monacoInstance.setPosition({
+ lineNumber: this.activeLine,
+ column: 1,
+ });
+ },
+
+ activeFileLabel() {
+ this.showHide();
+ },
+
+ dialog: {
+ handler(obj) {
+ const newObj = obj;
+ if (newObj.status) {
+ newObj.status = false;
+ this.openedFiles.map((file) => {
+ const f = file;
+ if (f.active) {
+ this.blobRaw = f.plain;
+ }
+ f.changed = false;
+ delete f.newContent;
+
+ return f;
+ });
+ this.editMode = false;
+ }
+ },
+ deep: true,
+ },
+
+ isTree() {
+ this.showHide();
+ },
+
+ openedFiles() {
+ this.showHide();
+ },
+
+ binary() {
+ this.showHide();
+ },
+
+ blobRaw() {
+ this.showHide();
+
+ if (this.isTree) return;
+
+ this.monacoInstance.setModel(null);
+
+ const languages = this.monaco.languages.getLanguages();
+ const languageID = Helper.getLanguageIDForFile(this.activeFile, languages);
+ const newModel = this.monaco.editor.createModel(this.blobRaw, languageID);
+
+ this.monacoInstance.setModel(newModel);
+ },
+ },
+};
+
+export default RepoEditor;
+</script>
+
+<template>
+<div id="ide"></div>
+</template>
diff --git a/app/assets/javascripts/repo/components/repo_file.vue b/app/assets/javascripts/repo/components/repo_file.vue
new file mode 100644
index 00000000000..f604bc22a26
--- /dev/null
+++ b/app/assets/javascripts/repo/components/repo_file.vue
@@ -0,0 +1,66 @@
+<script>
+import TimeAgoMixin from '../../vue_shared/mixins/timeago';
+
+const RepoFile = {
+ mixins: [TimeAgoMixin],
+ props: {
+ file: {
+ type: Object,
+ required: true,
+ },
+ isMini: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ loading: {
+ type: Object,
+ required: false,
+ default() { return { tree: false }; },
+ },
+ hasFiles: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ activeFile: {
+ type: Object,
+ required: true,
+ },
+ },
+
+ computed: {
+ canShowFile() {
+ return !this.loading.tree || this.hasFiles;
+ },
+ },
+
+ methods: {
+ linkClicked(file) {
+ this.$emit('linkclicked', file);
+ },
+ },
+};
+
+export default RepoFile;
+</script>
+
+<template>
+<tr class="file" v-if="canShowFile" :class="{'active': activeFile.url === file.url}">
+ <td @click.prevent="linkClicked(file)">
+ <i class="fa file-icon" v-if="!file.loading" :class="file.icon" :style="{'margin-left': file.level * 10 + 'px'}"></i>
+ <i class="fa fa-spinner fa-spin" v-if="file.loading" :style="{'margin-left': file.level * 10 + 'px'}"></i>
+ <a :href="file.url" class="repo-file-name" :title="file.url">{{file.name}}</a>
+ </td>
+
+ <td v-if="!isMini" class="hidden-sm hidden-xs">
+ <div class="commit-message">
+ <a :href="file.lastCommitUrl">{{file.lastCommitMessage}}</a>
+ </div>
+ </td>
+
+ <td v-if="!isMini" class="hidden-xs">
+ <span class="commit-update" :title="tooltipTitle(file.lastCommitUpdate)">{{timeFormated(file.lastCommitUpdate)}}</span>
+ </td>
+</tr>
+</template>
diff --git a/app/assets/javascripts/repo/components/repo_file_buttons.vue b/app/assets/javascripts/repo/components/repo_file_buttons.vue
new file mode 100644
index 00000000000..628d02ca704
--- /dev/null
+++ b/app/assets/javascripts/repo/components/repo_file_buttons.vue
@@ -0,0 +1,42 @@
+<script>
+import Store from '../stores/repo_store';
+import Helper from '../helpers/repo_helper';
+import RepoMixin from '../mixins/repo_mixin';
+
+const RepoFileButtons = {
+ data: () => Store,
+
+ mixins: [RepoMixin],
+
+ computed: {
+
+ rawDownloadButtonLabel() {
+ return this.binary ? 'Download' : 'Raw';
+ },
+
+ canPreview() {
+ return Helper.isKindaBinary();
+ },
+ },
+
+ methods: {
+ rawPreviewToggle: Store.toggleRawPreview,
+ },
+};
+
+export default RepoFileButtons;
+</script>
+
+<template>
+<div id="repo-file-buttons" v-if="isMini">
+ <a :href="activeFile.raw_path" target="_blank" class="btn btn-default raw" rel="noopener noreferrer">{{rawDownloadButtonLabel}}</a>
+
+ <div class="btn-group" role="group" aria-label="File actions">
+ <a :href="activeFile.blame_path" class="btn btn-default blame">Blame</a>
+ <a :href="activeFile.commits_path" class="btn btn-default history">History</a>
+ <a :href="activeFile.permalink" class="btn btn-default permalink">Permalink</a>
+ </div>
+
+ <a href="#" v-if="canPreview" @click.prevent="rawPreviewToggle" class="btn btn-default preview">{{activeFileLabel}}</a>
+</div>
+</template>
diff --git a/app/assets/javascripts/repo/components/repo_file_options.vue b/app/assets/javascripts/repo/components/repo_file_options.vue
new file mode 100644
index 00000000000..ba53ce0eecc
--- /dev/null
+++ b/app/assets/javascripts/repo/components/repo_file_options.vue
@@ -0,0 +1,25 @@
+<script>
+const RepoFileOptions = {
+ props: {
+ isMini: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ projectName: {
+ type: String,
+ required: true,
+ },
+ },
+};
+
+export default RepoFileOptions;
+</script>
+
+<template>
+<tr v-if="isMini" class="repo-file-options">
+ <td>
+ <span class="title">{{projectName}}</span>
+ </td>
+ </tr>
+</template>
diff --git a/app/assets/javascripts/repo/components/repo_loading_file.vue b/app/assets/javascripts/repo/components/repo_loading_file.vue
new file mode 100644
index 00000000000..38e9f16d041
--- /dev/null
+++ b/app/assets/javascripts/repo/components/repo_loading_file.vue
@@ -0,0 +1,51 @@
+<script>
+const RepoLoadingFile = {
+ props: {
+ loading: {
+ type: Object,
+ required: false,
+ default: {},
+ },
+ hasFiles: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ isMini: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
+
+ methods: {
+ lineOfCode(n) {
+ return `line-of-code-${n}`;
+ },
+ },
+};
+
+export default RepoLoadingFile;
+</script>
+
+<template>
+<tr v-if="loading.tree && !hasFiles" class="loading-file">
+ <td>
+ <div class="animation-container animation-container-small">
+ <div v-for="n in 6" :class="lineOfCode(n)" :key="n"></div>
+ </div>
+ </td>
+
+ <td v-if="!isMini" class="hidden-sm hidden-xs">
+ <div class="animation-container">
+ <div v-for="n in 6" :class="lineOfCode(n)" :key="n"></div>
+ </div>
+ </td>
+
+ <td v-if="!isMini" class="hidden-xs">
+ <div class="animation-container animation-container-small">
+ <div v-for="n in 6" :class="lineOfCode(n)" :key="n"></div>
+ </div>
+ </td>
+</tr>
+</template>
diff --git a/app/assets/javascripts/repo/components/repo_prev_directory.vue b/app/assets/javascripts/repo/components/repo_prev_directory.vue
new file mode 100644
index 00000000000..6a0d684052f
--- /dev/null
+++ b/app/assets/javascripts/repo/components/repo_prev_directory.vue
@@ -0,0 +1,26 @@
+<script>
+const RepoPreviousDirectory = {
+ props: {
+ prevUrl: {
+ type: String,
+ required: true,
+ },
+ },
+
+ methods: {
+ linkClicked(file) {
+ this.$emit('linkclicked', file);
+ },
+ },
+};
+
+export default RepoPreviousDirectory;
+</script>
+
+<template>
+<tr class="prev-directory">
+ <td colspan="3">
+ <a :href="prevUrl" @click.prevent="linkClicked(prevUrl)">..</a>
+ </td>
+</tr>
+</template>
diff --git a/app/assets/javascripts/repo/components/repo_preview.vue b/app/assets/javascripts/repo/components/repo_preview.vue
new file mode 100644
index 00000000000..2200754cbef
--- /dev/null
+++ b/app/assets/javascripts/repo/components/repo_preview.vue
@@ -0,0 +1,52 @@
+<script>
+import Store from '../stores/repo_store';
+
+export default {
+ data: () => Store,
+ mounted() {
+ this.highlightFile();
+ },
+ computed: {
+ html() {
+ return this.activeFile.html;
+ },
+ },
+
+ methods: {
+ highlightFile() {
+ $(this.$el).find('.file-content').syntaxHighlight();
+ },
+ },
+
+ watch: {
+ html() {
+ this.$nextTick(() => {
+ this.highlightFile();
+ });
+ },
+ },
+};
+</script>
+
+<template>
+<div>
+ <div
+ v-if="!activeFile.render_error"
+ v-html="activeFile.html">
+ </div>
+ <div
+ v-else-if="activeFile.tooLarge"
+ class="vertical-center render-error">
+ <p class="text-center">
+ The source could not be displayed because it is too large. You can <a :href="activeFile.raw_path">download</a> it instead.
+ </p>
+ </div>
+ <div
+ v-else
+ class="vertical-center render-error">
+ <p class="text-center">
+ The source could not be displayed because a rendering error occured. You can <a :href="activeFile.raw_path">download</a> it instead.
+ </p>
+ </div>
+</div>
+</template>
diff --git a/app/assets/javascripts/repo/components/repo_sidebar.vue b/app/assets/javascripts/repo/components/repo_sidebar.vue
new file mode 100644
index 00000000000..0d4f8c6635e
--- /dev/null
+++ b/app/assets/javascripts/repo/components/repo_sidebar.vue
@@ -0,0 +1,102 @@
+<script>
+import Service from '../services/repo_service';
+import Helper from '../helpers/repo_helper';
+import Store from '../stores/repo_store';
+import RepoPreviousDirectory from './repo_prev_directory.vue';
+import RepoFileOptions from './repo_file_options.vue';
+import RepoFile from './repo_file.vue';
+import RepoLoadingFile from './repo_loading_file.vue';
+import RepoMixin from '../mixins/repo_mixin';
+
+const RepoSidebar = {
+ mixins: [RepoMixin],
+ components: {
+ 'repo-file-options': RepoFileOptions,
+ 'repo-previous-directory': RepoPreviousDirectory,
+ 'repo-file': RepoFile,
+ 'repo-loading-file': RepoLoadingFile,
+ },
+
+ created() {
+ this.addPopEventListener();
+ },
+
+ data: () => Store,
+
+ methods: {
+ addPopEventListener() {
+ window.addEventListener('popstate', () => {
+ if (location.href.indexOf('#') > -1) return;
+ this.linkClicked({
+ url: location.href,
+ });
+ });
+ },
+
+ fileClicked(clickedFile) {
+ let file = clickedFile;
+
+ file.loading = true;
+ if (file.type === 'tree' && file.opened) {
+ file = Store.removeChildFilesOfTree(file);
+ file.loading = false;
+ } else {
+ Service.url = file.url;
+ Helper.getContent(file)
+ .then(() => {
+ file.loading = false;
+ Helper.scrollTabsRight();
+ })
+ .catch(Helper.loadingError);
+ }
+ },
+
+ goToPreviousDirectoryClicked(prevURL) {
+ Service.url = prevURL;
+ Helper.getContent(null)
+ .then(() => Helper.scrollTabsRight())
+ .catch(Helper.loadingError);
+ },
+ },
+};
+
+export default RepoSidebar;
+</script>
+
+<template>
+<div id="sidebar" :class="{'sidebar-mini' : isMini}" v-cloak>
+ <table class="table">
+ <thead v-if="!isMini">
+ <tr>
+ <th class="name">Name</th>
+ <th class="hidden-sm hidden-xs last-commit">Last Commit</th>
+ <th class="hidden-xs last-update">Last Update</th>
+ </tr>
+ </thead>
+ <tbody>
+ <repo-file-options
+ :is-mini="isMini"
+ :project-name="projectName"/>
+ <repo-previous-directory
+ v-if="isRoot"
+ :prev-url="prevURL"
+ @linkclicked="goToPreviousDirectoryClicked(prevURL)"/>
+ <repo-loading-file
+ v-for="n in 5"
+ :key="n"
+ :loading="loading"
+ :has-files="!!files.length"
+ :is-mini="isMini"/>
+ <repo-file
+ v-for="file in files"
+ :key="file.id"
+ :file="file"
+ :is-mini="isMini"
+ @linkclicked="fileClicked(file)"
+ :is-tree="isTree"
+ :has-files="!!files.length"
+ :active-file="activeFile"/>
+ </tbody>
+ </table>
+</div>
+</template>
diff --git a/app/assets/javascripts/repo/components/repo_tab.vue b/app/assets/javascripts/repo/components/repo_tab.vue
new file mode 100644
index 00000000000..fc66a8ea953
--- /dev/null
+++ b/app/assets/javascripts/repo/components/repo_tab.vue
@@ -0,0 +1,63 @@
+<script>
+import Store from '../stores/repo_store';
+
+const RepoTab = {
+ props: {
+ tab: {
+ type: Object,
+ required: true,
+ },
+ },
+
+ computed: {
+ closeLabel() {
+ if (this.tab.changed) {
+ return `${this.tab.name} changed`;
+ }
+ return `Close ${this.tab.name}`;
+ },
+ changedClass() {
+ const tabChangedObj = {
+ 'fa-times': !this.tab.changed,
+ 'fa-circle': this.tab.changed,
+ };
+ return tabChangedObj;
+ },
+ },
+
+ methods: {
+ tabClicked: Store.setActiveFiles,
+
+ xClicked(file) {
+ if (file.changed) return;
+ this.$emit('xclicked', file);
+ },
+ },
+};
+
+export default RepoTab;
+</script>
+
+<template>
+<li>
+ <a
+ href="#0"
+ class="close"
+ @click.prevent="xClicked(tab)"
+ :aria-label="closeLabel">
+ <i
+ class="fa"
+ :class="changedClass"
+ aria-hidden="true">
+ </i>
+ </a>
+
+ <a
+ href="#"
+ class="repo-tab"
+ :title="tab.url"
+ @click.prevent="tabClicked(tab)">
+ {{tab.name}}
+ </a>
+</li>
+</template>
diff --git a/app/assets/javascripts/repo/components/repo_tabs.vue b/app/assets/javascripts/repo/components/repo_tabs.vue
new file mode 100644
index 00000000000..bbd60d9d793
--- /dev/null
+++ b/app/assets/javascripts/repo/components/repo_tabs.vue
@@ -0,0 +1,32 @@
+<script>
+import Store from '../stores/repo_store';
+import RepoTab from './repo_tab.vue';
+import RepoMixin from '../mixins/repo_mixin';
+
+const RepoTabs = {
+ mixins: [RepoMixin],
+
+ components: {
+ 'repo-tab': RepoTab,
+ },
+
+ data: () => Store,
+
+ methods: {
+ xClicked(file) {
+ Store.removeFromOpenedFiles(file);
+ },
+ },
+};
+
+export default RepoTabs;
+</script>
+
+<template>
+<ul
+ v-if="isMini"
+ id="tabs">
+ <repo-tab v-for="tab in openedFiles" :key="tab.id" :tab="tab" :class="{'active' : tab.active}" @xclicked="xClicked"/>
+ <li class="tabs-divider" />
+</ul>
+</template>
diff --git a/app/assets/javascripts/repo/helpers/monaco_loader_helper.js b/app/assets/javascripts/repo/helpers/monaco_loader_helper.js
new file mode 100644
index 00000000000..c1a0e80f8f3
--- /dev/null
+++ b/app/assets/javascripts/repo/helpers/monaco_loader_helper.js
@@ -0,0 +1,24 @@
+/* global monaco */
+import RepoEditor from '../components/repo_editor.vue';
+import Store from '../stores/repo_store';
+import monacoLoader from '../monaco_loader';
+
+function repoEditorLoader() {
+ Store.monacoLoading = true;
+ return new Promise((resolve, reject) => {
+ monacoLoader(['vs/editor/editor.main'], () => {
+ Store.monaco = monaco;
+ Store.monacoLoading = false;
+ resolve(RepoEditor);
+ }, () => {
+ Store.monacoLoading = false;
+ reject();
+ });
+ });
+}
+
+const MonacoLoaderHelper = {
+ repoEditorLoader,
+};
+
+export default MonacoLoaderHelper;
diff --git a/app/assets/javascripts/repo/helpers/repo_helper.js b/app/assets/javascripts/repo/helpers/repo_helper.js
new file mode 100644
index 00000000000..17aaa0e1584
--- /dev/null
+++ b/app/assets/javascripts/repo/helpers/repo_helper.js
@@ -0,0 +1,300 @@
+/* global Flash */
+import Service from '../services/repo_service';
+import Store from '../stores/repo_store';
+import '../../flash';
+
+const RepoHelper = {
+ getDefaultActiveFile() {
+ return {
+ active: true,
+ binary: false,
+ extension: '',
+ html: '',
+ mime_type: '',
+ name: '',
+ plain: '',
+ size: 0,
+ url: '',
+ raw: false,
+ newContent: '',
+ changed: false,
+ loading: false,
+ };
+ },
+
+ key: '',
+
+ isTree(data) {
+ return Object.hasOwnProperty.call(data, 'blobs');
+ },
+
+ Time: window.performance
+ && window.performance.now
+ ? window.performance
+ : Date,
+
+ getFileExtension(fileName) {
+ return fileName.split('.').pop();
+ },
+
+ getBranch() {
+ return $('button.dropdown-menu-toggle').attr('data-ref');
+ },
+
+ getLanguageIDForFile(file, langs) {
+ const ext = RepoHelper.getFileExtension(file.name);
+ const foundLang = RepoHelper.findLanguage(ext, langs);
+
+ return foundLang ? foundLang.id : 'plaintext';
+ },
+
+ getFilePathFromFullPath(fullPath, branch) {
+ return fullPath.split(`${Store.projectUrl}/blob/${branch}`)[1];
+ },
+
+ findLanguage(ext, langs) {
+ return langs.find(lang => lang.extensions && lang.extensions.indexOf(`.${ext}`) > -1);
+ },
+
+ setDirectoryOpen(tree) {
+ const file = tree;
+ if (!file) return undefined;
+
+ file.opened = true;
+ file.icon = 'fa-folder-open';
+ RepoHelper.toURL(file.url, file.name);
+ return file;
+ },
+
+ isKindaBinary() {
+ const okExts = ['md', 'svg'];
+ return okExts.indexOf(Store.activeFile.extension) > -1;
+ },
+
+ setBinaryDataAsBase64(file) {
+ Service.getBase64Content(file.raw_path)
+ .then((response) => {
+ Store.blobRaw = response;
+ file.base64 = response; // eslint-disable-line no-param-reassign
+ })
+ .catch(RepoHelper.loadingError);
+ },
+
+ toggleFakeTab(loading, file) {
+ if (loading) return Store.addPlaceholderFile();
+ return Store.removeFromOpenedFiles(file);
+ },
+
+ setLoading(loading, file) {
+ if (Service.url.indexOf('blob') > -1) {
+ Store.loading.blob = loading;
+ return RepoHelper.toggleFakeTab(loading, file);
+ }
+
+ if (Service.url.indexOf('tree') > -1) Store.loading.tree = loading;
+
+ return undefined;
+ },
+
+ getNewMergedList(inDirectory, currentList, newList) {
+ const newListSorted = newList.sort(this.compareFilesCaseInsensitive);
+ if (!inDirectory) return newListSorted;
+ const indexOfFile = currentList.findIndex(file => file.url === inDirectory.url);
+ if (!indexOfFile) return newListSorted;
+ return RepoHelper.mergeNewListToOldList(newListSorted, currentList, inDirectory, indexOfFile);
+ },
+
+ mergeNewListToOldList(newList, oldList, inDirectory, indexOfFile) {
+ newList.reverse().forEach((newFile) => {
+ const fileIndex = indexOfFile + 1;
+ const file = newFile;
+ file.level = inDirectory.level + 1;
+ oldList.splice(fileIndex, 0, file);
+ });
+
+ return oldList;
+ },
+
+ compareFilesCaseInsensitive(a, b) {
+ const aName = a.name.toLowerCase();
+ const bName = b.name.toLowerCase();
+ if (a.level > 0) return 0;
+ if (aName < bName) { return -1; }
+ if (aName > bName) { return 1; }
+ return 0;
+ },
+
+ isRoot(url) {
+ // the url we are requesting -> split by the project URL. Grab the right side.
+ const isRoot = !!url.split(Store.projectUrl)[1]
+ // remove the first "/"
+ .slice(1)
+ // split this by "/"
+ .split('/')
+ // remove the first two items of the array... usually /tree/master.
+ .slice(2)
+ // we want to know the length of the array.
+ // If greater than 0 not root.
+ .length;
+ return isRoot;
+ },
+
+ getContent(treeOrFile) {
+ let file = treeOrFile;
+ // const loadingData = RepoHelper.setLoading(true);
+ return Service.getContent()
+ .then((response) => {
+ const data = response.data;
+ // RepoHelper.setLoading(false, loadingData);
+ Store.isTree = RepoHelper.isTree(data);
+ if (!Store.isTree) {
+ if (!file) file = data;
+ Store.binary = data.binary;
+
+ if (data.binary) {
+ // file might be undefined
+ RepoHelper.setBinaryDataAsBase64(data);
+ Store.setViewToPreview();
+ } else if (!Store.isPreviewView()) {
+ if (!data.render_error) {
+ Service.getRaw(data.raw_path)
+ .then((rawResponse) => {
+ Store.blobRaw = rawResponse.data;
+ data.plain = rawResponse.data;
+ RepoHelper.setFile(data, file);
+ }).catch(RepoHelper.loadingError);
+ }
+ }
+
+ if (Store.isPreviewView()) {
+ RepoHelper.setFile(data, file);
+ }
+
+ // if the file tree is empty
+ if (Store.files.length === 0) {
+ const parentURL = Service.blobURLtoParentTree(Service.url);
+ Service.url = parentURL;
+ RepoHelper.getContent();
+ }
+ } else {
+ // it's a tree
+ if (!file) Store.isRoot = RepoHelper.isRoot(Service.url);
+ file = RepoHelper.setDirectoryOpen(file);
+ const newDirectory = RepoHelper.dataToListOfFiles(data);
+ Store.addFilesToDirectory(file, Store.files, newDirectory);
+ Store.prevURL = Service.blobURLtoParentTree(Service.url);
+ }
+ }).catch(RepoHelper.loadingError);
+ },
+
+ setFile(data, file) {
+ const newFile = data;
+
+ newFile.url = file.url;
+ if (newFile.render_error === 'too_large' || newFile.render_error === 'collapsed') {
+ newFile.tooLarge = true;
+ }
+ newFile.newContent = '';
+
+ Store.addToOpenedFiles(newFile);
+ Store.setActiveFiles(newFile);
+ },
+
+ serializeBlob(blob) {
+ const simpleBlob = RepoHelper.serializeRepoEntity('blob', blob);
+ simpleBlob.lastCommitMessage = blob.last_commit.message;
+ simpleBlob.lastCommitUpdate = blob.last_commit.committed_date;
+ simpleBlob.loading = false;
+
+ return simpleBlob;
+ },
+
+ serializeTree(tree) {
+ return RepoHelper.serializeRepoEntity('tree', tree);
+ },
+
+ serializeSubmodule(submodule) {
+ return RepoHelper.serializeRepoEntity('submodule', submodule);
+ },
+
+ serializeRepoEntity(type, entity) {
+ const { url, name, icon, last_commit } = entity;
+ const returnObj = {
+ type,
+ name,
+ url,
+ icon: `fa-${icon}`,
+ level: 0,
+ loading: false,
+ };
+
+ if (entity.last_commit) {
+ returnObj.lastCommitUrl = `${Store.projectUrl}/commit/${last_commit.id}`;
+ } else {
+ returnObj.lastCommitUrl = '';
+ }
+ return returnObj;
+ },
+
+ scrollTabsRight() {
+ // wait for the transition. 0.1 seconds.
+ setTimeout(() => {
+ const tabs = document.getElementById('tabs');
+ if (!tabs) return;
+ tabs.scrollLeft = tabs.scrollWidth;
+ }, 200);
+ },
+
+ dataToListOfFiles(data) {
+ const a = [];
+
+ // push in blobs
+ data.blobs.forEach((blob) => {
+ a.push(RepoHelper.serializeBlob(blob));
+ });
+
+ data.trees.forEach((tree) => {
+ a.push(RepoHelper.serializeTree(tree));
+ });
+
+ data.submodules.forEach((submodule) => {
+ a.push(RepoHelper.serializeSubmodule(submodule));
+ });
+
+ return a;
+ },
+
+ genKey() {
+ return RepoHelper.Time.now().toFixed(3);
+ },
+
+ getStateKey() {
+ return RepoHelper.key;
+ },
+
+ setStateKey(key) {
+ RepoHelper.key = key;
+ },
+
+ toURL(url, title) {
+ const history = window.history;
+
+ RepoHelper.key = RepoHelper.genKey();
+
+ history.pushState({ key: RepoHelper.key }, '', url);
+
+ if (title) {
+ document.title = `${title} · GitLab`;
+ }
+ },
+
+ findOpenedFileFromActive() {
+ return Store.openedFiles.find(openedFile => Store.activeFile.url === openedFile.url);
+ },
+
+ loadingError() {
+ Flash('Unable to load the file at this time.');
+ },
+};
+
+export default RepoHelper;
diff --git a/app/assets/javascripts/repo/index.js b/app/assets/javascripts/repo/index.js
new file mode 100644
index 00000000000..3e37da1726e
--- /dev/null
+++ b/app/assets/javascripts/repo/index.js
@@ -0,0 +1,73 @@
+import $ from 'jquery';
+import Vue from 'vue';
+import Service from './services/repo_service';
+import Store from './stores/repo_store';
+import Repo from './components/repo.vue';
+import RepoEditButton from './components/repo_edit_button.vue';
+import Translate from '../vue_shared/translate';
+
+function initDropdowns() {
+ $('.js-tree-ref-target-holder').hide();
+}
+
+function addEventsForNonVueEls() {
+ $(document).on('change', '.dropdown', () => {
+ Store.targetBranch = $('.project-refs-target-form input[name="ref"]').val();
+ });
+
+ window.onbeforeunload = function confirmUnload(e) {
+ const hasChanged = Store.openedFiles
+ .some(file => file.changed);
+ if (!hasChanged) return undefined;
+ const event = e || window.event;
+ if (event) event.returnValue = 'Are you sure you want to lose unsaved changes?';
+ // For Safari
+ return 'Are you sure you want to lose unsaved changes?';
+ };
+}
+
+function setInitialStore(data) {
+ Store.service = Service;
+ Store.service.url = data.url;
+ Store.service.refsUrl = data.refsUrl;
+ Store.projectId = data.projectId;
+ Store.projectName = data.projectName;
+ Store.projectUrl = data.projectUrl;
+ Store.currentBranch = $('button.dropdown-menu-toggle').attr('data-ref');
+ Store.checkIsCommitable();
+}
+
+function initRepo(el) {
+ return new Vue({
+ el,
+ components: {
+ repo: Repo,
+ },
+ });
+}
+
+function initRepoEditButton(el) {
+ return new Vue({
+ el,
+ components: {
+ repoEditButton: RepoEditButton,
+ },
+ });
+}
+
+function initRepoBundle() {
+ const repo = document.getElementById('repo');
+ const editButton = document.querySelector('.editable-mode');
+ setInitialStore(repo.dataset);
+ addEventsForNonVueEls();
+ initDropdowns();
+
+ Vue.use(Translate);
+
+ initRepo(repo);
+ initRepoEditButton(editButton);
+}
+
+$(initRepoBundle);
+
+export default initRepoBundle;
diff --git a/app/assets/javascripts/repo/mixins/repo_mixin.js b/app/assets/javascripts/repo/mixins/repo_mixin.js
new file mode 100644
index 00000000000..c8e8238a0d3
--- /dev/null
+++ b/app/assets/javascripts/repo/mixins/repo_mixin.js
@@ -0,0 +1,17 @@
+import Store from '../stores/repo_store';
+
+const RepoMixin = {
+ computed: {
+ isMini() {
+ return !!Store.openedFiles.length;
+ },
+
+ changedFiles() {
+ const changedFileList = this.openedFiles
+ .filter(file => file.changed);
+ return changedFileList;
+ },
+ },
+};
+
+export default RepoMixin;
diff --git a/app/assets/javascripts/repo/monaco_loader.js b/app/assets/javascripts/repo/monaco_loader.js
new file mode 100644
index 00000000000..ad1370a7730
--- /dev/null
+++ b/app/assets/javascripts/repo/monaco_loader.js
@@ -0,0 +1,13 @@
+/* eslint-disable no-underscore-dangle, camelcase */
+/* global __webpack_public_path__ */
+
+import monacoContext from 'monaco-editor/dev/vs/loader';
+
+monacoContext.require.config({
+ paths: {
+ vs: `${__webpack_public_path__}monaco-editor/vs`,
+ },
+});
+
+window.__monaco_context__ = monacoContext;
+export default monacoContext.require;
diff --git a/app/assets/javascripts/repo/services/repo_service.js b/app/assets/javascripts/repo/services/repo_service.js
new file mode 100644
index 00000000000..17578f3bbf3
--- /dev/null
+++ b/app/assets/javascripts/repo/services/repo_service.js
@@ -0,0 +1,84 @@
+/* global Flash */
+import axios from 'axios';
+import Store from '../stores/repo_store';
+import Api from '../../api';
+import Helper from '../helpers/repo_helper';
+
+const RepoService = {
+ url: '',
+ options: {
+ params: {
+ format: 'json',
+ },
+ },
+ richExtensionRegExp: /md/,
+
+ checkCurrentBranchIsCommitable() {
+ const url = Store.service.refsUrl;
+ return axios.get(url, { params: {
+ ref: Store.currentBranch,
+ search: Store.currentBranch,
+ } });
+ },
+
+ getRaw(url) {
+ return axios.get(url, {
+ // Stop Axios from parsing a JSON file into a JS object
+ transformResponse: [res => res],
+ });
+ },
+
+ buildParams(url = this.url) {
+ // shallow clone object without reference
+ const params = Object.assign({}, this.options.params);
+
+ if (this.urlIsRichBlob(url)) params.viewer = 'rich';
+
+ return params;
+ },
+
+ urlIsRichBlob(url = this.url) {
+ const extension = Helper.getFileExtension(url);
+
+ return this.richExtensionRegExp.test(extension);
+ },
+
+ getContent(url = this.url) {
+ const params = this.buildParams(url);
+
+ return axios.get(url, {
+ params,
+ });
+ },
+
+ getBase64Content(url = this.url) {
+ const request = axios.get(url, {
+ responseType: 'arraybuffer',
+ });
+
+ return request.then(response => this.bufferToBase64(response.data));
+ },
+
+ bufferToBase64(data) {
+ return new Buffer(data, 'binary').toString('base64');
+ },
+
+ blobURLtoParentTree(url) {
+ const urlArray = url.split('/');
+ urlArray.pop();
+ const blobIndex = urlArray.lastIndexOf('blob');
+
+ if (blobIndex > -1) urlArray[blobIndex] = 'tree';
+
+ return urlArray.join('/');
+ },
+
+ commitFiles(payload, cb) {
+ Api.commitMultiple(Store.projectId, payload, (data) => {
+ Flash(`Your changes have been committed. Commit ${data.short_id} with ${data.stats.additions} additions, ${data.stats.deletions} deletions.`, 'notice');
+ cb();
+ });
+ },
+};
+
+export default RepoService;
diff --git a/app/assets/javascripts/repo/stores/repo_store.js b/app/assets/javascripts/repo/stores/repo_store.js
new file mode 100644
index 00000000000..bb605540aad
--- /dev/null
+++ b/app/assets/javascripts/repo/stores/repo_store.js
@@ -0,0 +1,225 @@
+/* global Flash */
+import Helper from '../helpers/repo_helper';
+import Service from '../services/repo_service';
+
+const RepoStore = {
+ monaco: {},
+ monacoLoading: false,
+ monacoInstance: {},
+ service: '',
+ editMode: false,
+ isTree: false,
+ isRoot: false,
+ prevURL: '',
+ projectId: '',
+ projectName: '',
+ projectUrl: '',
+ blobRaw: '',
+ currentBlobView: 'repo-preview',
+ openedFiles: [],
+ submitCommitsLoading: false,
+ dialog: {
+ open: false,
+ title: '',
+ status: false,
+ },
+ activeFile: Helper.getDefaultActiveFile(),
+ activeFileIndex: 0,
+ activeLine: 0,
+ activeFileLabel: 'Raw',
+ files: [],
+ isCommitable: false,
+ binary: false,
+ currentBranch: '',
+ targetBranch: 'new-branch',
+ commitMessage: '',
+ binaryTypes: {
+ png: false,
+ md: false,
+ svg: false,
+ unknown: false,
+ },
+ loading: {
+ tree: false,
+ blob: false,
+ },
+
+ resetBinaryTypes() {
+ Object.keys(RepoStore.binaryTypes).forEach((key) => {
+ RepoStore.binaryTypes[key] = false;
+ });
+ },
+
+ // mutations
+ checkIsCommitable() {
+ RepoStore.service.checkCurrentBranchIsCommitable()
+ .then((data) => {
+ // you shouldn't be able to make commits on commits or tags.
+ const { Branches, Commits, Tags } = data.data;
+ if (Branches && Branches.length) RepoStore.isCommitable = true;
+ if (Commits && Commits.length) RepoStore.isCommitable = false;
+ if (Tags && Tags.length) RepoStore.isCommitable = false;
+ }).catch(() => Flash('Failed to check if branch can be committed to.'));
+ },
+
+ addFilesToDirectory(inDirectory, currentList, newList) {
+ RepoStore.files = Helper.getNewMergedList(inDirectory, currentList, newList);
+ },
+
+ toggleRawPreview() {
+ RepoStore.activeFile.raw = !RepoStore.activeFile.raw;
+ RepoStore.activeFileLabel = RepoStore.activeFile.raw ? 'Display rendered file' : 'Display source';
+ },
+
+ setActiveFiles(file) {
+ if (RepoStore.isActiveFile(file)) return;
+ RepoStore.openedFiles = RepoStore.openedFiles
+ .map((openedFile, i) => RepoStore.setFileActivity(file, openedFile, i));
+
+ RepoStore.setActiveToRaw();
+
+ if (file.binary) {
+ RepoStore.blobRaw = file.base64;
+ } else if (file.newContent || file.plain) {
+ RepoStore.blobRaw = file.newContent || file.plain;
+ } else {
+ Service.getRaw(file.raw_path)
+ .then((rawResponse) => {
+ RepoStore.blobRaw = rawResponse.data;
+ Helper.findOpenedFileFromActive().plain = rawResponse.data;
+ }).catch(Helper.loadingError);
+ }
+
+ if (!file.loading) Helper.toURL(file.url, file.name);
+ RepoStore.binary = file.binary;
+ },
+
+ setFileActivity(file, openedFile, i) {
+ const activeFile = openedFile;
+ activeFile.active = file.url === activeFile.url;
+
+ if (activeFile.active) RepoStore.setActiveFile(activeFile, i);
+
+ return activeFile;
+ },
+
+ setActiveFile(activeFile, i) {
+ RepoStore.activeFile = Object.assign({}, RepoStore.activeFile, activeFile);
+ RepoStore.activeFileIndex = i;
+ },
+
+ setActiveToRaw() {
+ RepoStore.activeFile.raw = false;
+ // can't get vue to listen to raw for some reason so RepoStore for now.
+ RepoStore.activeFileLabel = 'Display source';
+ },
+
+ removeChildFilesOfTree(tree) {
+ let foundTree = false;
+ const treeToClose = tree;
+ let wereDone = false;
+ RepoStore.files = RepoStore.files.filter((file) => {
+ const isItTheTreeWeWant = file.url === treeToClose.url;
+ // if it's the next tree
+ if (foundTree && file.type === 'tree' && !isItTheTreeWeWant && file.level === treeToClose.level) {
+ wereDone = true;
+ return true;
+ }
+ if (wereDone) return true;
+
+ if (isItTheTreeWeWant) foundTree = true;
+
+ if (foundTree) return file.level <= treeToClose.level;
+ return true;
+ });
+
+ treeToClose.opened = false;
+ treeToClose.icon = 'fa-folder';
+ return treeToClose;
+ },
+
+ removeFromOpenedFiles(file) {
+ if (file.type === 'tree') return;
+ let foundIndex;
+ RepoStore.openedFiles = RepoStore.openedFiles.filter((openedFile, i) => {
+ if (openedFile.url === file.url) foundIndex = i;
+ return openedFile.url !== file.url;
+ });
+
+ // now activate the right tab based on what you closed.
+ if (RepoStore.openedFiles.length === 0) {
+ RepoStore.activeFile = {};
+ return;
+ }
+
+ if (RepoStore.openedFiles.length === 1 || foundIndex === 0) {
+ RepoStore.setActiveFiles(RepoStore.openedFiles[0]);
+ return;
+ }
+
+ if (foundIndex) {
+ if (foundIndex > 0) {
+ RepoStore.setActiveFiles(RepoStore.openedFiles[foundIndex - 1]);
+ }
+ }
+ },
+
+ addPlaceholderFile() {
+ const randomURL = Helper.Time.now();
+ const newFakeFile = {
+ active: false,
+ binary: true,
+ type: 'blob',
+ loading: true,
+ mime_type: 'loading',
+ name: 'loading',
+ url: randomURL,
+ fake: true,
+ };
+
+ RepoStore.openedFiles.push(newFakeFile);
+
+ return newFakeFile;
+ },
+
+ addToOpenedFiles(file) {
+ const openFile = file;
+
+ const openedFilesAlreadyExists = RepoStore.openedFiles
+ .some(openedFile => openedFile.url === openFile.url);
+
+ if (openedFilesAlreadyExists) return;
+
+ openFile.changed = false;
+ RepoStore.openedFiles.push(openFile);
+ },
+
+ setActiveFileContents(contents) {
+ if (!RepoStore.editMode) return;
+ const currentFile = RepoStore.openedFiles[RepoStore.activeFileIndex];
+ RepoStore.activeFile.newContent = contents;
+ RepoStore.activeFile.changed = RepoStore.activeFile.plain !== RepoStore.activeFile.newContent;
+ currentFile.changed = RepoStore.activeFile.changed;
+ currentFile.newContent = contents;
+ },
+
+ toggleBlobView() {
+ RepoStore.currentBlobView = RepoStore.isPreviewView() ? 'repo-editor' : 'repo-preview';
+ },
+
+ setViewToPreview() {
+ RepoStore.currentBlobView = 'repo-preview';
+ },
+
+ // getters
+
+ isActiveFile(file) {
+ return file && file.url === RepoStore.activeFile.url;
+ },
+
+ isPreviewView() {
+ return RepoStore.currentBlobView === 'repo-preview';
+ },
+};
+
+export default RepoStore;
diff --git a/app/assets/javascripts/right_sidebar.js b/app/assets/javascripts/right_sidebar.js
index d8f1fe10b26..fa958d75fa4 100644
--- a/app/assets/javascripts/right_sidebar.js
+++ b/app/assets/javascripts/right_sidebar.js
@@ -1,5 +1,6 @@
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-unused-vars, consistent-return, one-var, one-var-declaration-per-line, quotes, prefer-template, object-shorthand, comma-dangle, no-else-return, no-param-reassign, max-len */
+import _ from 'underscore';
import Cookies from 'js-cookie';
import SidebarHeightManager from './sidebar_height_manager';
diff --git a/app/assets/javascripts/shortcuts_issuable.js b/app/assets/javascripts/shortcuts_issuable.js
index 51448252c0f..0be141eb5f9 100644
--- a/app/assets/javascripts/shortcuts_issuable.js
+++ b/app/assets/javascripts/shortcuts_issuable.js
@@ -3,6 +3,7 @@
/* global ShortcutsNavigation */
/* global sidebar */
+import _ from 'underscore';
import 'mousetrap';
import './shortcuts_navigation';
@@ -58,7 +59,7 @@ import './shortcuts_navigation';
});
// If replyField already has some content, add a newline before our quote
separator = replyField.val().trim() !== "" && "\n\n" || '';
- replyField.val(function(_, current) {
+ replyField.val(function(a, current) {
return current + separator + quote.join('') + "\n";
});
diff --git a/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue b/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue
new file mode 100644
index 00000000000..422c02c7b7e
--- /dev/null
+++ b/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue
@@ -0,0 +1,82 @@
+<script>
+/* global Flash */
+import editForm from './edit_form.vue';
+
+export default {
+ components: {
+ editForm,
+ },
+ props: {
+ isConfidential: {
+ required: true,
+ type: Boolean,
+ },
+ isEditable: {
+ required: true,
+ type: Boolean,
+ },
+ service: {
+ required: true,
+ type: Object,
+ },
+ },
+ data() {
+ return {
+ edit: false,
+ };
+ },
+ computed: {
+ faEye() {
+ const eye = this.isConfidential ? 'fa-eye-slash' : 'fa-eye';
+ return {
+ [eye]: true,
+ };
+ },
+ },
+ methods: {
+ toggleForm() {
+ this.edit = !this.edit;
+ },
+ updateConfidentialAttribute(confidential) {
+ this.service.update('issue', { confidential })
+ .then(() => location.reload())
+ .catch(() => new Flash('Something went wrong trying to change the confidentiality of this issue'));
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="block confidentiality">
+ <div class="sidebar-collapsed-icon">
+ <i class="fa" :class="faEye" aria-hidden="true" data-hidden="true"></i>
+ </div>
+ <div class="title hide-collapsed">
+ Confidentiality
+ <a
+ v-if="isEditable"
+ class="pull-right confidential-edit"
+ href="#"
+ @click.prevent="toggleForm"
+ >
+ Edit
+ </a>
+ </div>
+ <div class="value confidential-value hide-collapsed">
+ <editForm
+ v-if="edit"
+ :toggle-form="toggleForm"
+ :is-confidential="isConfidential"
+ :update-confidential-attribute="updateConfidentialAttribute"
+ />
+ <div v-if="!isConfidential" class="no-value confidential-value">
+ <i class="fa fa-eye is-not-confidential"></i>
+ None
+ </div>
+ <div v-else class="value confidential-value hide-collapsed">
+ <i aria-hidden="true" data-hidden="true" class="fa fa-eye-slash is-confidential"></i>
+ This issue is confidential
+ </div>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/sidebar/components/confidential/edit_form.vue b/app/assets/javascripts/sidebar/components/confidential/edit_form.vue
new file mode 100644
index 00000000000..d578b663a54
--- /dev/null
+++ b/app/assets/javascripts/sidebar/components/confidential/edit_form.vue
@@ -0,0 +1,47 @@
+<script>
+import editFormButtons from './edit_form_buttons.vue';
+
+export default {
+ components: {
+ editFormButtons,
+ },
+ props: {
+ isConfidential: {
+ required: true,
+ type: Boolean,
+ },
+ toggleForm: {
+ required: true,
+ type: Function,
+ },
+ updateConfidentialAttribute: {
+ required: true,
+ type: Function,
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="dropdown open">
+ <div class="dropdown-menu confidential-warning-message">
+ <div>
+ <p v-if="!isConfidential">
+ You are going to turn on the confidentiality. This means that only team members with
+ <strong>at least Reporter access</strong>
+ are able to see and leave comments on the issue.
+ </p>
+ <p v-else>
+ You are going to turn off the confidentiality. This means
+ <strong>everyone</strong>
+ will be able to see and leave a comment on this issue.
+ </p>
+ <edit-form-buttons
+ :is-confidential="isConfidential"
+ :toggle-form="toggleForm"
+ :update-confidential-attribute="updateConfidentialAttribute"
+ />
+ </div>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/sidebar/components/confidential/edit_form_buttons.vue b/app/assets/javascripts/sidebar/components/confidential/edit_form_buttons.vue
new file mode 100644
index 00000000000..97af4a3f505
--- /dev/null
+++ b/app/assets/javascripts/sidebar/components/confidential/edit_form_buttons.vue
@@ -0,0 +1,45 @@
+<script>
+export default {
+ props: {
+ isConfidential: {
+ required: true,
+ type: Boolean,
+ },
+ toggleForm: {
+ required: true,
+ type: Function,
+ },
+ updateConfidentialAttribute: {
+ required: true,
+ type: Function,
+ },
+ },
+ computed: {
+ onOrOff() {
+ return this.isConfidential ? 'Turn Off' : 'Turn On';
+ },
+ updateConfidentialBool() {
+ return !this.isConfidential;
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="confidential-warning-message-actions">
+ <button
+ type="button"
+ class="btn btn-default append-right-10"
+ @click="toggleForm"
+ >
+ Cancel
+ </button>
+ <button
+ type="button"
+ class="btn btn-close"
+ @click.prevent="updateConfidentialAttribute(updateConfidentialBool)"
+ >
+ {{ onOrOff }}
+ </button>
+ </div>
+</template>
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/sidebar_time_tracking.js b/app/assets/javascripts/sidebar/components/time_tracking/sidebar_time_tracking.js
index 650e935b116..2d682215cf8 100644
--- a/app/assets/javascripts/sidebar/components/time_tracking/sidebar_time_tracking.js
+++ b/app/assets/javascripts/sidebar/components/time_tracking/sidebar_time_tracking.js
@@ -1,3 +1,5 @@
+import _ from 'underscore';
+
import '~/smart_interval';
import timeTracker from './time_tracker';
diff --git a/app/assets/javascripts/sidebar/sidebar_bundle.js b/app/assets/javascripts/sidebar/sidebar_bundle.js
index a9df66748c5..9edded3ead6 100644
--- a/app/assets/javascripts/sidebar/sidebar_bundle.js
+++ b/app/assets/javascripts/sidebar/sidebar_bundle.js
@@ -1,6 +1,7 @@
import Vue from 'vue';
import sidebarTimeTracking from './components/time_tracking/sidebar_time_tracking';
import sidebarAssignees from './components/assignees/sidebar_assignees';
+import confidential from './components/confidential/confidential_issue_sidebar.vue';
import Mediator from './sidebar_mediator';
@@ -10,13 +11,28 @@ function domContentLoaded() {
mediator.fetch();
const sidebarAssigneesEl = document.querySelector('#js-vue-sidebar-assignees');
-
+ const confidentialEl = document.querySelector('#js-confidential-entry-point');
// Only create the sidebarAssignees vue app if it is found in the DOM
// We currently do not use sidebarAssignees for the MR page
if (sidebarAssigneesEl) {
new Vue(sidebarAssignees).$mount(sidebarAssigneesEl);
}
+ if (confidentialEl) {
+ const dataNode = document.getElementById('js-confidential-issue-data');
+ const initialData = JSON.parse(dataNode.innerHTML);
+
+ const ConfidentialComp = Vue.extend(confidential);
+
+ new ConfidentialComp({
+ propsData: {
+ isConfidential: initialData.is_confidential,
+ isEditable: initialData.is_editable,
+ service: mediator.service,
+ },
+ }).$mount(confidentialEl);
+ }
+
new Vue(sidebarTimeTracking).$mount('#issuable-time-tracker');
}
diff --git a/app/assets/javascripts/sidebar_height_manager.js b/app/assets/javascripts/sidebar_height_manager.js
index 022415f22b2..2752fe2b911 100644
--- a/app/assets/javascripts/sidebar_height_manager.js
+++ b/app/assets/javascripts/sidebar_height_manager.js
@@ -1,6 +1,11 @@
+import _ from 'underscore';
+import Cookies from 'js-cookie';
+
export default {
init() {
if (!this.initialized) {
+ if (Cookies.get('new_nav') === 'true' && $('.js-issuable-sidebar').length) return;
+
this.$window = $(window);
this.$rightSidebar = $('.js-right-sidebar');
this.$navHeight = $('.navbar-gitlab').outerHeight() +
@@ -30,4 +35,3 @@ export default {
}
},
};
-
diff --git a/app/assets/javascripts/test_utils/index.js b/app/assets/javascripts/test_utils/index.js
index ef401abce2d..8875590f0f2 100644
--- a/app/assets/javascripts/test_utils/index.js
+++ b/app/assets/javascripts/test_utils/index.js
@@ -1,3 +1,5 @@
+import 'core-js/es6/map';
+import 'core-js/es6/set';
import simulateDrag from './simulate_drag';
// Export to global space for rspec to use
diff --git a/app/assets/javascripts/todos.js b/app/assets/javascripts/todos.js
index bba8b5abbb4..a606852c22c 100644
--- a/app/assets/javascripts/todos.js
+++ b/app/assets/javascripts/todos.js
@@ -52,6 +52,7 @@ export default class Todos {
}
updateRowStateClicked(e) {
+ e.stopPropagation();
e.preventDefault();
const target = e.target;
@@ -92,6 +93,7 @@ export default class Todos {
}
updateAllStateClicked(e) {
+ e.stopPropagation();
e.preventDefault();
const target = e.currentTarget;
@@ -142,6 +144,7 @@ export default class Todos {
if (gl.utils.isMetaClick(e)) {
const windowTarget = '_blank';
const selected = e.target;
+ e.stopPropagation();
e.preventDefault();
if (selected.tagName === 'IMG') {
diff --git a/app/assets/javascripts/u2f/authenticate.js b/app/assets/javascripts/u2f/authenticate.js
index cd5280948fd..8821b22477f 100644
--- a/app/assets/javascripts/u2f/authenticate.js
+++ b/app/assets/javascripts/u2f/authenticate.js
@@ -3,6 +3,8 @@
/* global U2FError */
/* global U2FUtil */
+import _ from 'underscore';
+
// Authenticate U2F (universal 2nd factor) devices for users to authenticate with.
//
// State Flow #1: setup -> in_progress -> authenticated -> POST to server
diff --git a/app/assets/javascripts/u2f/register.js b/app/assets/javascripts/u2f/register.js
index 1234d17b8fd..3a2534d553b 100644
--- a/app/assets/javascripts/u2f/register.js
+++ b/app/assets/javascripts/u2f/register.js
@@ -3,6 +3,8 @@
/* global U2FError */
/* global U2FUtil */
+import _ from 'underscore';
+
// Register U2F (universal 2nd factor) devices for users to authenticate with.
//
// State Flow #1: setup -> in_progress -> registered -> POST to server
diff --git a/app/assets/javascripts/username_validator.js b/app/assets/javascripts/username_validator.js
index a348d69153c..bb34d5d2008 100644
--- a/app/assets/javascripts/username_validator.js
+++ b/app/assets/javascripts/username_validator.js
@@ -1,5 +1,7 @@
/* eslint-disable comma-dangle, consistent-return, class-methods-use-this, arrow-parens, no-param-reassign, max-len */
+import _ from 'underscore';
+
const debounceTimeoutDuration = 1000;
const invalidInputClass = 'gl-field-error-outline';
const successInputClass = 'gl-field-success-outline';
diff --git a/app/assets/javascripts/users/activity_calendar.js b/app/assets/javascripts/users/activity_calendar.js
index f091e319f44..5e947769f8a 100644
--- a/app/assets/javascripts/users/activity_calendar.js
+++ b/app/assets/javascripts/users/activity_calendar.js
@@ -1,3 +1,4 @@
+import _ from 'underscore';
import d3 from 'd3';
const LOADING_HTML = `
@@ -6,6 +7,14 @@ const LOADING_HTML = `
</div>
`;
+function getSystemDate(systemUtcOffsetSeconds) {
+ const date = new Date();
+ const localUtcOffsetMinutes = 0 - date.getTimezoneOffset();
+ const systemUtcOffsetMinutes = systemUtcOffsetSeconds / 60;
+ date.setMinutes((date.getMinutes() - localUtcOffsetMinutes) + systemUtcOffsetMinutes);
+ return date;
+}
+
function formatTooltipText({ date, count }) {
const dateObject = new Date(date);
const dateDayName = gl.utils.getDayName(dateObject);
@@ -21,7 +30,7 @@ function formatTooltipText({ date, count }) {
const initColorKey = () => d3.scale.linear().range(['#acd5f2', '#254e77']).domain([0, 3]);
export default class ActivityCalendar {
- constructor(container, timestamps, calendarActivitiesPath) {
+ constructor(container, timestamps, calendarActivitiesPath, utcOffset = 0) {
this.calendarActivitiesPath = calendarActivitiesPath;
this.clickDay = this.clickDay.bind(this);
this.currentSelectedDate = '';
@@ -36,7 +45,7 @@ export default class ActivityCalendar {
this.timestampsTmp = [];
let group = 0;
- const today = new Date();
+ const today = getSystemDate(utcOffset);
today.setHours(0, 0, 0, 0, 0);
const oneYearAgo = new Date(today);
diff --git a/app/assets/javascripts/users/user_tabs.js b/app/assets/javascripts/users/user_tabs.js
index 5fe6603ce7b..1215b265e28 100644
--- a/app/assets/javascripts/users/user_tabs.js
+++ b/app/assets/javascripts/users/user_tabs.js
@@ -150,15 +150,21 @@ export default class UserTabs {
const $calendarWrap = this.$parentEl.find('.user-calendar');
const calendarPath = $calendarWrap.data('calendarPath');
const calendarActivitiesPath = $calendarWrap.data('calendarActivitiesPath');
+ const utcOffset = $calendarWrap.data('utcOffset');
+ let utcFormatted = 'UTC';
+ if (utcOffset !== 0) {
+ utcFormatted = `UTC${utcOffset > 0 ? '+' : ''}${(utcOffset / 3600)}`;
+ }
$.ajax({
dataType: 'json',
url: calendarPath,
success: (activityData) => {
$calendarWrap.html(CALENDAR_TEMPLATE);
+ $calendarWrap.find('.calendar-hint').append(`(Timezone: ${utcFormatted})`);
// eslint-disable-next-line no-new
- new ActivityCalendar('.js-contrib-calendar', activityData, calendarActivitiesPath);
+ new ActivityCalendar('.js-contrib-calendar', activityData, calendarActivitiesPath, utcOffset);
},
});
diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js
index 5728afb4c59..16ebf5916dc 100644
--- a/app/assets/javascripts/users_select.js
+++ b/app/assets/javascripts/users_select.js
@@ -1,6 +1,7 @@
/* eslint-disable func-names, space-before-function-paren, one-var, no-var, prefer-rest-params, wrap-iife, quotes, max-len, one-var-declaration-per-line, vars-on-top, prefer-arrow-callback, consistent-return, comma-dangle, object-shorthand, no-shadow, no-unused-vars, no-else-return, no-self-compare, prefer-template, no-unused-expressions, no-lonely-if, yoda, prefer-spread, no-void, camelcase, no-param-reassign */
/* global Issuable */
/* global emitSidebarEvent */
+import _ from 'underscore';
// TODO: remove eventHub hack after code splitting refactor
window.emitSidebarEvent = window.emitSidebarEvent || $.noop;
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.js
index a01cb8cc202..982b5e8e373 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.js
@@ -1,3 +1,5 @@
+import tooltip from '../../vue_shared/directives/tooltip';
+
export default {
name: 'MRWidgetAuthor',
props: {
@@ -5,11 +7,14 @@ export default {
showAuthorName: { type: Boolean, required: false, default: true },
showAuthorTooltip: { type: Boolean, required: false, default: false },
},
+ directives: {
+ tooltip,
+ },
template: `
<a
:href="author.webUrl || author.web_url"
- class="author-link"
- :class="{ 'has-tooltip': showAuthorTooltip }"
+ class="author-link inline"
+ :v-tooltip="showAuthorTooltip"
:title="author.name">
<img
:src="author.avatarUrl || author.avatar_url"
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js
index 744a1cd24fa..e98d147733c 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js
@@ -1,8 +1,8 @@
/* global Flash */
import '~/lib/utils/datetime_utility';
-import { statusIconEntityMap } from '../../vue_shared/ci_status_icons';
import MemoryUsage from './mr_widget_memory_usage';
+import StatusIcon from './mr_widget_status_icon';
import MRWidgetService from '../services/mr_widget_service';
export default {
@@ -13,11 +13,7 @@ export default {
},
components: {
'mr-widget-memory-usage': MemoryUsage,
- },
- computed: {
- svg() {
- return statusIconEntityMap.icon_status_success;
- },
+ 'status-icon': StatusIcon,
},
methods: {
formatDate(date) {
@@ -51,51 +47,51 @@ export default {
},
},
template: `
- <div class="mr-widget-heading">
+ <div class="mr-widget-heading deploy-heading">
<div v-for="deployment in mr.deployments">
- <div class="ci-widget">
+ <div class="ci-widget media">
<div class="ci-status-icon ci-status-icon-success">
<span class="js-icon-link icon-link">
- <span class="ci-status-icon"
- v-html="svg"
- aria-hidden="true"></span>
+ <status-icon status="success" />
</span>
</div>
- <span>
- <span
- v-if="hasDeploymentMeta(deployment)">
- Deployed to
- </span>
- <a
- v-if="hasDeploymentMeta(deployment)"
- :href="deployment.url"
- target="_blank"
- rel="noopener noreferrer nofollow"
- class="js-deploy-meta">
- {{deployment.name}}
- </a>
- <span
- v-if="hasExternalUrls(deployment)">
- on
- </span>
- <a
- v-if="hasExternalUrls(deployment)"
- :href="deployment.external_url"
- target="_blank"
- rel="noopener noreferrer nofollow"
- class="js-deploy-url">
- <i
- class="fa fa-external-link"
- aria-hidden="true" />
- {{deployment.external_url_formatted}}
- </a>
- <span
- v-if="hasDeploymentTime(deployment)"
- :data-title="deployment.deployed_at_formatted"
- class="js-deploy-time"
- data-toggle="tooltip"
- data-placement="top">
- {{formatDate(deployment.deployed_at)}}
+ <div class="media-body space-children">
+ <span>
+ <span
+ v-if="hasDeploymentMeta(deployment)">
+ Deployed to
+ </span>
+ <a
+ v-if="hasDeploymentMeta(deployment)"
+ :href="deployment.url"
+ target="_blank"
+ rel="noopener noreferrer nofollow"
+ class="js-deploy-meta inline">
+ {{deployment.name}}
+ </a>
+ <span
+ v-if="hasExternalUrls(deployment)">
+ on
+ </span>
+ <a
+ v-if="hasExternalUrls(deployment)"
+ :href="deployment.external_url"
+ target="_blank"
+ rel="noopener noreferrer nofollow"
+ class="js-deploy-url inline">
+ <i
+ class="fa fa-external-link"
+ aria-hidden="true" />
+ {{deployment.external_url_formatted}}
+ </a>
+ <span
+ v-if="hasDeploymentTime(deployment)"
+ :data-title="deployment.deployed_at_formatted"
+ class="js-deploy-time"
+ data-toggle="tooltip"
+ data-placement="top">
+ {{formatDate(deployment.deployed_at)}}
+ </span>
</span>
<button
type="button"
@@ -104,13 +100,13 @@ export default {
class="btn btn-default btn-xs">
Stop environment
</button>
- </span>
+ <mr-widget-memory-usage
+ v-if="deployment.metrics_url"
+ :metrics-url="deployment.metrics_url"
+ :metrics-monitoring-url="deployment.metrics_monitoring_url"
+ />
+ </div>
</div>
- <mr-widget-memory-usage
- v-if="deployment.metrics_url"
- :metrics-url="deployment.metrics_url"
- :metrics-monitoring-url="deployment.metrics_monitoring_url"
- />
</div>
</div>
`,
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.js
index 8430548903c..c05a76a3b4a 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.js
@@ -1,3 +1,4 @@
+import tooltip from '../../vue_shared/directives/tooltip';
import '../../lib/utils/text_utility';
export default {
@@ -5,6 +6,9 @@ export default {
props: {
mr: { type: Object, required: true },
},
+ directives: {
+ tooltip,
+ },
computed: {
shouldShowCommitsBehindText() {
return this.mr.divergedCommitsCount > 0;
@@ -29,18 +33,51 @@ export default {
},
template: `
<div class="mr-source-target">
- <div
- v-if="mr.isOpen"
- class="pull-right">
+ <div class="normal">
+ <strong>
+ Request to merge
+ <span
+ class="label-branch"
+ :class="{'label-truncated': isBranchTitleLong(mr.sourceBranch)}"
+ :title="isBranchTitleLong(mr.sourceBranch) ? mr.sourceBranch : ''"
+ data-placement="bottom"
+ :v-tooltip="isBranchTitleLong(mr.sourceBranch)"
+ v-html="mr.sourceBranchLink"></span>
+ <button
+ v-tooltip
+ class="btn btn-transparent btn-clipboard"
+ data-title="Copy branch name to clipboard"
+ :data-clipboard-text="branchNameClipboardData">
+ <i
+ aria-hidden="true"
+ class="fa fa-clipboard"></i>
+ </button>
+ into
+ <span
+ class="label-branch"
+ :v-tooltip="isBranchTitleLong(mr.sourceBranch)"
+ :class="{'label-truncatedtooltip': isBranchTitleLong(mr.targetBranch)}"
+ :title="isBranchTitleLong(mr.targetBranch) ? mr.targetBranch : ''"
+ data-placement="bottom">
+ <a :href="mr.targetBranchTreePath">{{mr.targetBranch}}</a>
+ </span>
+ </strong>
+ <span
+ v-if="shouldShowCommitsBehindText"
+ class="diverged-commits-count">
+ (<a :href="mr.targetBranchPath">{{mr.divergedCommitsCount}} {{commitsText}} behind</a>)
+ </span>
+ </div>
+ <div v-if="mr.isOpen">
<a
href="#modal_merge_info"
data-toggle="modal"
- class="btn inline btn-grouped btn-sm">
+ class="btn btn-small inline">
Check out branch
</a>
- <span class="dropdown inline prepend-left-5">
+ <span class="dropdown inline prepend-left-10">
<a
- class="btn btn-sm dropdown-toggle"
+ class="btn btn-xs dropdown-toggle"
data-toggle="dropdown"
aria-label="Download as"
role="button">
@@ -69,38 +106,6 @@ export default {
</ul>
</span>
</div>
- <div class="normal">
- <strong>
- Request to merge
- <span
- class="label-branch"
- :class="{'label-truncated has-tooltip': isBranchTitleLong(mr.sourceBranch)}"
- :title="isBranchTitleLong(mr.sourceBranch) ? mr.sourceBranch : ''"
- data-placement="bottom"
- v-html="mr.sourceBranchLink"></span>
- <button
- class="btn btn-transparent btn-clipboard has-tooltip"
- data-title="Copy branch name to clipboard"
- :data-clipboard-text="branchNameClipboardData">
- <i
- aria-hidden="true"
- class="fa fa-clipboard"></i>
- </button>
- into
- <span
- class="label-branch"
- :class="{'label-truncated has-tooltip': isBranchTitleLong(mr.targetBranch)}"
- :title="isBranchTitleLong(mr.targetBranch) ? mr.targetBranch : ''"
- data-placement="bottom">
- <a :href="mr.targetBranchTreePath">{{mr.targetBranch}}</a>
- </span>
- </strong>
- <span
- v-if="shouldShowCommitsBehindText"
- class="diverged-commits-count">
- (<a :href="mr.targetBranchPath">{{mr.divergedCommitsCount}} {{commitsText}} behind</a>)
- </span>
- </div>
</div>
`,
};
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_memory_usage.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_memory_usage.js
index 534e2a88eff..a4e34116c33 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_memory_usage.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_memory_usage.js
@@ -120,13 +120,12 @@ export default {
},
template: `
<div class="mr-info-list clearfix mr-memory-usage js-mr-memory-usage">
- <div class="legend"></div>
<p
v-if="shouldShowLoading"
class="usage-info js-usage-info usage-info-loading">
<i
class="fa fa-spinner fa-spin usage-info-load-spinner"
- aria-hidden="true" />Loading deployment statistics.
+ aria-hidden="true" />Loading deployment statistics
</p>
<p
v-if="shouldShowMemoryGraph"
@@ -136,12 +135,12 @@ export default {
<p
v-if="shouldShowLoadFailure"
class="usage-info js-usage-info usage-info-failed">
- Failed to load deployment statistics.
+ Failed to load deployment statistics
</p>
<p
v-if="shouldShowMetricsUnavailable"
class="usage-info js-usage-info usage-info-unavailable">
- Deployment statistics are not available currently.
+ Deployment statistics are not available currently
</p>
<mr-memory-graph
v-if="shouldShowMemoryGraph"
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.js
index 2fecebce7a0..1d9f9863dd9 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.js
@@ -16,7 +16,7 @@ export default {
<a
data-toggle="modal"
href="#modal_merge_info">
- command line.
+ command line
</a>
</section>
`,
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.js
index c02e10128e2..6c2e9ba1d30 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.js
@@ -29,58 +29,55 @@ export default {
},
template: `
<div class="mr-widget-heading">
- <div class="ci-widget">
+ <div class="ci-widget media">
<template v-if="hasCIError">
- <div class="ci-status-icon ci-status-icon-failed ci-error js-ci-error">
- <span class="js-icon-link icon-link">
- <span
- v-html="svg"
- aria-hidden="true"></span>
- </span>
+ <div class="ci-status-icon ci-status-icon-failed ci-error js-ci-error append-right-10">
+ <span
+ v-html="svg"
+ aria-hidden="true"></span>
+ </div>
+ <div class="media-body">
+ Could not connect to the CI server. Please check your settings and try again
</div>
- <span>Could not connect to the CI server. Please check your settings and try again.</span>
</template>
<template v-else>
- <div>
+ <div class="ci-status-icon append-right-10">
<a
class="icon-link"
:href="this.status.details_path">
<ci-icon :status="status" />
</a>
</div>
- <span>
- Pipeline
- <a
- :href="mr.pipeline.path"
- class="pipeline-id">#{{mr.pipeline.id}}</a>
- {{mr.pipeline.details.status.label}}
- </span>
- <span
- v-if="mr.pipeline.details.stages.length > 0">
- with {{stageText}}
- </span>
- <div class="mr-widget-pipeline-graph">
- <div class="stage-cell">
- <div
- v-if="mr.pipeline.details.stages.length > 0"
- v-for="stage in mr.pipeline.details.stages"
- class="stage-container dropdown js-mini-pipeline-graph">
- <pipeline-stage :stage="stage" />
- </div>
- </div>
+ <div class="media-body">
+ <span>
+ Pipeline
+ <a
+ :href="mr.pipeline.path"
+ class="pipeline-id">#{{mr.pipeline.id}}</a>
+ </span>
+ <span class="mr-widget-pipeline-graph">
+ <span class="stage-cell">
+ <div
+ v-if="mr.pipeline.details.stages.length > 0"
+ v-for="stage in mr.pipeline.details.stages"
+ class="stage-container dropdown js-mini-pipeline-graph">
+ <pipeline-stage :stage="stage" />
+ </div>
+ </span>
+ </span>
+ <span>
+ {{mr.pipeline.details.status.label}} for
+ <a
+ :href="mr.pipeline.commit.commit_path"
+ class="commit-sha js-commit-link">
+ {{mr.pipeline.commit.short_id}}</a>.
+ </span>
+ <span
+ v-if="mr.pipeline.coverage"
+ class="js-mr-coverage">
+ Coverage {{mr.pipeline.coverage}}%
+ </span>
</div>
- <span>
- for
- <a
- :href="mr.pipeline.commit.commit_path"
- class="commit-sha js-commit-link">
- {{mr.pipeline.commit.short_id}}</a>.
- </span>
- <span
- v-if="mr.pipeline.coverage"
- class="js-mr-coverage">
- Coverage {{mr.pipeline.coverage}}%.
- </span>
</template>
</div>
</div>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.js
index 205804670fa..563267ad044 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.js
@@ -2,37 +2,32 @@ export default {
name: 'MRWidgetRelatedLinks',
props: {
relatedLinks: { type: Object, required: true },
+ state: { type: String, required: false },
},
computed: {
hasLinks() {
const { closing, mentioned, assignToMe } = this.relatedLinks;
return closing || mentioned || assignToMe;
},
- },
- methods: {
- hasMultipleIssues(text) {
- return !text ? false : text.match(/<\/a> and <a/);
- },
- issueLabel(field) {
- return this.hasMultipleIssues(this.relatedLinks[field]) ? 'issues' : 'issue';
- },
- verbLabel(field) {
- return this.hasMultipleIssues(this.relatedLinks[field]) ? 'are' : 'is';
+ closesText() {
+ if (this.state === 'merged') {
+ return 'Closed';
+ }
+ if (this.state === 'closed') {
+ return 'Did not close';
+ }
+ return 'Closes';
},
},
template: `
<section
v-if="hasLinks"
class="mr-info-list mr-links">
- <div class="legend"></div>
<p v-if="relatedLinks.closing">
- Closes {{issueLabel('closing')}}
- <span v-html="relatedLinks.closing"></span>.
+ {{closesText}} <span v-html="relatedLinks.closing"></span>
</p>
<p v-if="relatedLinks.mentioned">
- <span class="capitalize">{{issueLabel('mentioned')}}</span>
- <span v-html="relatedLinks.mentioned"></span>
- {{verbLabel('mentioned')}} mentioned but will not be closed.
+ Mentions <span v-html="relatedLinks.mentioned"></span>
</p>
<p v-if="relatedLinks.assignToMe">
<span v-html="relatedLinks.assignToMe"></span>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.js
new file mode 100644
index 00000000000..b01c923311b
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.js
@@ -0,0 +1,36 @@
+import ciIcon from '../../vue_shared/components/ci_icon.vue';
+import loadingIcon from '../../vue_shared/components/loading_icon.vue';
+
+export default {
+ props: {
+ status: { type: String, required: true },
+ showDisabledButton: { type: Boolean, required: false },
+ },
+ components: {
+ ciIcon,
+ loadingIcon,
+ },
+ computed: {
+ statusObj() {
+ return {
+ group: this.status,
+ icon: `icon_status_${this.status}`,
+ };
+ },
+ },
+ template: `
+ <div class="space-children flex-container-block append-right-10">
+ <div v-if="status === 'loading'" class="mr-widget-icon">
+ <loading-icon />
+ </div>
+ <ci-icon v-else :status="statusObj" />
+ <button
+ v-if="showDisabledButton"
+ type="button"
+ class="btn btn-success btn-small"
+ disabled="true">
+ Merge
+ </button>
+ </div>
+ `,
+};
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_archived.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_archived.js
index c7f25a1697c..2b16a2d6817 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_archived.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_archived.js
@@ -1,16 +1,26 @@
+import statusIcon from '../mr_widget_status_icon';
+
export default {
name: 'MRWidgetArchived',
+ components: {
+ statusIcon,
+ },
template: `
- <div class="mr-widget-body">
- <button
- type="button"
- class="btn btn-success btn-small"
- disabled="true">
- Merge
- </button>
- <span class="bold">
- This project is archived, write access has been disabled.
- </span>
+ <div class="mr-widget-body media">
+ <div class="space-children">
+ <status-icon status="failed" />
+ <button
+ type="button"
+ class="btn btn-success btn-small"
+ disabled="true">
+ Merge
+ </button>
+ </div>
+ <div class="media-body">
+ <span class="bold">
+ This project is archived, write access has been disabled
+ </span>
+ </div>
</div>
`,
};
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.js
index 4063859d5d0..5648208f7b1 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.js
@@ -1,4 +1,5 @@
import eventHub from '../../event_hub';
+import statusIcon from '../mr_widget_status_icon';
export default {
name: 'MRWidgetAutoMergeFailed',
@@ -10,6 +11,9 @@ export default {
isRefreshing: false,
};
},
+ components: {
+ statusIcon,
+ },
methods: {
refreshWidget() {
this.isRefreshing = true;
@@ -19,18 +23,16 @@ export default {
},
},
template: `
- <div class="mr-widget-body">
- <button
- class="btn btn-success btn-small"
- disabled="true"
- type="button">
- Merge
- </button>
- <span class="bold danger">
- This merge request failed to be merged automatically.
+ <div class="mr-widget-body media">
+ <status-icon status="failed" />
+ <div class="media-body space-children">
+ <span class="bold">
+ <template v-if="mr.mergeError">{{mr.mergeError}}.</template>
+ This merge request failed to be merged automatically
+ </span>
<button
@click="refreshWidget"
- :class="{ disabled: isRefreshing }"
+ :disabled="isRefreshing"
type="button"
class="btn btn-xs btn-default">
<i
@@ -39,9 +41,6 @@ export default {
aria-hidden="true" />
Refresh
</button>
- </span>
- <div class="merge-error-text danger bold">
- {{mr.mergeError}}
</div>
</div>
`,
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.js
index 8515b54e62d..aaf9d3304a4 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.js
@@ -1,19 +1,18 @@
+import statusIcon from '../mr_widget_status_icon';
+
export default {
name: 'MRWidgetChecking',
+ components: {
+ statusIcon,
+ },
template: `
- <div class="mr-widget-body">
- <button
- type="button"
- class="btn btn-success btn-small"
- disabled="true">
- Merge
- </button>
- <span class="bold">
- Checking ability to merge automatically.
- <i
- class="fa fa-spinner fa-spin"
- aria-hidden="true" />
- </span>
+ <div class="mr-widget-body media">
+ <status-icon status="loading" showDisabledButton />
+ <div class="media-body space-children">
+ <span class="bold">
+ Checking ability to merge automatically
+ </span>
+ </div>
</div>
`,
};
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.js
index fc2e42c6821..4078aad7f83 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.js
@@ -1,4 +1,5 @@
import mrWidgetAuthorTime from '../../components/mr_widget_author_time';
+import statusIcon from '../mr_widget_status_icon';
export default {
name: 'MRWidgetClosed',
@@ -7,24 +8,28 @@ export default {
},
components: {
'mr-widget-author-and-time': mrWidgetAuthorTime,
+ statusIcon,
},
template: `
- <div class="mr-widget-body">
- <mr-widget-author-and-time
- actionText="Closed by"
- :author="mr.closedBy"
- :dateTitle="mr.updatedAt"
- :dateReadable="mr.closedAt"
- />
- <section>
- <p>
- The changes were not merged into
- <a
- :href="mr.targetBranchPath"
- class="label-branch">
- {{mr.targetBranch}}</a>.
- </p>
- </section>
+ <div class="mr-widget-body media">
+ <status-icon status="failed" />
+ <div class="media-body">
+ <mr-widget-author-and-time
+ actionText="Closed by"
+ :author="mr.closedBy"
+ :dateTitle="mr.updatedAt"
+ :dateReadable="mr.closedAt"
+ />
+ <section class="mr-info-list">
+ <p>
+ The changes were not merged into
+ <a
+ :href="mr.targetBranchPath"
+ class="label-branch">
+ {{mr.targetBranch}}</a>
+ </p>
+ </section>
+ </div>
</div>
`,
};
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.js
index 36596c6f37e..f9cb79a0bc1 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.js
@@ -1,27 +1,25 @@
+import statusIcon from '../mr_widget_status_icon';
+
export default {
name: 'MRWidgetConflicts',
props: {
mr: { type: Object, required: true },
},
+ components: {
+ statusIcon,
+ },
template: `
- <div class="mr-widget-body">
- <button
- type="button"
- class="btn btn-success btn-small"
- disabled="true">
- Merge
- </button>
- <span class="bold">
- There are merge conflicts.
- <span v-if="!mr.canMerge">
- Resolve these conflicts or ask someone with write access to this repository to merge it locally.
+ <div class="mr-widget-body media">
+ <status-icon status="failed" showDisabledButton />
+ <div class="media-body space-children">
+ <span class="bold">
+ There are merge conflicts<span v-if="!mr.canMerge">.</span>
+ <span v-if="!mr.canMerge">
+ Resolve these conflicts or ask someone with write access to this repository to merge it locally
+ </span>
</span>
- </span>
- <div
- v-if="mr.canMerge"
- class="btn-group">
<a
- v-if="mr.conflictResolutionPath"
+ v-if="mr.canMerge && mr.conflictResolutionPath"
:href="mr.conflictResolutionPath"
class="btn btn-default btn-xs js-resolve-conflicts-button">
Resolve conflicts
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.js
index 600b4d42e3d..1cb24549d53 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.js
@@ -1,3 +1,4 @@
+import statusIcon from '../mr_widget_status_icon';
import eventHub from '../../event_hub';
export default {
@@ -38,39 +39,40 @@ export default {
}
},
},
+ components: {
+ statusIcon,
+ },
template: `
- <div class="mr-widget-body">
- <button
- class="btn btn-success btn-small"
- disabled="true"
- type="button">
- Merge
- </button>
- <span
- v-if="!isRefreshing"
- class="bold danger">
- <span
- class="has-error-message"
- v-if="mr.mergeError">
- {{mr.mergeError}}
- </span>
- <span v-else>Merge failed.</span>
- <span
- :class="{ 'has-custom-error': mr.mergeError }">
- Refreshing in {{timerText}} to show the updated status...
+ <div class="mr-widget-body media">
+ <template v-if="isRefreshing">
+ <status-icon status="loading" />
+ <span class="media-body bold js-refresh-label">
+ Refreshing now
</span>
- <button
- @click="refresh"
- class="btn btn-default btn-xs js-refresh-button"
- type="button">
- Refresh now
- </button>
- </span>
- <span
- v-if="isRefreshing"
- class="bold js-refresh-label">
- Refreshing now...
- </span>
+ </template>
+ <template v-else>
+ <status-icon status="failed" showDisabledButton />
+ <div class="media-body space-children">
+ <span class="bold">
+ <span
+ class="has-error-message"
+ v-if="mr.mergeError">
+ {{mr.mergeError}}.
+ </span>
+ <span v-else>Merge failed.</span>
+ <span
+ :class="{ 'has-custom-error': mr.mergeError }">
+ Refreshing in {{timerText}} to show the updated status...
+ </span>
+ </span>
+ <button
+ @click="refresh"
+ class="btn btn-default btn-xs js-refresh-button"
+ type="button">
+ Refresh now
+ </button>
+ </div>
+ </template>
</div>
`,
};
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_locked.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_locked.js
deleted file mode 100644
index 0bd31731a0b..00000000000
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_locked.js
+++ /dev/null
@@ -1,24 +0,0 @@
-export default {
- name: 'MRWidgetLocked',
- props: {
- mr: { type: Object, required: true },
- },
- template: `
- <div class="mr-widget-body mr-state-locked">
- <span class="state-label">Locked</span>
- This merge request is in the process of being merged, during which time it is locked and cannot be closed.
- <i
- class="fa fa-spinner fa-spin"
- aria-hidden="true" />
- <section class="mr-info-list mr-links">
- <div class="legend"></div>
- <p>
- The changes will be merged into
- <span class="label-branch">
- <a :href="mr.targetBranchPath">{{mr.targetBranch}}</a>
- </span>.
- </p>
- </section>
- </div>
- `,
-};
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.js
index 419d174f3ff..bdfd4d9667c 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.js
@@ -1,5 +1,5 @@
/* global Flash */
-
+import statusIcon from '../mr_widget_status_icon';
import MRWidgetAuthor from '../../components/mr_widget_author';
import eventHub from '../../event_hub';
@@ -11,6 +11,7 @@ export default {
},
components: {
'mr-widget-author': MRWidgetAuthor,
+ statusIcon,
},
data() {
return {
@@ -61,56 +62,56 @@ export default {
},
},
template: `
- <div class="mr-widget-body">
- <h4>
- Set by
- <mr-widget-author :author="mr.setToMWPSBy" />
- to be merged automatically when the pipeline succeeds.
- <a
- v-if="mr.canCancelAutomaticMerge"
- @click.prevent="cancelAutomaticMerge"
- :disabled="isCancellingAutoMerge"
- role="button"
- href="#"
- class="btn btn-xs btn-default js-cancel-auto-merge">
- <i
- v-if="isCancellingAutoMerge"
- class="fa fa-spinner fa-spin"
- aria-hidden="true" />
- Cancel automatic merge
- </a>
- </h4>
- <section class="mr-info-list">
- <div class="legend"></div>
- <p>The changes will be merged into
- <a
- :href="mr.targetBranchPath"
- class="label-branch">
- {{mr.targetBranch}}
- </a>.
- </p>
- <p v-if="mr.shouldRemoveSourceBranch">
- The source branch will be removed.
- </p>
- <p
- v-else
- class="with-button">
- The source branch will not be removed.
+ <div class="mr-widget-body media">
+ <status-icon status="success" />
+ <div class="media-body">
+ <h4>
+ Set by
+ <mr-widget-author :author="mr.setToMWPSBy" />
+ to be merged automatically when the pipeline succeeds
<a
- v-if="canRemoveSourceBranch"
- :disabled="isRemovingSourceBranch"
- @click.prevent="removeSourceBranch"
+ v-if="mr.canCancelAutomaticMerge"
+ @click.prevent="cancelAutomaticMerge"
+ :disabled="isCancellingAutoMerge"
role="button"
- class="btn btn-xs btn-default js-remove-source-branch"
- href="#">
+ href="#"
+ class="btn btn-xs btn-default js-cancel-auto-merge">
<i
- v-if="isRemovingSourceBranch"
- class="fa fa-spinner fa-spin"
- aria-hidden="true" />
- Remove source branch
+ v-if="isCancellingAutoMerge"
+ class="fa fa-spinner fa-spin"
+ aria-hidden="true" />
+ Cancel automatic merge
</a>
- </p>
- </section>
+ </h4>
+ <section class="mr-info-list">
+ <p>The changes will be merged into
+ <a
+ :href="mr.targetBranchPath"
+ class="label-branch">
+ {{mr.targetBranch}}
+ </a>
+ </p>
+ <p v-if="mr.shouldRemoveSourceBranch">
+ The source branch will be removed
+ </p>
+ <p v-else>
+ The source branch will not be removed
+ <a
+ v-if="canRemoveSourceBranch"
+ :disabled="isRemovingSourceBranch"
+ @click.prevent="removeSourceBranch"
+ role="button"
+ class="btn btn-xs btn-default js-remove-source-branch"
+ href="#">
+ <i
+ v-if="isRemovingSourceBranch"
+ class="fa fa-spinner fa-spin"
+ aria-hidden="true" />
+ Remove source branch
+ </a>
+ </p>
+ </section>
+ </div>
</div>
`,
};
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.js
index c7d32d18141..e452260a4d0 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.js
@@ -1,6 +1,9 @@
/* global Flash */
import mrWidgetAuthorTime from '../../components/mr_widget_author_time';
+import tooltip from '../../../vue_shared/directives/tooltip';
+import loadingIcon from '../../../vue_shared/components/loading_icon.vue';
+import statusIcon from '../mr_widget_status_icon';
import eventHub from '../../event_hub';
export default {
@@ -9,14 +12,19 @@ export default {
mr: { type: Object, required: true },
service: { type: Object, required: true },
},
- components: {
- 'mr-widget-author-and-time': mrWidgetAuthorTime,
- },
data() {
return {
isMakingRequest: false,
};
},
+ directives: {
+ tooltip,
+ },
+ components: {
+ 'mr-widget-author-and-time': mrWidgetAuthorTime,
+ loadingIcon,
+ statusIcon,
+ },
computed: {
shouldShowRemoveSourceBranch() {
const { sourceBranchRemoved, isRemovingSourceBranch, canRemoveSourceBranch } = this.mr;
@@ -55,75 +63,77 @@ export default {
},
},
template: `
- <div class="mr-widget-body">
- <mr-widget-author-and-time
- actionText="Merged by"
- :author="mr.mergedBy"
- :dateTitle="mr.updatedAt"
- :dateReadable="mr.mergedAt" />
- <section class="mr-info-list">
- <div class="legend"></div>
- <p>
- The changes were merged into
- <span class="label-branch">
- <a :href="mr.targetBranchPath">{{mr.targetBranch}}</a>
- </span>
- </p>
- <p v-if="mr.sourceBranchRemoved">The source branch has been removed.</p>
- <p v-if="shouldShowRemoveSourceBranch">
- You can remove source branch now.
- <button
- @click="removeSourceBranch"
- :class="{ disabled: isMakingRequest }"
- type="button"
- class="btn btn-xs btn-default js-remove-branch-button">
- Remove Source Branch
- </button>
- </p>
- <p v-if="shouldShowSourceBranchRemoving">
- <i
- class="fa fa-spinner fa-spin"
- aria-hidden="true" />
- The source branch is being removed.
- </p>
- </section>
- <div
- v-if="shouldShowMergedButtons"
- class="merged-buttons clearfix">
- <a
- v-if="mr.canRevertInCurrentMR"
- class="btn btn-close btn-sm has-tooltip"
- href="#modal-revert-commit"
- data-toggle="modal"
- data-container="body"
- title="Revert this merge request in a new merge request">
- Revert
- </a>
- <a
- v-else-if="mr.revertInForkPath"
- class="btn btn-close btn-sm has-tooltip"
- data-method="post"
- :href="mr.revertInForkPath"
- title="Revert this merge request in a new merge request">
- Revert
- </a>
- <a
- v-if="mr.canCherryPickInCurrentMR"
- class="btn btn-default btn-sm has-tooltip"
- href="#modal-cherry-pick-commit"
- data-toggle="modal"
- data-container="body"
- title="Cherry-pick this merge request in a new merge request">
- Cherry-pick
- </a>
- <a
- v-else-if="mr.cherryPickInForkPath"
- class="btn btn-default btn-sm has-tooltip"
- data-method="post"
- :href="mr.cherryPickInForkPath"
- title="Cherry-pick this merge request in a new merge request">
- Cherry-pick
- </a>
+ <div class="mr-widget-body media">
+ <status-icon status="success" />
+ <div class="media-body">
+ <div class="space-children">
+ <mr-widget-author-and-time
+ actionText="Merged by"
+ :author="mr.mergedBy"
+ :dateTitle="mr.updatedAt"
+ :dateReadable="mr.mergedAt" />
+ <a
+ v-if="mr.canRevertInCurrentMR"
+ v-tooltip
+ class="btn btn-close btn-xs"
+ href="#modal-revert-commit"
+ data-toggle="modal"
+ data-container="body"
+ title="Revert this merge request in a new merge request">
+ Revert
+ </a>
+ <a
+ v-else-if="mr.revertInForkPath"
+ v-tooltip
+ class="btn btn-close btn-xs"
+ data-method="post"
+ :href="mr.revertInForkPath"
+ title="Revert this merge request in a new merge request">
+ Revert
+ </a>
+ <a
+ v-if="mr.canCherryPickInCurrentMR"
+ v-tooltip
+ class="btn btn-default btn-xs"
+ href="#modal-cherry-pick-commit"
+ data-toggle="modal"
+ data-container="body"
+ title="Cherry-pick this merge request in a new merge request">
+ Cherry-pick
+ </a>
+ <a
+ v-else-if="mr.cherryPickInForkPath"
+ v-tooltip
+ class="btn btn-default btn-xs"
+ data-method="post"
+ :href="mr.cherryPickInForkPath"
+ title="Cherry-pick this merge request in a new merge request">
+ Cherry-pick
+ </a>
+ </div>
+ <section class="mr-info-list">
+ <p>
+ The changes were merged into
+ <span class="label-branch">
+ <a :href="mr.targetBranchPath">{{mr.targetBranch}}</a>
+ </span>
+ </p>
+ <p v-if="mr.sourceBranchRemoved">The source branch has been removed</p>
+ <p v-if="shouldShowRemoveSourceBranch" class="space-children">
+ <span>You can remove source branch now</span>
+ <button
+ @click="removeSourceBranch"
+ :disabled="isMakingRequest"
+ type="button"
+ class="btn btn-xs btn-default js-remove-branch-button">
+ Remove Source Branch
+ </button>
+ </p>
+ <p v-if="shouldShowSourceBranchRemoving">
+ <loading-icon inline />
+ <span>The source branch is being removed</span>
+ </p>
+ </section>
</div>
</div>
`,
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merging.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merging.js
new file mode 100644
index 00000000000..f6d1a4feeb2
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merging.js
@@ -0,0 +1,29 @@
+import statusIcon from '../mr_widget_status_icon';
+
+export default {
+ name: 'MRWidgetMerging',
+ props: {
+ mr: { type: Object, required: true },
+ },
+ components: {
+ statusIcon,
+ },
+ template: `
+ <div class="mr-widget-body mr-state-locked media">
+ <status-icon status="loading" />
+ <div class="media-body">
+ <h4>
+ This merge request is in the process of being merged
+ </h4>
+ <section class="mr-info-list">
+ <p>
+ The changes will be merged into
+ <span class="label-branch">
+ <a :href="mr.targetBranchPath">{{mr.targetBranch}}</a>
+ </span>
+ </p>
+ </section>
+ </div>
+ </div>
+ `,
+};
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.js
index 328382485f6..9f0a359d01a 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.js
@@ -1,3 +1,5 @@
+import statusIcon from '../mr_widget_status_icon';
+import tooltip from '../../../vue_shared/directives/tooltip';
import mrWidgetMergeHelp from '../../components/mr_widget_merge_help';
export default {
@@ -5,30 +7,37 @@ export default {
props: {
mr: { type: Object, required: true },
},
+ directives: {
+ tooltip,
+ },
components: {
'mr-widget-merge-help': mrWidgetMergeHelp,
+ statusIcon,
},
computed: {
missingBranchName() {
return this.mr.sourceBranchRemoved ? 'source' : 'target';
},
+ message() {
+ return `If the ${this.missingBranchName} branch exists in your local repository, you can merge this merge request manually using the command line`;
+ },
},
template: `
- <div class="mr-widget-body">
- <button
- type="button"
- class="btn btn-success btn-small"
- disabled="true">
- Merge
- </button>
- <span class="bold js-branch-text">
- <span class="capitalize">
- {{missingBranchName}}
- </span> branch does not exist.
- Please restore the {{missingBranchName}} branch or use a different {{missingBranchName}} branch.
- </span>
- <mr-widget-merge-help
- :missing-branch="missingBranchName" />
+ <div class="mr-widget-body media">
+ <status-icon status="failed" showDisabledButton />
+ <div class="media-body space-children">
+ <span class="bold js-branch-text">
+ <span class="capitalize">
+ {{missingBranchName}}
+ </span> branch does not exist.
+ Please restore it or use a different {{missingBranchName}} branch
+ <i
+ v-tooltip
+ class="fa fa-question-circle"
+ :title="message"
+ :aria-label="message"></i>
+ </span>
+ </div>
</div>
`,
};
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_not_allowed.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_not_allowed.js
index 07169b349be..797511d4e3a 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_not_allowed.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_not_allowed.js
@@ -1,17 +1,19 @@
+import statusIcon from '../mr_widget_status_icon';
+
export default {
name: 'MRWidgetNotAllowed',
+ components: {
+ statusIcon,
+ },
template: `
- <div class="mr-widget-body">
- <button
- type="button"
- class="btn btn-success btn-small"
- disabled="true">
- Merge
- </button>
- <span class="bold">
- Ready to be merged automatically.
- Ask someone with write access to this repository to merge this request.
- </span>
+ <div class="mr-widget-body media">
+ <status-icon status="success" showDisabledButton />
+ <div class="media-body space-children">
+ <span class="bold">
+ Ready to be merged automatically.
+ Ask someone with write access to this repository to merge this request
+ </span>
+ </div>
</div>
`,
};
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_nothing_to_merge.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_nothing_to_merge.js
index 375a382615a..ebfd6765934 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_nothing_to_merge.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_nothing_to_merge.js
@@ -12,7 +12,7 @@ export default {
return { emptyStateSVG };
},
template: `
- <div class="mr-widget-body empty-state">
+ <div class="mr-widget-body mr-widget-empty-state">
<div class="row">
<div class="artwork col-sm-5 col-sm-push-7 col-xs-12 text-center">
<span v-html="emptyStateSVG"></span>
@@ -29,12 +29,14 @@ export default {
Currently there are no changes in this merge request's source branch.
Please push new commits or use a different branch.
</p>
- <a
- v-if="mr.newBlobPath"
- :href="mr.newBlobPath"
- class="btn btn-inverted btn-save">
- Create file
- </a>
+ <div>
+ <a
+ v-if="mr.newBlobPath"
+ :href="mr.newBlobPath"
+ class="btn btn-inverted btn-save">
+ Create file
+ </a>
+ </div>
</div>
</div>
</div>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.js
index 31c53b679ed..167a0d4613a 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.js
@@ -1,16 +1,18 @@
+import statusIcon from '../mr_widget_status_icon';
+
export default {
name: 'MRWidgetPipelineBlocked',
+ components: {
+ statusIcon,
+ },
template: `
- <div class="mr-widget-body">
- <button
- type="button"
- class="btn btn-success btn-small"
- disabled="true">
- Merge
- </button>
- <span class="bold">
- Pipeline blocked. The pipeline for this merge request requires a manual action to proceed.
- </span>
+ <div class="mr-widget-body media">
+ <status-icon status="failed" showDisabledButton />
+ <div class="media-body space-children">
+ <span class="bold">
+ Pipeline blocked. The pipeline for this merge request requires a manual action to proceed
+ </span>
+ </div>
</div>
`,
};
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_failed.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_failed.js
index 002820123ca..c5be9a0530a 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_failed.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_failed.js
@@ -1,16 +1,18 @@
+import statusIcon from '../mr_widget_status_icon';
+
export default {
name: 'MRWidgetPipelineBlocked',
+ components: {
+ statusIcon,
+ },
template: `
- <div class="mr-widget-body">
- <button
- class="btn btn-success btn-small"
- disabled="true"
- type="button">
- Merge
- </button>
- <span class="bold">
- The pipeline for this merge request failed. Please retry the job or push a new commit to fix the failure.
- </span>
+ <div class="mr-widget-body media">
+ <status-icon status="failed" showDisabledButton />
+ <div class="media-body space-children">
+ <span class="bold">
+ The pipeline for this merge request failed. Please retry the job or push a new commit to fix the failure
+ </span>
+ </div>
</div>
`,
};
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js
index fcd4fdaf09f..65187754009 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js
@@ -1,8 +1,8 @@
/* global Flash */
-
import successSvg from 'icons/_icon_status_success.svg';
import warningSvg from 'icons/_icon_status_warning.svg';
import simplePoll from '~/lib/utils/simple_poll';
+import statusIcon from '../mr_widget_status_icon';
import eventHub from '../../event_hub';
export default {
@@ -25,6 +25,9 @@ export default {
warningSvg,
};
},
+ components: {
+ statusIcon,
+ },
computed: {
commitMessageLinkTitle() {
const withDesc = 'Include description in commit message';
@@ -196,84 +199,98 @@ export default {
},
},
template: `
- <div class="mr-widget-body">
- <span class="btn-group">
- <button
- @click="handleMergeButtonClick()"
- :disabled="isMergeButtonDisabled"
- :class="mergeButtonClass"
- type="button">
- <i
- v-if="isMakingRequest"
- class="fa fa-spinner fa-spin"
- aria-hidden="true" />
- {{mergeButtonText}}
- </button>
- <button
- v-if="shouldShowMergeOptionsDropdown"
- :disabled="isMergeButtonDisabled"
- type="button"
- class="btn btn-small btn-info dropdown-toggle"
- data-toggle="dropdown">
- <i
- class="fa fa-caret-down"
- aria-hidden="true" />
- <span class="sr-only">
- Select merge moment
+ <div class="mr-widget-body media">
+ <status-icon status="success" />
+ <div class="media-body">
+ <div class="media space-children">
+ <span class="btn-group">
+ <button
+ @click="handleMergeButtonClick()"
+ :disabled="isMergeButtonDisabled"
+ :class="mergeButtonClass"
+ type="button">
+ <i
+ v-if="isMakingRequest"
+ class="fa fa-spinner fa-spin"
+ aria-hidden="true" />
+ {{mergeButtonText}}
+ </button>
+ <button
+ v-if="shouldShowMergeOptionsDropdown"
+ :disabled="isMergeButtonDisabled"
+ type="button"
+ class="btn btn-small btn-info dropdown-toggle js-merge-moment"
+ data-toggle="dropdown"
+ aria-label="Select merge moment">
+ <i
+ class="fa fa-chevron-down"
+ aria-hidden="true" />
+ </button>
+ <ul
+ v-if="shouldShowMergeOptionsDropdown"
+ class="dropdown-menu dropdown-menu-right"
+ role="menu">
+ <li>
+ <a
+ @click.prevent="handleMergeButtonClick(true)"
+ class="merge_when_pipeline_succeeds"
+ href="#">
+ <span class="media">
+ <span
+ v-html="successSvg"
+ class="merge-opt-icon"
+ aria-hidden="true"></span>
+ <span class="media-body merge-opt-title">Merge when pipeline succeeds</span>
+ </span>
+ </a>
+ </li>
+ <li>
+ <a
+ @click.prevent="handleMergeButtonClick(false, true)"
+ class="accept-merge-request"
+ href="#">
+ <span class="media">
+ <span
+ v-html="warningSvg"
+ class="merge-opt-icon"
+ aria-hidden="true"></span>
+ <span class="media-body merge-opt-title">Merge immediately</span>
+ </span>
+ </a>
+ </li>
+ </ul>
</span>
- </button>
- <ul
- v-if="shouldShowMergeOptionsDropdown"
- class="dropdown-menu dropdown-menu-right"
- role="menu">
- <li>
- <a
- @click.prevent="handleMergeButtonClick(true)"
- class="merge_when_pipeline_succeeds"
- href="#">
- <span
- v-html="successSvg"
- class="merge-opt-icon"
- aria-hidden="true"></span>
- <span class="merge-opt-title">Merge when pipeline succeeds</span>
- </a>
- </li>
- <li>
- <a
- @click.prevent="handleMergeButtonClick(false, true)"
- class="accept-merge-request"
- href="#">
- <span
- v-html="warningSvg"
- class="merge-opt-icon"
- aria-hidden="true"></span>
- <span class="merge-opt-title">Merge immediately</span>
- </a>
- </li>
- </ul>
- </span>
- <template v-if="isMergeAllowed()">
- <label class="spacing">
- <input
- id="remove-source-branch-input"
- v-model="removeSourceBranch"
- :disabled="isRemoveSourceBranchButtonDisabled"
- type="checkbox"/> Remove source branch
- </label>
+ <div class="media-body space-children">
+ <template v-if="isMergeAllowed()">
+ <label>
+ <input
+ id="remove-source-branch-input"
+ v-model="removeSourceBranch"
+ :disabled="isRemoveSourceBranchButtonDisabled"
+ type="checkbox"/> Remove source branch
+ </label>
- <!-- Placeholder for EE extension of this component -->
- <squash-before-merge
- v-if="shouldShowSquashBeforeMerge"
- :mr="mr"
- :is-merge-button-disabled="isMergeButtonDisabled" />
+ <!-- Placeholder for EE extension of this component -->
+ <squash-before-merge
+ v-if="shouldShowSquashBeforeMerge"
+ :mr="mr"
+ :is-merge-button-disabled="isMergeButtonDisabled" />
- <button
- @click="toggleCommitMessageEditor"
- :disabled="isMergeButtonDisabled"
- class="btn btn-default btn-xs"
- type="button">
- Modify commit message
- </button>
+ <button
+ @click="toggleCommitMessageEditor"
+ :disabled="isMergeButtonDisabled"
+ class="btn btn-default btn-xs"
+ type="button">
+ Modify commit message
+ </button>
+ </template>
+ <template v-else>
+ <span class="bold">
+ The pipeline for this merge request failed. Please retry the job or push a new commit to fix the failure
+ </span>
+ </template>
+ </div>
+ </div>
<div
v-if="showCommitMessageEditor"
class="prepend-top-default commit-message-editor">
@@ -293,7 +310,7 @@ export default {
rows="14"
name="Commit message"></textarea>
</div>
- <p class="hint">Try to keep the first line under 52 characters and the others under 72.</p>
+ <p class="hint">Try to keep the first line under 52 characters and the others under 72</p>
<div class="hint">
<a
@click.prevent="updateCommitMessage"
@@ -302,12 +319,7 @@ export default {
</div>
</div>
</div>
- </template>
- <template v-else>
- <span class="bold">
- The pipeline for this merge request failed. Please retry the job or push a new commit to fix the failure.
- </span>
- </template>
+ </div>
</div>
`,
};
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_sha_mismatch.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_sha_mismatch.js
index 79f8ef408e6..89f38e5bd2a 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_sha_mismatch.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_sha_mismatch.js
@@ -1,16 +1,18 @@
+import statusIcon from '../mr_widget_status_icon';
+
export default {
name: 'MRWidgetSHAMismatch',
+ components: {
+ statusIcon,
+ },
template: `
- <div class="mr-widget-body">
- <button
- type="button"
- class="btn btn-success btn-small"
- disabled="true">
- Merge
- </button>
- <span class="bold">
- The source branch HEAD has recently changed. Please reload the page and review the changes before merging.
- </span>
+ <div class="mr-widget-body media">
+ <status-icon status="failed" showDisabledButton />
+ <div class="media-body space-children">
+ <span class="bold">
+ The source branch HEAD has recently changed. Please reload the page and review the changes before merging
+ </span>
+ </div>
</div>
`,
};
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_unresolved_discussions.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_unresolved_discussions.js
index f4ab2d9fa58..d762ca6e640 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_unresolved_discussions.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_unresolved_discussions.js
@@ -1,27 +1,27 @@
+import statusIcon from '../mr_widget_status_icon';
+
export default {
name: 'MRWidgetUnresolvedDiscussions',
props: {
mr: { type: Object, required: true },
},
+ components: {
+ statusIcon,
+ },
template: `
- <div class="mr-widget-body">
- <button
- type="button"
- class="btn btn-success btn-small"
- disabled="true">
- Merge
- </button>
- <span class="bold">
- There are unresolved discussions. Please resolve these discussions
- <span v-if="mr.canCreateIssue">or</span>
- <span v-else>.</span>
- </span>
- <a
- v-if="mr.createIssueToResolveDiscussionsPath"
- :href="mr.createIssueToResolveDiscussionsPath"
- class="btn btn-default btn-xs js-create-issue">
- Create an issue to resolve them later
- </a>
+ <div class="mr-widget-body media">
+ <status-icon status="failed" showDisabledButton />
+ <div class="media-body space-children">
+ <span class="bold">
+ There are unresolved discussions. Please resolve these discussions
+ </span>
+ <a
+ v-if="mr.createIssueToResolveDiscussionsPath"
+ :href="mr.createIssueToResolveDiscussionsPath"
+ class="btn btn-default btn-xs js-create-issue">
+ Create an issue to resolve them later
+ </a>
+ </div>
</div>
`,
};
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_wip.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_wip.js
index cb02ffe93bd..b11a06899cf 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_wip.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_wip.js
@@ -1,4 +1,6 @@
/* global Flash */
+import statusIcon from '../mr_widget_status_icon';
+import tooltip from '../../../vue_shared/directives/tooltip';
import eventHub from '../../event_hub';
export default {
@@ -7,11 +9,17 @@ export default {
mr: { type: Object, required: true },
service: { type: Object, required: true },
},
+ directives: {
+ tooltip,
+ },
data() {
return {
isMakingRequest: false,
};
},
+ components: {
+ statusIcon,
+ },
methods: {
removeWIP() {
this.isMakingRequest = true;
@@ -29,20 +37,20 @@ export default {
},
},
template: `
- <div class="mr-widget-body">
- <button
- type="button"
- class="btn btn-success btn-small"
- disabled="true">
- Merge</button>
- <span class="bold">
- This merge request is currently Work In Progress and therefore unable to merge
- </span>
- <template v-if="mr.removeWIPPath">
- <i
- class="fa fa-question-circle has-tooltip"
- title="When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged." />
+ <div class="mr-widget-body media">
+ <status-icon status="failed" :showDisabledButton="Boolean(mr.removeWIPPath)" />
+ <div class="media-body space-children">
+ <span class="bold">
+ This is a Work in Progress
+ <i
+ v-tooltip
+ class="fa fa-question-circle"
+ title="When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
+ aria-label="When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged">
+ </i>
+ </span>
<button
+ v-if="mr.removeWIPPath"
@click="removeWIP"
:disabled="isMakingRequest"
type="button"
@@ -53,7 +61,7 @@ export default {
aria-hidden="true" />
Resolve WIP status
</button>
- </template>
+ </div>
</div>
`,
};
diff --git a/app/assets/javascripts/vue_merge_request_widget/dependencies.js b/app/assets/javascripts/vue_merge_request_widget/dependencies.js
index fe5e1bbb55c..49340c232c8 100644
--- a/app/assets/javascripts/vue_merge_request_widget/dependencies.js
+++ b/app/assets/javascripts/vue_merge_request_widget/dependencies.js
@@ -1,7 +1,7 @@
/**
* This file is the centerpiece of an attempt to reduce potential conflicts
* between the CE and EE versions of the MR widget. EE additions to the MR widget should
- * be contained in the ./vue_merge_request_widget/ee directory, and should **extend**
+ * be contained in the ee/vue_merge_request_widget directory, and should **extend**
* rather than mutate CE MR Widget code.
*
* This file should be the only source of conflicts between EE and CE. EE-only components should
@@ -19,7 +19,7 @@ export { default as WidgetRelatedLinks } from './components/mr_widget_related_li
export { default as MergedState } from './components/states/mr_widget_merged';
export { default as FailedToMerge } from './components/states/mr_widget_failed_to_merge';
export { default as ClosedState } from './components/states/mr_widget_closed';
-export { default as LockedState } from './components/states/mr_widget_locked';
+export { default as MergingState } from './components/states/mr_widget_merging';
export { default as WipState } from './components/states/mr_widget_wip';
export { default as ArchivedState } from './components/states/mr_widget_archived';
export { default as ConflictsState } from './components/states/mr_widget_conflicts';
diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js
index 2339a00ddd0..0042c48816f 100644
--- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js
+++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js
@@ -8,7 +8,7 @@ import {
WidgetRelatedLinks,
MergedState,
ClosedState,
- LockedState,
+ MergingState,
WipState,
ArchivedState,
ConflictsState,
@@ -35,8 +35,14 @@ import {
export default {
el: '#js-vue-mr-widget',
name: 'MRWidget',
+ props: {
+ mrData: {
+ type: Object,
+ required: false,
+ },
+ },
data() {
- const store = new MRWidgetStore(gl.mrWidgetData);
+ const store = new MRWidgetStore(this.mrData || window.gl.mrWidgetData);
const service = this.createService(store);
return {
mr: store,
@@ -206,7 +212,7 @@ export default {
'mr-widget-related-links': WidgetRelatedLinks,
'mr-widget-merged': MergedState,
'mr-widget-closed': ClosedState,
- 'mr-widget-locked': LockedState,
+ 'mr-widget-merging': MergingState,
'mr-widget-failed-to-merge': FailedToMerge,
'mr-widget-wip': WipState,
'mr-widget-archived': ArchivedState,
@@ -234,14 +240,21 @@ export default {
v-if="shouldRenderDeployments"
:mr="mr"
:service="service" />
- <component
- :is="componentName"
- :mr="mr"
- :service="service" />
- <mr-widget-related-links
- v-if="shouldRenderRelatedLinks"
- :related-links="mr.relatedLinks" />
- <mr-widget-merge-help v-if="shouldRenderMergeHelp" />
+ <div class="mr-widget-section">
+ <component
+ :is="componentName"
+ :mr="mr"
+ :service="service" />
+ <mr-widget-related-links
+ v-if="shouldRenderRelatedLinks"
+ :state="mr.state"
+ :related-links="mr.relatedLinks" />
+ </div>
+ <div
+ class="mr-widget-footer"
+ v-if="shouldRenderMergeHelp">
+ <mr-widget-merge-help />
+ </div>
</div>
`,
};
diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
index fddafb0ddfa..fbea764b739 100644
--- a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
+++ b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
@@ -73,6 +73,7 @@ export default class MergeRequestStore {
this.canCancelAutomaticMerge = !!data.cancel_merge_when_pipeline_succeeds_path;
this.hasSHAChanged = this.sha !== data.diff_head_sha;
this.canBeMerged = data.can_be_merged || false;
+ this.mergeOngoing = data.merge_ongoing;
// Cherry-pick and Revert actions related
this.canCherryPickInCurrentMR = currentUser.can_cherry_pick_on_current_merge_request || false;
@@ -94,6 +95,11 @@ export default class MergeRequestStore {
}
setState(data) {
+ if (this.mergeOngoing) {
+ this.state = 'merging';
+ return;
+ }
+
if (this.isOpen) {
this.state = getStateKey.call(this, data);
} else {
@@ -104,9 +110,6 @@ export default class MergeRequestStore {
case 'closed':
this.state = 'closed';
break;
- case 'locked':
- this.state = 'locked';
- break;
default:
this.state = null;
}
diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/state_maps.js b/app/assets/javascripts/vue_merge_request_widget/stores/state_maps.js
index 605dd3a1ff4..9074a064a6d 100644
--- a/app/assets/javascripts/vue_merge_request_widget/stores/state_maps.js
+++ b/app/assets/javascripts/vue_merge_request_widget/stores/state_maps.js
@@ -1,7 +1,7 @@
const stateToComponentMap = {
merged: 'mr-widget-merged',
closed: 'mr-widget-closed',
- locked: 'mr-widget-locked',
+ merging: 'mr-widget-merging',
conflicts: 'mr-widget-conflicts',
missingBranch: 'mr-widget-missing-branch',
workInProgress: 'mr-widget-wip',
@@ -20,7 +20,7 @@ const stateToComponentMap = {
};
const statesToShowHelpWidget = [
- 'locked',
+ 'merging',
'conflicts',
'workInProgress',
'readyToMerge',
diff --git a/app/assets/javascripts/vue_shared/components/header_ci_component.vue b/app/assets/javascripts/vue_shared/components/header_ci_component.vue
index bdc059f4a03..d305bd6acdc 100644
--- a/app/assets/javascripts/vue_shared/components/header_ci_component.vue
+++ b/app/assets/javascripts/vue_shared/components/header_ci_component.vue
@@ -120,7 +120,7 @@ export default {
</a>
<a
- v-if="action.type === 'ujs-link'"
+ v-else-if="action.type === 'ujs-link'"
:href="action.path"
data-method="post"
rel="nofollow"
@@ -129,7 +129,7 @@ export default {
</a>
<button
- v-else="action.type === 'button'"
+ v-else-if="action.type === 'button'"
@click="onClickAction(action)"
:disabled="action.isLoading"
:class="action.cssClass"
diff --git a/app/assets/javascripts/vue_shared/components/popup_dialog.vue b/app/assets/javascripts/vue_shared/components/popup_dialog.vue
new file mode 100644
index 00000000000..7d339c0e753
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/popup_dialog.vue
@@ -0,0 +1,67 @@
+<script>
+const PopupDialog = {
+ name: 'popup-dialog',
+
+ props: {
+ open: Boolean,
+ title: String,
+ body: String,
+ kind: {
+ type: String,
+ default: 'primary',
+ },
+ closeButtonLabel: {
+ type: String,
+ default: 'Cancel',
+ },
+ primaryButtonLabel: {
+ type: String,
+ default: 'Save changes',
+ },
+ },
+
+ computed: {
+ typeOfClass() {
+ const className = `btn-${this.kind}`;
+ const returnObj = {};
+ returnObj[className] = true;
+ return returnObj;
+ },
+ },
+
+ methods: {
+ close() {
+ this.$emit('toggle', false);
+ },
+
+ yesClick() {
+ this.$emit('submit', true);
+ },
+
+ noClick() {
+ this.$emit('submit', false);
+ },
+ },
+};
+
+export default PopupDialog;
+</script>
+<template>
+<div class="modal popup-dialog" tabindex="-1" v-show="open" role="dialog">
+ <div class="modal-dialog" role="document">
+ <div class="modal-content">
+ <div class="modal-header">
+ <button type="button" class="close" @click="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
+ <h4 class="modal-title">{{this.title}}</h4>
+ </div>
+ <div class="modal-body">
+ <p>{{this.body}}</p>
+ </div>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-default" data-dismiss="modal" @click="noClick">{{closeButtonLabel}}</button>
+ <button type="button" class="btn" :class="typeOfClass" @click="yesClick">{{primaryButtonLabel}}</button>
+ </div>
+ </div>
+ </div>
+</div>
+</template>
diff --git a/app/assets/javascripts/wikis.js b/app/assets/javascripts/wikis.js
index 00676bcb0b3..a0025ddb598 100644
--- a/app/assets/javascripts/wikis.js
+++ b/app/assets/javascripts/wikis.js
@@ -1,14 +1,9 @@
-/* global Breakpoints */
-
-import 'vendor/jquery.nicescroll';
-import './breakpoints';
+import bp from './breakpoints';
export default class Wikis {
constructor() {
- this.bp = Breakpoints.get();
this.sidebarEl = document.querySelector('.js-wiki-sidebar');
this.sidebarExpanded = false;
- $(this.sidebarEl).niceScroll();
const sidebarToggles = document.querySelectorAll('.js-sidebar-wiki-toggle');
for (let i = 0; i < sidebarToggles.length; i += 1) {
@@ -43,15 +38,15 @@ export default class Wikis {
this.renderSidebar();
}
- sidebarCanCollapse() {
- const bootstrapBreakpoint = this.bp.getBreakpointSize();
+ static sidebarCanCollapse() {
+ const bootstrapBreakpoint = bp.getBreakpointSize();
return bootstrapBreakpoint === 'xs' || bootstrapBreakpoint === 'sm';
}
renderSidebar() {
if (!this.sidebarEl) return;
const { classList } = this.sidebarEl;
- if (this.sidebarExpanded || !this.sidebarCanCollapse()) {
+ if (this.sidebarExpanded || !Wikis.sidebarCanCollapse()) {
if (!classList.contains('right-sidebar-expanded')) {
classList.remove('right-sidebar-collapsed');
classList.add('right-sidebar-expanded');
diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss
index 6ce331a9129..b2b3297e880 100644
--- a/app/assets/stylesheets/framework.scss
+++ b/app/assets/stylesheets/framework.scss
@@ -26,6 +26,7 @@
@import "framework/lists";
@import "framework/logo";
@import "framework/markdown_area";
+@import "framework/media_object";
@import "framework/mobile";
@import "framework/modal";
@import "framework/nav";
diff --git a/app/assets/stylesheets/framework/avatar.scss b/app/assets/stylesheets/framework/avatar.scss
index cb41df8a88d..486d88efbc5 100644
--- a/app/assets/stylesheets/framework/avatar.scss
+++ b/app/assets/stylesheets/framework/avatar.scss
@@ -100,6 +100,8 @@
margin: 0;
align-self: center;
}
+
+ &.s40 { min-width: 40px; min-height: 40px; }
}
.avatar-counter {
diff --git a/app/assets/stylesheets/framework/calendar.scss b/app/assets/stylesheets/framework/calendar.scss
index 0ac095f7d8f..0ded4a3b423 100644
--- a/app/assets/stylesheets/framework/calendar.scss
+++ b/app/assets/stylesheets/framework/calendar.scss
@@ -45,6 +45,7 @@
margin-top: -23px;
float: right;
font-size: 12px;
+ direction: ltr;
}
.pika-single.gitlab-theme {
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index 5e374360359..293aa194528 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -372,6 +372,10 @@ table {
background: $gl-success !important;
}
+.dz-message {
+ margin: 0;
+}
+
.space-right {
margin-right: 10px;
}
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index 3f934403147..1bb04b59a2a 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -574,6 +574,7 @@
.dropdown-input-field,
.default-dropdown-input {
+ display: block;
width: 100%;
min-height: 30px;
padding: 0 7px;
@@ -722,3 +723,57 @@
@include set-invisible;
overflow: hidden;
}
+
+// TODO: change global style and remove mixin
+@mixin new-style-dropdown($selector: '') {
+ #{$selector}.dropdown-menu,
+ #{$selector}.dropdown-menu-nav {
+ .divider {
+ margin: 6px 0;
+ }
+
+ li {
+ padding: 0 1px;
+
+ &.dropdown-header {
+ padding: 8px 16px;
+ }
+
+ a {
+ border-radius: 0;
+ padding: 8px 16px;
+
+ &.is-focused,
+ &:hover,
+ &:active,
+ &:focus {
+ background-color: $gray-darker;
+ }
+
+ &.is-active {
+ font-weight: inherit;
+
+ &::before {
+ top: 16px;
+ }
+ }
+ }
+ }
+
+ &.dropdown-menu-selectable {
+ li {
+ a {
+ padding: 8px 40px;
+
+ &.is-active::before {
+ left: 16px;
+ }
+ }
+ }
+ }
+ }
+
+ #{$selector}.dropdown-menu-align-right {
+ margin-top: 2px;
+ }
+}
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index 1c4238bc564..b677882eba4 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -4,6 +4,8 @@
*/
header {
+ @include new-style-dropdown;
+
transition: padding $sidebar-transition-duration;
&.navbar-empty {
@@ -24,7 +26,7 @@ header {
&.navbar-gitlab {
padding: 0 16px;
- z-index: 400;
+ z-index: 1000;
margin-bottom: 0;
min-height: $header-height;
background-color: $gray-light;
@@ -313,25 +315,6 @@ header {
.impersonation i {
color: $red-500;
}
-
- // TODO: fallback to global style
- .dropdown-menu,
- .dropdown-menu-nav {
- li {
- padding: 0 1px;
-
- a {
- border-radius: 0;
- padding: 8px 16px;
-
- &:hover,
- &:active,
- &:focus {
- background-color: $gray-darker;
- }
- }
- }
- }
}
.with-performance-bar header.navbar-gitlab {
@@ -342,9 +325,9 @@ header {
li {
.badge {
position: inherit;
- top: -3px;
+ top: -8px;
font-weight: normal;
- margin-left: -12px;
+ margin-left: -11px;
font-size: 11px;
color: $white-light;
padding: 1px 5px 2px;
diff --git a/app/assets/stylesheets/framework/highlight.scss b/app/assets/stylesheets/framework/highlight.scss
index 71d5949b023..c63114f85b4 100644
--- a/app/assets/stylesheets/framework/highlight.scss
+++ b/app/assets/stylesheets/framework/highlight.scss
@@ -47,7 +47,7 @@
font-family: $monospace_font;
display: block;
font-size: $code_font_size !important;
- line-height: 19px;
+ min-height: 19px;
white-space: nowrap;
i {
diff --git a/app/assets/stylesheets/framework/layout.scss b/app/assets/stylesheets/framework/layout.scss
index 67c3287ed74..bd0367f86dd 100644
--- a/app/assets/stylesheets/framework/layout.scss
+++ b/app/assets/stylesheets/framework/layout.scss
@@ -109,18 +109,20 @@ body {
}
}
-
-/* The following prevents side effects related to iOS Safari's implementation of -webkit-overflow-scrolling: touch,
-which is applied to the body by jquery.nicescroling plugin to force hardware acceleration for momentum scrolling. Side
-effects are commonly related to inconsisent z-index behavior (e.g. tooltips). By applying the following to direct children
-of the body element here, we negate cascading side effects but allow momentum scrolling to be applied to the body */
-
-.navbar,
-.page-gutter,
-.page-with-sidebar {
- -webkit-overflow-scrolling: auto;
+.page-with-sidebar > .content-wrapper {
+ min-height: calc(100vh - #{$header-height});
}
.with-performance-bar .page-with-sidebar {
margin-top: $header-height + $performance-bar-height;
}
+
+[v-cloak] {
+ display: none;
+}
+
+.vertical-center {
+ min-height: 100vh;
+ display: flex;
+ align-items: center;
+}
diff --git a/app/assets/stylesheets/framework/media_object.scss b/app/assets/stylesheets/framework/media_object.scss
new file mode 100644
index 00000000000..b573052c14a
--- /dev/null
+++ b/app/assets/stylesheets/framework/media_object.scss
@@ -0,0 +1,8 @@
+.media {
+ display: flex;
+ align-items: flex-start;
+}
+
+.media-body {
+ flex: 1;
+}
diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss
index 88e7ba117d5..d386ac5ba9c 100644
--- a/app/assets/stylesheets/framework/nav.scss
+++ b/app/assets/stylesheets/framework/nav.scss
@@ -251,7 +251,6 @@
// Applies on /dashboard/issues
.project-item-select-holder {
- display: block;
margin: 0;
}
}
@@ -283,6 +282,31 @@
}
}
+.project-item-select-holder.btn-group {
+ display: flex;
+ max-width: 350px;
+ overflow: hidden;
+
+ @media(max-width: $screen-xs-max) {
+ width: 100%;
+ max-width: none;
+ }
+
+ .new-project-item-link {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ .new-project-item-select-button {
+ width: 32px;
+ }
+}
+
+.new-project-item-select-button .fa-caret-down {
+ margin-left: 2px;
+}
+
.layout-nav {
width: 100%;
background: $gray-light;
diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss
index 09b60ad1676..40e8a928e6e 100644
--- a/app/assets/stylesheets/framework/sidebar.scss
+++ b/app/assets/stylesheets/framework/sidebar.scss
@@ -78,15 +78,12 @@
.right-sidebar {
border-left: 1px solid $border-color;
+ height: calc(100% - #{$header-height});
&.affix {
position: fixed;
top: $header-height;
}
-
- &:not(.affix-top) {
- min-height: 100%;
- }
}
.with-performance-bar .right-sidebar.affix {
diff --git a/app/assets/stylesheets/framework/tw_bootstrap_variables.scss b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss
index b666223b120..4c35e3a9c3c 100644
--- a/app/assets/stylesheets/framework/tw_bootstrap_variables.scss
+++ b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss
@@ -162,3 +162,5 @@ $pre-color: $gl-text-color !default;
$pre-border-color: $border-color;
$table-bg-accent: $gray-light;
+
+$zindex-popover: 900;
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index bf5f124d142..96409b10b99 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -339,6 +339,8 @@ a > code {
@extend .ref-name;
}
+@include new-style-dropdown('.git-revision-dropdown');
+
/**
* Apply Markdown typography
*
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 0df6f24bfe6..3c109a5a929 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -88,6 +88,7 @@ $indigo-950: #1a1a40;
$black: #000;
$black-transparent: rgba(0, 0, 0, 0.3);
+$almost-black: #242424;
$border-white-light: darken($white-light, $darken-border-factor);
$border-white-normal: darken($white-normal, $darken-border-factor);
@@ -206,7 +207,6 @@ $general-hover-transition-curve: linear;
$highlight-changes-color: rgb(235, 255, 232);
$performance-bar-height: 35px;
-
/*
* Common component specific colors
*/
@@ -316,6 +316,12 @@ $badge-bg: rgba(0, 0, 0, 0.07);
$badge-color: $gl-text-color-secondary;
/*
+ * Status icons
+ */
+$status-icon-size: 22px;
+$status-icon-margin: $gl-btn-padding;
+
+/*
* Award emoji
*/
$award-emoji-menu-shadow: rgba(0, 0, 0, .175);
@@ -614,6 +620,13 @@ $color-average-score: $orange-400;
$color-low-score: $red-400;
/*
+Repo editor
+*/
+$repo-editor-grey: #f6f7f9;
+$repo-editor-grey-darker: #e9ebee;
+$repo-editor-linear-gradient: linear-gradient(to right, $repo-editor-grey 0%, $repo-editor-grey-darker, 20%, $repo-editor-grey 40%, $repo-editor-grey 100%);
+
+/*
Performance Bar
*/
$perf-bar-text: #999;
@@ -624,3 +637,11 @@ $perf-bar-bucket-bg: #111;
$perf-bar-bucket-color: #ccc;
$perf-bar-bucket-box-shadow-from: rgba($white-light, .2);
$perf-bar-bucket-box-shadow-to: rgba($black, .25);
+
+
+/*
+Project Templates Icons
+*/
+$rails: #c00;
+$node: #353535;
+$java: #70ad51;
diff --git a/app/assets/stylesheets/new_nav.scss b/app/assets/stylesheets/new_nav.scss
index 1c4a84de7ec..795ee91af8b 100644
--- a/app/assets/stylesheets/new_nav.scss
+++ b/app/assets/stylesheets/new_nav.scss
@@ -312,6 +312,10 @@ header.navbar-gitlab-new {
// TODO: fallback to global style
.dropdown-menu {
+ .divider {
+ margin: 6px 0;
+ }
+
li {
padding: 0 1px;
diff --git a/app/assets/stylesheets/new_sidebar.scss b/app/assets/stylesheets/new_sidebar.scss
index 2ed6dfbe7c2..faedd207e01 100644
--- a/app/assets/stylesheets/new_sidebar.scss
+++ b/app/assets/stylesheets/new_sidebar.scss
@@ -8,20 +8,25 @@ $active-color: $indigo-700;
$active-hover-background: $active-background;
$active-hover-color: $gl-text-color;
$inactive-badge-background: rgba(0, 0, 0, .08);
-$hover-background: $indigo-700;
-$hover-color: $white-light;
+$hover-background: $white-light;
+$hover-color: $gl-text-color;
$inactive-color: $gl-text-color-secondary;
$new-sidebar-width: 220px;
+$new-sidebar-collapsed-width: 50px;
.page-with-new-sidebar {
- @media (min-width: $screen-sm-min) {
+ @media (min-width: $screen-md-min) {
+ padding-left: $new-sidebar-collapsed-width;
+ }
+
+ @media (min-width: $screen-lg-min) {
padding-left: $new-sidebar-width;
}
// Override position: absolute
.right-sidebar {
position: fixed;
- height: 100%;
+ height: calc(100% - #{$header-height});
}
.issues-bulk-update.right-sidebar.right-sidebar-expanded .issuable-sidebar-header {
@@ -29,8 +34,15 @@ $new-sidebar-width: 220px;
}
}
+.page-with-icon-sidebar {
+ @media (min-width: $screen-sm-min) {
+ padding-left: $new-sidebar-collapsed-width;
+ }
+}
+
.context-header {
position: relative;
+ margin-right: 2px;
a {
border-bottom: 1px solid $border-color;
@@ -39,26 +51,16 @@ $new-sidebar-width: 220px;
align-items: center;
padding: 10px 16px 10px 10px;
color: $gl-text-color;
+ }
- @media (max-width: $screen-xs-max) {
- padding-right: 30px;
- }
-
- &:hover {
- background-color: $hover-background;
- color: $hover-color;
- border-color: $hover-background;
-
- .avatar-container {
- border-color: transparent;
- }
-
- .settings-avatar {
- background-color: $indigo-500;
+ &:hover,
+ a:hover {
+ background-color: $hover-background;
+ color: $hover-color;
- i {
- color: $hover-color;
- }
+ .settings-avatar {
+ i {
+ color: $hover-color;
}
}
}
@@ -73,32 +75,6 @@ $new-sidebar-width: 220px;
overflow: hidden;
text-overflow: ellipsis;
}
-
-
- &:hover {
- .close-nav-button {
- color: $white-light;
- }
- }
-
- .close-nav-button {
- display: none;
- position: absolute;
- top: 0;
- right: 0;
- height: 100%;
- background-color: transparent;
- border: 0;
- padding: 0 10px;
-
- @media (max-width: $screen-xs-max) {
- display: block;
- }
-
- &:hover {
- color: $gl-text-color;
- }
- }
}
.settings-avatar {
@@ -125,6 +101,20 @@ $new-sidebar-width: 220px;
background-color: $gray-normal;
box-shadow: inset -2px 0 0 $border-color;
+ &.sidebar-icons-only {
+ width: $new-sidebar-collapsed-width;
+ overflow-x: hidden;
+
+ .badge,
+ .project-title {
+ display: none;
+ }
+
+ .nav-item-name {
+ opacity: 0;
+ }
+ }
+
&.nav-sidebar-expanded {
left: 0;
}
@@ -196,7 +186,7 @@ $new-sidebar-width: 220px;
> li {
a {
- padding: 8px 16px 8px 50px;
+ padding: 8px 16px 8px 40px;
&:hover,
&:focus {
@@ -219,7 +209,77 @@ $new-sidebar-width: 220px;
}
.sidebar-top-level-items {
+ margin-bottom: 60px;
+
> li {
+ > a {
+ @media (min-width: $screen-sm-min) {
+ margin-right: 2px;
+ }
+
+ &:hover {
+ color: $gl-text-color;
+
+ svg {
+ fill: $gl-text-color;
+ }
+ }
+ }
+
+ &.is-showing-fly-out {
+ > a {
+ margin-right: 2px;
+ }
+
+ .sidebar-sub-level-items {
+ @media (min-width: $screen-sm-min) {
+ position: fixed;
+ top: 0;
+ left: $new-sidebar-width;
+ min-width: 150px;
+ margin-top: -1px;
+ padding: 8px 1px;
+ background-color: $white-light;
+ box-shadow: 2px 1px 3px $dropdown-shadow-color;
+ border: 1px solid $gray-darker;
+ border-left: 0;
+ border-radius: 0 3px 3px 0;
+
+ &::before {
+ content: "";
+ position: absolute;
+ top: -30px;
+ bottom: -30px;
+ left: -10px;
+ right: -30px;
+ z-index: -1;
+ }
+
+ &.is-above {
+ margin-top: 1px;
+ }
+
+ > .active {
+ box-shadow: none;
+
+ > a {
+ background-color: transparent;
+ }
+ }
+
+ a {
+ padding: 8px 16px;
+ color: $gl-text-color;
+
+ &:hover,
+ &:focus {
+ background-color: $gray-darker;
+ }
+ }
+ }
+ }
+ }
+
.badge {
background-color: $inactive-badge-background;
color: $inactive-color;
@@ -228,6 +288,11 @@ $new-sidebar-width: 220px;
&.active {
background: $active-background;
+ > a {
+ margin-left: 4px;
+ padding-left: 12px;
+ }
+
.badge {
color: $active-color;
font-weight: 600;
@@ -238,20 +303,100 @@ $new-sidebar-width: 220px;
}
}
- > a:hover {
- background-color: $hover-background;
- color: $hover-color;
+ &.active > a:hover,
+ &.is-over > a {
+ background-color: $white-light;
+ }
+ }
+}
+
+
+// Collapsed nav
+
+.toggle-sidebar-button,
+.close-nav-button {
+ width: $new-sidebar-width - 2px;
+ position: fixed;
+ bottom: 0;
+ padding: 16px;
+ background-color: $gray-normal;
+ border: 0;
+ border-top: 2px solid $border-color;
+ color: $gl-text-color-secondary;
+ display: flex;
+ align-items: center;
+
+ i {
+ font-size: 20px;
+ margin-right: 8px;
+ }
+
+ .fa-angle-double-right {
+ display: none;
+ }
+
+ &:hover {
+ background-color: $border-color;
+ color: $gl-text-color;
+ }
+}
+
+.toggle-sidebar-button {
+ @media (max-width: $screen-xs-max) {
+ display: none;
+ }
+}
+
+
+.sidebar-icons-only {
+ .context-header {
+ height: 61px;
+
+ a {
+ padding: 10px 4px;
+ }
+ }
+
+ li a {
+ padding: 12px 15px;
+ }
+
+ .sidebar-top-level-items > li {
+ &.active a {
+ padding-left: 12px;
+ }
- svg {
- fill: $hover-color;
+ .sidebar-sub-level-items {
+ @media (min-width: $screen-sm-min) {
+ left: $new-sidebar-collapsed-width;
}
- .badge {
- background-color: $indigo-500;
- color: $hover-color;
+ &:not(.flyout-list) {
+ display: none;
}
}
}
+
+ .toggle-sidebar-button {
+ width: $new-sidebar-collapsed-width - 2px;
+ padding: 16px 18px;
+
+ .collapse-text,
+ .fa-angle-double-left {
+ display: none;
+ }
+
+ .fa-angle-double-right {
+ display: block;
+ }
+ }
+}
+
+
+// Mobile nav
+
+.close-nav-button {
+ display: none;
}
.toggle-mobile-nav {
@@ -273,6 +418,12 @@ $new-sidebar-width: 220px;
}
}
+@media (max-width: $screen-xs-max) {
+ .close-nav-button {
+ display: flex;
+ }
+}
+
.mobile-overlay {
display: none;
diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss
index 6039cda96d8..e5b467a2691 100644
--- a/app/assets/stylesheets/pages/boards.scss
+++ b/app/assets/stylesheets/pages/boards.scss
@@ -165,6 +165,7 @@
.board-title {
padding-top: ($gl-padding - 3px);
+ padding-bottom: $gl-padding;
}
}
}
@@ -178,6 +179,7 @@
position: relative;
margin: 0;
padding: $gl-padding;
+ padding-bottom: ($gl-padding + 3px);
font-size: 1em;
border-bottom: 1px solid $border-color;
}
diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss
index 28c99d8e57c..486424fb729 100644
--- a/app/assets/stylesheets/pages/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
@@ -235,8 +235,18 @@
display: none;
}
+ .sidebar-container {
+ width: calc(100% + 100px);
+ padding-right: 100px;
+ height: 100%;
+ overflow-y: scroll;
+ overflow-x: hidden;
+ -webkit-overflow-scrolling: touch;
+ }
+
.blocks-container {
padding: 0 $gl-padding;
+ width: 289px;
}
.block {
@@ -259,7 +269,15 @@
padding: 16px 0;
}
+ .trigger-build-variables {
+ margin: 0;
+ overflow-x: auto;
+ -ms-overflow-style: scrollbar;
+ -webkit-overflow-scrolling: touch;
+ }
+
.trigger-build-variable {
+ font-weight: normal;
color: $code-color;
}
@@ -326,6 +344,7 @@
border-top: 1px solid $border-color;
border-bottom: 1px solid $border-color;
max-height: 300px;
+ width: 289px;
overflow: auto;
svg {
diff --git a/app/assets/stylesheets/pages/cycle_analytics.scss b/app/assets/stylesheets/pages/cycle_analytics.scss
index 87b50c7687e..6753eb08285 100644
--- a/app/assets/stylesheets/pages/cycle_analytics.scss
+++ b/app/assets/stylesheets/pages/cycle_analytics.scss
@@ -1,4 +1,6 @@
#cycle-analytics {
+ @include new-style-dropdown;
+
max-width: 1000px;
margin: 24px auto 0;
position: relative;
@@ -110,10 +112,6 @@
.js-ca-dropdown {
top: $gl-padding-top;
-
- .dropdown-menu-align-right {
- margin-top: 2px;
- }
}
.content-list {
@@ -446,24 +444,6 @@
margin-bottom: 20px;
}
}
-
- // TODO: fallback to global style
- .dropdown-menu {
- li {
- padding: 0 1px;
-
- a {
- border-radius: 0;
- padding: 8px 16px;
-
- &:hover,
- &:active,
- &:focus {
- background-color: $gray-darker;
- }
- }
- }
- }
}
.cycle-analytics-overview {
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index 398fd4444ea..215bedc04fd 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -395,12 +395,11 @@
background-color: transparent;
border: 0;
color: $gl-link-color;
- transition: color 0.1s linear;
+ font-weight: 600;
&:hover,
&:focus {
outline: none;
- text-decoration: underline;
color: $gl-link-hover-color;
}
}
@@ -559,3 +558,72 @@
outline: 0;
}
}
+
+.diff-files-changed {
+ .commit-stat-summary {
+ @include new-style-dropdown;
+ z-index: -1;
+
+ @media (min-width: $screen-sm-min) {
+ margin-left: -$gl-padding;
+ padding-left: $gl-padding;
+ background-color: $white-light;
+ }
+ }
+
+ @media (min-width: $screen-sm-min) {
+ position: -webkit-sticky;
+ position: sticky;
+ top: 34px;
+ background-color: $white-light;
+ z-index: 190;
+
+ &.diff-files-changed-merge-request {
+ top: 84px;
+ }
+
+ + .files,
+ + .alert {
+ margin-top: 1px;
+ }
+
+ &:not(.is-stuck) .diff-stats-additions-deletions-collapsed {
+ display: none;
+ }
+
+ &.is-stuck {
+ padding-top: 0;
+ padding-bottom: 0;
+ border-bottom: 1px solid $white-dark;
+ transform: translateY(16px);
+
+ .diff-stats-additions-deletions-expanded,
+ .inline-parallel-buttons {
+ display: none;
+ }
+
+ + .files,
+ + .alert {
+ margin-top: 30px;
+ }
+ }
+ }
+}
+
+.diff-file-changes {
+ width: 450px;
+ z-index: 150;
+
+ @media (min-width: $screen-sm-min) {
+ left: $gl-padding;
+ }
+
+ a {
+ padding-top: 8px;
+ padding-bottom: 8px;
+ }
+}
+
+.diff-file-changes-path {
+ @include str-truncated(78%);
+}
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 6da14320914..d14b976374c 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -5,13 +5,37 @@
margin-right: auto;
}
+.is-confidential {
+ color: $orange-600;
+ background-color: $orange-50;
+ border-radius: 3px;
+ padding: 5px;
+ margin: 0 3px 0 -4px;
+}
+
+.is-not-confidential {
+ border-radius: 3px;
+ padding: 5px;
+ margin: 0 3px 0 -4px;
+}
+
+.confidentiality {
+ .is-not-confidential {
+ margin: auto;
+ }
+
+ .is-confidential {
+ margin: auto;
+ }
+}
+
.limit-container-width {
.detail-page-header,
.page-content-header,
.commit-box,
.info-well,
.commit-ci-menu,
- .files-changed,
+ .files-changed-inner,
.limited-header-width,
.limited-width-notes {
@extend .fixed-width-container;
@@ -328,9 +352,17 @@
margin-bottom: 10px;
color: $issuable-sidebar-color;
+ svg {
+ fill: $issuable-sidebar-color;
+ }
+
&:hover,
&:hover .todo-undone {
color: $gl-text-color;
+
+ svg {
+ fill: $gl-text-color;
+ }
}
span {
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 4693b2434c7..6bb013cca85 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -2,10 +2,35 @@
* MR -> show: Automerge widget
*
*/
+
+.space-children {
+ @include clearfix;
+
+ > * {
+ float: left;
+ }
+
+ > *:not(:last-child) {
+ margin-right: 10px;
+ }
+}
+
.mr-state-widget {
color: $gl-text-color;
border: 1px solid $border-color;
border-radius: 2px;
+ line-height: 28px;
+
+ .mr-widget-heading,
+ .mr-widget-section,
+ .mr-widget-footer {
+ padding: $gl-padding;
+ border-top: solid 1px $border-color;
+ }
+
+ .mr-widget-footer {
+ padding: 0;
+ }
form {
margin-bottom: 0;
@@ -15,15 +40,35 @@
}
}
+ label {
+ margin-bottom: 0;
+ }
+
+ .btn {
+ font-size: $gl-font-size;
+
+ &[disabled] {
+ opacity: 0.3;
+ }
+
+ &.btn-xs {
+ line-height: 1;
+ padding: 5px 10px;
+ margin-top: 1px;
+ }
+
+ &.dropdown-toggle {
+ .fa {
+ color: inherit;
+ }
+ }
+ }
+
.accept-merge-holder {
.accept-action {
display: inline-block;
float: left;
- .btn-success.dropdown-toggle .fa {
- color: inherit;
- }
-
.accept-merge-request {
&.ci-pending,
&.ci-running {
@@ -84,77 +129,64 @@
.ci-widget {
color: $gl-text-color;
- display: -webkit-flex;
display: flex;
- -webkit-align-items: center;
- align-items: center;
- padding: $gl-padding-top $gl-padding 0;
-
- svg {
- position: relative;
- top: 1px;
- overflow: visible;
- }
-
- > span {
- padding-right: 4px;
- }
@media (max-width: $screen-xs-max) {
flex-wrap: wrap;
}
+ }
- .icon-link > .ci-status-icon > svg {
- width: 22px;
- height: 22px;
- margin-right: 8px;
- }
+ .mr-widget-icon {
+ font-size: 22px;
+ margin-right: $status-icon-margin;
+ }
- .ci-error {
- margin-right: $btn-side-margin;
- }
+ .ci-status-icon svg {
+ width: $status-icon-size;
+ height: $status-icon-size;
+ margin: 3px 0;
+ position: relative;
+ overflow: visible;
+ display: block;
}
- .mr-widget-body,
- .mr-widget-footer {
- margin: 16px;
+ .mr-widget-body {
+ @include clearfix;
+
+ &.media > *:first-child {
+ margin-right: 10px;
+ }
}
.mr-widget-pipeline-graph {
- flex-shrink: 0;
+ padding: 0 4px;
.dropdown-menu {
- margin-top: 11px;
z-index: 300;
}
.ci-action-icon-wrapper {
line-height: 16px;
}
+ }
- @media (max-width: $screen-xs-max) {
- order: 1;
- margin-top: $gl-padding-top;
- border-radius: 3px;
- background-color: $white-light;
- border: 1px solid $gray-darker;
- width: 100%;
- text-align: center;
+ .mini-pipeline-graph-dropdown-toggle {
+ vertical-align: top;
+ }
- .dropdown-menu {
- margin-left: -97.5px;
- }
+ .mini-pipeline-graph-dropdown-menu .mini-pipeline-graph-dropdown-item {
+ display: flex;
+ align-items: center;
- .arrow-up::before,
- .arrow-up::after, {
- margin-left: 97.5px;
- }
+ .ci-status-text,
+ .ci-status-icon {
+ top: 0;
+ margin-right: 10px;
}
}
.normal {
- color: $gl-text-color;
- font-size: 15px;
+ line-height: 28px;
}
.capitalize {
@@ -165,9 +197,8 @@
@extend .ref-name;
color: $gl-text-color;
- font-weight: bold;
+ font-weight: 600;
overflow: hidden;
- margin: 0 3px;
word-break: break-all;
&.label-truncated {
@@ -189,52 +220,19 @@
}
}
- .js-deployment-link {
- display: inline-block;
- }
-
.mr-widget-help {
- margin: $gl-padding;
- color: $ci-skipped-color;
- }
-
- .mr-info-list {
-
- &.mr-links {
- margin-left: 28px;
- }
-
- &.mr-memory-usage {
- margin: 5px 0 10px 25px;
- }
- }
-
- .mr-widget-heading {
- .btn-default.btn-xs {
- margin-left: 5px;
- }
- }
-
- .mr-widget-body {
- .btn {
- font-size: 15px;
- }
-
- .btn-group .btn {
- padding: 5px 10px;
-
- &.dropdown-toggle {
- padding: 5px 7px;
- }
- }
+ padding: 10px 16px 10px 48px;
+ font-style: italic;
}
.mr-widget-body {
h4 {
- font-weight: bold;
- font-size: 15px;
- margin: 5px 0;
- color: $gl-text-color;
+ float: left;
+ font-weight: 600;
+ font-size: 14px;
+ line-height: inherit;
+ margin-top: 0;
+ margin-bottom: 0;
&.has-conflicts .fa-exclamation-triangle {
color: $gl-warning;
@@ -255,18 +253,16 @@
}
.spacing {
- margin: 0 $gl-padding;
+ margin: 0 0 0 10px;
}
.bold {
- font-weight: bold;
- font-size: 15px;
+ font-weight: 600;
color: $gl-gray-light;
}
.state-label {
- font-size: 16px;
- font-weight: bold;
+ font-weight: 600;
padding-right: 10px;
}
@@ -274,16 +270,6 @@
color: $gl-danger;
}
- .mr-widget-help {
- margin: $gl-padding 0;
- }
-
- .with-button {
- position: relative;
- top: 6px;
- margin-bottom: 24px;
- }
-
.spacing,
.bold {
vertical-align: middle;
@@ -294,15 +280,8 @@
padding: 5px;
}
- .merge-opt-icon,
- .merge-opt-title {
- display: inline-block;
- float: left;
- }
-
- .merge-opt-icon svg {
- height: 15px;
- width: 15px;
+ .merge-opt-icon {
+ line-height: 1.5;
}
.merge-opt-title {
@@ -316,34 +295,15 @@
}
}
- .has-error-message + .has-custom-error {
- margin-left: 0;
- }
-
.has-custom-error {
display: inline-block;
- margin-left: 70px;
- }
-
- .merge-error-text {
- margin-left: 70px;
}
@media (max-width: $screen-xs-max) {
- h4 {
- font-size: 14px;
- }
-
p {
font-size: 13px;
}
- .btn,
- .btn-group,
- .accept-action {
- margin-bottom: 4px;
- }
-
.btn-grouped {
float: none;
margin-right: 0;
@@ -367,19 +327,16 @@
}
}
- &.mr-state-locked .mr-info-list {
- margin-top: 10px;
- margin-left: 12px;
- }
+ &.mr-widget-empty-state {
+ line-height: 20px;
- &.empty-state {
.artwork {
margin-bottom: $gl-padding;
}
.text {
span {
- font-weight: bold;
+ font-weight: 600;
}
p {
@@ -389,10 +346,6 @@
}
}
- .mr-widget-footer {
- border-top: 1px solid $gray-darker;
- }
-
.ci-coverage {
float: right;
}
@@ -497,8 +450,6 @@
}
.btn-clipboard {
- @extend .pull-right;
-
margin-right: 20px;
margin-top: 5px;
position: absolute;
@@ -506,56 +457,29 @@
}
}
+.mr-links {
+ padding-left: $status-icon-size + $status-icon-margin;
+}
+
.mr-info-list {
+ clear: left;
position: relative;
- margin: 10px 0 $gl-padding 12px;
+ padding-top: 4px;
p {
- margin: 6px 0;
+ margin: 0;
position: relative;
- padding-left: 15px;
-
- &::before {
- content: '';
- position: absolute;
- border-top: 2px solid $border-color;
- height: 1px;
- top: 9px;
- width: 8px;
- left: 0;
- }
+ padding: 4px 0;
&:last-child {
- margin-bottom: 0;
+ padding-bottom: 0;
}
}
-
- .legend {
- height: 100%;
- width: 2px;
- background: $border-color;
- position: absolute;
- top: -9px;
- }
}
.mr-info-list.mr-memory-usage {
- .legend {
- height: 65%;
- top: 0;
-
- @media (max-width: $screen-xs-max) {
- height: 20px;
- }
- }
-
p {
float: left;
- padding-left: 21px;
-
- &::before {
- top: 13px;
- }
}
.memory-graph-container {
@@ -565,12 +489,13 @@
}
.mr-source-target {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: space-between;
+ align-items: center;
background-color: $gray-light;
- border-radius: 3px 3px 0 0;
- border-bottom: 1px solid $border-color;
- padding: 0 $gl-padding;
- margin-bottom: 6px;
- line-height: 44px;
+ border-radius: $border-radius-default $border-radius-default 0 0;
+ padding: $gl-padding / 2 $gl-padding;
.dropdown-toggle .fa {
color: $gl-text-color;
@@ -679,20 +604,16 @@
}
.merged-buttons {
- margin-top: 20px;
-
.btn {
float: left;
-
- &:not(:last-child) {
- margin-right: 10px;
- }
}
}
.mr-version-controls {
+ position: relative;
background: $gray-light;
color: $gl-text-color;
+ z-index: 199;
.mr-version-menus-container {
display: -webkit-flex;
@@ -801,20 +722,8 @@
}
.mr-memory-usage {
- p.usage-info-loading,
- p.usage-info-unavailable,
- p.usage-info-failed {
- margin-bottom: 5px;
- }
-
p.usage-info-loading .usage-info-load-spinner {
margin-right: 10px;
font-size: 16px;
}
-
- @media (max-width: $screen-md-min) {
- .mr-info-list.mr-memory-usage .legend {
- height: 80%;
- }
- }
}
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index cdb1e65e4be..b4468d6d0a2 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -104,40 +104,39 @@
}
.confidential-issue-warning {
- background-color: $gray-normal;
- border-radius: 3px;
+ color: $orange-600;
+ background-color: $orange-50;
+ border-radius: $border-radius-default $border-radius-default 0 0;
+ border: 1px solid $border-gray-normal;
+ border-bottom: none;
padding: 3px 12px;
margin: auto;
- margin-top: 0;
- text-align: center;
- font-size: 12px;
align-items: center;
+}
- @media (max-width: $screen-md-max) {
- // On smaller devices the warning becomes the fourth item in the list,
- // rather than centering, and grows to span the full width of the
- // comment area.
- order: 4;
- margin: 6px auto;
- width: 100%;
- }
-
+.confidential-value {
.fa {
- margin-right: 8px;
+ background-color: inherit;
}
}
-.right-sidebar-expanded {
- .confidential-issue-warning {
- // When the sidebar is open the warning becomes the fourth item in the list,
- // rather than centering, and grows to span the full width of the
- // comment area.
- order: 4;
- margin: 6px auto;
- width: 100%;
+.confidential-warning-message {
+ line-height: 1.5;
+ padding: 16px;
+
+ .confidential-warning-message-actions {
+ display: flex;
+
+ button {
+ flex-grow: 1;
+ }
}
}
+.confidential-issue-warning + .md-area {
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
+}
.discussion-form {
padding: $gl-padding-top $gl-padding $gl-padding;
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 2bb867052f6..0a194f3707f 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -453,7 +453,10 @@ ul.notes {
}
.note-actions {
+ align-self: flex-start;
flex-shrink: 0;
+ display: inline-flex;
+ align-items: center;
// For PhantomJS that does not support flex
float: right;
margin-left: 10px;
@@ -463,18 +466,12 @@ ul.notes {
float: none;
margin-left: 0;
}
-
- .note-action-button {
- margin-left: 8px;
- }
-
- .more-actions-toggle {
- margin-left: 2px;
- }
}
.more-actions {
- display: inline-block;
+ float: right; // phantomjs fallback
+ display: flex;
+ align-items: flex-end;
.tooltip {
white-space: nowrap;
@@ -482,16 +479,10 @@ ul.notes {
}
.more-actions-toggle {
- padding: 0;
-
&:hover .icon,
&:focus .icon {
color: $blue-600;
}
-
- .icon {
- padding: 0 6px;
- }
}
.more-actions-dropdown {
@@ -519,28 +510,42 @@ ul.notes {
@include notes-media('max', $screen-md-max) {
float: none;
margin-left: 0;
+ }
+}
- .note-action-button {
- margin-left: 0;
- }
+.note-actions-item {
+ margin-left: 15px;
+ display: flex;
+ align-items: center;
+
+ &.more-actions {
+ // compensate for narrow icon
+ margin-left: 10px;
}
}
.note-action-button {
- display: inline;
- line-height: 20px;
+ line-height: 1;
+ padding: 0;
+ min-width: 16px;
+ color: $gray-darkest;
.fa {
- color: $gray-darkest;
position: relative;
- font-size: 17px;
+ font-size: 16px;
}
+
+
svg {
height: 16px;
width: 16px;
- fill: $gray-darkest;
+ top: 0;
vertical-align: text-top;
+
+ path {
+ fill: currentColor;
+ }
}
.award-control-icon-positive,
@@ -613,10 +618,7 @@ ul.notes {
.note-role {
position: relative;
- top: -2px;
- display: inline-block;
- padding-left: 7px;
- padding-right: 7px;
+ padding: 0 7px;
color: $notes-role-color;
font-size: 12px;
line-height: 20px;
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index d3862df20d3..85d1905ad40 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -220,7 +220,11 @@
position: relative;
vertical-align: middle;
height: 22px;
- margin: 3px 6px 3px 0;
+ margin: 3px 0;
+
+ + .stage-container {
+ margin-left: 6px;
+ }
// Hack to show a button tooltip inline
button.has-tooltip + .tooltip {
@@ -820,6 +824,7 @@ button.mini-pipeline-graph-dropdown-toggle {
* Top arrow in the dropdown in the mini pipeline graph
*/
.mini-pipeline-graph-dropdown-menu {
+ z-index: 200;
&::before,
&::after {
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index b3a90dff89a..d01326637ea 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -7,7 +7,8 @@
}
.new_project,
-.edit-project {
+.edit-project,
+.import-project {
.sharing-and-permissions {
.header {
@@ -36,7 +37,6 @@
}
select {
- background: transparent;
transition: background 2s ease-out;
&.highlight-changes {
@@ -282,6 +282,8 @@
}
.project-repo-buttons {
+ @include new-style-dropdown;
+
.project-action-button .dropdown-menu {
max-height: 250px;
overflow-y: auto;
@@ -456,6 +458,7 @@ a.deploy-project-label {
}
}
+.project-template,
.project-import {
.form-group {
margin-bottom: 5px;
@@ -470,7 +473,44 @@ a.deploy-project-label {
.btn {
padding: 8px;
- margin-left: 10px;
+ margin-right: 10px;
+ }
+
+ .blank-option {
+ min-width: 70px;
+ }
+
+ .btn-template-icon {
+ height: 24px;
+ width: inherit;
+ display: block;
+ margin: 0 auto 4px;
+ font-size: 24px;
+
+ @media (min-width: $screen-xs-max) {
+ top: 0;
+ }
+ }
+
+ @media (max-width: $screen-xs-max) {
+ .btn-template-icon {
+ display: inline-block;
+ height: 14px;
+ font-size: 14px;
+ margin: 0;
+ }
+ }
+
+ .icon-rails path {
+ fill: $rails;
+ }
+
+ .icon-node-express path {
+ fill: $node;
+ }
+
+ .icon-java-spring path {
+ fill: $java;
}
> div {
@@ -480,6 +520,97 @@ a.deploy-project-label {
}
}
+.project-templates-buttons .btn:last-child {
+ margin-right: 0;
+}
+
+.create-project-options {
+ display: flex;
+
+ @media (max-width: $screen-xs-max) {
+ display: block;
+ }
+
+ .first-column {
+ @media(min-width: $screen-xs-min) {
+ max-width: 50%;
+ padding-right: 30px;
+ }
+
+ @media(max-width: $screen-xs-max) {
+ max-width: 100%;
+ width: 100%;
+ }
+ }
+
+ .second-column {
+ @media(min-width: $screen-xs-min) {
+ width: 50%;
+ flex: 1;
+ padding-left: 30px;
+ position: relative;
+ }
+
+ @media(max-width: $screen-xs-max) {
+ max-width: 100%;
+ width: 100%;
+ padding-left: 0;
+ position: relative;
+ }
+
+ // Mobile
+ @media (max-width: $screen-xs-max) {
+ padding-top: 30px;
+ }
+
+ &::before {
+ content: "OR";
+ position: absolute;
+ left: -10px;
+ top: 50%;
+ z-index: 10;
+ padding: 8px 0;
+ text-align: center;
+ background-color: $white-light;
+ color: $gl-text-color-tertiary;
+ transform: translateY(-50%);
+ font-size: 12px;
+ font-weight: bold;
+ line-height: 20px;
+
+ // Mobile
+ @media (max-width: $screen-xs-max) {
+ left: 50%;
+ top: 0;
+ transform: translateX(-50%);
+ padding: 0 8px;
+ }
+ }
+
+ &::after {
+ content: "";
+ position: absolute;
+ background-color: $border-color;
+ bottom: 0;
+ left: 0;
+ right: auto;
+ height: 100%;
+ width: 1px;
+ top: 0;
+
+ // Mobile
+ @media (max-width: $screen-xs-max) {
+ top: 10px;
+ left: 10px;
+ right: 10px;
+ height: 1px;
+ width: auto;
+ }
+ }
+ }
+}
+
+
.project-stats {
font-size: 0;
text-align: center;
diff --git a/app/assets/stylesheets/pages/repo.scss b/app/assets/stylesheets/pages/repo.scss
new file mode 100644
index 00000000000..ad17078c98a
--- /dev/null
+++ b/app/assets/stylesheets/pages/repo.scss
@@ -0,0 +1,413 @@
+.fade-enter-active,
+.fade-leave-active {
+ transition: opacity .5s;
+}
+
+.monaco-loader {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: $black-transparent;
+}
+
+.modal.popup-dialog {
+ display: block;
+ background-color: $black-transparent;
+ z-index: 2100;
+
+ @media (min-width: $screen-md-min) {
+ .modal-dialog {
+ width: 600px;
+ margin: 30px auto;
+ }
+ }
+}
+
+.project-refs-form,
+.project-refs-target-form {
+ display: inline-block;
+
+ &.disabled {
+ opacity: 0.5;
+ pointer-events: none;
+ }
+}
+
+.fade-enter,
+.fade-leave-to {
+ opacity: 0;
+}
+
+.commit-message {
+ @include str-truncated(250px);
+}
+
+.editable-mode {
+ display: inline-block;
+}
+
+.blob-viewer[data-type="rich"] {
+ margin: 20px;
+}
+
+.repository-view.tree-content-holder {
+ border: 1px solid $border-color;
+ border-radius: $border-radius-default;
+ color: $almost-black;
+
+ .panel-right {
+ display: inline-block;
+ width: 80%;
+
+ .monaco-editor.vs {
+ .line-numbers {
+ cursor: pointer;
+
+ &:hover {
+ text-decoration: underline;
+ }
+ }
+
+ .cursor {
+ display: none !important;
+ }
+ }
+
+ &.edit-mode {
+ .blob-viewer-container {
+ overflow: hidden;
+ }
+
+ .monaco-editor.vs {
+ .cursor {
+ background: $black;
+ border-color: $black;
+ display: block !important;
+ }
+ }
+ }
+
+ .blob-viewer-container {
+ height: calc(100vh - 63px);
+ overflow: auto;
+ }
+
+ #tabs {
+ padding-left: 0;
+ margin-bottom: 0;
+ display: flex;
+ white-space: nowrap;
+ width: 100%;
+ overflow-y: hidden;
+ overflow-x: auto;
+
+ li {
+ animation: swipeRightAppear ease-in 0.1s;
+ animation-iteration-count: 1;
+ transform-origin: 0% 50%;
+ list-style-type: none;
+ background: $gray-normal;
+ display: inline-block;
+ padding: 10px 18px;
+ border-right: 1px solid $white-dark;
+ border-bottom: 1px solid $white-dark;
+ white-space: nowrap;
+
+ &.remove {
+ animation: swipeRightDissapear ease-in 0.1s;
+ animation-iteration-count: 1;
+ transform-origin: 0% 50%;
+
+ a {
+ width: 0;
+ }
+ }
+
+ &.active {
+ background: $white-light;
+ border-bottom: none;
+ }
+
+ a {
+ @include str-truncated(100px);
+ color: $black;
+ display: inline-block;
+ width: 100px;
+ text-align: center;
+ vertical-align: middle;
+
+ &.close {
+ width: auto;
+ font-size: 15px;
+ opacity: 1;
+ margin-right: -6px;
+ }
+ }
+
+ i.fa.fa-times,
+ i.fa.fa-circle {
+ float: right;
+ margin-top: 3px;
+ margin-left: 15px;
+ color: $gray-darkest;
+ }
+
+ i.fa.fa-circle {
+ color: $brand-success;
+ }
+
+ &.tabs-divider {
+ width: 100%;
+ background-color: $white-light;
+ border-right: none;
+ border-top-right-radius: 2px;
+ }
+ }
+ }
+
+ #repo-file-buttons {
+ background-color: $white-light;
+ border-bottom: 1px solid $white-normal;
+ padding: 5px 10px;
+ position: relative;
+ border-top: 1px solid $white-normal;
+ margin-top: -5px;
+ }
+
+ #binary-viewer {
+ height: 80vh;
+ overflow: auto;
+ margin: 0;
+
+ .blob-viewer {
+ padding-top: 20px;
+ padding-left: 20px;
+ }
+
+ .binary-unknown {
+ text-align: center;
+ padding-top: 100px;
+ background: $gray-light;
+ height: 100%;
+ font-size: 17px;
+
+ span {
+ display: block;
+ }
+ }
+ }
+ }
+
+ #commit-area {
+ background: $gray-light;
+ padding: 20px;
+
+ span.help-block {
+ padding-top: 7px;
+ margin-top: 0;
+ }
+ }
+
+ #view-toggler {
+ height: 41px;
+ position: relative;
+ display: block;
+ border-bottom: 1px solid $white-normal;
+ background: $white-light;
+ margin-top: -5px;
+ }
+
+ #binary-viewer {
+ img {
+ max-width: 100%;
+ }
+ }
+
+ #sidebar {
+
+ &.sidebar-mini {
+ display: inline-block;
+ vertical-align: top;
+ width: 20%;
+ border-right: 1px solid $white-normal;
+ height: calc(100vh + 20px);
+ overflow: auto;
+ }
+
+ table {
+ margin-bottom: 0;
+ }
+
+ tr {
+ animation: fadein 0.5s;
+ cursor: pointer;
+
+ &.repo-file-options td {
+ padding: 0;
+ border-top: none;
+ background: $gray-light;
+ width: 100%;
+ display: inline-block;
+
+ &:first-child {
+ border-top-left-radius: 2px;
+ }
+
+ .title {
+ display: inline-block;
+ font-size: 10px;
+ text-transform: uppercase;
+ font-weight: bold;
+ color: $gray-darkest;
+ width: 185px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ vertical-align: middle;
+ padding: 2px 16px;
+ }
+ }
+
+ .fa {
+ margin-right: 5px;
+ }
+
+ td {
+ white-space: nowrap;
+ }
+ }
+
+ a {
+ color: $almost-black;
+ display: inline-block;
+ vertical-align: middle;
+ }
+
+ ul {
+ list-style-type: none;
+ padding: 0;
+
+ li {
+ border-bottom: 1px solid $border-gray-normal;
+ padding: 10px 20px;
+
+ a {
+ color: $almost-black;
+ }
+
+ .fa {
+ font-size: $code_font_size;
+ margin-right: 5px;
+ }
+ }
+ }
+ }
+
+}
+
+.animation-container {
+ background: $repo-editor-grey;
+ height: 40px;
+ overflow: hidden;
+ position: relative;
+
+ &.animation-container-small {
+ height: 12px;
+ }
+
+ &::before {
+ animation-duration: 1s;
+ animation-fill-mode: forwards;
+ animation-iteration-count: infinite;
+ animation-name: blockTextShine;
+ animation-timing-function: linear;
+ background-image: $repo-editor-linear-gradient;
+ background-repeat: no-repeat;
+ background-size: 800px 45px;
+ content: ' ';
+ display: block;
+ height: 100%;
+ position: relative;
+ }
+
+ div {
+ background: $white-light;
+ height: 6px;
+ left: 0;
+ position: absolute;
+ right: 0;
+ }
+
+ .line-of-code-1 {
+ left: 0;
+ top: 8px;
+ }
+
+ .line-of-code-2 {
+ left: 150px;
+ top: 0;
+ height: 10px;
+ }
+
+ .line-of-code-3 {
+ left: 0;
+ top: 23px;
+ }
+
+ .line-of-code-4 {
+ left: 0;
+ top: 38px;
+ }
+
+ .line-of-code-5 {
+ left: 200px;
+ top: 28px;
+ height: 10px;
+ }
+
+ .line-of-code-6 {
+ top: 14px;
+ left: 230px;
+ height: 10px;
+ }
+}
+
+.render-error {
+ min-height: calc(100vh - 63px);
+
+ p {
+ width: 100%;
+ }
+}
+
+@keyframes blockTextShine {
+ 0% {
+ transform: translateX(-468px);
+ }
+
+ 100% {
+ transform: translateX(468px);
+ }
+}
+
+@keyframes swipeRightAppear {
+ 0% {
+ transform: scaleX(0.00);
+ }
+
+ 100% {
+ transform: scaleX(1.00);
+ }
+}
+
+@keyframes swipeRightDissapear {
+ 0% {
+ transform: scaleX(1.00);
+ }
+
+ 100% {
+ transform: scaleX(0.00);
+ }
+}
diff --git a/app/assets/stylesheets/pages/settings.scss b/app/assets/stylesheets/pages/settings.scss
index d69a8e0995c..15df51e9c69 100644
--- a/app/assets/stylesheets/pages/settings.scss
+++ b/app/assets/stylesheets/pages/settings.scss
@@ -54,8 +54,7 @@
.settings-content {
max-height: 1px;
overflow-y: scroll;
- margin-right: -20px;
- padding-right: 130px;
+ padding-right: 110px;
animation: collapseMaxHeight 300ms ease-out;
&.expanded {
@@ -87,6 +86,23 @@
overflow: hidden;
margin-top: 20px;
}
+
+ .sub-section {
+ margin-bottom: 32px;
+ padding: 16px;
+ border: 1px solid $border-color;
+ background-color: $gray-light;
+ }
+
+ .bs-callout,
+ .checkbox:first-child,
+ .help-block {
+ margin-top: 0;
+ }
+
+ .label-light {
+ margin-bottom: 0;
+ }
}
.settings-list-icon {
diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss
index e0f46172769..0028e207f3e 100644
--- a/app/assets/stylesheets/pages/tree.scss
+++ b/app/assets/stylesheets/pages/tree.scss
@@ -1,4 +1,5 @@
.tree-holder {
+ @include new-style-dropdown;
.nav-block {
margin: 10px 0;
@@ -28,6 +29,10 @@
margin-right: 15px;
}
+ .tree-ref-target-holder {
+ display: inline-block;
+ }
+
.repo-breadcrumb {
li:last-of-type {
position: relative;
@@ -86,7 +91,7 @@
}
.add-to-tree {
- vertical-align: top;
+ vertical-align: middle;
padding: 6px 10px;
}
@@ -202,28 +207,6 @@
}
}
}
-
- // TODO: fallback to global style
- .dropdown-menu:not(.dropdown-menu-selectable) {
- li {
- padding: 0 1px;
-
- &.dropdown-header {
- padding: 8px 16px;
- }
-
- a {
- border-radius: 0;
- padding: 8px 16px;
-
- &:hover,
- &:active,
- &:focus {
- background-color: $gray-darker;
- }
- }
- }
- }
}
.blob-commit-info {
@@ -237,6 +220,9 @@
}
.blob-upload-dropzone-previews {
+ display: flex;
+ justify-content: center;
+ align-items: center;
text-align: center;
border: 2px;
border-style: dashed;
diff --git a/app/assets/stylesheets/pages/wiki.scss b/app/assets/stylesheets/pages/wiki.scss
index 45c21c5d274..fa6bdd297eb 100644
--- a/app/assets/stylesheets/pages/wiki.scss
+++ b/app/assets/stylesheets/pages/wiki.scss
@@ -95,12 +95,22 @@
}
.right-sidebar.wiki-sidebar {
- padding: $gl-padding 0;
+ padding: 0;
&.right-sidebar-collapsed {
display: none;
}
+ .sidebar-container {
+ padding: $gl-padding 0;
+ width: calc(100% + 100px);
+ padding-right: 100px;
+ height: 100%;
+ overflow-y: scroll;
+ overflow-x: hidden;
+ -webkit-overflow-scrolling: touch;
+ }
+
.blocks-container {
padding: 0 $gl-padding;
}
diff --git a/app/controllers/admin/appearances_controller.rb b/app/controllers/admin/appearances_controller.rb
index 4b0ec54b3f4..92df1c8dff0 100644
--- a/app/controllers/admin/appearances_controller.rb
+++ b/app/controllers/admin/appearances_controller.rb
@@ -45,7 +45,7 @@ class Admin::AppearancesController < Admin::ApplicationController
# Use callbacks to share common setup or constraints between actions.
def set_appearance
- @appearance = Appearance.last || Appearance.new
+ @appearance = Appearance.current || Appearance.new
end
# Only allow a trusted parameter "white list" through.
diff --git a/app/controllers/admin/health_check_controller.rb b/app/controllers/admin/health_check_controller.rb
index caf4c138da8..65a17828feb 100644
--- a/app/controllers/admin/health_check_controller.rb
+++ b/app/controllers/admin/health_check_controller.rb
@@ -1,5 +1,12 @@
class Admin::HealthCheckController < Admin::ApplicationController
def show
@errors = HealthCheck::Utils.process_checks(['standard'])
+ @failing_storage_statuses = Gitlab::Git::Storage::Health.for_failing_storages
+ end
+
+ def reset_storage_health
+ Gitlab::Git::Storage::CircuitBreaker.reset_all!
+ redirect_to admin_health_check_path,
+ notice: _('Git storage health information has been reset')
end
end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index d14b1dbecf6..1d92ea11bda 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -52,6 +52,15 @@ class ApplicationController < ActionController::Base
head :forbidden, retry_after: Gitlab::Auth::UniqueIpsLimiter.config.unique_ips_limit_time_window
end
+ rescue_from Gitlab::Git::Storage::Inaccessible, GRPC::Unavailable, Gitlab::Git::CommandError do |exception|
+ Raven.capture_exception(exception) if sentry_enabled?
+ log_exception(exception)
+
+ headers['Retry-After'] = exception.retry_after if exception.respond_to?(:retry_after)
+
+ render_503
+ end
+
def redirect_back_or_default(default: root_path, options: {})
redirect_to request.referer.present? ? :back : default, options
end
@@ -108,7 +117,7 @@ class ApplicationController < ActionController::Base
Raven.capture_exception(exception) if sentry_enabled?
application_trace = ActionDispatch::ExceptionWrapper.new(env, exception).application_trace
- application_trace.map!{ |t| " #{t}\n" }
+ application_trace.map! { |t| " #{t}\n" }
logger.error "\n#{exception.class.name} (#{exception.message}):\n#{application_trace.join}"
end
@@ -152,6 +161,19 @@ class ApplicationController < ActionController::Base
head :unprocessable_entity
end
+ def render_503
+ respond_to do |format|
+ format.html do
+ render(
+ file: Rails.root.join("public", "503"),
+ layout: false,
+ status: :service_unavailable
+ )
+ end
+ format.any { head :service_unavailable }
+ end
+ end
+
def no_cache_headers
response.headers["Cache-Control"] = "no-cache, no-store, max-age=0, must-revalidate"
response.headers["Pragma"] = "no-cache"
diff --git a/app/controllers/concerns/authenticates_with_two_factor.rb b/app/controllers/concerns/authenticates_with_two_factor.rb
index ea441b1736b..b75e401a8df 100644
--- a/app/controllers/concerns/authenticates_with_two_factor.rb
+++ b/app/controllers/concerns/authenticates_with_two_factor.rb
@@ -69,7 +69,7 @@ module AuthenticatesWithTwoFactor
if U2fRegistration.authenticate(user, u2f_app_id, user_params[:device_response], session[:challenge])
# Remove any lingering user data from login
session.delete(:otp_user_id)
- session.delete(:challenges)
+ session.delete(:challenge)
remember_me(user) if user_params[:remember_me] == '1'
sign_in(user)
diff --git a/app/controllers/concerns/cycle_analytics_params.rb b/app/controllers/concerns/cycle_analytics_params.rb
index 52e06f4945a..1ab107168c0 100644
--- a/app/controllers/concerns/cycle_analytics_params.rb
+++ b/app/controllers/concerns/cycle_analytics_params.rb
@@ -6,6 +6,13 @@ module CycleAnalyticsParams
end
def start_date(params)
- params[:start_date] == '30' ? 30.days.ago : 90.days.ago
+ case params[:start_date]
+ when '7'
+ 7.days.ago
+ when '30'
+ 30.days.ago
+ else
+ 90.days.ago
+ end
end
end
diff --git a/app/controllers/concerns/renders_blob.rb b/app/controllers/concerns/renders_blob.rb
index 54dcd7c61ce..ba7adcfea86 100644
--- a/app/controllers/concerns/renders_blob.rb
+++ b/app/controllers/concerns/renders_blob.rb
@@ -1,7 +1,7 @@
module RendersBlob
extend ActiveSupport::Concern
- def render_blob_json(blob)
+ def blob_json(blob)
viewer =
case params[:viewer]
when 'rich'
@@ -11,13 +11,21 @@ module RendersBlob
else
blob.simple_viewer
end
- return render_404 unless viewer
- render json: {
+ return unless viewer
+
+ {
html: view_to_html_string("projects/blob/_viewer", viewer: viewer, load_async: false)
}
end
+ def render_blob_json(blob)
+ json = blob_json(blob)
+ return render_404 unless json
+
+ render json: json
+ end
+
def conditionally_expand_blob(blob)
blob.expand! if params[:expanded] == 'true'
end
diff --git a/app/controllers/dashboard/projects_controller.rb b/app/controllers/dashboard/projects_controller.rb
index 91c1e4dff79..f71ab702e71 100644
--- a/app/controllers/dashboard/projects_controller.rb
+++ b/app/controllers/dashboard/projects_controller.rb
@@ -45,13 +45,17 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
end
def load_projects(finder_params)
- ProjectsFinder.new(params: finder_params, current_user: current_user)
- .execute.includes(:route, namespace: :route)
+ ProjectsFinder
+ .new(params: finder_params, current_user: current_user)
+ .execute
+ .includes(:route, :creator, namespace: :route)
end
def load_events
- @events = Event.in_projects(load_projects(params.merge(non_public: true)))
- @events = event_filter.apply_filter(@events).with_associations
- @events = @events.limit(20).offset(params[:offset] || 0)
+ projects = load_projects(params.merge(non_public: true))
+
+ @events = EventCollection
+ .new(projects, offset: params[:offset].to_i, filter: event_filter)
+ .to_a
end
end
diff --git a/app/controllers/dashboard/todos_controller.rb b/app/controllers/dashboard/todos_controller.rb
index 59e5b5e4775..a8b2b93b458 100644
--- a/app/controllers/dashboard/todos_controller.rb
+++ b/app/controllers/dashboard/todos_controller.rb
@@ -13,7 +13,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController
end
def destroy
- TodoService.new.mark_todos_as_done_by_ids([params[:id]], current_user)
+ TodoService.new.mark_todos_as_done_by_ids(params[:id], current_user)
respond_to do |format|
format.html do
@@ -37,7 +37,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController
end
def restore
- TodoService.new.mark_todos_as_pending_by_ids([params[:id]], current_user)
+ TodoService.new.mark_todos_as_pending_by_ids(params[:id], current_user)
render json: todos_counts
end
diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb
index f9c31920302..19a5db6fd17 100644
--- a/app/controllers/dashboard_controller.rb
+++ b/app/controllers/dashboard_controller.rb
@@ -29,9 +29,9 @@ class DashboardController < Dashboard::ApplicationController
current_user.authorized_projects
end
- @events = Event.in_projects(projects)
- @events = @event_filter.apply_filter(@events).with_associations
- @events = @events.limit(20).offset(params[:offset] || 0)
+ @events = EventCollection
+ .new(projects, offset: params[:offset].to_i, filter: @event_filter)
+ .to_a
end
def set_show_full_reference
diff --git a/app/controllers/explore/projects_controller.rb b/app/controllers/explore/projects_controller.rb
index 741879dee35..762c6ebf3a3 100644
--- a/app/controllers/explore/projects_controller.rb
+++ b/app/controllers/explore/projects_controller.rb
@@ -6,7 +6,7 @@ class Explore::ProjectsController < Explore::ApplicationController
def index
params[:sort] ||= 'latest_activity_desc'
@sort = params[:sort]
- @projects = load_projects.page(params[:page])
+ @projects = load_projects
respond_to do |format|
format.html
@@ -21,7 +21,7 @@ class Explore::ProjectsController < Explore::ApplicationController
def trending
params[:trending] = true
@sort = params[:sort]
- @projects = load_projects.page(params[:page])
+ @projects = load_projects
respond_to do |format|
format.html
@@ -34,7 +34,7 @@ class Explore::ProjectsController < Explore::ApplicationController
end
def starred
- @projects = load_projects.reorder('star_count DESC').page(params[:page])
+ @projects = load_projects.reorder('star_count DESC')
respond_to do |format|
format.html
@@ -50,6 +50,9 @@ class Explore::ProjectsController < Explore::ApplicationController
def load_projects
ProjectsFinder.new(current_user: current_user, params: params)
- .execute.includes(:route, namespace: :route)
+ .execute
+ .includes(:route, namespace: :route)
+ .page(params[:page])
+ .without_count
end
end
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index 27137ffde54..f76b3f69e9e 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -160,9 +160,9 @@ class GroupsController < Groups::ApplicationController
end
def load_events
- @events = Event.in_projects(@projects)
- @events = event_filter.apply_filter(@events).with_associations
- @events = @events.limit(20).offset(params[:offset] || 0)
+ @events = EventCollection
+ .new(@projects, offset: params[:offset].to_i, filter: event_filter)
+ .to_a
end
def user_actions
diff --git a/app/controllers/import/github_controller.rb b/app/controllers/import/github_controller.rb
index 53a5981e564..baa6645e5ce 100644
--- a/app/controllers/import/github_controller.rb
+++ b/app/controllers/import/github_controller.rb
@@ -68,15 +68,15 @@ class Import::GithubController < Import::BaseController
end
def new_import_url
- public_send("new_import_#{provider}_url")
+ public_send("new_import_#{provider}_url") # rubocop:disable GitlabSecurity/PublicSend
end
def status_import_url
- public_send("status_import_#{provider}_url")
+ public_send("status_import_#{provider}_url") # rubocop:disable GitlabSecurity/PublicSend
end
def callback_import_url
- public_send("callback_import_#{provider}_url")
+ public_send("callback_import_#{provider}_url") # rubocop:disable GitlabSecurity/PublicSend
end
def provider_unauthorized
diff --git a/app/controllers/import/gitlab_controller.rb b/app/controllers/import/gitlab_controller.rb
index 73837ffbe67..407154e59a0 100644
--- a/app/controllers/import/gitlab_controller.rb
+++ b/app/controllers/import/gitlab_controller.rb
@@ -15,7 +15,7 @@ class Import::GitlabController < Import::BaseController
@already_added_projects = current_user.created_projects.where(import_type: "gitlab")
already_added_projects_names = @already_added_projects.pluck(:import_source)
- @repos = @repos.to_a.reject{ |repo| already_added_projects_names.include? repo["path_with_namespace"] }
+ @repos = @repos.to_a.reject { |repo| already_added_projects_names.include? repo["path_with_namespace"] }
end
def jobs
diff --git a/app/controllers/import/gitlab_projects_controller.rb b/app/controllers/import/gitlab_projects_controller.rb
index 36d246d185b..510813846a4 100644
--- a/app/controllers/import/gitlab_projects_controller.rb
+++ b/app/controllers/import/gitlab_projects_controller.rb
@@ -12,15 +12,7 @@ class Import::GitlabProjectsController < Import::BaseController
return redirect_back_or_default(options: { alert: "You need to upload a GitLab project export archive." })
end
- import_upload_path = Gitlab::ImportExport.import_upload_path(filename: project_params[:file].original_filename)
-
- FileUtils.mkdir_p(File.dirname(import_upload_path))
- FileUtils.copy_entry(project_params[:file].path, import_upload_path)
-
- @project = Gitlab::ImportExport::ProjectCreator.new(project_params[:namespace_id],
- current_user,
- import_upload_path,
- project_params[:path]).execute
+ @project = ::Projects::GitlabProjectsImportService.new(current_user, project_params).execute
if @project.saved?
redirect_to(
diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb
index 323d5d26eb6..b4213574561 100644
--- a/app/controllers/omniauth_callbacks_controller.rb
+++ b/app/controllers/omniauth_callbacks_controller.rb
@@ -34,12 +34,11 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
if @user.two_factor_enabled?
prompt_for_two_factor(@user)
else
- log_audit_event(@user, with: :ldap)
+ log_audit_event(@user, with: oauth['provider'])
sign_in_and_redirect(@user)
end
else
- flash[:alert] = "Access denied for your LDAP account."
- redirect_to new_user_session_path
+ fail_ldap_login
end
end
@@ -123,9 +122,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
sign_in_and_redirect(@user)
end
else
- error_message = @user.errors.full_messages.to_sentence
-
- return redirect_to omniauth_error_path(oauth['provider'], error: error_message)
+ fail_login
end
end
@@ -145,6 +142,18 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
def oauth
@oauth ||= request.env['omniauth.auth']
end
+
+ def fail_login
+ error_message = @user.errors.full_messages.to_sentence
+
+ return redirect_to omniauth_error_path(oauth['provider'], error: error_message)
+ end
+
+ def fail_ldap_login
+ flash[:alert] = 'Access denied for your LDAP account.'
+
+ redirect_to new_user_session_path
+ end
def log_audit_event(user, options = {})
AuditEventService.new(user, user, options)
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index 49ea2945675..a2e8c10857d 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -37,16 +37,11 @@ class Projects::BlobController < Projects::ApplicationController
respond_to do |format|
format.html do
- environment_params = @repository.branch_exists?(@ref) ? { ref: @ref } : { commit: @commit }
- @environment = EnvironmentsFinder.new(@project, current_user, environment_params).execute.last
-
- @last_commit = @repository.last_commit_for_path(@commit.id, @blob.path)
-
- render 'show'
+ show_html
end
format.json do
- render_blob_json(@blob)
+ show_json
end
end
end
@@ -190,4 +185,34 @@ class Projects::BlobController < Projects::ApplicationController
@last_commit_sha = Gitlab::Git::Commit
.last_for_path(@repository, @ref, @path).sha
end
+
+ def show_html
+ environment_params = @repository.branch_exists?(@ref) ? { ref: @ref } : { commit: @commit }
+ @environment = EnvironmentsFinder.new(@project, current_user, environment_params).execute.last
+ @last_commit = @repository.last_commit_for_path(@commit.id, @blob.path)
+
+ render 'show'
+ end
+
+ def show_json
+ json = blob_json(@blob)
+ return render_404 unless json
+
+ render json: json.merge(
+ path: blob.path,
+ name: blob.name,
+ extension: blob.extension,
+ size: blob.raw_size,
+ mime_type: blob.mime_type,
+ binary: blob.raw_binary?,
+ simple_viewer: blob.simple_viewer&.class&.partial_name,
+ rich_viewer: blob.rich_viewer&.class&.partial_name,
+ show_viewer_switcher: !!blob.show_viewer_switcher?,
+ render_error: blob.simple_viewer&.render_error || blob.rich_viewer&.render_error,
+ raw_path: project_raw_path(project, @id),
+ blame_path: project_blame_path(project, @id),
+ commits_path: project_commits_path(project, @id),
+ permalink: project_blob_path(project, File.join(@commit.id, @path))
+ )
+ end
end
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index e2ccabb22db..8893a514207 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -212,7 +212,7 @@ class Projects::IssuesController < Projects::ApplicationController
end
def create_merge_request
- result = MergeRequests::CreateFromIssueService.new(project, current_user, issue_iid: issue.iid).execute
+ result = ::MergeRequests::CreateFromIssueService.new(project, current_user, issue_iid: issue.iid).execute
if result[:status] == :success
render json: MergeRequestCreateSerializer.new.represent(result[:merge_request])
@@ -257,18 +257,6 @@ class Projects::IssuesController < Projects::ApplicationController
return render_404 unless @project.feature_available?(:issues, current_user)
end
- def redirect_to_external_issue_tracker
- external = @project.external_issue_tracker
-
- return unless external
-
- if action_name == 'new'
- redirect_to external.new_issue_path
- else
- redirect_to external.issue_tracker_path
- end
- end
-
def issue_params
params.require(:issue).permit(*issue_params_attributes)
end
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index d361e661d0e..4de814d0ca8 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -67,11 +67,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
@noteable = @merge_request
@commits_count = @merge_request.commits_count
- if @merge_request.locked_long_ago?
- @merge_request.unlock_mr
- @merge_request.close
- end
-
labels
set_pipeline_variables
diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb
index 30181ac3bdf..1fc276b8c03 100644
--- a/app/controllers/projects/tree_controller.rb
+++ b/app/controllers/projects/tree_controller.rb
@@ -24,12 +24,19 @@ class Projects::TreeController < Projects::ApplicationController
end
end
- @last_commit = @repository.last_commit_for_path(@commit.id, @tree.path) || @commit
-
respond_to do |format|
- format.html
- # Disable cache so browser history works
- format.js { no_cache_headers }
+ format.html do
+ @last_commit = @repository.last_commit_for_path(@commit.id, @tree.path) || @commit
+ end
+
+ format.js do
+ # Disable cache so browser history works
+ no_cache_headers
+ end
+
+ format.json do
+ render json: TreeSerializer.new(project: @project, repository: @repository, ref: @ref).represent(@tree)
+ end
end
end
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 2d7cbd4614e..1d24563a6a6 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -7,6 +7,7 @@ class ProjectsController < Projects::ApplicationController
before_action :repository, except: [:index, :new, :create]
before_action :assign_ref_vars, only: [:show], if: :repo_exists?
before_action :tree, only: [:show], if: [:repo_exists?, :project_view_files?]
+ before_action :project_export_enabled, only: [:export, :download_export, :remove_export, :generate_new_export]
# Authorize
before_action :authorize_admin_project!, only: [:edit, :update, :housekeeping, :download_export, :export, :remove_export, :generate_new_export]
@@ -220,21 +221,34 @@ class ProjectsController < Projects::ApplicationController
end
def refs
- branches = BranchesFinder.new(@repository, params).execute.map(&:name)
+ find_refs = params['find']
- options = {
- s_('RefSwitcher|Branches') => branches.take(100)
- }
+ find_branches = true
+ find_tags = true
+ find_commits = true
+
+ unless find_refs.nil?
+ find_branches = find_refs.include?('branches')
+ find_tags = find_refs.include?('tags')
+ find_commits = find_refs.include?('commits')
+ end
- unless @repository.tag_count.zero?
- tags = TagsFinder.new(@repository, params).execute.map(&:name)
+ options = {}
- options[s_('RefSwitcher|Tags')] = tags.take(100)
+ if find_branches
+ branches = BranchesFinder.new(@repository, params).execute.take(100).map(&:name)
+ options[s_('RefSwitcher|Branches')] = branches
+ end
+
+ if find_tags && @repository.tag_count.nonzero?
+ tags = TagsFinder.new(@repository, params).execute.take(100).map(&:name)
+
+ options[s_('RefSwitcher|Tags')] = tags
end
# If reference is commit id - we should add it to branch/tag selectbox
ref = Addressable::URI.unescape(params[:ref])
- if ref && options.flatten(2).exclude?(ref) && ref =~ /\A[0-9a-zA-Z]{6,52}\z/
+ if find_commits && ref && options.flatten(2).exclude?(ref) && ref =~ /\A[0-9a-zA-Z]{6,52}\z/
options['Commits'] = [ref]
end
@@ -288,10 +302,11 @@ class ProjectsController < Projects::ApplicationController
end
def load_events
- @events = @project.events.recent
- @events = event_filter.apply_filter(@events).with_associations
- limit = (params[:limit] || 20).to_i
- @events = @events.limit(limit).offset(params[:offset] || 0)
+ projects = Project.where(id: @project.id)
+
+ @events = EventCollection
+ .new(projects, offset: params[:offset].to_i, filter: event_filter)
+ .to_a
end
def project_params
@@ -324,6 +339,7 @@ class ProjectsController < Projects::ApplicationController
:runners_token,
:tag_list,
:visibility_level,
+ :template_name,
project_feature_attributes: %i[
builds_access_level
@@ -375,4 +391,8 @@ class ProjectsController < Projects::ApplicationController
url_for(params)
end
+
+ def project_export_enabled
+ render_404 unless current_application_settings.project_export_enabled?
+ end
end
diff --git a/app/controllers/unicorn_test_controller.rb b/app/controllers/unicorn_test_controller.rb
index b7a1a046be0..ed04bd1f77d 100644
--- a/app/controllers/unicorn_test_controller.rb
+++ b/app/controllers/unicorn_test_controller.rb
@@ -1,12 +1,14 @@
+# :nocov:
if Rails.env.test?
class UnicornTestController < ActionController::Base
def pid
render plain: Process.pid.to_s
end
-
+
def kill
Process.kill(params[:signal], Process.pid)
render plain: 'Bye!'
end
end
end
+# :nocov:
diff --git a/app/finders/admin/projects_finder.rb b/app/finders/admin/projects_finder.rb
index a5ba791a513..7176bfe22d6 100644
--- a/app/finders/admin/projects_finder.rb
+++ b/app/finders/admin/projects_finder.rb
@@ -18,7 +18,7 @@ class Admin::ProjectsFinder
end
def execute
- items = Project.with_statistics
+ items = Project.without_deleted.with_statistics
items = items.in_namespace(namespace_id) if namespace_id.present?
items = items.where(visibility_level: visibility_level) if visibility_level.present?
items = items.with_push if with_push.present?
diff --git a/app/finders/todos_finder.rb b/app/finders/todos_finder.rb
index 3fe37c75381..b276116f0c6 100644
--- a/app/finders/todos_finder.rb
+++ b/app/finders/todos_finder.rb
@@ -95,9 +95,18 @@ class TodosFinder
@project
end
+ def project_ids(items)
+ ids = items.except(:order).select(:project_id)
+ if Gitlab::Database.mysql?
+ # To make UPDATE work on MySQL, wrap it in a SELECT with an alias
+ ids = Todo.except(:order).select('*').from("(#{ids.to_sql}) AS t")
+ end
+
+ ids
+ end
+
def projects(items)
- item_project_ids = items.reorder(nil).select(:project_id)
- ProjectsFinder.new(current_user: current_user, project_ids_relation: item_project_ids).execute
+ ProjectsFinder.new(current_user: current_user, project_ids_relation: project_ids(items)).execute
end
def type?
diff --git a/app/helpers/appearances_helper.rb b/app/helpers/appearances_helper.rb
index 16136d02530..cdf5fa5d4b7 100644
--- a/app/helpers/appearances_helper.rb
+++ b/app/helpers/appearances_helper.rb
@@ -20,7 +20,7 @@ module AppearancesHelper
end
def brand_item
- @appearance ||= Appearance.first
+ @appearance ||= Appearance.current
end
def brand_header_logo
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 14dc9bd9d62..bcee81bdc15 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -305,4 +305,12 @@ module ApplicationHelper
def show_new_nav?
cookies["new_nav"] == "true"
end
+
+ def collapsed_sidebar?
+ cookies["sidebar_collapsed"] == "true"
+ end
+
+ def show_new_repo?
+ cookies["new_repo"] == "true" && body_data_page != 'projects:show'
+ end
end
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index 6825adcb39f..150188f0b65 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -146,6 +146,7 @@ module ApplicationSettingsHelper
:plantuml_enabled,
:plantuml_url,
:polling_interval_multiplier,
+ :project_export_enabled,
:prometheus_metrics_enabled,
:recaptcha_enabled,
:recaptcha_private_key,
diff --git a/app/helpers/avatars_helper.rb b/app/helpers/avatars_helper.rb
index 0e068d4b51c..4b51269533c 100644
--- a/app/helpers/avatars_helper.rb
+++ b/app/helpers/avatars_helper.rb
@@ -19,7 +19,8 @@ module AvatarsHelper
class: %W[avatar has-tooltip s#{avatar_size}].push(*options[:css_class]),
alt: "#{user_name}'s avatar",
title: user_name,
- data: data_attributes
+ data: data_attributes,
+ lazy: true
)
end
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index e964d7a5e16..18075ee8be7 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -118,7 +118,7 @@ module BlobHelper
icon("#{file_type_icon_class('file', mode, name)} fw")
end
- def blob_raw_url
+ def blob_raw_path
if @build && @entry
raw_project_job_artifacts_path(@project, @build, path: @entry.path)
elsif @snippet
@@ -235,7 +235,7 @@ module BlobHelper
title = 'Open raw'
end
- link_to icon, blob_raw_url, class: 'btn btn-sm has-tooltip', target: '_blank', rel: 'noopener noreferrer', title: title, data: { container: 'body' }
+ link_to icon, blob_raw_path, class: 'btn btn-sm has-tooltip', target: '_blank', rel: 'noopener noreferrer', title: title, data: { container: 'body' }
end
def blob_render_error_reason(viewer)
@@ -270,7 +270,7 @@ module BlobHelper
options << link_to('view the source', '#', class: 'js-blob-viewer-switch-btn', data: { viewer: 'simple' })
end
- options << link_to('download it', blob_raw_url, target: '_blank', rel: 'noopener noreferrer')
+ options << link_to('download it', blob_raw_path, target: '_blank', rel: 'noopener noreferrer')
options
end
diff --git a/app/helpers/defer_script_tag_helper.rb b/app/helpers/defer_script_tag_helper.rb
new file mode 100644
index 00000000000..e1567556e5e
--- /dev/null
+++ b/app/helpers/defer_script_tag_helper.rb
@@ -0,0 +1,6 @@
+module DeferScriptTagHelper
+ # Override the default ActionView `javascript_include_tag` helper to support page specific deferred loading
+ def javascript_include_tag(*sources)
+ super(*sources, defer: true)
+ end
+end
diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb
index 91ddd73fac1..28f591a4e22 100644
--- a/app/helpers/diff_helper.rb
+++ b/app/helpers/diff_helper.rb
@@ -88,15 +88,15 @@ module DiffHelper
end
def submodule_link(blob, ref, repository = @repository)
- tree, commit = submodule_links(blob, ref, repository)
- commit_id = if commit.nil?
+ project_url, tree_url = submodule_links(blob, ref, repository)
+ commit_id = if tree_url.nil?
Commit.truncate_sha(blob.id)
else
- link_to Commit.truncate_sha(blob.id), commit
+ link_to Commit.truncate_sha(blob.id), tree_url
end
[
- content_tag(:span, link_to(truncate(blob.name, length: 40), tree)),
+ content_tag(:span, link_to(truncate(blob.name, length: 40), project_url)),
'@',
content_tag(:span, commit_id, class: 'commit-sha')
].join(' ').html_safe
@@ -148,6 +148,24 @@ module DiffHelper
options
end
+ def diff_file_changed_icon(diff_file)
+ if diff_file.deleted_file? || diff_file.renamed_file?
+ "minus"
+ elsif diff_file.new_file?
+ "plus"
+ else
+ "adjust"
+ end
+ end
+
+ def diff_file_changed_icon_color(diff_file)
+ if diff_file.deleted_file?
+ "cred"
+ elsif diff_file.new_file?
+ "cgreen"
+ end
+ end
+
private
def diff_btn(title, name, selected)
diff --git a/app/helpers/dropdowns_helper.rb b/app/helpers/dropdowns_helper.rb
index ac8c518ac84..ff305fa39b4 100644
--- a/app/helpers/dropdowns_helper.rb
+++ b/app/helpers/dropdowns_helper.rb
@@ -48,11 +48,11 @@ module DropdownsHelper
end
end
- def dropdown_title(title, back: false)
+ def dropdown_title(title, options: {})
content_tag :div, class: "dropdown-title" do
title_output = ""
- if back
+ if options.fetch(:back, false)
title_output << content_tag(:button, class: "dropdown-title-button dropdown-menu-back", aria: { label: "Go back" }, type: "button") do
icon('arrow-left')
end
@@ -60,14 +60,25 @@ module DropdownsHelper
title_output << content_tag(:span, title)
- title_output << content_tag(:button, class: "dropdown-title-button dropdown-menu-close", aria: { label: "Close" }, type: "button") do
- icon('times', class: 'dropdown-menu-close-icon')
+ if options.fetch(:close, true)
+ title_output << content_tag(:button, class: "dropdown-title-button dropdown-menu-close", aria: { label: "Close" }, type: "button") do
+ icon('times', class: 'dropdown-menu-close-icon')
+ end
end
title_output.html_safe
end
end
+ def dropdown_input(placeholder, input_id: nil)
+ content_tag :div, class: "dropdown-input" do
+ filter_output = text_field_tag input_id, nil, class: "dropdown-input-field dropdown-no-filter", placeholder: placeholder, autocomplete: 'off'
+ filter_output << icon('times', class: "dropdown-input-clear js-dropdown-input-clear", role: "button")
+
+ filter_output.html_safe
+ end
+ end
+
def dropdown_filter(placeholder, search_id: nil)
content_tag :div, class: "dropdown-input" do
filter_output = search_field_tag search_id, nil, class: "dropdown-input-field", placeholder: placeholder, autocomplete: 'off'
diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb
index 1f7db9b2eb8..d4a91e533c1 100644
--- a/app/helpers/gitlab_routing_helper.rb
+++ b/app/helpers/gitlab_routing_helper.rb
@@ -47,14 +47,6 @@ module GitlabRoutingHelper
project_pipeline_path(pipeline.project, pipeline.id, *args)
end
- def milestone_path(entity, *args)
- if entity.is_group_milestone?
- group_milestone_path(entity.group, entity, *args)
- elsif entity.is_project_milestone?
- project_milestone_path(entity.project, entity, *args)
- end
- end
-
def issue_url(entity, *args)
project_issue_url(entity.project, entity, *args)
end
@@ -67,14 +59,6 @@ module GitlabRoutingHelper
project_pipeline_url(pipeline.project, pipeline.id, *args)
end
- def milestone_url(entity, *args)
- if entity.is_group_milestone?
- group_milestone_url(entity.group, entity, *args)
- elsif entity.is_project_milestone?
- project_milestone_url(entity.project, entity, *args)
- end
- end
-
def pipeline_job_url(pipeline, build, *args)
project_job_url(pipeline.project, build.id, *args)
end
diff --git a/app/helpers/graph_helper.rb b/app/helpers/graph_helper.rb
index 2e9b72e9613..c53ea4519da 100644
--- a/app/helpers/graph_helper.rb
+++ b/app/helpers/graph_helper.rb
@@ -3,7 +3,7 @@ module GraphHelper
refs = ""
# Commit::ref_names already strips the refs/XXX from important refs (e.g. refs/heads/XXX)
# so anything leftover is internally used by GitLab
- commit_refs = commit.ref_names(repo).reject{ |name| name.starts_with?('refs/') }
+ commit_refs = commit.ref_names(repo).reject { |name| name.starts_with?('refs/') }
refs << commit_refs.join(' ')
# append note count
diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb
index 8cd61f738e1..4123a96911f 100644
--- a/app/helpers/groups_helper.rb
+++ b/app/helpers/groups_helper.rb
@@ -59,7 +59,7 @@ module GroupsHelper
end
def remove_group_message(group)
- _("You are going to remove %{group_name}.\nRemoved groups CANNOT be restored!\nAre you ABSOLUTELY sure?") %
+ _("You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?") %
{ group_name: group.name }
end
diff --git a/app/helpers/icons_helper.rb b/app/helpers/icons_helper.rb
index f29faeca22d..9a404832423 100644
--- a/app/helpers/icons_helper.rb
+++ b/app/helpers/icons_helper.rb
@@ -1,4 +1,5 @@
module IconsHelper
+ extend self
include FontAwesome::Rails::IconHelper
# Creates an icon tag given icon name(s) and possible icon modifiers.
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index f4fad7150e8..70ea35fab1e 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -151,7 +151,7 @@ module IssuablesHelper
end
def issuable_labels_tooltip(labels, limit: 5)
- first, last = labels.partition.with_index{ |_, i| i < limit }
+ first, last = labels.partition.with_index { |_, i| i < limit }
label_names = first.collect(&:name)
label_names << "and #{last.size} more" unless last.empty?
@@ -234,7 +234,7 @@ module IssuablesHelper
end
def issuables_count_for_state(issuable_type, state, finder: nil)
- finder ||= public_send("#{issuable_type}_finder")
+ finder ||= public_send("#{issuable_type}_finder") # rubocop:disable GitlabSecurity/PublicSend
cache_key = finder.state_counter_cache_key
@counts ||= {}
@@ -329,7 +329,7 @@ module IssuablesHelper
end
def selected_template(issuable)
- params[:issuable_template] if issuable_templates(issuable).any?{ |template| template[:name] == params[:issuable_template] }
+ params[:issuable_template] if issuable_templates(issuable).any? { |template| template[:name] == params[:issuable_template] }
end
def issuable_todo_button_data(issuable, todo, is_collapsed)
diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb
index 4b99de1b6a5..e60513b35c7 100644
--- a/app/helpers/labels_helper.rb
+++ b/app/helpers/labels_helper.rb
@@ -43,11 +43,11 @@ module LabelsHelper
def label_filter_path(subject, label, type: :issue)
case subject
when Group
- send("#{type.to_s.pluralize}_group_path",
+ send("#{type.to_s.pluralize}_group_path", # rubocop:disable GitlabSecurity/PublicSend
subject,
label_name: [label.name])
when Project
- send("namespace_project_#{type.to_s.pluralize}_path",
+ send("namespace_project_#{type.to_s.pluralize}_path", # rubocop:disable GitlabSecurity/PublicSend
subject.namespace,
subject,
label_name: [label.name])
diff --git a/app/helpers/milestones_routing_helper.rb b/app/helpers/milestones_routing_helper.rb
new file mode 100644
index 00000000000..766d5262018
--- /dev/null
+++ b/app/helpers/milestones_routing_helper.rb
@@ -0,0 +1,17 @@
+module MilestonesRoutingHelper
+ def milestone_path(milestone, *args)
+ if milestone.is_group_milestone?
+ group_milestone_path(milestone.group, milestone, *args)
+ elsif milestone.is_project_milestone?
+ project_milestone_path(milestone.project, milestone, *args)
+ end
+ end
+
+ def milestone_url(milestone, *args)
+ if milestone.is_group_milestone?
+ group_milestone_url(milestone.group, milestone, *args)
+ elsif milestone.is_project_milestone?
+ project_milestone_url(milestone.project, milestone, *args)
+ end
+ end
+end
diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb
index b1205b8529b..b63b3b70903 100644
--- a/app/helpers/nav_helper.rb
+++ b/app/helpers/nav_helper.rb
@@ -2,6 +2,7 @@ module NavHelper
def page_with_sidebar_class
class_name = page_gutter_class
class_name << 'page-with-new-sidebar' if defined?(@new_sidebar) && @new_sidebar
+ class_name << 'page-with-icon-sidebar' if collapsed_sidebar? && @new_sidebar
class_name
end
diff --git a/app/helpers/pagination_helper.rb b/app/helpers/pagination_helper.rb
new file mode 100644
index 00000000000..83dd76a01dd
--- /dev/null
+++ b/app/helpers/pagination_helper.rb
@@ -0,0 +1,21 @@
+module PaginationHelper
+ def paginate_collection(collection, remote: nil)
+ if collection.is_a?(Kaminari::PaginatableWithoutCount)
+ paginate_without_count(collection)
+ elsif collection.respond_to?(:total_pages)
+ paginate_with_count(collection, remote: remote)
+ end
+ end
+
+ def paginate_without_count(collection)
+ render(
+ 'kaminari/gitlab/without_count',
+ previous_path: path_to_prev_page(collection),
+ next_path: path_to_next_page(collection)
+ )
+ end
+
+ def paginate_with_count(collection, remote: nil)
+ paginate(collection, remote: remote, theme: 'gitlab')
+ end
+end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 34ff6107eab..09cfd06dad3 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -80,7 +80,7 @@ module ProjectsHelper
end
def remove_project_message(project)
- _("You are going to remove %{project_name_with_namespace}.\nRemoved project CANNOT be restored!\nAre you ABSOLUTELY sure?") %
+ _("You are going to remove %{project_name_with_namespace}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?") %
{ project_name_with_namespace: project.name_with_namespace }
end
@@ -225,6 +225,26 @@ module ProjectsHelper
end
end
+ # Returns true if any projects are present.
+ #
+ # If the relation has a LIMIT applied we'll cast the relation to an Array
+ # since repeated any? checks would otherwise result in multiple COUNT queries
+ # being executed.
+ #
+ # If no limit is applied we'll just issue a COUNT since the result set could
+ # be too large to load into memory.
+ def any_projects?(projects)
+ if projects.limit_value
+ projects.to_a.any?
+ else
+ projects.except(:offset).any?
+ end
+ end
+
+ def has_projects_or_name?(projects, params)
+ !!(params[:name] || any_projects?(projects))
+ end
+
private
def repo_children_classes(field)
diff --git a/app/helpers/storage_health_helper.rb b/app/helpers/storage_health_helper.rb
new file mode 100644
index 00000000000..544c9efb845
--- /dev/null
+++ b/app/helpers/storage_health_helper.rb
@@ -0,0 +1,37 @@
+module StorageHealthHelper
+ def failing_storage_health_message(storage_health)
+ storage_name = content_tag(:strong, h(storage_health.storage_name))
+ host_names = h(storage_health.failing_on_hosts.to_sentence)
+ translation_params = { storage_name: storage_name,
+ host_names: host_names,
+ failed_attempts: storage_health.total_failures }
+
+ translation = n_('%{storage_name}: failed storage access attempt on host:',
+ '%{storage_name}: %{failed_attempts} failed storage access attempts:',
+ storage_health.total_failures) % translation_params
+
+ translation.html_safe
+ end
+
+ def message_for_circuit_breaker(circuit_breaker)
+ maximum_failures = circuit_breaker.failure_count_threshold
+ current_failures = circuit_breaker.failure_count
+ permanently_broken = circuit_breaker.circuit_broken? && current_failures >= maximum_failures
+
+ translation_params = { number_of_failures: current_failures,
+ maximum_failures: maximum_failures,
+ number_of_seconds: circuit_breaker.failure_wait_time }
+
+ if permanently_broken
+ s_("%{number_of_failures} of %{maximum_failures} failures. GitLab will not "\
+ "retry automatically. Reset storage information when the problem is "\
+ "resolved.") % translation_params
+ elsif circuit_breaker.circuit_broken?
+ _("%{number_of_failures} of %{maximum_failures} failures. GitLab will "\
+ "block access for %{number_of_seconds} seconds.") % translation_params
+ else
+ _("%{number_of_failures} of %{maximum_failures} failures. GitLab will "\
+ "allow access on the next attempt.") % translation_params
+ end
+ end
+end
diff --git a/app/helpers/submodule_helper.rb b/app/helpers/submodule_helper.rb
index b24039fb349..88f7702db1e 100644
--- a/app/helpers/submodule_helper.rb
+++ b/app/helpers/submodule_helper.rb
@@ -1,5 +1,5 @@
module SubmoduleHelper
- include Gitlab::ShellAdapter
+ extend self
VALID_SUBMODULE_PROTOCOLS = %w[http https git ssh].freeze
@@ -59,7 +59,7 @@ module SubmoduleHelper
return true if url_no_dotgit == [Gitlab.config.gitlab.url, '/', namespace, '/',
project].join('')
url_with_dotgit = url_no_dotgit + '.git'
- url_with_dotgit == gitlab_shell.url_to_repo([namespace, '/', project].join(''))
+ url_with_dotgit == Gitlab::Shell.new.url_to_repo([namespace, '/', project].join(''))
end
def relative_self_url?(url)
diff --git a/app/models/appearance.rb b/app/models/appearance.rb
index f9c48482be7..ff15689ecac 100644
--- a/app/models/appearance.rb
+++ b/app/models/appearance.rb
@@ -8,7 +8,27 @@ class Appearance < ActiveRecord::Base
validates :logo, file_size: { maximum: 1.megabyte }
validates :header_logo, file_size: { maximum: 1.megabyte }
+ validate :single_appearance_row, on: :create
+
mount_uploader :logo, AttachmentUploader
mount_uploader :header_logo, AttachmentUploader
has_many :uploads, as: :model, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
+
+ CACHE_KEY = 'current_appearance'.freeze
+
+ after_commit :flush_redis_cache
+
+ def self.current
+ Rails.cache.fetch(CACHE_KEY) { first }
+ end
+
+ def flush_redis_cache
+ Rails.cache.delete(CACHE_KEY)
+ end
+
+ def single_appearance_row
+ if self.class.any?
+ errors.add(:single_appearance_row, 'Only 1 appearances row can exist')
+ end
+ end
end
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index bd7c4cd45ea..8e446ff6dd8 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -241,6 +241,7 @@ class ApplicationSetting < ActiveRecord::Base
performance_bar_allowed_group_id: nil,
plantuml_enabled: false,
plantuml_url: nil,
+ project_export_enabled: true,
recaptcha_enabled: false,
repository_checks_enabled: true,
repository_storages: ['default'],
diff --git a/app/models/blob_viewer/base.rb b/app/models/blob_viewer/base.rb
index 35965d01692..bf3453b3063 100644
--- a/app/models/blob_viewer/base.rb
+++ b/app/models/blob_viewer/base.rb
@@ -82,7 +82,7 @@ module BlobViewer
# format of the blob.
#
# Prefer to implement a client-side viewer, where the JS component loads the
- # binary from `blob_raw_url` and does its own format validation and error
+ # binary from `blob_raw_path` and does its own format validation and error
# rendering, especially for potentially large binary formats.
def render_error
if too_large?
diff --git a/app/models/blob_viewer/server_side.rb b/app/models/blob_viewer/server_side.rb
index fbc1b520c01..86afcc86aa0 100644
--- a/app/models/blob_viewer/server_side.rb
+++ b/app/models/blob_viewer/server_side.rb
@@ -17,7 +17,7 @@ module BlobViewer
# build artifacts, can only be rendered using a client-side viewer,
# since we do not want to read large amounts of data into memory on the
# server side. Client-side viewers use JS and can fetch the file from
- # `blob_raw_url` using AJAX.
+ # `blob_raw_path` using AJAX.
return :server_side_but_stored_externally if blob.stored_externally?
super
diff --git a/app/models/broadcast_message.rb b/app/models/broadcast_message.rb
index 944725d91c3..3692bcc680d 100644
--- a/app/models/broadcast_message.rb
+++ b/app/models/broadcast_message.rb
@@ -14,9 +14,15 @@ class BroadcastMessage < ActiveRecord::Base
default_value_for :color, '#E75E40'
default_value_for :font, '#FFFFFF'
+ CACHE_KEY = 'broadcast_message_current'.freeze
+
+ after_commit :flush_redis_cache
+
def self.current
- Rails.cache.fetch("broadcast_message_current", expires_in: 1.minute) do
- where('ends_at > :now AND starts_at <= :now', now: Time.zone.now).order([:created_at, :id]).to_a
+ Rails.cache.fetch(CACHE_KEY) do
+ where('ends_at > :now AND starts_at <= :now', now: Time.zone.now)
+ .reorder(id: :asc)
+ .to_a
end
end
@@ -31,4 +37,8 @@ class BroadcastMessage < ActiveRecord::Base
def ended?
ends_at < Time.zone.now
end
+
+ def flush_redis_cache
+ Rails.cache.delete(CACHE_KEY)
+ end
end
diff --git a/app/models/commit.rb b/app/models/commit.rb
index 7940733f557..638fddc5d3d 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -55,7 +55,8 @@ class Commit
end
def from_hash(hash, project)
- new(Gitlab::Git::Commit.new(hash), project)
+ raw_commit = Gitlab::Git::Commit.new(project.repository.raw, hash)
+ new(raw_commit, project)
end
def valid_hash?(key)
@@ -320,21 +321,11 @@ class Commit
end
def raw_diffs(*args)
- if Gitlab::GitalyClient.feature_enabled?(:commit_raw_diffs)
- Gitlab::GitalyClient::CommitService.new(project.repository).diff_from_parent(self, *args)
- else
- raw.diffs(*args)
- end
+ raw.diffs(*args)
end
def raw_deltas
- @deltas ||= Gitlab::GitalyClient.migrate(:commit_deltas) do |is_enabled|
- if is_enabled
- Gitlab::GitalyClient::CommitService.new(project.repository).commit_deltas(self)
- else
- raw.deltas
- end
- end
+ @deltas ||= raw.deltas
end
def diffs(diff_options = nil)
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 935ffe343ff..3731b7c8577 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -16,6 +16,7 @@ module Issuable
include TimeTrackable
include Importable
include Editable
+ include AfterCommitQueue
# This object is used to gather issuable meta data for displaying
# upvotes, downvotes, notes and closing merge requests count for issues and merge requests
diff --git a/app/models/concerns/referable.rb b/app/models/concerns/referable.rb
index da803c7f481..10f4be72016 100644
--- a/app/models/concerns/referable.rb
+++ b/app/models/concerns/referable.rb
@@ -25,6 +25,18 @@ module Referable
to_reference(from_project)
end
+ def referable_inspect
+ if respond_to?(:id)
+ "#<#{self.class.name} id:#{id} #{to_reference(full: true)}>"
+ else
+ "#<#{self.class.name} #{to_reference(full: true)}>"
+ end
+ end
+
+ def inspect
+ referable_inspect
+ end
+
module ClassMethods
# The character that prefixes the actual reference identifier
#
diff --git a/app/models/concerns/spammable.rb b/app/models/concerns/spammable.rb
index bd75f25a210..f2707022a4b 100644
--- a/app/models/concerns/spammable.rb
+++ b/app/models/concerns/spammable.rb
@@ -58,7 +58,7 @@ module Spammable
options.fetch(:spam_title, false)
end
- public_send(attr.first) if attr && respond_to?(attr.first.to_sym)
+ public_send(attr.first) if attr && respond_to?(attr.first.to_sym) # rubocop:disable GitlabSecurity/PublicSend
end
def spam_description
@@ -66,12 +66,12 @@ module Spammable
options.fetch(:spam_description, false)
end
- public_send(attr.first) if attr && respond_to?(attr.first.to_sym)
+ public_send(attr.first) if attr && respond_to?(attr.first.to_sym) # rubocop:disable GitlabSecurity/PublicSend
end
def spammable_text
result = self.class.spammable_attrs.map do |attr|
- public_send(attr.first)
+ public_send(attr.first) # rubocop:disable GitlabSecurity/PublicSend
end
result.reject(&:blank?).join("\n")
diff --git a/app/models/concerns/token_authenticatable.rb b/app/models/concerns/token_authenticatable.rb
index 1ca7f91dc03..a7d5de48c66 100644
--- a/app/models/concerns/token_authenticatable.rb
+++ b/app/models/concerns/token_authenticatable.rb
@@ -44,7 +44,8 @@ module TokenAuthenticatable
end
define_method("ensure_#{token_field}!") do
- send("reset_#{token_field}!") if read_attribute(token_field).blank?
+ send("reset_#{token_field}!") if read_attribute(token_field).blank? # rubocop:disable GitlabSecurity/PublicSend
+
read_attribute(token_field)
end
diff --git a/app/models/conversational_development_index/metric.rb b/app/models/conversational_development_index/metric.rb
index f42f516f99a..0bee62f954f 100644
--- a/app/models/conversational_development_index/metric.rb
+++ b/app/models/conversational_development_index/metric.rb
@@ -13,9 +13,7 @@ module ConversationalDevelopmentIndex
end
def percentage_score(feature)
- return 100 if leader_score(feature).zero?
-
- 100 * instance_score(feature) / leader_score(feature)
+ self["percentage_#{feature}"]
end
end
end
diff --git a/app/models/event.rb b/app/models/event.rb
index 8d93a228494..f2a560a6b56 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -48,6 +48,7 @@ class Event < ActiveRecord::Base
belongs_to :author, class_name: "User"
belongs_to :project
belongs_to :target, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations
+ has_one :push_event_payload, foreign_key: :event_id
# For Hash only
serialize :data # rubocop:disable Cop/ActiveRecordSerialize
@@ -55,19 +56,51 @@ class Event < ActiveRecord::Base
# Callbacks
after_create :reset_project_activity
after_create :set_last_repository_updated_at, if: :push?
+ after_create :replicate_event_for_push_events_migration
# Scopes
scope :recent, -> { reorder(id: :desc) }
scope :code_push, -> { where(action: PUSHED) }
- scope :in_projects, ->(projects) do
- where(project_id: projects.pluck(:id)).recent
+ scope :in_projects, -> (projects) do
+ sub_query = projects
+ .except(:order)
+ .select(1)
+ .where('projects.id = events.project_id')
+
+ where('EXISTS (?)', sub_query).recent
+ end
+
+ scope :with_associations, -> do
+ # We're using preload for "push_event_payload" as otherwise the association
+ # is not always available (depending on the query being built).
+ includes(:author, :project, project: :namespace)
+ .preload(:target, :push_event_payload)
end
- scope :with_associations, -> { includes(:author, :project, project: :namespace).preload(:target) }
scope :for_milestone_id, ->(milestone_id) { where(target_type: "Milestone", target_id: milestone_id) }
+ self.inheritance_column = 'action'
+
class << self
+ def find_sti_class(action)
+ if action.to_i == PUSHED
+ PushEvent
+ else
+ Event
+ end
+ end
+
+ def subclass_from_attributes(attrs)
+ # Without this Rails will keep calling this method on the returned class,
+ # resulting in an infinite loop.
+ return unless self == Event
+
+ action = attrs.with_indifferent_access[inheritance_column].to_i
+
+ PushEvent if action == PUSHED
+ end
+
# Update Gitlab::ContributionsCalendar#activity_dates if this changes
def contributions
where("action = ? OR (target_type IN (?) AND action IN (?)) OR (target_type = ? AND action = ?)",
@@ -290,6 +323,16 @@ class Event < ActiveRecord::Base
@commits ||= (data[:commits] || []).reverse
end
+ def commit_title
+ commit = commits.last
+
+ commit[:message] if commit
+ end
+
+ def commit_id
+ commit_to || commit_from
+ end
+
def commits_count
data[:total_commits_count] || commits.count || 0
end
@@ -385,6 +428,16 @@ class Event < ActiveRecord::Base
user ? author_id == user.id : false
end
+ # We're manually replicating data into the new table since database triggers
+ # are not dumped to db/schema.rb. This could mean that a new installation
+ # would not have the triggers in place, thus losing events data in GitLab
+ # 10.0.
+ def replicate_event_for_push_events_migration
+ new_attributes = attributes.with_indifferent_access.except(:title, :data)
+
+ EventForMigration.create!(new_attributes)
+ end
+
private
def recent_update?
diff --git a/app/models/event_collection.rb b/app/models/event_collection.rb
new file mode 100644
index 00000000000..8b8244314af
--- /dev/null
+++ b/app/models/event_collection.rb
@@ -0,0 +1,98 @@
+# A collection of events to display in an event list.
+#
+# An EventCollection is meant to be used for displaying events to a user (e.g.
+# in a controller), it's not suitable for building queries that are used for
+# building other queries.
+class EventCollection
+ # To prevent users from putting too much pressure on the database by cycling
+ # through thousands of events we put a limit on the number of pages.
+ MAX_PAGE = 10
+
+ # projects - An ActiveRecord::Relation object that returns the projects for
+ # which to retrieve events.
+ # filter - An EventFilter instance to use for filtering events.
+ def initialize(projects, limit: 20, offset: 0, filter: nil)
+ @projects = projects
+ @limit = limit
+ @offset = offset
+ @filter = filter
+ end
+
+ # Returns an Array containing the events.
+ def to_a
+ return [] if current_page > MAX_PAGE
+
+ relation = if Gitlab::Database.join_lateral_supported?
+ relation_with_join_lateral
+ else
+ relation_without_join_lateral
+ end
+
+ relation.with_associations.to_a
+ end
+
+ private
+
+ # Returns the events relation to use when JOIN LATERAL is not supported.
+ #
+ # This relation simply gets all the events for all authorized projects, then
+ # limits that set.
+ def relation_without_join_lateral
+ events = filtered_events.in_projects(projects)
+
+ paginate_events(events)
+ end
+
+ # Returns the events relation to use when JOIN LATERAL is supported.
+ #
+ # This relation is built using JOIN LATERAL, producing faster queries than a
+ # regular LIMIT + OFFSET approach.
+ def relation_with_join_lateral
+ projects_for_lateral = projects.select(:id).to_sql
+
+ lateral = filtered_events
+ .limit(limit_for_join_lateral)
+ .where('events.project_id = projects_for_lateral.id')
+ .to_sql
+
+ # The outer query does not need to re-apply the filters since the JOIN
+ # LATERAL body already takes care of this.
+ outer = base_relation
+ .from("(#{projects_for_lateral}) projects_for_lateral")
+ .joins("JOIN LATERAL (#{lateral}) AS #{Event.table_name} ON true")
+
+ paginate_events(outer)
+ end
+
+ def filtered_events
+ @filter ? @filter.apply_filter(base_relation) : base_relation
+ end
+
+ def paginate_events(events)
+ events.limit(@limit).offset(@offset)
+ end
+
+ def base_relation
+ # We want to have absolute control over the event queries being built, thus
+ # we're explicitly opting out of any default scopes that may be set.
+ Event.unscoped.recent
+ end
+
+ def limit_for_join_lateral
+ # Applying the OFFSET on the inside of a JOIN LATERAL leads to incorrect
+ # results. To work around this we need to increase the inner limit for every
+ # page.
+ #
+ # This means that on page 1 we use LIMIT 20, and an outer OFFSET of 0. On
+ # page 2 we use LIMIT 40 and an outer OFFSET of 20.
+ @limit + @offset
+ end
+
+ def current_page
+ (@offset / @limit) + 1
+ end
+
+ def projects
+ @projects.except(:order)
+ end
+end
diff --git a/app/models/event_for_migration.rb b/app/models/event_for_migration.rb
new file mode 100644
index 00000000000..a1672da5eec
--- /dev/null
+++ b/app/models/event_for_migration.rb
@@ -0,0 +1,5 @@
+# This model is used to replicate events between the old "events" table and the
+# new "events_for_migration" table that will replace "events" in GitLab 10.0.
+class EventForMigration < ActiveRecord::Base
+ self.table_name = 'events_for_migration'
+end
diff --git a/app/models/key.rb b/app/models/key.rb
index cb8f10f6d55..49bc26122fa 100644
--- a/app/models/key.rb
+++ b/app/models/key.rb
@@ -16,8 +16,6 @@ class Key < ActiveRecord::Base
presence: true,
length: { maximum: 5000 },
format: { with: /\A(ssh|ecdsa)-.*\Z/ }
- validates :key,
- format: { without: /\n|\r/, message: 'should be a single line' }
validates :fingerprint,
uniqueness: true,
presence: { message: 'cannot be generated' }
@@ -31,6 +29,7 @@ class Key < ActiveRecord::Base
after_destroy :post_destroy_hook
def key=(value)
+ value&.delete!("\n\r")
value.strip! unless value.blank?
write_attribute(:key, value)
end
diff --git a/app/models/member.rb b/app/models/member.rb
index dc9247bc9a0..b26b5017183 100644
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -276,6 +276,13 @@ class Member < ActiveRecord::Base
@notification_setting ||= user.notification_settings_for(source)
end
+ def notifiable?(type, opts = {})
+ # always notify when there isn't a user yet
+ return true if user.blank?
+
+ NotificationRecipientService.notifiable?(user, type, notifiable_options.merge(opts))
+ end
+
private
def send_invite
@@ -332,4 +339,8 @@ class Member < ActiveRecord::Base
def notification_service
NotificationService.new
end
+
+ def notifiable_options
+ {}
+ end
end
diff --git a/app/models/members/group_member.rb b/app/models/members/group_member.rb
index 47040f95533..661e668dbf9 100644
--- a/app/models/members/group_member.rb
+++ b/app/models/members/group_member.rb
@@ -30,6 +30,10 @@ class GroupMember < Member
'Group'
end
+ def notifiable_options
+ { group: group }
+ end
+
private
def send_invite
diff --git a/app/models/members/project_member.rb b/app/models/members/project_member.rb
index c0e17f4bfc8..b6f1dd272cd 100644
--- a/app/models/members/project_member.rb
+++ b/app/models/members/project_member.rb
@@ -87,6 +87,10 @@ class ProjectMember < Member
project.owner == user
end
+ def notifiable_options
+ { project: project }
+ end
+
private
def delete_member_todos
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 8ca850b6d96..ac08dc0ee1f 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -8,6 +8,7 @@ class MergeRequest < ActiveRecord::Base
include CreatedAtFilterable
ignore_column :position
+ ignore_column :locked_at
belongs_to :target_project, class_name: "Project"
belongs_to :source_project, class_name: "Project"
@@ -61,16 +62,6 @@ class MergeRequest < ActiveRecord::Base
transition locked: :opened
end
- after_transition any => :locked do |merge_request, transition|
- merge_request.locked_at = Time.now
- merge_request.save
- end
-
- after_transition locked: (any - :locked) do |merge_request, transition|
- merge_request.locked_at = nil
- merge_request.save
- end
-
state :opened
state :closed
state :merged
@@ -171,7 +162,7 @@ class MergeRequest < ActiveRecord::Base
target = unscoped.where(target_project_id: relation).select(:id)
union = Gitlab::SQL::Union.new([source, target])
- where("merge_requests.id IN (#{union.to_sql})")
+ where("merge_requests.id IN (#{union.to_sql})") # rubocop:disable GitlabSecurity/SqlInjection
end
WIP_REGEX = /\A\s*(\[WIP\]\s*|WIP:\s*|WIP\s+)+\s*/i.freeze
@@ -392,6 +383,12 @@ class MergeRequest < ActiveRecord::Base
'Source project is not a fork of the target project'
end
+ def merge_ongoing?
+ return false unless merge_jid
+
+ Gitlab::SidekiqStatus.num_running([merge_jid]) > 0
+ end
+
def closed_without_fork?
closed? && source_project_missing?
end
@@ -446,7 +443,8 @@ class MergeRequest < ActiveRecord::Base
end
def reload_diff_if_branch_changed
- if source_branch_changed? || target_branch_changed?
+ if (source_branch_changed? || target_branch_changed?) &&
+ (source_branch_head && target_branch_head)
reload_diff
end
end
@@ -725,12 +723,6 @@ class MergeRequest < ActiveRecord::Base
end
end
- def locked_long_ago?
- return false unless locked?
-
- locked_at.nil? || locked_at < (Time.now - 1.day)
- end
-
def has_ci?
has_ci_integration = source_project.try(:ci_service)
uses_gitlab_ci = all_pipelines.any?
@@ -801,11 +793,7 @@ class MergeRequest < ActiveRecord::Base
end
def fetch_ref
- target_project.repository.fetch_ref(
- source_project.repository.path_to_repo,
- "refs/heads/#{source_branch}",
- ref_path
- )
+ write_ref
update_column(:ref_fetched, true)
end
@@ -948,4 +936,17 @@ class MergeRequest < ActiveRecord::Base
true
end
+
+ private
+
+ def write_ref
+ target_project.repository.with_repo_branch_commit(
+ source_project.repository, source_branch) do |commit|
+ if commit
+ target_project.repository.write_ref(ref_path, commit.sha)
+ else
+ raise Rugged::ReferenceError, 'source repository is empty'
+ end
+ end
+ end
end
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index ec87aee9310..58050e1f438 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -85,11 +85,7 @@ class MergeRequestDiff < ActiveRecord::Base
def raw_diffs(options = {})
if options[:ignore_whitespace_change]
- @diffs_no_whitespace ||=
- Gitlab::Git::Compare.new(
- repository.raw_repository,
- safe_start_commit_sha,
- head_commit_sha).diffs(options)
+ @diffs_no_whitespace ||= compare.diffs(options)
else
@raw_diffs ||= {}
@raw_diffs[options] ||= load_diffs(options)
@@ -286,9 +282,7 @@ class MergeRequestDiff < ActiveRecord::Base
def load_commits
commits = st_commits.presence || merge_request_diff_commits
- commits.map do |commit|
- Commit.new(Gitlab::Git::Commit.new(commit.to_hash), merge_request.source_project)
- end
+ commits.map { |commit| Commit.from_hash(commit.to_hash, project) }
end
def save_diffs
diff --git a/app/models/merge_request_diff_commit.rb b/app/models/merge_request_diff_commit.rb
index cafdbe11849..670b26d4ca3 100644
--- a/app/models/merge_request_diff_commit.rb
+++ b/app/models/merge_request_diff_commit.rb
@@ -26,7 +26,7 @@ class MergeRequestDiffCommit < ActiveRecord::Base
def to_hash
Gitlab::Git::Commit::SERIALIZE_KEYS.each_with_object({}) do |key, hash|
- hash[key] = public_send(key)
+ hash[key] = public_send(key) # rubocop:disable GitlabSecurity/PublicSend
end
end
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index 48d00764965..01e0d0155a3 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -149,7 +149,9 @@ class Milestone < ActiveRecord::Base
end
##
- # Returns the String necessary to reference this Milestone in Markdown
+ # Returns the String necessary to reference this Milestone in Markdown. Group
+ # milestones only support name references, and do not support cross-project
+ # references.
#
# format - Symbol format to use (default: :iid, optional: :name)
#
@@ -161,12 +163,16 @@ class Milestone < ActiveRecord::Base
# Milestone.first.to_reference(same_namespace_project) # => "gitlab-ce%1"
#
def to_reference(from_project = nil, format: :iid, full: false)
- return if is_group_milestone?
+ return if is_group_milestone? && format != :name
format_reference = milestone_format_reference(format)
reference = "#{self.class.reference_prefix}#{format_reference}"
- "#{project.to_reference(from_project, full: full)}#{reference}"
+ if project
+ "#{project.to_reference(from_project, full: full)}#{reference}"
+ else
+ reference
+ end
end
def reference_link_text(from_project = nil)
diff --git a/app/models/network/graph.rb b/app/models/network/graph.rb
index 2bc00a082df..0e5acb22d50 100644
--- a/app/models/network/graph.rb
+++ b/app/models/network/graph.rb
@@ -206,7 +206,7 @@ module Network
# Visit branching chains
leaves.each do |l|
- parents = l.parents(@map).select{|p| p.space.zero?}
+ parents = l.parents(@map).select {|p| p.space.zero?}
parents.each do |p|
place_chain(p, l.time)
end
diff --git a/app/models/note.rb b/app/models/note.rb
index d0e3bc0bfed..a752c897d63 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -77,20 +77,20 @@ class Note < ActiveRecord::Base
# Scopes
scope :for_commit_id, ->(commit_id) { where(noteable_type: "Commit", commit_id: commit_id) }
- scope :system, ->{ where(system: true) }
- scope :user, ->{ where(system: false) }
- scope :common, ->{ where(noteable_type: ["", nil]) }
- scope :fresh, ->{ order(created_at: :asc, id: :asc) }
- scope :updated_after, ->(time){ where('updated_at > ?', time) }
- scope :inc_author_project, ->{ includes(:project, :author) }
- scope :inc_author, ->{ includes(:author) }
+ scope :system, -> { where(system: true) }
+ scope :user, -> { where(system: false) }
+ scope :common, -> { where(noteable_type: ["", nil]) }
+ scope :fresh, -> { order(created_at: :asc, id: :asc) }
+ scope :updated_after, ->(time) { where('updated_at > ?', time) }
+ scope :inc_author_project, -> { includes(:project, :author) }
+ scope :inc_author, -> { includes(:author) }
scope :inc_relations_for_view, -> do
includes(:project, :author, :updated_by, :resolved_by, :award_emoji, :system_note_metadata)
end
- scope :diff_notes, ->{ where(type: %w(LegacyDiffNote DiffNote)) }
- scope :new_diff_notes, ->{ where(type: 'DiffNote') }
- scope :non_diff_notes, ->{ where(type: ['Note', 'DiscussionNote', nil]) }
+ scope :diff_notes, -> { where(type: %w(LegacyDiffNote DiffNote)) }
+ scope :new_diff_notes, -> { where(type: 'DiffNote') }
+ scope :non_diff_notes, -> { where(type: ['Note', 'DiscussionNote', nil]) }
scope :with_associations, -> do
# FYI noteable cannot be loaded for LegacyDiffNote for commits
diff --git a/app/models/notification_recipient.rb b/app/models/notification_recipient.rb
new file mode 100644
index 00000000000..dc862565a71
--- /dev/null
+++ b/app/models/notification_recipient.rb
@@ -0,0 +1,142 @@
+class NotificationRecipient
+ attr_reader :user, :type
+ def initialize(
+ user, type,
+ custom_action: nil,
+ target: nil,
+ acting_user: nil,
+ project: nil,
+ group: nil,
+ skip_read_ability: false
+ )
+ unless NotificationSetting.levels.key?(type) || type == :subscription
+ raise ArgumentError, "invalid type: #{type.inspect}"
+ end
+
+ @custom_action = custom_action
+ @acting_user = acting_user
+ @target = target
+ @project = project || default_project
+ @group = group || @project&.group
+ @user = user
+ @type = type
+ @skip_read_ability = skip_read_ability
+ end
+
+ def notification_setting
+ @notification_setting ||= find_notification_setting
+ end
+
+ def raw_notification_level
+ notification_setting&.level&.to_sym
+ end
+
+ def notification_level
+ # custom is treated the same as watch if it's enabled - otherwise it's
+ # set to :custom, meaning to send exactly when our type is :participating
+ # or :mention.
+ @notification_level ||=
+ case raw_notification_level
+ when :custom
+ if @custom_action && notification_setting&.event_enabled?(@custom_action)
+ :watch
+ else
+ :custom
+ end
+ else
+ raw_notification_level
+ end
+ end
+
+ def notifiable?
+ return false unless has_access?
+ return false if own_activity?
+
+ return true if @type == :subscription
+
+ return false if notification_level.nil? || notification_level == :disabled
+
+ return %i[participating mention].include?(@type) if notification_level == :custom
+
+ return false if %i[watch participating].include?(notification_level) && excluded_watcher_action?
+
+ return false unless NotificationSetting.levels[notification_level] <= NotificationSetting.levels[@type]
+
+ return false if unsubscribed?
+
+ true
+ end
+
+ def unsubscribed?
+ return false unless @target
+ return false unless @target.respond_to?(:subscriptions)
+
+ subscription = @target.subscriptions.find_by_user_id(@user.id)
+ subscription && !subscription.subscribed
+ end
+
+ def own_activity?
+ return false unless @acting_user
+ return false if @acting_user.notified_of_own_activity?
+
+ user == @acting_user
+ end
+
+ def has_access?
+ DeclarativePolicy.subject_scope do
+ return false unless user.can?(:receive_notifications)
+ return true if @skip_read_ability
+
+ return false if @project && !user.can?(:read_project, @project)
+
+ return true unless read_ability
+ return true unless DeclarativePolicy.has_policy?(@target)
+
+ user.can?(read_ability, @target)
+ end
+ end
+
+ def excluded_watcher_action?
+ return false unless @custom_action
+ return false if raw_notification_level == :custom
+
+ NotificationSetting::EXCLUDED_WATCHER_EVENTS.include?(@custom_action)
+ end
+
+ private
+
+ def read_ability
+ return nil if @skip_read_ability
+ return @read_ability if instance_variable_defined?(:@read_ability)
+
+ @read_ability =
+ case @target
+ when Issuable
+ :"read_#{@target.to_ability_name}"
+ when Ci::Pipeline
+ :read_build # We have build trace in pipeline emails
+ when ActiveRecord::Base
+ :"read_#{@target.class.model_name.name.underscore}"
+ else
+ nil
+ end
+ end
+
+ def default_project
+ return nil if @target.nil?
+ return @target if @target.is_a?(Project)
+ return @target.project if @target.respond_to?(:project)
+ end
+
+ def find_notification_setting
+ project_setting = @project && user.notification_settings_for(@project)
+
+ return project_setting unless project_setting.nil? || project_setting.global?
+
+ group_setting = @group && user.notification_settings_for(@group)
+
+ return group_setting unless group_setting.nil? || group_setting.global?
+
+ user.global_notification_setting
+ end
+end
diff --git a/app/models/notification_setting.rb b/app/models/notification_setting.rb
index 9b1cac64c44..245f8dddcf9 100644
--- a/app/models/notification_setting.rb
+++ b/app/models/notification_setting.rb
@@ -66,6 +66,6 @@ class NotificationSetting < ActiveRecord::Base
alias_method :failed_pipeline?, :failed_pipeline
def event_enabled?(event)
- respond_to?(event) && !!public_send(event)
+ respond_to?(event) && !!public_send(event) # rubocop:disable GitlabSecurity/PublicSend
end
end
diff --git a/app/models/project.rb b/app/models/project.rb
index d85782782aa..7cdd00bc17b 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -75,6 +75,7 @@ class Project < ActiveRecord::Base
attr_accessor :new_default_branch
attr_accessor :old_path_with_namespace
+ attr_accessor :template_name
attr_writer :pipeline_status
alias_attribute :title, :name
@@ -163,7 +164,7 @@ class Project < ActiveRecord::Base
has_many :todos
has_many :notification_settings, as: :source, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
- has_one :import_data, class_name: 'ProjectImportData'
+ has_one :import_data, class_name: 'ProjectImportData', inverse_of: :project, autosave: true
has_one :project_feature
has_one :statistics, class_name: 'ProjectStatistics'
@@ -192,6 +193,7 @@ class Project < ActiveRecord::Base
accepts_nested_attributes_for :variables, allow_destroy: true
accepts_nested_attributes_for :project_feature
+ accepts_nested_attributes_for :import_data
delegate :name, to: :owner, allow_nil: true, prefix: true
delegate :count, to: :forks, prefix: true
@@ -413,7 +415,7 @@ class Project < ActiveRecord::Base
union = Gitlab::SQL::Union.new([projects, namespaces])
- where("projects.id IN (#{union.to_sql})")
+ where("projects.id IN (#{union.to_sql})") # rubocop:disable GitlabSecurity/SqlInjection
end
def search_by_title(query)
@@ -588,8 +590,6 @@ class Project < ActiveRecord::Base
project_import_data.credentials ||= {}
project_import_data.credentials = project_import_data.credentials.merge(credentials)
end
-
- project_import_data.save
end
def import?
@@ -825,7 +825,7 @@ class Project < ActiveRecord::Base
if template.nil?
# If no template, we should create an instance. Ex `build_gitlab_ci_service`
- public_send("build_#{service_name}_service")
+ public_send("build_#{service_name}_service") # rubocop:disable GitlabSecurity/PublicSend
else
Service.build_from_template(id, template)
end
@@ -941,7 +941,7 @@ class Project < ActiveRecord::Base
end
def repo
- repository.raw
+ repository.rugged
end
def url_to_repo
@@ -1046,13 +1046,16 @@ class Project < ActiveRecord::Base
end
def change_head(branch)
- repository.before_change_head
- repository.rugged.references.create('HEAD',
- "refs/heads/#{branch}",
- force: true)
- repository.copy_gitattributes(branch)
- repository.after_change_head
- reload_default_branch
+ if repository.branch_exists?(branch)
+ repository.before_change_head
+ repository.write_ref('HEAD', "refs/heads/#{branch}")
+ repository.copy_gitattributes(branch)
+ repository.after_change_head
+ reload_default_branch
+ else
+ errors.add(:base, "Could not change HEAD: branch '#{branch}' does not exist")
+ false
+ end
end
def forked_from?(project)
@@ -1326,7 +1329,7 @@ class Project < ActiveRecord::Base
end
def append_or_update_attribute(name, value)
- old_values = public_send(name.to_s)
+ old_values = public_send(name.to_s) # rubocop:disable GitlabSecurity/PublicSend
if Project.reflect_on_association(name).try(:macro) == :has_many && old_values.any?
update_attribute(name, old_values + value)
diff --git a/app/models/project_feature.rb b/app/models/project_feature.rb
index c8fabb16dc1..fb1db0255aa 100644
--- a/app/models/project_feature.rb
+++ b/app/models/project_feature.rb
@@ -55,7 +55,7 @@ class ProjectFeature < ActiveRecord::Base
end
def access_level(feature)
- public_send(ProjectFeature.access_level_attribute(feature))
+ public_send(ProjectFeature.access_level_attribute(feature)) # rubocop:disable GitlabSecurity/PublicSend
end
def builds_enabled?
@@ -80,7 +80,7 @@ class ProjectFeature < ActiveRecord::Base
# which cannot be higher than repository access level
def repository_children_level
validator = lambda do |field|
- level = public_send(field) || ProjectFeature::ENABLED
+ level = public_send(field) || ProjectFeature::ENABLED # rubocop:disable GitlabSecurity/PublicSend
not_allowed = level > repository_access_level
self.errors.add(field, "cannot have higher visibility level than repository access level") if not_allowed
end
diff --git a/app/models/project_import_data.rb b/app/models/project_import_data.rb
index 37730474324..6da6632f4f2 100644
--- a/app/models/project_import_data.rb
+++ b/app/models/project_import_data.rb
@@ -1,7 +1,7 @@
require 'carrierwave/orm/activerecord'
class ProjectImportData < ActiveRecord::Base
- belongs_to :project
+ belongs_to :project, inverse_of: :import_data
attr_encrypted :credentials,
key: Gitlab::Application.secrets.db_key_base,
marshal: true,
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
index c2414885368..9ee3a533c1e 100644
--- a/app/models/project_services/jira_service.rb
+++ b/app/models/project_services/jira_service.rb
@@ -104,7 +104,7 @@ class JiraService < IssueTrackerService
def close_issue(entity, external_issue)
issue = jira_request { client.Issue.find(external_issue.iid) }
- return if issue.nil? || issue.resolution.present? || !jira_issue_transition_id.present?
+ return if issue.nil? || has_resolution?(issue) || !jira_issue_transition_id.present?
commit_id = if entity.is_a?(Commit)
entity.id
@@ -118,7 +118,7 @@ class JiraService < IssueTrackerService
# may or may not be allowed. Refresh the issue after transition and check
# if it is closed, so we don't have one comment for every commit.
issue = jira_request { client.Issue.find(issue.key) } if transition_issue(issue)
- add_issue_solved_comment(issue, commit_id, commit_url) if issue.resolution
+ add_issue_solved_comment(issue, commit_id, commit_url) if has_resolution?(issue)
end
def create_cross_reference_note(mentioned, noteable, author)
@@ -216,6 +216,10 @@ class JiraService < IssueTrackerService
end
end
+ def has_resolution?(issue)
+ issue.respond_to?(:resolution) && issue.resolution.present?
+ end
+
def comment_exists?(issue, message)
comments = jira_request { issue.comments }
diff --git a/app/models/project_statistics.rb b/app/models/project_statistics.rb
index aeaf63abab9..715b215d1db 100644
--- a/app/models/project_statistics.rb
+++ b/app/models/project_statistics.rb
@@ -14,7 +14,7 @@ class ProjectStatistics < ActiveRecord::Base
def refresh!(only: nil)
STATISTICS_COLUMNS.each do |column, generator|
if only.blank? || only.include?(column)
- public_send("update_#{column}")
+ public_send("update_#{column}") # rubocop:disable GitlabSecurity/PublicSend
end
end
diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb
index e8929a35836..698fdf7a20c 100644
--- a/app/models/project_wiki.rb
+++ b/app/models/project_wiki.rb
@@ -113,10 +113,10 @@ class ProjectWiki
return false
end
- def update_page(page, content, format = :markdown, message = nil)
+ def update_page(page, content:, title: nil, format: :markdown, message: nil)
commit = commit_details(:updated, message, page.title)
- wiki.update_page(page, page.name, format.to_sym, content, commit)
+ wiki.update_page(page, title || page.name, format.to_sym, content, commit)
update_project_activity
end
diff --git a/app/models/push_event.rb b/app/models/push_event.rb
new file mode 100644
index 00000000000..3f1ff979de6
--- /dev/null
+++ b/app/models/push_event.rb
@@ -0,0 +1,126 @@
+class PushEvent < Event
+ # This validation exists so we can't accidentally use PushEvent with a
+ # different "action" value.
+ validate :validate_push_action
+
+ # Authors are required as they're used to display who pushed data.
+ #
+ # We're just validating the presence of the ID here as foreign key constraints
+ # should ensure the ID points to a valid user.
+ validates :author_id, presence: true
+
+ # The project is required to build links to commits, commit ranges, etc.
+ #
+ # We're just validating the presence of the ID here as foreign key constraints
+ # should ensure the ID points to a valid project.
+ validates :project_id, presence: true
+
+ # The "data" field must not be set for push events since it's not used and a
+ # waste of space.
+ validates :data, absence: true
+
+ # These fields are also not used for push events, thus storing them would be a
+ # waste.
+ validates :target_id, absence: true
+ validates :target_type, absence: true
+
+ def self.sti_name
+ PUSHED
+ end
+
+ def push?
+ true
+ end
+
+ def push_with_commits?
+ !!(commit_from && commit_to)
+ end
+
+ def tag?
+ return super unless push_event_payload
+
+ push_event_payload.tag?
+ end
+
+ def branch?
+ return super unless push_event_payload
+
+ push_event_payload.branch?
+ end
+
+ def valid_push?
+ return super unless push_event_payload
+
+ push_event_payload.ref.present?
+ end
+
+ def new_ref?
+ return super unless push_event_payload
+
+ push_event_payload.created?
+ end
+
+ def rm_ref?
+ return super unless push_event_payload
+
+ push_event_payload.removed?
+ end
+
+ def commit_from
+ return super unless push_event_payload
+
+ push_event_payload.commit_from
+ end
+
+ def commit_to
+ return super unless push_event_payload
+
+ push_event_payload.commit_to
+ end
+
+ def ref_name
+ return super unless push_event_payload
+
+ push_event_payload.ref
+ end
+
+ def ref_type
+ return super unless push_event_payload
+
+ push_event_payload.ref_type
+ end
+
+ def branch_name
+ return super unless push_event_payload
+
+ ref_name
+ end
+
+ def tag_name
+ return super unless push_event_payload
+
+ ref_name
+ end
+
+ def commit_title
+ return super unless push_event_payload
+
+ push_event_payload.commit_title
+ end
+
+ def commit_id
+ commit_to || commit_from
+ end
+
+ def commits_count
+ return super unless push_event_payload
+
+ push_event_payload.commit_count
+ end
+
+ def validate_push_action
+ return if action == PUSHED
+
+ errors.add(:action, "the action #{action.inspect} is not valid")
+ end
+end
diff --git a/app/models/push_event_payload.rb b/app/models/push_event_payload.rb
new file mode 100644
index 00000000000..6cdb1cd4fe9
--- /dev/null
+++ b/app/models/push_event_payload.rb
@@ -0,0 +1,22 @@
+class PushEventPayload < ActiveRecord::Base
+ include ShaAttribute
+
+ belongs_to :event, inverse_of: :push_event_payload
+
+ validates :event_id, :commit_count, :action, :ref_type, presence: true
+ validates :commit_title, length: { maximum: 70 }
+
+ sha_attribute :commit_from
+ sha_attribute :commit_to
+
+ enum action: {
+ created: 0,
+ removed: 1,
+ pushed: 2
+ }
+
+ enum ref_type: {
+ branch: 0,
+ tag: 1
+ }
+end
diff --git a/app/models/redirect_route.rb b/app/models/redirect_route.rb
index 964175ddab8..090fbd61e6f 100644
--- a/app/models/redirect_route.rb
+++ b/app/models/redirect_route.rb
@@ -8,5 +8,13 @@ class RedirectRoute < ActiveRecord::Base
presence: true,
uniqueness: { case_sensitive: false }
- scope :matching_path_and_descendants, -> (path) { where('redirect_routes.path = ? OR redirect_routes.path LIKE ?', path, "#{sanitize_sql_like(path)}/%") }
+ scope :matching_path_and_descendants, -> (path) do
+ wheres = if Gitlab::Database.postgresql?
+ 'LOWER(redirect_routes.path) = LOWER(?) OR LOWER(redirect_routes.path) LIKE LOWER(?)'
+ else
+ 'redirect_routes.path = ? OR redirect_routes.path LIKE ?'
+ end
+
+ where(wheres, path, "#{sanitize_sql_like(path)}/%")
+ end
end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 4e9fe759fdc..a761302b06b 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -64,6 +64,8 @@ class Repository
@raw_repository ||= initialize_raw_repository
end
+ alias_method :raw, :raw_repository
+
# Return absolute path to repository
def path_to_repo
@path_to_repo ||= File.expand_path(
@@ -130,16 +132,13 @@ class Repository
return []
end
- ref ||= root_ref
-
- args = %W(
- #{Gitlab.config.git.bin_path} log #{ref} --pretty=%H --skip #{offset}
- --max-count #{limit} --grep=#{query} --regexp-ignore-case
- )
- args = args.concat(%W(-- #{path})) if path.present?
-
- git_log_results = Gitlab::Popen.popen(args, path_to_repo).first.lines
- git_log_results.map { |c| commit(c.chomp) }.compact
+ raw_repository.gitaly_migrate(:commits_by_message) do |is_enabled|
+ if is_enabled
+ find_commits_by_message_by_gitaly(query, ref, path, limit, offset)
+ else
+ find_commits_by_message_by_shelling_out(query, ref, path, limit, offset)
+ end
+ end
end
def find_branch(name, fresh_repo: true)
@@ -225,7 +224,7 @@ class Repository
# This will still fail if the file is corrupted (e.g. 0 bytes)
begin
- rugged.references.create(keep_around_ref_name(sha), sha, force: true)
+ write_ref(keep_around_ref_name(sha), sha)
rescue Rugged::ReferenceError => ex
Rails.logger.error "Unable to create keep-around reference for repository #{path}: #{ex}"
rescue Rugged::OSError => ex
@@ -238,6 +237,10 @@ class Repository
ref_exists?(keep_around_ref_name(sha))
end
+ def write_ref(ref_path, sha)
+ rugged.references.create(ref_path, sha, force: true)
+ end
+
def diverging_commit_counts(branch)
root_ref_hash = raw_repository.rev_parse_target(root_ref).oid
cache.fetch(:"diverging_commit_counts_#{branch.name}") do
@@ -301,7 +304,7 @@ class Repository
expire_method_caches(to_refresh)
- to_refresh.each { |method| send(method) }
+ to_refresh.each { |method| send(method) } # rubocop:disable GitlabSecurity/PublicSend
end
def expire_branch_cache(branch_name = nil)
@@ -613,17 +616,26 @@ class Repository
end
def last_commit_for_path(sha, path)
- sha = last_commit_id_for_path(sha, path)
- commit(sha)
+ raw_repository.gitaly_migrate(:last_commit_for_path) do |is_enabled|
+ if is_enabled
+ last_commit_for_path_by_gitaly(sha, path)
+ else
+ last_commit_for_path_by_rugged(sha, path)
+ end
+ end
end
- # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/383
def last_commit_id_for_path(sha, path)
key = path.blank? ? "last_commit_id_for_path:#{sha}" : "last_commit_id_for_path:#{sha}:#{Digest::SHA1.hexdigest(path)}"
cache.fetch(key) do
- args = %W(#{Gitlab.config.git.bin_path} rev-list --max-count=1 #{sha} -- #{path})
- Gitlab::Popen.popen(args, path_to_repo).first.strip
+ raw_repository.gitaly_migrate(:last_commit_for_path) do |is_enabled|
+ if is_enabled
+ last_commit_for_path_by_gitaly(sha, path).id
+ else
+ last_commit_id_for_path_by_shelling_out(sha, path)
+ end
+ end
end
end
@@ -678,8 +690,8 @@ class Repository
end
def refs_contains_sha(ref_type, sha)
- args = %W(#{Gitlab.config.git.bin_path} #{ref_type} --contains #{sha})
- names = Gitlab::Popen.popen(args, path_to_repo).first
+ args = %W(#{ref_type} --contains #{sha})
+ names = run_git(args).first
if names.respond_to?(:split)
names = names.split("\n").map(&:strip)
@@ -757,7 +769,7 @@ class Repository
index = Gitlab::Git::Index.new(raw_repository)
if start_commit
- index.read_tree(start_commit.raw_commit.tree)
+ index.read_tree(start_commit.rugged_commit.tree)
parents = [start_commit.sha]
else
parents = []
@@ -957,15 +969,17 @@ class Repository
return [] if empty_repo? || query.blank?
offset = 2
- args = %W(#{Gitlab.config.git.bin_path} grep -i -I -n --before-context #{offset} --after-context #{offset} -E -e #{Regexp.escape(query)} #{ref || root_ref})
- Gitlab::Popen.popen(args, path_to_repo).first.scrub.split(/^--$/)
+ args = %W(grep -i -I -n --before-context #{offset} --after-context #{offset} -E -e #{Regexp.escape(query)} #{ref || root_ref})
+
+ run_git(args).first.scrub.split(/^--$/)
end
def search_files_by_name(query, ref)
return [] if empty_repo? || query.blank?
- args = %W(#{Gitlab.config.git.bin_path} ls-tree --full-tree -r #{ref || root_ref} --name-status | #{Regexp.escape(query)})
- Gitlab::Popen.popen(args, path_to_repo).first.lines.map(&:strip)
+ args = %W(ls-tree --full-tree -r #{ref || root_ref} --name-status | #{Regexp.escape(query)})
+
+ run_git(args).first.lines.map(&:strip)
end
def with_repo_branch_commit(start_repository, start_branch_name)
@@ -975,12 +989,10 @@ class Repository
if start_repository == self
start_branch_name
else
- tmp_ref = "refs/tmp/#{SecureRandom.hex}/head"
-
- fetch_ref(
+ tmp_ref = fetch_ref(
start_repository.path_to_repo,
"#{Gitlab::Git::BRANCH_REF_PREFIX}#{start_branch_name}",
- tmp_ref
+ "refs/tmp/#{SecureRandom.hex}/head"
)
start_repository.commit(start_branch_name).sha
@@ -1010,8 +1022,13 @@ class Repository
end
def fetch_ref(source_path, source_ref, target_ref)
- args = %W(#{Gitlab.config.git.bin_path} fetch --no-tags -f #{source_path} #{source_ref}:#{target_ref})
- Gitlab::Popen.popen(args, path_to_repo)
+ args = %W(fetch --no-tags -f #{source_path} #{source_ref}:#{target_ref})
+ message, status = run_git(args)
+
+ # Make sure ref was created, and raise Rugged::ReferenceError when not
+ raise Rugged::ReferenceError, message if status != 0
+
+ target_ref
end
def create_ref(ref, ref_path)
@@ -1092,6 +1109,12 @@ class Repository
private
+ def run_git(args)
+ circuit_breaker.perform do
+ Gitlab::Popen.popen([Gitlab.config.git.bin_path, *args], path_to_repo)
+ end
+ end
+
def blob_data_at(sha, path)
blob = blob_at(sha, path)
return unless blob
@@ -1101,7 +1124,9 @@ class Repository
end
def refs_directory_exists?
- File.exist?(File.join(path_to_repo, 'refs'))
+ circuit_breaker.perform do
+ File.exist?(File.join(path_to_repo, 'refs'))
+ end
end
def cache
@@ -1138,6 +1163,21 @@ class Repository
Rugged::Commit.create(rugged, params)
end
+ def last_commit_for_path_by_gitaly(sha, path)
+ c = raw_repository.gitaly_commit_client.last_commit_for_path(sha, path)
+ commit(c)
+ end
+
+ def last_commit_for_path_by_rugged(sha, path)
+ sha = last_commit_id_for_path_by_shelling_out(sha, path)
+ commit(sha)
+ end
+
+ def last_commit_id_for_path_by_shelling_out(sha, path)
+ args = %W(rev-list --max-count=1 #{sha} -- #{path})
+ run_git(args).first.strip
+ end
+
def repository_storage_path
@project.repository_storage_path
end
@@ -1145,4 +1185,29 @@ class Repository
def initialize_raw_repository
Gitlab::Git::Repository.new(project.repository_storage, disk_path + '.git')
end
+
+ def circuit_breaker
+ @circuit_breaker ||= Gitlab::Git::Storage::CircuitBreaker.for_storage(project.repository_storage)
+ end
+
+ def find_commits_by_message_by_shelling_out(query, ref, path, limit, offset)
+ ref ||= root_ref
+
+ args = %W(
+ log #{ref} --pretty=%H --skip #{offset}
+ --max-count #{limit} --grep=#{query} --regexp-ignore-case
+ )
+ args = args.concat(%W(-- #{path})) if path.present?
+
+ git_log_results = run_git(args).first.lines
+
+ git_log_results.map { |c| commit(c.chomp) }.compact
+ end
+
+ def find_commits_by_message_by_gitaly(query, ref, path, limit, offset)
+ raw_repository
+ .gitaly_commit_client
+ .commits_by_message(query, revision: ref, path: path, limit: limit, offset: offset)
+ .map { |c| commit(c) }
+ end
end
diff --git a/app/models/user.rb b/app/models/user.rb
index 6e66c587a1f..2b25736bb26 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -47,6 +47,11 @@ class User < ActiveRecord::Base
devise :lockable, :recoverable, :rememberable, :trackable,
:validatable, :omniauthable, :confirmable, :registerable
+ # devise overrides #inspect, so we manually use the Referable one
+ def inspect
+ referable_inspect
+ end
+
# Override Devise::Models::Trackable#update_tracked_fields!
# to limit database writes to at most once every hour
def update_tracked_fields!(request)
@@ -143,6 +148,8 @@ class User < ActiveRecord::Base
uniqueness: { case_sensitive: false }
validate :namespace_uniq, if: :username_changed?
+ validate :namespace_move_dir_allowed, if: :username_changed?
+
validate :avatar_type, if: ->(user) { user.avatar.present? && user.avatar_changed? }
validate :unique_email, if: :email_changed?
validate :owns_notification_email, if: :notification_email_changed?
@@ -482,6 +489,12 @@ class User < ActiveRecord::Base
end
end
+ def namespace_move_dir_allowed
+ if namespace&.any_project_has_container_registry_tags?
+ errors.add(:username, 'cannot be changed if a personal project has container registry tags.')
+ end
+ end
+
def avatar_type
unless avatar.image?
errors.add :avatar, "only images allowed"
@@ -523,7 +536,7 @@ class User < ActiveRecord::Base
union = Gitlab::SQL::Union
.new([groups.select(:id), authorized_projects.select(:namespace_id)])
- Group.where("namespaces.id IN (#{union.to_sql})")
+ Group.where("namespaces.id IN (#{union.to_sql})") # rubocop:disable GitlabSecurity/SqlInjection
end
# Returns a relation of groups the user has access to, including their parent
@@ -627,7 +640,11 @@ class User < ActiveRecord::Base
end
def projects_limit_left
- projects_limit - personal_projects.count
+ projects_limit - personal_projects_count
+ end
+
+ def personal_projects_count
+ @personal_projects_count ||= personal_projects.count
end
def projects_limit_percent
@@ -641,16 +658,14 @@ class User < ActiveRecord::Base
events = events.where(project_id: project_ids) if project_ids
# Use the latest event that has not been pushed or merged recently
- events.recent.find do |event|
- project = Project.find_by_id(event.project_id)
- next unless project
-
- if project.repository.branch_exists?(event.branch_name)
- merge_requests = MergeRequest.where("created_at >= ?", event.created_at)
- .where(source_project_id: project.id,
- source_branch: event.branch_name)
- merge_requests.empty?
- end
+ events.includes(:project).recent.find do |event|
+ next unless event.project.repository.branch_exists?(event.branch_name)
+
+ merge_requests = MergeRequest.where("created_at >= ?", event.created_at)
+ .where(source_project_id: event.project.id,
+ source_branch: event.branch_name)
+
+ merge_requests.empty?
end
end
@@ -711,9 +726,9 @@ class User < ActiveRecord::Base
end
def sanitize_attrs
- %w[username skype linkedin twitter].each do |attr|
- value = public_send(attr)
- public_send("#{attr}=", Sanitize.clean(value)) if value.present?
+ %i[skype linkedin twitter].each do |attr|
+ value = self[attr]
+ self[attr] = Sanitize.clean(value) if value.present?
end
end
@@ -772,7 +787,7 @@ class User < ActiveRecord::Base
def with_defaults
User.defaults.each do |k, v|
- public_send("#{k}=", v)
+ public_send("#{k}=", v) # rubocop:disable GitlabSecurity/PublicSend
end
self
@@ -818,7 +833,7 @@ class User < ActiveRecord::Base
{
name: name,
username: username,
- avatar_url: avatar_url
+ avatar_url: avatar_url(only_path: false)
}
end
@@ -912,7 +927,7 @@ class User < ActiveRecord::Base
def ci_authorized_runners
@ci_authorized_runners ||= begin
runner_ids = Ci::RunnerProject
- .where("ci_runner_projects.project_id IN (#{ci_projects_union.to_sql})")
+ .where("ci_runner_projects.project_id IN (#{ci_projects_union.to_sql})") # rubocop:disable GitlabSecurity/SqlInjection
.select(:runner_id)
Ci::Runner.specific.where(id: runner_ids)
end
@@ -1054,6 +1069,7 @@ class User < ActiveRecord::Base
# Added according to https://github.com/plataformatec/devise/blob/7df57d5081f9884849ca15e4fde179ef164a575f/README.md#activejob-integration
def send_devise_notification(notification, *args)
+ return true unless can?(:receive_notifications)
devise_mailer.send(notification, self, *args).deliver_later
end
diff --git a/app/models/wiki_page.rb b/app/models/wiki_page.rb
index 148998bc9be..5c7c2204374 100644
--- a/app/models/wiki_page.rb
+++ b/app/models/wiki_page.rb
@@ -180,31 +180,50 @@ class WikiPage
#
# Returns the String SHA1 of the newly created page
# or False if the save was unsuccessful.
- def create(attr = {})
- @attributes.merge!(attr)
+ def create(attrs = {})
+ @attributes.merge!(attrs)
- save :create_page, title, content, format, message
+ save(page_details: title) do
+ wiki.create_page(title, content, format, message)
+ end
end
# Updates an existing Wiki Page, creating a new version.
#
- # new_content - The raw markup content to replace the existing.
- # format - Optional symbol representing the content format.
- # See ProjectWiki::MARKUPS Hash for available formats.
- # message - Optional commit message to set on the new version.
- # last_commit_sha - Optional last commit sha to validate the page unchanged.
+ # attrs - Hash of attributes to be updated on the page.
+ # :content - The raw markup content to replace the existing.
+ # :format - Optional symbol representing the content format.
+ # See ProjectWiki::MARKUPS Hash for available formats.
+ # :message - Optional commit message to set on the new version.
+ # :last_commit_sha - Optional last commit sha to validate the page unchanged.
+ # :title - The Title to replace existing title
#
# Returns the String SHA1 of the newly created page
# or False if the save was unsuccessful.
- def update(new_content, format: :markdown, message: nil, last_commit_sha: nil)
- @attributes[:content] = new_content
- @attributes[:format] = format
-
+ def update(attrs = {})
+ last_commit_sha = attrs.delete(:last_commit_sha)
if last_commit_sha && last_commit_sha != self.last_commit_sha
raise PageChangedError.new("You are attempting to update a page that has changed since you started editing it.")
end
- save :update_page, @page, content, format, message
+ attrs.slice!(:content, :format, :message, :title)
+ @attributes.merge!(attrs)
+ page_details =
+ if title.present? && @page.title != title
+ title
+ else
+ @page.url_path
+ end
+
+ save(page_details: page_details) do
+ wiki.update_page(
+ @page,
+ content: content,
+ format: format,
+ message: attrs[:message],
+ title: title
+ )
+ end
end
# Destroys the Wiki Page.
@@ -236,30 +255,19 @@ class WikiPage
attributes[:format] = @page.format
end
- def save(method, *args)
- saved = false
+ def save(page_details:)
+ return unless valid?
- project_wiki = wiki
- if valid? && project_wiki.send(method, *args)
-
- page_details = if method == :update_page
- # Use url_path instead of path to omit format extension
- @page.url_path
- else
- title
- end
-
- page_title, page_dir = project_wiki.page_title_and_dir(page_details)
- gollum_wiki = project_wiki.wiki
- @page = gollum_wiki.paged(page_title, page_dir)
+ unless yield
+ errors.add(:base, wiki.error_message)
+ return false
+ end
- set_attributes
+ page_title, page_dir = wiki.page_title_and_dir(page_details)
+ gollum_wiki = wiki.wiki
+ @page = gollum_wiki.paged(page_title, page_dir)
- @persisted = true
- saved = true
- else
- errors.add(:base, project_wiki.error_message) if project_wiki.error_message
- end
- saved
+ set_attributes
+ @persisted = errors.blank?
end
end
diff --git a/app/serializers/analytics_build_entity.rb b/app/serializers/analytics_build_entity.rb
index ad7ad020b03..bdc22d71202 100644
--- a/app/serializers/analytics_build_entity.rb
+++ b/app/serializers/analytics_build_entity.rb
@@ -35,6 +35,6 @@ class AnalyticsBuildEntity < Grape::Entity
private
def url_to(route, build, id = nil)
- public_send("#{route}_url", build.project.namespace, build.project, id || build)
+ public_send("#{route}_url", build.project.namespace, build.project, id || build) # rubocop:disable GitlabSecurity/PublicSend
end
end
diff --git a/app/serializers/analytics_issue_entity.rb b/app/serializers/analytics_issue_entity.rb
index 44c50f18613..b7d95ea020f 100644
--- a/app/serializers/analytics_issue_entity.rb
+++ b/app/serializers/analytics_issue_entity.rb
@@ -24,6 +24,6 @@ class AnalyticsIssueEntity < Grape::Entity
private
def url_to(route, id)
- public_send("#{route}_url", request.project.namespace, request.project, id)
+ public_send("#{route}_url", request.project.namespace, request.project, id) # rubocop:disable GitlabSecurity/PublicSend
end
end
diff --git a/app/serializers/blob_entity.rb b/app/serializers/blob_entity.rb
new file mode 100644
index 00000000000..56f173e5a27
--- /dev/null
+++ b/app/serializers/blob_entity.rb
@@ -0,0 +1,17 @@
+class BlobEntity < Grape::Entity
+ include RequestAwareEntity
+
+ expose :id, :path, :name, :mode
+
+ expose :last_commit do |blob|
+ request.project.repository.last_commit_for_path(blob.commit_id, blob.path)
+ end
+
+ expose :icon do |blob|
+ IconsHelper.file_type_icon_class('file', blob.mode, blob.name)
+ end
+
+ expose :url do |blob|
+ project_blob_path(request.project, File.join(request.ref, blob.path))
+ end
+end
diff --git a/app/serializers/job_entity.rb b/app/serializers/job_entity.rb
index d6de43bcbcb..72e56a2c77f 100644
--- a/app/serializers/job_entity.rb
+++ b/app/serializers/job_entity.rb
@@ -46,6 +46,6 @@ class JobEntity < Grape::Entity
end
def path_to(route, build)
- send("#{route}_path", build.project.namespace, build.project, build)
+ send("#{route}_path", build.project.namespace, build.project, build) # rubocop:disable GitlabSecurity/PublicSend
end
end
diff --git a/app/serializers/merge_request_entity.rb b/app/serializers/merge_request_entity.rb
index 7f17f2bf604..07650ce6f20 100644
--- a/app/serializers/merge_request_entity.rb
+++ b/app/serializers/merge_request_entity.rb
@@ -2,7 +2,6 @@ class MergeRequestEntity < IssuableEntity
include RequestAwareEntity
expose :in_progress_merge_commit_sha
- expose :locked_at
expose :merge_commit_sha
expose :merge_error
expose :merge_params
@@ -32,6 +31,7 @@ class MergeRequestEntity < IssuableEntity
expose :head_pipeline, with: PipelineDetailsEntity, as: :pipeline
# Booleans
+ expose :merge_ongoing?, as: :merge_ongoing
expose :work_in_progress?, as: :work_in_progress
expose :source_branch_exists?, as: :source_branch_exists
expose :mergeable_discussions_state?, as: :mergeable_discussions_state
diff --git a/app/serializers/submodule_entity.rb b/app/serializers/submodule_entity.rb
new file mode 100644
index 00000000000..9a7eb5e7880
--- /dev/null
+++ b/app/serializers/submodule_entity.rb
@@ -0,0 +1,23 @@
+class SubmoduleEntity < Grape::Entity
+ include RequestAwareEntity
+
+ expose :id, :path, :name, :mode
+
+ expose :icon do |blob|
+ 'archive'
+ end
+
+ expose :project_url do |blob|
+ submodule_links(blob, request).first
+ end
+
+ expose :tree_url do |blob|
+ submodule_links(blob, request).last
+ end
+
+ private
+
+ def submodule_links(blob, request)
+ @submodule_links ||= SubmoduleHelper.submodule_links(blob, request.ref, request.repository)
+ end
+end
diff --git a/app/serializers/tree_entity.rb b/app/serializers/tree_entity.rb
new file mode 100644
index 00000000000..555e5cf83bd
--- /dev/null
+++ b/app/serializers/tree_entity.rb
@@ -0,0 +1,17 @@
+class TreeEntity < Grape::Entity
+ include RequestAwareEntity
+
+ expose :id, :path, :name, :mode
+
+ expose :last_commit do |tree|
+ request.project.repository.last_commit_for_path(tree.commit_id, tree.path)
+ end
+
+ expose :icon do |tree|
+ IconsHelper.file_type_icon_class('folder', tree.mode, tree.name)
+ end
+
+ expose :url do |tree|
+ project_tree_path(request.project, File.join(request.ref, tree.path))
+ end
+end
diff --git a/app/serializers/tree_root_entity.rb b/app/serializers/tree_root_entity.rb
new file mode 100644
index 00000000000..23b65aa4a4c
--- /dev/null
+++ b/app/serializers/tree_root_entity.rb
@@ -0,0 +1,8 @@
+# TODO: Inherit from TreeEntity, when `Tree` implements `id` and `name` like `Gitlab::Git::Tree`.
+class TreeRootEntity < Grape::Entity
+ expose :path
+
+ expose :trees, using: TreeEntity
+ expose :blobs, using: BlobEntity
+ expose :submodules, using: SubmoduleEntity
+end
diff --git a/app/serializers/tree_serializer.rb b/app/serializers/tree_serializer.rb
new file mode 100644
index 00000000000..713ade23bc9
--- /dev/null
+++ b/app/serializers/tree_serializer.rb
@@ -0,0 +1,3 @@
+class TreeSerializer < BaseSerializer
+ entity TreeRootEntity
+end
diff --git a/app/services/auth/container_registry_authentication_service.rb b/app/services/auth/container_registry_authentication_service.rb
index 5e151b0f044..7dae5880931 100644
--- a/app/services/auth/container_registry_authentication_service.rb
+++ b/app/services/auth/container_registry_authentication_service.rb
@@ -103,6 +103,8 @@ module Auth
build_can_pull?(requested_project) || user_can_pull?(requested_project)
when 'push'
build_can_push?(requested_project) || user_can_push?(requested_project)
+ when '*'
+ user_can_admin?(requested_project)
else
false
end
@@ -120,6 +122,11 @@ module Auth
(requested_project == project || can?(current_user, :build_read_container_image, requested_project))
end
+ def user_can_admin?(requested_project)
+ has_authentication_ability?(:admin_container_image) &&
+ can?(current_user, :admin_container_image, requested_project)
+ end
+
def user_can_pull?(requested_project)
has_authentication_ability?(:read_container_image) &&
can?(current_user, :read_container_image, requested_project)
diff --git a/app/services/ci/register_job_service.rb b/app/services/ci/register_job_service.rb
index b951e8d0c9f..414f672cc6a 100644
--- a/app/services/ci/register_job_service.rb
+++ b/app/services/ci/register_job_service.rb
@@ -30,6 +30,7 @@ module Ci
# with StateMachines::InvalidTransition or StaleObjectError when doing run! or save method.
build.runner_id = runner.id
build.run!
+ register_success(build)
return Result.new(build, true)
rescue StateMachines::InvalidTransition, ActiveRecord::StaleObjectError
@@ -46,6 +47,7 @@ module Ci
end
end
+ register_failure
Result.new(nil, valid)
end
@@ -81,5 +83,27 @@ module Ci
def shared_runner_build_limits_feature_enabled?
ENV['DISABLE_SHARED_RUNNER_BUILD_MINUTES_LIMIT'].to_s != 'true'
end
+
+ def register_failure
+ failed_attempt_counter.increment
+ attempt_counter.increment
+ end
+
+ def register_success(job)
+ job_queue_duration_seconds.observe({ shared_runner: @runner.shared? }, Time.now - job.created_at)
+ attempt_counter.increment
+ end
+
+ def failed_attempt_counter
+ @failed_attempt_counter ||= Gitlab::Metrics.counter(:job_register_attempts_failed_total, "Counts the times a runner tries to register a job")
+ end
+
+ def attempt_counter
+ @attempt_counter ||= Gitlab::Metrics.counter(:job_register_attempts_total, "Counts the times a runner tries to register a job")
+ end
+
+ def job_queue_duration_seconds
+ @job_queue_duration_seconds ||= Gitlab::Metrics.histogram(:job_queue_duration_seconds, 'Request handling execution time')
+ end
end
end
diff --git a/app/services/event_create_service.rb b/app/services/event_create_service.rb
index 0f3a485a3fd..0b7e4f187f7 100644
--- a/app/services/event_create_service.rb
+++ b/app/services/event_create_service.rb
@@ -71,7 +71,14 @@ class EventCreateService
end
def push(project, current_user, push_data)
- create_event(project, current_user, Event::PUSHED, data: push_data)
+ # We're using an explicit transaction here so that any errors that may occur
+ # when creating push payload data will result in the event creation being
+ # rolled back as well.
+ Event.transaction do
+ event = create_event(project, current_user, Event::PUSHED)
+
+ PushEventPayloadService.new(event, push_data).execute
+ end
Users::ActivityService.new(current_user, 'push').execute
end
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index ea497729115..b84a6fd2b7d 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -2,11 +2,8 @@ class IssuableBaseService < BaseService
private
def create_milestone_note(issuable)
- milestone = issuable.milestone
- return if milestone && milestone.is_group_milestone?
-
SystemNoteService.change_milestone(
- issuable, issuable.project, current_user, milestone)
+ issuable, issuable.project, current_user, issuable.milestone)
end
def create_labels_note(issuable, old_labels)
@@ -182,7 +179,6 @@ class IssuableBaseService < BaseService
if params.present? && create_issuable(issuable, params, label_ids: label_ids)
after_create(issuable)
- issuable.create_cross_references!(current_user)
execute_hooks(issuable)
invalidate_cache_counts(issuable, users: issuable.assignees)
end
@@ -288,7 +284,7 @@ class IssuableBaseService < BaseService
todo_service.mark_todo(issuable, current_user)
when 'done'
todo = TodosFinder.new(current_user).execute.find_by(target: issuable)
- todo_service.mark_todos_as_done([todo], current_user) if todo
+ todo_service.mark_todos_as_done_by_ids(todo, current_user) if todo
end
end
diff --git a/app/services/issues/create_service.rb b/app/services/issues/create_service.rb
index 718a7ac1f22..234fcbede03 100644
--- a/app/services/issues/create_service.rb
+++ b/app/services/issues/create_service.rb
@@ -15,11 +15,15 @@ module Issues
def before_create(issue)
spam_check(issue, current_user)
issue.move_to_end
+
+ # current_user (defined in BaseService) is not available within run_after_commit block
+ user = current_user
+ issue.run_after_commit do
+ NewIssueWorker.perform_async(issue.id, user.id)
+ end
end
def after_create(issuable)
- event_service.open_issue(issuable, current_user)
- notification_service.new_issue(issuable, current_user)
todo_service.new_issue(issuable, current_user)
user_agent_detail_service.create
resolve_discussions_with_issue(issuable)
diff --git a/app/services/labels/transfer_service.rb b/app/services/labels/transfer_service.rb
index d2ece354efc..775efed48eb 100644
--- a/app/services/labels/transfer_service.rb
+++ b/app/services/labels/transfer_service.rb
@@ -37,7 +37,7 @@ module Labels
union = Gitlab::SQL::Union.new(label_ids)
- Label.where("labels.id IN (#{union.to_sql})").reorder(nil).uniq
+ Label.where("labels.id IN (#{union.to_sql})").reorder(nil).uniq # rubocop:disable GitlabSecurity/SqlInjection
end
def group_labels_applied_to_issues
diff --git a/app/services/merge_requests/create_service.rb b/app/services/merge_requests/create_service.rb
index 19189e64acf..fa0c0b7175c 100644
--- a/app/services/merge_requests/create_service.rb
+++ b/app/services/merge_requests/create_service.rb
@@ -12,20 +12,32 @@ module MergeRequests
merge_request.source_project = source_project
merge_request.source_branch = params[:source_branch]
merge_request.merge_params['force_remove_source_branch'] = params.delete(:force_remove_source_branch)
- merge_request.head_pipeline = head_pipeline_for(merge_request)
create(merge_request)
end
+ def before_create(merge_request)
+ # current_user (defined in BaseService) is not available within run_after_commit block
+ user = current_user
+ merge_request.run_after_commit do
+ NewMergeRequestWorker.perform_async(merge_request.id, user.id)
+ end
+ end
+
def after_create(issuable)
event_service.open_mr(issuable, current_user)
- notification_service.new_merge_request(issuable, current_user)
todo_service.new_merge_request(issuable, current_user)
issuable.cache_merge_request_closes_issues!(current_user)
+ update_merge_requests_head_pipeline(issuable)
end
private
+ def update_merge_requests_head_pipeline(merge_request)
+ pipeline = head_pipeline_for(merge_request)
+ merge_request.update(head_pipeline_id: pipeline.id) if pipeline
+ end
+
def head_pipeline_for(merge_request)
return unless merge_request.source_project
diff --git a/app/services/notification_recipient_service.rb b/app/services/notification_recipient_service.rb
index 9ac561e4bd2..21c9c314a2a 100644
--- a/app/services/notification_recipient_service.rb
+++ b/app/services/notification_recipient_service.rb
@@ -1,331 +1,288 @@
#
# Used by NotificationService to determine who should receive notification
#
-class NotificationRecipientService
- attr_reader :project
-
- def initialize(project)
- @project = project
+module NotificationRecipientService
+ def self.notifiable_users(users, *args)
+ users.compact.map { |u| NotificationRecipient.new(u, *args) }.select(&:notifiable?).map(&:user)
end
- def build_recipients(target, current_user, action:, previous_assignee: nil, skip_current_user: true)
- custom_action = build_custom_key(action, target)
-
- recipients = participants(target, current_user)
- recipients = add_project_watchers(recipients)
- recipients = add_custom_notifications(recipients, custom_action)
- recipients = reject_mention_users(recipients)
-
- # Re-assign is considered as a mention of the new assignee so we add the
- # new assignee to the list of recipients after we rejected users with
- # the "on mention" notification level
- case custom_action
- when :reassign_merge_request
- recipients << previous_assignee if previous_assignee
- recipients << target.assignee
- when :reassign_issue
- previous_assignees = Array(previous_assignee)
- recipients.concat(previous_assignees)
- recipients.concat(target.assignees)
- end
-
- recipients = reject_muted_users(recipients)
- recipients = add_subscribed_users(recipients, target)
-
- if [:new_issue, :new_merge_request].include?(custom_action)
- recipients = add_labels_subscribers(recipients, target)
- end
-
- recipients = reject_unsubscribed_users(recipients, target)
- recipients = reject_users_without_access(recipients, target)
+ def self.notifiable?(user, *args)
+ NotificationRecipient.new(user, *args).notifiable?
+ end
- recipients.delete(current_user) if skip_current_user && !current_user.notified_of_own_activity?
+ def self.build_recipients(*a)
+ Builder::Default.new(*a).recipient_users
+ end
- recipients.uniq
+ def self.build_new_note_recipients(*a)
+ Builder::NewNote.new(*a).recipient_users
end
- def build_pipeline_recipients(target, current_user, action:)
- return [] unless current_user
+ module Builder
+ class Base
+ def initialize(*)
+ raise 'abstract'
+ end
- custom_action =
- case action.to_s
- when 'failed'
- :failed_pipeline
- when 'success'
- :success_pipeline
+ def build!
+ raise 'abstract'
end
- notification_setting = notification_setting_for_user_project(current_user, target.project)
+ def filter!
+ recipients.select!(&:notifiable?)
+ end
- return [] if notification_setting.mention? || notification_setting.disabled?
+ def acting_user
+ current_user
+ end
- return [] if notification_setting.custom? && !notification_setting.event_enabled?(custom_action)
+ def target
+ raise 'abstract'
+ end
- return [] if (notification_setting.watch? || notification_setting.participating?) && NotificationSetting::EXCLUDED_WATCHER_EVENTS.include?(custom_action)
+ # rubocop:disable Rails/Delegate
+ def project
+ target.project
+ end
- reject_users_without_access([current_user], target)
- end
+ def recipients
+ @recipients ||= []
+ end
- def build_relabeled_recipients(target, current_user, labels:)
- recipients = add_labels_subscribers([], target, labels: labels)
- recipients = reject_unsubscribed_users(recipients, target)
- recipients = reject_users_without_access(recipients, target)
- recipients.delete(current_user) unless current_user.notified_of_own_activity?
- recipients.uniq
- end
+ def <<(pair)
+ users, type = pair
- def build_new_note_recipients(note)
- target = note.noteable
+ if users.is_a?(ActiveRecord::Relation)
+ users = users.includes(:notification_settings)
+ end
- ability, subject = if note.for_personal_snippet?
- [:read_personal_snippet, note.noteable]
- else
- [:read_project, note.project]
- end
+ users = Array(users)
+ users.compact!
+ recipients.concat(users.map { |u| make_recipient(u, type) })
+ end
- mentioned_users = note.mentioned_users.select { |user| user.can?(ability, subject) }
+ def user_scope
+ User.includes(:notification_settings)
+ end
- # Add all users participating in the thread (author, assignee, comment authors)
- recipients = participants(target, note.author) || mentioned_users
+ def make_recipient(user, type)
+ NotificationRecipient.new(
+ user, type,
+ project: project,
+ custom_action: custom_action,
+ target: target,
+ acting_user: acting_user
+ )
+ end
- unless note.for_personal_snippet?
- # Merge project watchers
- recipients = add_project_watchers(recipients)
+ def recipient_users
+ @recipient_users ||=
+ begin
+ build!
+ filter!
+ users = recipients.map(&:user)
+ users.uniq!
+ users.freeze
+ end
+ end
- # Merge project with custom notification
- recipients = add_custom_notifications(recipients, :new_note)
- end
+ def custom_action
+ nil
+ end
- # Reject users with Mention notification level, except those mentioned in _this_ note.
- recipients = reject_mention_users(recipients - mentioned_users)
- recipients = recipients + mentioned_users
+ protected
- recipients = reject_muted_users(recipients)
+ def add_participants(user)
+ return unless target.respond_to?(:participants)
- recipients = add_subscribed_users(recipients, note.noteable)
- recipients = reject_unsubscribed_users(recipients, note.noteable)
- recipients = reject_users_without_access(recipients, note.noteable)
+ self << [target.participants(user), :watch]
+ end
- recipients.delete(note.author) unless note.author.notified_of_own_activity?
- recipients.uniq
- end
+ # Get project/group users with CUSTOM notification level
+ def add_custom_notifications
+ user_ids = []
- # Remove users with disabled notifications from array
- # Also remove duplications and nil recipients
- def reject_muted_users(users)
- reject_users(users, :disabled)
- end
+ # Users with a notification setting on group or project
+ user_ids += user_ids_notifiable_on(project, :custom)
+ user_ids += user_ids_notifiable_on(project.group, :custom)
- protected
+ # Users with global level custom
+ user_ids_with_project_level_global = user_ids_notifiable_on(project, :global)
+ user_ids_with_group_level_global = user_ids_notifiable_on(project.group, :global)
- # Ensure that if we modify this array, we aren't modifying the memoised
- # participants on the target.
- def participants(target, user)
- return unless target.respond_to?(:participants)
+ global_users_ids = user_ids_with_project_level_global.concat(user_ids_with_group_level_global)
+ user_ids += user_ids_with_global_level_custom(global_users_ids, custom_action)
- target.participants(user).dup
- end
+ self << [user_scope.where(id: user_ids), :watch]
+ end
- # Get project/group users with CUSTOM notification level
- def add_custom_notifications(recipients, action)
- user_ids = []
+ def add_project_watchers
+ self << [project_watchers, :watch]
+ end
- # Users with a notification setting on group or project
- user_ids += user_ids_notifiable_on(project, :custom, action)
- user_ids += user_ids_notifiable_on(project.group, :custom, action)
+ # Get project users with WATCH notification level
+ def project_watchers
+ project_members_ids = user_ids_notifiable_on(project)
- # Users with global level custom
- user_ids_with_project_level_global = user_ids_notifiable_on(project, :global)
- user_ids_with_group_level_global = user_ids_notifiable_on(project.group, :global)
+ user_ids_with_project_global = user_ids_notifiable_on(project, :global)
+ user_ids_with_group_global = user_ids_notifiable_on(project.group, :global)
- global_users_ids = user_ids_with_project_level_global.concat(user_ids_with_group_level_global)
- user_ids += user_ids_with_global_level_custom(global_users_ids, action)
+ user_ids = user_ids_with_global_level_watch((user_ids_with_project_global + user_ids_with_group_global).uniq)
- recipients.concat(User.find(user_ids))
- end
+ user_ids_with_project_setting = select_project_members_ids(user_ids_with_project_global, user_ids)
+ user_ids_with_group_setting = select_group_members_ids(project.group, project_members_ids, user_ids_with_group_global, user_ids)
- def add_project_watchers(recipients)
- recipients.concat(project_watchers).compact
- end
+ user_scope.where(id: user_ids_with_project_setting.concat(user_ids_with_group_setting).uniq)
+ end
- # Get project users with WATCH notification level
- def project_watchers
- project_members_ids = user_ids_notifiable_on(project)
+ def add_subscribed_users
+ return unless target.respond_to? :subscribers
- user_ids_with_project_global = user_ids_notifiable_on(project, :global)
- user_ids_with_group_global = user_ids_notifiable_on(project.group, :global)
+ self << [target.subscribers(project), :subscription]
+ end
- user_ids = user_ids_with_global_level_watch((user_ids_with_project_global + user_ids_with_group_global).uniq)
+ def user_ids_notifiable_on(resource, notification_level = nil)
+ return [] unless resource
- user_ids_with_project_setting = select_project_members_ids(project, user_ids_with_project_global, user_ids)
- user_ids_with_group_setting = select_group_members_ids(project.group, project_members_ids, user_ids_with_group_global, user_ids)
+ scope = resource.notification_settings
- User.where(id: user_ids_with_project_setting.concat(user_ids_with_group_setting).uniq).to_a
- end
+ if notification_level
+ scope = scope.where(level: NotificationSetting.levels[notification_level])
+ end
- # Remove users with notification level 'Mentioned'
- def reject_mention_users(users)
- reject_users(users, :mention)
- end
+ scope.pluck(:user_id)
+ end
- def add_subscribed_users(recipients, target)
- return recipients unless target.respond_to? :subscribers
+ # Build a list of user_ids based on project notification settings
+ def select_project_members_ids(global_setting, user_ids_global_level_watch)
+ user_ids = user_ids_notifiable_on(project, :watch)
- recipients + target.subscribers(project)
- end
+ # If project setting is global, add to watch list if global setting is watch
+ user_ids + (global_setting & user_ids_global_level_watch)
+ end
- def user_ids_notifiable_on(resource, notification_level = nil, action = nil)
- return [] unless resource
+ # Build a list of user_ids based on group notification settings
+ def select_group_members_ids(group, project_members, global_setting, user_ids_global_level_watch)
+ uids = user_ids_notifiable_on(group, :watch)
- if notification_level
- settings = resource.notification_settings.where(level: NotificationSetting.levels[notification_level])
- settings = settings.select { |setting| setting.event_enabled?(action) } if action.present?
- settings.map(&:user_id)
- else
- resource.notification_settings.pluck(:user_id)
- end
- end
+ # Group setting is global, add to user_ids list if global setting is watch
+ uids + (global_setting & user_ids_global_level_watch) - project_members
+ end
- # Build a list of user_ids based on project notification settings
- def select_project_members_ids(project, global_setting, user_ids_global_level_watch)
- user_ids = user_ids_notifiable_on(project, :watch)
+ def user_ids_with_global_level_watch(ids)
+ settings_with_global_level_of(:watch, ids).pluck(:user_id)
+ end
- # If project setting is global, add to watch list if global setting is watch
- global_setting.each do |user_id|
- if user_ids_global_level_watch.include?(user_id)
- user_ids << user_id
+ def user_ids_with_global_level_custom(ids, action)
+ settings_with_global_level_of(:custom, ids).pluck(:user_id)
end
- end
- user_ids
- end
+ def settings_with_global_level_of(level, ids)
+ NotificationSetting.where(
+ user_id: ids,
+ source_type: nil,
+ level: NotificationSetting.levels[level]
+ )
+ end
- # Build a list of user_ids based on group notification settings
- def select_group_members_ids(group, project_members, global_setting, user_ids_global_level_watch)
- uids = user_ids_notifiable_on(group, :watch)
+ def add_labels_subscribers(labels: nil)
+ return unless target.respond_to? :labels
- # Group setting is watch, add to user_ids list if user is not project member
- user_ids = []
- uids.each do |user_id|
- if project_members.exclude?(user_id)
- user_ids << user_id
+ (labels || target.labels).each do |label|
+ self << [label.subscribers(project), :subscription]
+ end
end
end
- # Group setting is global, add to user_ids list if global setting is watch
- global_setting.each do |user_id|
- if project_members.exclude?(user_id) && user_ids_global_level_watch.include?(user_id)
- user_ids << user_id
+ class Default < Base
+ attr_reader :target
+ attr_reader :current_user
+ attr_reader :action
+ attr_reader :previous_assignee
+ attr_reader :skip_current_user
+ def initialize(target, current_user, action:, previous_assignee: nil, skip_current_user: true)
+ @target = target
+ @current_user = current_user
+ @action = action
+ @previous_assignee = previous_assignee
+ @skip_current_user = skip_current_user
end
- end
-
- user_ids
- end
-
- def user_ids_with_global_level_watch(ids)
- settings_with_global_level_of(:watch, ids).pluck(:user_id)
- end
-
- def user_ids_with_global_level_custom(ids, action)
- settings = settings_with_global_level_of(:custom, ids)
- settings = settings.select { |setting| setting.event_enabled?(action) }
- settings.map(&:user_id)
- end
- def settings_with_global_level_of(level, ids)
- NotificationSetting.where(
- user_id: ids,
- source_type: nil,
- level: NotificationSetting.levels[level]
- )
- end
+ def build!
+ add_participants(current_user)
+ add_project_watchers
+ add_custom_notifications
+
+ # Re-assign is considered as a mention of the new assignee
+ case custom_action
+ when :reassign_merge_request
+ self << [previous_assignee, :mention]
+ self << [target.assignee, :mention]
+ when :reassign_issue
+ previous_assignees = Array(previous_assignee)
+ self << [previous_assignees, :mention]
+ self << [target.assignees, :mention]
+ end
+
+ add_subscribed_users
+
+ if [:new_issue, :new_merge_request].include?(custom_action)
+ add_labels_subscribers
+ end
+ end
- # Reject users which has certain notification level
- #
- # Example:
- # reject_users(users, :watch, project)
- #
- def reject_users(users, level)
- level = level.to_s
+ def acting_user
+ current_user if skip_current_user
+ end
- unless NotificationSetting.levels.keys.include?(level)
- raise 'Invalid notification level'
+ # Build event key to search on custom notification level
+ # Check NotificationSetting::EMAIL_EVENTS
+ def custom_action
+ @custom_action ||= "#{action}_#{target.class.model_name.name.underscore}".to_sym
+ end
end
- users = users.to_a.compact.uniq
- users = users.select { |u| u.can?(:receive_notifications) }
-
- users.reject do |user|
- global_notification_setting = user.global_notification_setting
-
- next global_notification_setting.level == level unless project
-
- setting = user.notification_settings_for(project)
-
- if project.group && (setting.nil? || setting.global?)
- setting = user.notification_settings_for(project.group)
+ class NewNote < Base
+ attr_reader :note
+ def initialize(note)
+ @note = note
end
- # reject users who globally set mention notification and has no setting per project/group
- next global_notification_setting.level == level unless setting
-
- # reject users who set mention notification in project
- next true if setting.level == level
-
- # reject users who have mention level in project and disabled in global settings
- setting.global? && global_notification_setting.level == level
- end
- end
+ def target
+ note.noteable
+ end
- def reject_unsubscribed_users(recipients, target)
- return recipients unless target.respond_to? :subscriptions
+ # NOTE: may be nil, in the case of a PersonalSnippet
+ #
+ # (this is okay because NotificationRecipient is written
+ # to handle nil projects)
+ def project
+ note.project
+ end
- recipients.reject do |user|
- subscription = target.subscriptions.find_by_user_id(user.id)
- subscription && !subscription.subscribed
- end
- end
+ def build!
+ # Add all users participating in the thread (author, assignee, comment authors)
+ add_participants(note.author)
+ self << [note.mentioned_users, :mention]
- def reject_users_without_access(recipients, target)
- ability = case target
- when Issuable
- :"read_#{target.to_ability_name}"
- when Ci::Pipeline
- :read_build # We have build trace in pipeline emails
- end
+ unless note.for_personal_snippet?
+ # Merge project watchers
+ add_project_watchers
- return recipients unless ability
+ # Merge project with custom notification
+ add_custom_notifications
+ end
- recipients.select do |user|
- user.can?(ability, target)
- end
- end
+ add_subscribed_users
+ end
- def add_labels_subscribers(recipients, target, labels: nil)
- return recipients unless target.respond_to? :labels
+ def custom_action
+ :new_note
+ end
- (labels || target.labels).each do |label|
- recipients += label.subscribers(project)
+ def acting_user
+ note.author
+ end
end
-
- recipients
- end
-
- # Build event key to search on custom notification level
- # Check NotificationSetting::EMAIL_EVENTS
- def build_custom_key(action, object)
- "#{action}_#{object.class.model_name.name.underscore}".to_sym
- end
-
- def notification_setting_for_user_project(user, project)
- project_setting = user.notification_settings_for(project)
-
- return project_setting unless project_setting.global?
-
- group_setting = user.notification_settings_for(project.group)
-
- return group_setting unless group_setting.global?
-
- user.global_notification_setting
end
end
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index b94921d2a08..4267879b03d 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -10,9 +10,11 @@ class NotificationService
# only if ssh key is not deploy key
#
# This is security email so it will be sent
- # even if user disabled notifications
+ # even if user disabled notifications. However,
+ # it won't be sent to internal users like the
+ # ghost user or the EE support bot.
def new_key(key)
- if key.user
+ if key.user&.can?(:receive_notifications)
mailer.new_ssh_key_email(key.id).deliver_later
end
end
@@ -22,14 +24,14 @@ class NotificationService
# This is a security email so it will be sent even if the user user disabled
# notifications
def new_gpg_key(gpg_key)
- if gpg_key.user
+ if gpg_key.user&.can?(:receive_notifications)
mailer.new_gpg_key_email(gpg_key.id).deliver_later
end
end
# Always notify user about email added to profile
def new_email(email)
- if email.user
+ if email.user&.can?(:receive_notifications)
mailer.new_email_email(email.id).deliver_later
end
end
@@ -42,7 +44,7 @@ class NotificationService
# * users with custom level checked with "new issue"
#
def new_issue(issue, current_user)
- new_resource_email(issue, issue.project, :new_issue_email)
+ new_resource_email(issue, :new_issue_email)
end
# When issue text is updated, we should send an email to:
@@ -52,7 +54,6 @@ class NotificationService
def new_mentions_in_issue(issue, new_mentioned_users, current_user)
new_mentions_in_resource_email(
issue,
- issue.project,
new_mentioned_users,
current_user,
:new_mention_in_issue_email
@@ -67,7 +68,7 @@ class NotificationService
# * users with custom level checked with "close issue"
#
def close_issue(issue, current_user)
- close_resource_email(issue, issue.project, current_user, :closed_issue_email)
+ close_resource_email(issue, current_user, :closed_issue_email)
end
# When we reassign an issue we should send an email to:
@@ -77,7 +78,7 @@ class NotificationService
# * users with custom level checked with "reassign issue"
#
def reassigned_issue(issue, current_user, previous_assignees = [])
- recipients = NotificationRecipientService.new(issue.project).build_recipients(
+ recipients = NotificationRecipientService.build_recipients(
issue,
current_user,
action: "reassign",
@@ -102,7 +103,7 @@ class NotificationService
# * watchers of the issue's labels
#
def relabeled_issue(issue, added_labels, current_user)
- relabeled_resource_email(issue, issue.project, added_labels, current_user, :relabeled_issue_email)
+ relabeled_resource_email(issue, added_labels, current_user, :relabeled_issue_email)
end
# When create a merge request we should send an email to:
@@ -113,7 +114,7 @@ class NotificationService
# * users with custom level checked with "new merge request"
#
def new_merge_request(merge_request, current_user)
- new_resource_email(merge_request, merge_request.target_project, :new_merge_request_email)
+ new_resource_email(merge_request, :new_merge_request_email)
end
# When merge request text is updated, we should send an email to:
@@ -123,7 +124,6 @@ class NotificationService
def new_mentions_in_merge_request(merge_request, new_mentioned_users, current_user)
new_mentions_in_resource_email(
merge_request,
- merge_request.target_project,
new_mentioned_users,
current_user,
:new_mention_in_merge_request_email
@@ -137,7 +137,7 @@ class NotificationService
# * users with custom level checked with "reassign merge request"
#
def reassigned_merge_request(merge_request, current_user)
- reassign_resource_email(merge_request, merge_request.target_project, current_user, :reassigned_merge_request_email)
+ reassign_resource_email(merge_request, current_user, :reassigned_merge_request_email)
end
# When we add labels to a merge request we should send an email to:
@@ -145,21 +145,20 @@ class NotificationService
# * watchers of the mr's labels
#
def relabeled_merge_request(merge_request, added_labels, current_user)
- relabeled_resource_email(merge_request, merge_request.target_project, added_labels, current_user, :relabeled_merge_request_email)
+ relabeled_resource_email(merge_request, added_labels, current_user, :relabeled_merge_request_email)
end
def close_mr(merge_request, current_user)
- close_resource_email(merge_request, merge_request.target_project, current_user, :closed_merge_request_email)
+ close_resource_email(merge_request, current_user, :closed_merge_request_email)
end
def reopen_issue(issue, current_user)
- reopen_resource_email(issue, issue.project, current_user, :issue_status_changed_email, 'reopened')
+ reopen_resource_email(issue, current_user, :issue_status_changed_email, 'reopened')
end
def merge_mr(merge_request, current_user)
close_resource_email(
merge_request,
- merge_request.target_project,
current_user,
:merged_merge_request_email,
skip_current_user: !merge_request.merge_when_pipeline_succeeds?
@@ -169,7 +168,6 @@ class NotificationService
def reopen_mr(merge_request, current_user)
reopen_resource_email(
merge_request,
- merge_request.target_project,
current_user,
:merge_request_status_email,
'reopened'
@@ -177,7 +175,7 @@ class NotificationService
end
def resolve_all_discussions(merge_request, current_user)
- recipients = NotificationRecipientService.new(merge_request.target_project).build_recipients(
+ recipients = NotificationRecipientService.build_recipients(
merge_request,
current_user,
action: "resolve_all_discussions")
@@ -189,6 +187,8 @@ class NotificationService
# Notify new user with email after creation
def new_user(user, token = nil)
+ return true unless notifiable?(user, :mention)
+
# Don't email omniauth created users
mailer.new_user_email(user.id, token).deliver_later unless user.identities.any?
end
@@ -202,7 +202,7 @@ class NotificationService
notify_method = "note_#{note.to_ability_name}_email".to_sym
- recipients = NotificationRecipientService.new(note.project).build_new_note_recipients(note)
+ recipients = NotificationRecipientService.build_new_note_recipients(note)
recipients.each do |recipient|
mailer.send(notify_method, recipient.id, note.id).deliver_later
end
@@ -210,19 +210,27 @@ class NotificationService
# Members
def new_access_request(member)
+ return true unless member.notifiable?(:subscription)
+
mailer.member_access_requested_email(member.real_source_type, member.id).deliver_later
end
def decline_access_request(member)
+ return true unless member.notifiable?(:subscription)
+
mailer.member_access_denied_email(member.real_source_type, member.source_id, member.user_id).deliver_later
end
# Project invite
def invite_project_member(project_member, token)
+ return true unless project_member.notifiable?(:subscription)
+
mailer.member_invited_email(project_member.real_source_type, project_member.id, token).deliver_later
end
def accept_project_invite(project_member)
+ return true unless project_member.notifiable?(:subscription)
+
mailer.member_invite_accepted_email(project_member.real_source_type, project_member.id).deliver_later
end
@@ -236,10 +244,14 @@ class NotificationService
end
def new_project_member(project_member)
+ return true unless project_member.notifiable?(:mention, skip_read_ability: true)
+
mailer.member_access_granted_email(project_member.real_source_type, project_member.id).deliver_later
end
def update_project_member(project_member)
+ return true unless project_member.notifiable?(:mention)
+
mailer.member_access_granted_email(project_member.real_source_type, project_member.id).deliver_later
end
@@ -253,6 +265,9 @@ class NotificationService
end
def decline_group_invite(group_member)
+ # always send this one, since it's a response to the user's own
+ # action
+
mailer.member_invite_declined_email(
group_member.real_source_type,
group_member.group.id,
@@ -262,16 +277,19 @@ class NotificationService
end
def new_group_member(group_member)
+ return true unless group_member.notifiable?(:mention)
+
mailer.member_access_granted_email(group_member.real_source_type, group_member.id).deliver_later
end
def update_group_member(group_member)
+ return true unless group_member.notifiable?(:mention)
+
mailer.member_access_granted_email(group_member.real_source_type, group_member.id).deliver_later
end
def project_was_moved(project, old_path_with_namespace)
- recipients = project.team.members
- recipients = NotificationRecipientService.new(project).reject_muted_users(recipients)
+ recipients = notifiable_users(project.team.members, :mention, project: project)
recipients.each do |recipient|
mailer.project_was_moved_email(
@@ -283,7 +301,7 @@ class NotificationService
end
def issue_moved(issue, new_issue, current_user)
- recipients = NotificationRecipientService.new(issue.project).build_recipients(issue, current_user, action: 'moved')
+ recipients = NotificationRecipientService.build_recipients(issue, current_user, action: 'moved')
recipients.map do |recipient|
email = mailer.issue_moved_email(recipient, issue, new_issue, current_user)
@@ -293,10 +311,14 @@ class NotificationService
end
def project_exported(project, current_user)
+ return true unless notifiable?(current_user, :mention, project: project)
+
mailer.project_was_exported_email(current_user, project).deliver_later
end
def project_not_exported(project, current_user, errors)
+ return true unless notifiable?(current_user, :mention, project: project)
+
mailer.project_was_not_exported_email(current_user, project, errors).deliver_later
end
@@ -305,10 +327,10 @@ class NotificationService
return unless mailer.respond_to?(email_template)
- recipients ||= NotificationRecipientService.new(pipeline.project).build_pipeline_recipients(
- pipeline,
- pipeline.user,
- action: pipeline.status
+ recipients ||= notifiable_users(
+ [pipeline.user], :watch,
+ custom_action: :"#{pipeline.status}_pipeline",
+ target: pipeline
).map(&:notification_email)
if recipients.any?
@@ -318,16 +340,16 @@ class NotificationService
protected
- def new_resource_email(target, project, method)
- recipients = NotificationRecipientService.new(project).build_recipients(target, target.author, action: "new")
+ def new_resource_email(target, method)
+ recipients = NotificationRecipientService.build_recipients(target, target.author, action: "new")
recipients.each do |recipient|
mailer.send(method, recipient.id, target.id).deliver_later
end
end
- def new_mentions_in_resource_email(target, project, new_mentioned_users, current_user, method)
- recipients = NotificationRecipientService.new(project).build_recipients(target, current_user, action: "new")
+ def new_mentions_in_resource_email(target, new_mentioned_users, current_user, method)
+ recipients = NotificationRecipientService.build_recipients(target, current_user, action: "new")
recipients = recipients & new_mentioned_users
recipients.each do |recipient|
@@ -335,10 +357,10 @@ class NotificationService
end
end
- def close_resource_email(target, project, current_user, method, skip_current_user: true)
+ def close_resource_email(target, current_user, method, skip_current_user: true)
action = method == :merged_merge_request_email ? "merge" : "close"
- recipients = NotificationRecipientService.new(project).build_recipients(
+ recipients = NotificationRecipientService.build_recipients(
target,
current_user,
action: action,
@@ -350,11 +372,11 @@ class NotificationService
end
end
- def reassign_resource_email(target, project, current_user, method)
+ def reassign_resource_email(target, current_user, method)
previous_assignee_id = previous_record(target, 'assignee_id')
previous_assignee = User.find_by(id: previous_assignee_id) if previous_assignee_id
- recipients = NotificationRecipientService.new(project).build_recipients(
+ recipients = NotificationRecipientService.build_recipients(
target,
current_user,
action: "reassign",
@@ -372,8 +394,14 @@ class NotificationService
end
end
- def relabeled_resource_email(target, project, labels, current_user, method)
- recipients = NotificationRecipientService.new(project).build_relabeled_recipients(target, current_user, labels: labels)
+ def relabeled_resource_email(target, labels, current_user, method)
+ recipients = labels.flat_map { |l| l.subscribers(target.project) }
+ recipients = notifiable_users(
+ recipients, :subscription,
+ target: target,
+ acting_user: current_user
+ )
+
label_names = labels.map(&:name)
recipients.each do |recipient|
@@ -381,8 +409,8 @@ class NotificationService
end
end
- def reopen_resource_email(target, project, current_user, method, status)
- recipients = NotificationRecipientService.new(project).build_recipients(target, current_user, action: "reopen")
+ def reopen_resource_email(target, current_user, method, status)
+ recipients = NotificationRecipientService.build_recipients(target, current_user, action: "reopen")
recipients.each do |recipient|
mailer.send(method, recipient.id, target.id, status, current_user.id).deliver_later
@@ -400,4 +428,14 @@ class NotificationService
object.previous_changes[attribute].first
end
end
+
+ private
+
+ def notifiable?(*args)
+ NotificationRecipientService.notifiable?(*args)
+ end
+
+ def notifiable_users(*args)
+ NotificationRecipientService.notifiable_users(*args)
+ end
end
diff --git a/app/services/projects/autocomplete_service.rb b/app/services/projects/autocomplete_service.rb
index fc85f398935..724a77c873a 100644
--- a/app/services/projects/autocomplete_service.rb
+++ b/app/services/projects/autocomplete_service.rb
@@ -5,7 +5,15 @@ module Projects
end
def milestones
- @project.milestones.active.reorder(due_date: :asc, title: :asc).select([:iid, :title])
+ finder_params = {
+ project_ids: [@project.id],
+ state: :active,
+ order: { due_date: :asc, title: :asc }
+ }
+
+ finder_params[:group_ids] = [@project.group.id] if @project.group
+
+ MilestonesFinder.new(finder_params).execute.select([:iid, :title])
end
def merge_requests
diff --git a/app/services/projects/create_from_template_service.rb b/app/services/projects/create_from_template_service.rb
new file mode 100644
index 00000000000..87d9ed7a0e6
--- /dev/null
+++ b/app/services/projects/create_from_template_service.rb
@@ -0,0 +1,15 @@
+module Projects
+ class CreateFromTemplateService < BaseService
+ def initialize(user, params)
+ @current_user, @params = user, params.dup
+ end
+
+ def execute
+ params[:file] = Gitlab::ProjectTemplate.find(params[:template_name]).file
+
+ GitlabProjectsImportService.new(@current_user, @params).execute
+ ensure
+ params[:file]&.close
+ end
+ end
+end
diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb
index e874a2d8789..48578b6d9e5 100644
--- a/app/services/projects/create_service.rb
+++ b/app/services/projects/create_service.rb
@@ -5,6 +5,10 @@ module Projects
end
def execute
+ if @params[:template_name]&.present?
+ return ::Projects::CreateFromTemplateService.new(current_user, params).execute
+ end
+
forked_from_project_id = params.delete(:forked_from_project_id)
import_data = params.delete(:import_data)
@skip_wiki = params.delete(:skip_wiki)
diff --git a/app/services/projects/gitlab_projects_import_service.rb b/app/services/projects/gitlab_projects_import_service.rb
new file mode 100644
index 00000000000..4ca6414b73b
--- /dev/null
+++ b/app/services/projects/gitlab_projects_import_service.rb
@@ -0,0 +1,36 @@
+# This service is an adapter used to for the GitLab Import feature, and
+# creating a project from a template.
+# The latter will under the hood just import an archive supplied by GitLab.
+module Projects
+ class GitlabProjectsImportService
+ attr_reader :current_user, :params
+
+ def initialize(user, params)
+ @current_user, @params = user, params.dup
+ end
+
+ def execute
+ FileUtils.mkdir_p(File.dirname(import_upload_path))
+ FileUtils.copy_entry(file.path, import_upload_path)
+
+ Gitlab::ImportExport::ProjectCreator.new(params[:namespace_id],
+ current_user,
+ import_upload_path,
+ params[:path]).execute
+ end
+
+ private
+
+ def import_upload_path
+ @import_upload_path ||= Gitlab::ImportExport.import_upload_path(filename: tmp_filename)
+ end
+
+ def tmp_filename
+ "#{SecureRandom.hex}_#{params[:path]}"
+ end
+
+ def file
+ params[:file]
+ end
+ end
+end
diff --git a/app/services/projects/import_service.rb b/app/services/projects/import_service.rb
index 50ec3651515..c3bf0031409 100644
--- a/app/services/projects/import_service.rb
+++ b/app/services/projects/import_service.rb
@@ -34,8 +34,12 @@ module Projects
def import_repository
raise Error, 'Blocked import URL.' if Gitlab::UrlBlocker.blocked_url?(project.import_url)
+ # We should return early for a GitHub import because the new GitHub
+ # importer fetch the project repositories for us.
+ return if project.github_import?
+
begin
- if project.github_import? || project.gitea_import?
+ if project.gitea_import?
fetch_repository
else
clone_repository
@@ -55,7 +59,7 @@ module Projects
end
def fetch_repository
- project.create_repository
+ project.ensure_repository
project.repository.add_remote(project.import_type, project.import_url)
project.repository.set_remote_as_mirror(project.import_type)
project.repository.fetch_remote(project.import_type, forced: true)
diff --git a/app/services/projects/update_pages_service.rb b/app/services/projects/update_pages_service.rb
index 749a1cc56d8..394b336a638 100644
--- a/app/services/projects/update_pages_service.rb
+++ b/app/services/projects/update_pages_service.rb
@@ -33,8 +33,10 @@ module Projects
success
end
rescue => e
+ register_failure
error(e.message)
ensure
+ register_attempt
build.erase_artifacts! unless build.has_expiring_artifacts?
end
@@ -168,5 +170,21 @@ module Projects
def sha
build.sha
end
+
+ def register_attempt
+ pages_deployments_total_counter.increment
+ end
+
+ def register_failure
+ pages_deployments_failed_total_counter.increment
+ end
+
+ def pages_deployments_total_counter
+ @pages_deployments_total_counter ||= Gitlab::Metrics.counter(:pages_deployments_total, "Counter of GitLab Pages deployments triggered")
+ end
+
+ def pages_deployments_failed_total_counter
+ @pages_deployments_failed_total_counter ||= Gitlab::Metrics.counter(:pages_deployments_failed_total, "Counter of GitLab Pages deployments which failed")
+ end
end
end
diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb
index d81035e4eba..cf69007bc3b 100644
--- a/app/services/projects/update_service.rb
+++ b/app/services/projects/update_service.rb
@@ -10,7 +10,7 @@ module Projects
end
if changing_default_branch?
- project.change_head(params[:default_branch])
+ return error("Could not set the default branch") unless project.change_head(params[:default_branch])
end
if project.update_attributes(params.except(:default_branch))
diff --git a/app/services/push_event_payload_service.rb b/app/services/push_event_payload_service.rb
new file mode 100644
index 00000000000..b0a389c85f9
--- /dev/null
+++ b/app/services/push_event_payload_service.rb
@@ -0,0 +1,120 @@
+# Service class for creating push event payloads as stored in the
+# "push_event_payloads" table.
+#
+# Example:
+#
+# data = Gitlab::DataBuilder::Push.build(...)
+# event = Event.create(...)
+#
+# PushEventPayloadService.new(event, data).execute
+class PushEventPayloadService
+ # event - The event this push payload belongs to.
+ # push_data - A Hash produced by `Gitlab::DataBuilder::Push.build` to use for
+ # building the push payload.
+ def initialize(event, push_data)
+ @event = event
+ @push_data = push_data
+ end
+
+ # Creates and returns a new PushEventPayload row.
+ #
+ # This method will raise upon encountering validation errors.
+ #
+ # Returns an instance of PushEventPayload.
+ def execute
+ @event.build_push_event_payload(
+ commit_count: commit_count,
+ action: action,
+ ref_type: ref_type,
+ commit_from: commit_from_id,
+ commit_to: commit_to_id,
+ ref: trimmed_ref,
+ commit_title: commit_title,
+ event_id: @event.id
+ )
+
+ @event.push_event_payload.save!
+ @event.push_event_payload
+ end
+
+ # Returns the commit title to use.
+ #
+ # The commit title is limited to the first line and a maximum of 70
+ # characters.
+ def commit_title
+ commit = @push_data.fetch(:commits).last
+
+ return nil unless commit && commit[:message]
+
+ raw_msg = commit[:message]
+
+ # Find where the first line ends, without turning the entire message into an
+ # Array of lines (this is a waste of memory for large commit messages).
+ index = raw_msg.index("\n")
+ message = index ? raw_msg[0..index] : raw_msg
+
+ message.strip.truncate(70)
+ end
+
+ def commit_from_id
+ if create?
+ nil
+ else
+ revision_before
+ end
+ end
+
+ def commit_to_id
+ if remove?
+ nil
+ else
+ revision_after
+ end
+ end
+
+ def commit_count
+ @push_data.fetch(:total_commits_count)
+ end
+
+ def ref
+ @push_data.fetch(:ref)
+ end
+
+ def revision_before
+ @push_data.fetch(:before)
+ end
+
+ def revision_after
+ @push_data.fetch(:after)
+ end
+
+ def trimmed_ref
+ Gitlab::Git.ref_name(ref)
+ end
+
+ def create?
+ Gitlab::Git.blank_ref?(revision_before)
+ end
+
+ def remove?
+ Gitlab::Git.blank_ref?(revision_after)
+ end
+
+ def action
+ if create?
+ :created
+ elsif remove?
+ :removed
+ else
+ :pushed
+ end
+ end
+
+ def ref_type
+ if Gitlab::Git.tag_ref?(ref)
+ :tag
+ else
+ :branch
+ end
+ end
+end
diff --git a/app/services/quick_actions/interpret_service.rb b/app/services/quick_actions/interpret_service.rb
index c22bf7498bb..c7832c47e1a 100644
--- a/app/services/quick_actions/interpret_service.rb
+++ b/app/services/quick_actions/interpret_service.rb
@@ -511,7 +511,12 @@ module QuickActions
users = extract_references(params, :user)
if users.empty?
- users = User.where(username: params.split(' ').map(&:strip))
+ users =
+ if params == 'me'
+ [current_user]
+ else
+ User.where(username: params.split(' ').map(&:strip))
+ end
end
users
diff --git a/app/services/submit_usage_ping_service.rb b/app/services/submit_usage_ping_service.rb
index 17857ca62f2..14171bce782 100644
--- a/app/services/submit_usage_ping_service.rb
+++ b/app/services/submit_usage_ping_service.rb
@@ -1,6 +1,16 @@
class SubmitUsagePingService
URL = 'https://version.gitlab.com/usage_data'.freeze
+ METRICS = %w[leader_issues instance_issues percentage_issues leader_notes instance_notes
+ percentage_notes leader_milestones instance_milestones percentage_milestones
+ leader_boards instance_boards percentage_boards leader_merge_requests
+ instance_merge_requests percentage_merge_requests leader_ci_pipelines
+ instance_ci_pipelines percentage_ci_pipelines leader_environments instance_environments
+ percentage_environments leader_deployments instance_deployments percentage_deployments
+ leader_projects_prometheus_active instance_projects_prometheus_active
+ percentage_projects_prometheus_active leader_service_desk_issues instance_service_desk_issues
+ percentage_service_desk_issues].freeze
+
include Gitlab::CurrentSettings
def execute
@@ -27,15 +37,7 @@ class SubmitUsagePingService
return unless response['conv_index'].present?
ConversationalDevelopmentIndex::Metric.create!(
- response['conv_index'].slice(
- 'leader_issues', 'instance_issues', 'leader_notes', 'instance_notes',
- 'leader_milestones', 'instance_milestones', 'leader_boards', 'instance_boards',
- 'leader_merge_requests', 'instance_merge_requests', 'leader_ci_pipelines',
- 'instance_ci_pipelines', 'leader_environments', 'instance_environments',
- 'leader_deployments', 'instance_deployments', 'leader_projects_prometheus_active',
- 'instance_projects_prometheus_active', 'leader_service_desk_issues',
- 'instance_service_desk_issues'
- )
+ response['conv_index'].slice(*METRICS)
)
end
end
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index 2dbee9c246e..1763f64a4e4 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -142,7 +142,8 @@ module SystemNoteService
#
# Returns the created Note object
def change_milestone(noteable, project, author, milestone)
- body = milestone.nil? ? 'removed milestone' : "changed milestone to #{milestone.to_reference(project)}"
+ format = milestone&.is_group_milestone? ? :name : :iid
+ body = milestone.nil? ? 'removed milestone' : "changed milestone to #{milestone.to_reference(project, format: format)}"
create_note(NoteSummary.new(noteable, project, author, body, action: 'milestone'))
end
diff --git a/app/services/todo_service.rb b/app/services/todo_service.rb
index 322c6286365..6ee96d6a0f8 100644
--- a/app/services/todo_service.rb
+++ b/app/services/todo_service.rb
@@ -170,20 +170,22 @@ class TodoService
# When user marks some todos as done
def mark_todos_as_done(todos, current_user)
- update_todos_state_by_ids(todos.select(&:id), current_user, :done)
+ update_todos_state(todos, current_user, :done)
end
def mark_todos_as_done_by_ids(ids, current_user)
- update_todos_state_by_ids(ids, current_user, :done)
+ todos = todos_by_ids(ids, current_user)
+ mark_todos_as_done(todos, current_user)
end
# When user marks some todos as pending
def mark_todos_as_pending(todos, current_user)
- update_todos_state_by_ids(todos.select(&:id), current_user, :pending)
+ update_todos_state(todos, current_user, :pending)
end
def mark_todos_as_pending_by_ids(ids, current_user)
- update_todos_state_by_ids(ids, current_user, :pending)
+ todos = todos_by_ids(ids, current_user)
+ mark_todos_as_pending(todos, current_user)
end
# When user marks an issue as todo
@@ -198,9 +200,11 @@ class TodoService
private
- def update_todos_state_by_ids(ids, current_user, state)
- todos = current_user.todos.where(id: ids)
+ def todos_by_ids(ids, current_user)
+ current_user.todos.where(id: Array(ids))
+ end
+ def update_todos_state(todos, current_user, state)
# Only update those that are not really on that state
todos = todos.where.not(state: state)
todos_ids = todos.pluck(:id)
diff --git a/app/services/wiki_pages/update_service.rb b/app/services/wiki_pages/update_service.rb
index c628e6781af..93cbd9a509f 100644
--- a/app/services/wiki_pages/update_service.rb
+++ b/app/services/wiki_pages/update_service.rb
@@ -1,7 +1,7 @@
module WikiPages
class UpdateService < WikiPages::BaseService
def execute(page)
- if page.update(@params[:content], format: @params[:format], message: @params[:message], last_commit_sha: @params[:last_commit_sha])
+ if page.update(@params)
execute_hooks(page, 'update')
end
diff --git a/app/uploaders/personal_file_uploader.rb b/app/uploaders/personal_file_uploader.rb
index ef70871624b..3298ad104ec 100644
--- a/app/uploaders/personal_file_uploader.rb
+++ b/app/uploaders/personal_file_uploader.rb
@@ -4,7 +4,7 @@ class PersonalFileUploader < FileUploader
end
def self.base_dir
- File.join(root_dir, 'system')
+ File.join(root_dir, '-', 'system')
end
private
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index 8bb2a563990..8bf6556079b 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -48,6 +48,12 @@
= select(:application_setting, :enabled_git_access_protocol, [['Both SSH and HTTP(S)', nil], ['Only SSH', 'ssh'], ['Only HTTP(S)', 'http']], {}, class: 'form-control')
%span.help-block#clone-protocol-help
Allow only the selected protocols to be used for Git access.
+ .form-group
+ .col-sm-offset-2.col-sm-10
+ .checkbox
+ = f.label :project_export_enabled do
+ = f.check_box :project_export_enabled
+ Project export enabled
%fieldset
%legend Account and Limit Settings
@@ -322,7 +328,7 @@
\. This setting requires a
= link_to 'restart', help_page_path('administration/restart_gitlab')
to take effect.
- = link_to icon('question-circle'), help_page_path('administration/monitoring/performance/introduction')
+ = link_to icon('question-circle'), help_page_path('administration/monitoring/prometheus/index')
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
diff --git a/app/views/admin/health_check/_failing_storages.html.haml b/app/views/admin/health_check/_failing_storages.html.haml
new file mode 100644
index 00000000000..6830201538d
--- /dev/null
+++ b/app/views/admin/health_check/_failing_storages.html.haml
@@ -0,0 +1,15 @@
+- if failing_storages.any?
+ = _('There are problems accessing Git storage: ')
+ %ul
+ - failing_storages.each do |storage_health|
+ %li
+ = failing_storage_health_message(storage_health)
+ %ul
+ - storage_health.failing_circuit_breakers.each do |circuit_breaker|
+ %li
+ #{circuit_breaker.hostname}: #{message_for_circuit_breaker(circuit_breaker)}
+
+ = _("Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again.")
+ .prepend-top-10
+ = button_to _("Reset git storage health information"), reset_storage_health_admin_health_check_path,
+ method: :post, class: 'btn btn-default'
diff --git a/app/views/admin/health_check/show.html.haml b/app/views/admin/health_check/show.html.haml
index f16f59623f7..517db50b97f 100644
--- a/app/views/admin/health_check/show.html.haml
+++ b/app/views/admin/health_check/show.html.haml
@@ -1,22 +1,22 @@
- @no_container = true
-- page_title "Health Check"
+- page_title _('Health Check')
+- no_errors = @errors.blank? && @failing_storage_statuses.blank?
= render 'admin/monitoring/head'
%div{ class: container_class }
- %h3.page-title
- Health Check
+ %h3.page-title= page_title
.bs-callout.clearfix
.pull-left
%p
- Access token is
+ #{ s_('HealthCheck|Access token is') }
%code#health-check-token= current_application_settings.health_check_access_token
.prepend-top-10
- = button_to "Reset health check access token", reset_health_check_token_admin_application_settings_path,
+ = button_to _("Reset health check access token"), reset_health_check_token_admin_application_settings_path,
method: :put, class: 'btn btn-default',
- data: { confirm: 'Are you sure you want to reset the health check token?' }
+ data: { confirm: _('Are you sure you want to reset the health check token?') }
%p.light
- Health information can be retrieved from the following endpoints. More information is available
- = link_to 'here', help_page_path('user/admin_area/monitoring/health_check')
+ #{ _('Health information can be retrieved from the following endpoints. More information is available') }
+ = link_to s_('More information is available|here'), help_page_path('user/admin_area/monitoring/health_check')
%ul
%li
%code= readiness_url(token: current_application_settings.health_check_access_token)
@@ -29,14 +29,15 @@
.panel.panel-default
.panel-heading
Current Status:
- - if @errors.blank?
+ - if no_errors
= icon('circle', class: 'cgreen')
- Healthy
+ #{ s_('HealthCheck|Healthy') }
- else
= icon('warning', class: 'cred')
- Unhealthy
+ #{ s_('HealthCheck|Unhealthy') }
.panel-body
- - if @errors.blank?
- No Health Problems Detected
+ - if no_errors
+ #{ s_('HealthCheck|No Health Problems Detected') }
- else
= @errors
+ = render partial: 'failing_storages', object: @failing_storage_statuses
diff --git a/app/views/ci/lints/show.html.haml b/app/views/ci/lints/show.html.haml
index dfbc7772698..e6408f35201 100644
--- a/app/views/ci/lints/show.html.haml
+++ b/app/views/ci/lints/show.html.haml
@@ -1,6 +1,6 @@
- page_title "CI Lint"
- page_description "Validate your GitLab CI configuration file"
-- content_for :page_specific_javascripts do
+- content_for :library_javascripts do
= page_specific_javascript_tag('lib/ace.js')
%h2 Check your .gitlab-ci.yml
diff --git a/app/views/dashboard/projects/_blank_state_admin_welcome.html.haml b/app/views/dashboard/projects/_blank_state_admin_welcome.html.haml
index 209afd4aab4..57544559824 100644
--- a/app/views/dashboard/projects/_blank_state_admin_welcome.html.haml
+++ b/app/views/dashboard/projects/_blank_state_admin_welcome.html.haml
@@ -28,6 +28,6 @@
%h3.blank-state-title
Create a group
%p.blank-state-text
- Groups are a great way to organise projects and people.
+ Groups are a great way to organize projects and people.
= link_to new_group_path, class: "btn btn-new" do
New group
diff --git a/app/views/dashboard/projects/index.html.haml b/app/views/dashboard/projects/index.html.haml
index ec6cb1a9624..c546252455a 100644
--- a/app/views/dashboard/projects/index.html.haml
+++ b/app/views/dashboard/projects/index.html.haml
@@ -13,10 +13,8 @@
- if show_callout?('user_callout_dismissed')
= render 'shared/user_callout'
- - if @projects.any? || params[:name]
+ - if has_projects_or_name?(@projects, params)
= render 'dashboard/projects_head'
-
- - if @projects.any? || params[:name]
= render 'projects'
- else
= render "zero_authorized_projects"
diff --git a/app/views/dashboard/projects/starred.html.haml b/app/views/dashboard/projects/starred.html.haml
index ae1d733a516..14f9f8cd70a 100644
--- a/app/views/dashboard/projects/starred.html.haml
+++ b/app/views/dashboard/projects/starred.html.haml
@@ -9,7 +9,7 @@
%div{ class: container_class }
= render 'dashboard/projects_head'
- - if @projects.any? || params[:filter_projects]
+ - if params[:filter_projects] || any_projects?(@projects)
= render 'projects'
- else
%h3 You don't have starred projects yet
diff --git a/app/views/events/_commit.html.haml b/app/views/events/_commit.html.haml
index ad434a64556..98cdcca3ecc 100644
--- a/app/views/events/_commit.html.haml
+++ b/app/views/events/_commit.html.haml
@@ -1,5 +1,5 @@
%li.commit
.commit-row-title
- = link_to truncate_sha(commit[:id]), project_commit_path(project, commit[:id]), class: "commit-sha", alt: '', title: truncate_sha(commit[:id])
+ = link_to truncate_sha(event.commit_id), project_commit_path(project, event.commit_id), class: "commit-sha", alt: '', title: truncate_sha(event.commit_id)
&middot;
- = markdown event_commit_title(commit[:message]), project: project, pipeline: :single_line, author: event.author
+ = markdown event_commit_title(event.commit_title), project: project, pipeline: :single_line, author: event.author
diff --git a/app/views/events/_event_push.atom.haml b/app/views/events/_event_push.atom.haml
index 9fcacfbbf36..bf655f9d21a 100644
--- a/app/views/events/_event_push.atom.haml
+++ b/app/views/events/_event_push.atom.haml
@@ -1,14 +1,13 @@
%div{ xmlns: "http://www.w3.org/1999/xhtml" }
- - event.commits.first(15).each do |commit|
- %p
- %strong= commit[:author][:name]
- = link_to "(##{truncate_sha(commit[:id])})", project_commit_path(event.project, id: commit[:id])
- %i
- at
- = commit[:timestamp].to_time.to_s(:short)
- %blockquote= markdown(escape_once(commit[:message]), pipeline: :atom, project: event.project, author: event.author)
- - if event.commits_count > 15
+ %p
+ %strong= event.author_name
+ = link_to "(#{truncate_sha(event.commit_id)})", project_commit_path(event.project, event.commit_id)
+ %i
+ at
+ = event.created_at.to_s(:short)
+ %blockquote= markdown(escape_once(event.commit_title), pipeline: :atom, project: event.project, author: event.author)
+ - if event.commits_count > 1
%p
%i
\... and
- = pluralize(event.commits_count - 15, "more commit")
+ = pluralize(event.commits_count - 1, "more commit")
diff --git a/app/views/events/event/_push.html.haml b/app/views/events/event/_push.html.haml
index 54b414cc62a..973c652ad88 100644
--- a/app/views/events/event/_push.html.haml
+++ b/app/views/events/event/_push.html.haml
@@ -14,9 +14,7 @@
- if event.push_with_commits?
.event-body
%ul.well-list.event_commits
- - few_commits = event.commits[0...2]
- - few_commits.each do |commit|
- = render "events/commit", commit: commit, project: project, event: event
+ = render "events/commit", project: project, event: event
- create_mr = event.new_ref? && create_mr_button?(project.default_branch, event.ref_name, project) && event.authored_by?(current_user)
- if event.commits_count > 1
@@ -44,9 +42,6 @@
= link_to create_mr_path(project.default_branch, event.ref_name, project) do
Create Merge Request
- elsif event.rm_ref?
- - repository = project.repository
- - last_commit = repository.commit(event.commit_from)
- - if last_commit
- .event-body
- %ul.well-list.event_commits
- = render "events/commit", commit: last_commit, project: project, event: event
+ .event-body
+ %ul.well-list.event_commits
+ = render "events/commit", project: project, event: event
diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml
index 56e628a2b74..b18b3dd5766 100644
--- a/app/views/help/_shortcuts.html.haml
+++ b/app/views/help/_shortcuts.html.haml
@@ -121,7 +121,7 @@
.key g
.key p
%td
- Go to the project's home page
+ Go to the project's overview page
%tr
%td.shortcut
.key g
diff --git a/app/views/import/fogbugz/new_user_map.html.haml b/app/views/import/fogbugz/new_user_map.html.haml
index c52a515226e..84e0009487f 100644
--- a/app/views/import/fogbugz/new_user_map.html.haml
+++ b/app/views/import/fogbugz/new_user_map.html.haml
@@ -10,7 +10,7 @@
Customize how FogBugz email addresses and usernames are imported into GitLab.
In the next step, you'll be able to select the projects you want to import.
%p
- The user map is a mapping of the FogBugz users that participated on your projects to the way their email address and usernames wil be imported into GitLab. You can change this by populating the table below.
+ The user map is a mapping of the FogBugz users that participated on your projects to the way their email address and usernames will be imported into GitLab. You can change this by populating the table below.
%ul
%li
%strong Default: Map a FogBugz account ID to a full name
diff --git a/app/views/import/gitlab_projects/new.html.haml b/app/views/import/gitlab_projects/new.html.haml
index 767dffb5589..008e8287aa3 100644
--- a/app/views/import/gitlab_projects/new.html.haml
+++ b/app/views/import/gitlab_projects/new.html.haml
@@ -1,25 +1,43 @@
- page_title "GitLab Import"
- header_title "Projects", root_path
+- content_for :page_specific_javascripts do
+ = webpack_bundle_tag 'project_import_gl'
+
%h3.page-title
= icon('gitlab')
Import an exported GitLab project
%hr
-= form_tag import_gitlab_project_path, class: 'form-horizontal', multipart: true do
- %p
- Project will be imported as
- %strong
- #{@namespace.name}/#{@path}
+= form_tag import_gitlab_project_path, class: 'new_project', multipart: true do
+ .row
+ .form-group.col-xs-12.col-sm-6
+ = label_tag :namespace_id, 'Project path', class: 'label-light'
+ .form-group
+ .input-group
+ - if current_user.can_select_namespace?
+ .input-group-addon
+ = root_url
+ = select_tag :namespace_id, namespaces_options(namespace_id_from(params) || :current_user, display_path: true, extra_group: namespace_id_from(params)), class: 'select2 js-select-namespace', tabindex: 1
- %p
- To move or copy an entire GitLab project from another GitLab installation to this one, navigate to the original project's settings page, generate an export file, and upload it here.
- .form-group
- = hidden_field_tag :namespace_id, @namespace.id
- = hidden_field_tag :path, @path
- = label_tag :file, class: 'control-label' do
- %span GitLab project export
- .col-sm-10
- = file_field_tag :file, class: ''
+ - else
+ .input-group-addon.static-namespace
+ #{root_url}#{current_user.username}/
+ = hidden_field_tag :namespace_id, value: current_user.namespace_id
+ .form-group.col-xs-12.col-sm-6.project-path
+ = label_tag :path, 'Project name', class: 'label-light'
+ = text_field_tag :path, nil, placeholder: "my-awesome-project", class: "js-path-name form-control", tabindex: 2, autofocus: true, required: true
- .form-actions
- = submit_tag 'Import project', class: 'btn btn-create'
+ .row
+ .form-group.col-md-12
+ To move or copy an entire GitLab project from another GitLab installation to this one, navigate to the original project's settings page, generate an export file, and upload it here.
+ .row
+ .form-group.col-sm-12
+ = hidden_field_tag :namespace_id, @namespace.id
+ = hidden_field_tag :path, @path
+ = label_tag :file, 'GitLab project export', class: 'label-light'
+ .form-group
+ = file_field_tag :file, class: ''
+ .row
+ .form-actions
+ = submit_tag 'Import project', class: 'btn btn-create'
+ = link_to 'Cancel', new_project_path, class: 'btn btn-cancel'
diff --git a/app/views/kaminari/gitlab/_without_count.html.haml b/app/views/kaminari/gitlab/_without_count.html.haml
new file mode 100644
index 00000000000..250029c4475
--- /dev/null
+++ b/app/views/kaminari/gitlab/_without_count.html.haml
@@ -0,0 +1,8 @@
+.gl-pagination
+ %ul.pagination.clearfix
+ - if previous_path
+ %li.prev
+ = link_to(t('views.pagination.previous'), previous_path, rel: 'prev')
+ - if next_path
+ %li.next
+ = link_to(t('views.pagination.next'), next_path, rel: 'next')
diff --git a/app/views/layouts/_bootlint.haml b/app/views/layouts/_bootlint.haml
index 69280687a9d..d603a74c4e4 100644
--- a/app/views/layouts/_bootlint.haml
+++ b/app/views/layouts/_bootlint.haml
@@ -1,4 +1,5 @@
+-# haml-lint:disable InlineJavaScript
:javascript
- jQuery(document).ready(function() {
- javascript:(function(){var s=document.createElement("script");s.onload=function(){bootlint.showLintReportForCurrentDocument([], {hasProblems: false, problemFree: false});};s.src="https://maxcdn.bootstrapcdn.com/bootlint/latest/bootlint.min.js";document.body.appendChild(s)})();
- });
+ window.onload = function() {
+ var s=document.createElement("script");s.onload=function(){bootlint.showLintReportForCurrentDocument([], {hasProblems: false, problemFree: false});};s.src="https://maxcdn.bootstrapcdn.com/bootlint/latest/bootlint.min.js";document.body.appendChild(s);
+ }
diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml
index 6ad22958df3..3babdae3968 100644
--- a/app/views/layouts/_head.html.haml
+++ b/app/views/layouts/_head.html.haml
@@ -38,6 +38,9 @@
= Gon::Base.render_data
+ - if content_for?(:library_javascripts)
+ = yield :library_javascripts
+
= webpack_bundle_tag "webpack_runtime"
= webpack_bundle_tag "common"
= webpack_bundle_tag "locale"
diff --git a/app/views/layouts/_init_auto_complete.html.haml b/app/views/layouts/_init_auto_complete.html.haml
index 9704c9ec624..fe0ec35d003 100644
--- a/app/views/layouts/_init_auto_complete.html.haml
+++ b/app/views/layouts/_init_auto_complete.html.haml
@@ -4,6 +4,7 @@
- if project
-# haml-lint:disable InlineJavaScript
:javascript
+ gl = window.gl || {};
gl.GfmAutoComplete = gl.GfmAutoComplete || {};
gl.GfmAutoComplete.dataSources = {
members: "#{members_project_autocomplete_sources_path(project, type: noteable_type, type_id: params[:id])}",
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index bc3293fd100..b32cfe158bb 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -44,7 +44,7 @@
= icon('tachometer fw')
%li
= link_to assigned_issues_dashboard_path, title: 'Issues', aria: { label: "Issues" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
- = icon('hashtag fw')
+ = custom_icon('issues')
- issues_count = assigned_issuables_count(:issues)
%span.badge.issues-count{ class: ('hidden' if issues_count.zero?) }
= number_with_delimiter(issues_count)
diff --git a/app/views/layouts/header/_new.html.haml b/app/views/layouts/header/_new.html.haml
index 60940dba475..2c1c23d6ea9 100644
--- a/app/views/layouts/header/_new.html.haml
+++ b/app/views/layouts/header/_new.html.haml
@@ -38,7 +38,7 @@
= icon('tachometer fw')
%li
= link_to assigned_issues_dashboard_path, title: 'Issues', aria: { label: "Issues" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
- = icon('hashtag fw')
+ = custom_icon('issues')
- issues_count = assigned_issuables_count(:issues)
%span.badge.issues-count{ class: ('hidden' if issues_count.zero?) }
= number_with_delimiter(issues_count)
diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml
index 8605380848d..261445ecd2b 100644
--- a/app/views/layouts/nav/_group.html.haml
+++ b/app/views/layouts/nav/_group.html.haml
@@ -25,7 +25,7 @@
%span
Members
- if current_user && can?(current_user, :admin_group, @group)
- = nav_link(path: %w[groups#projects groups#edit]) do
+ = nav_link(path: %w[groups#projects groups#edit ci_cd#show]) do
= link_to edit_group_path(@group), title: 'Settings' do
%span
Settings
diff --git a/app/views/layouts/nav/_new_admin_sidebar.html.haml b/app/views/layouts/nav/_new_admin_sidebar.html.haml
index e2f6e9c1aa2..0b4a9d92bea 100644
--- a/app/views/layouts/nav/_new_admin_sidebar.html.haml
+++ b/app/views/layouts/nav/_new_admin_sidebar.html.haml
@@ -1,12 +1,9 @@
-.nav-sidebar
+.nav-sidebar{ class: ("sidebar-icons-only" if collapsed_sidebar?) }
.context-header
= link_to admin_root_path, title: 'Admin Overview' do
.avatar-container.s40.settings-avatar
= icon('wrench')
.project-title Admin Area
- = button_tag class: 'close-nav-button', type: 'button' do
- %span.sr-only Close sidebar
- = icon ('times')
%ul.sidebar-top-level-items
= nav_link(controller: %w(dashboard admin projects users groups jobs runners cohorts), html_options: {class: 'home'}) do
= link_to admin_root_path, title: 'Overview', class: 'shortcuts-tree' do
@@ -110,7 +107,7 @@
= nav_link(controller: :spam_logs) do
= link_to admin_spam_logs_path, title: "Spam Logs" do
.nav-icon-container
- = custom_icon('mr_bold')
+ = custom_icon('spam_logs')
%span.nav-item-name
Spam Logs
@@ -149,3 +146,5 @@
= custom_icon('settings')
%span.nav-item-name
Settings
+
+ = render 'shared/sidebar_toggle_button'
diff --git a/app/views/layouts/nav/_new_group_sidebar.html.haml b/app/views/layouts/nav/_new_group_sidebar.html.haml
index fdfd7e60732..c7dabbd8237 100644
--- a/app/views/layouts/nav/_new_group_sidebar.html.haml
+++ b/app/views/layouts/nav/_new_group_sidebar.html.haml
@@ -1,20 +1,17 @@
-.nav-sidebar
+.nav-sidebar{ class: ("sidebar-icons-only" if collapsed_sidebar?) }
.context-header
= link_to group_path(@group), title: @group.name do
.avatar-container.s40.group-avatar
= image_tag group_icon(@group), class: "avatar s40 avatar-tile"
.group-title
= @group.name
- = button_tag class: 'close-nav-button', type: 'button' do
- %span.sr-only Close sidebar
- = icon ('times')
%ul.sidebar-top-level-items
= nav_link(path: ['groups#show', 'groups#activity', 'groups#subgroups'], html_options: { class: 'home' }) do
- = link_to group_path(@group), title: 'About group' do
+ = link_to group_path(@group), title: 'Group overview' do
.nav-icon-container
= custom_icon('project')
%span.nav-item-name
- About
+ Overview
%ul.sidebar-sub-level-items
= nav_link(path: ['groups#show', 'groups#subgroups'], html_options: { class: 'home' }) do
@@ -85,6 +82,8 @@
Projects
= nav_link(controller: :ci_cd) do
- = link_to group_settings_ci_cd_path(@group), title: 'Pipelines' do
+ = link_to group_settings_ci_cd_path(@group), title: 'CI / CD' do
%span
- Pipelines
+ CI / CD
+
+ = render 'shared/sidebar_toggle_button'
diff --git a/app/views/layouts/nav/_new_profile_sidebar.html.haml b/app/views/layouts/nav/_new_profile_sidebar.html.haml
index f715d8a63f9..edae009a28e 100644
--- a/app/views/layouts/nav/_new_profile_sidebar.html.haml
+++ b/app/views/layouts/nav/_new_profile_sidebar.html.haml
@@ -1,12 +1,9 @@
-.nav-sidebar
+.nav-sidebar{ class: ("sidebar-icons-only" if collapsed_sidebar?) }
.context-header
= link_to profile_path, title: 'Profile Settings' do
.avatar-container.s40.settings-avatar
= icon('user')
.project-title User Settings
- = button_tag class: 'close-nav-button', type: 'button' do
- %span.sr-only Close sidebar
- = icon ('times')
%ul.sidebar-top-level-items
= nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
= link_to profile_path, title: 'Profile Settings' do
@@ -83,3 +80,5 @@
= custom_icon('authentication_log')
%span.nav-item-name
Authentication log
+
+ = render 'shared/sidebar_toggle_button'
diff --git a/app/views/layouts/nav/_new_project_sidebar.html.haml b/app/views/layouts/nav/_new_project_sidebar.html.haml
index 1a2c9325583..e0477c29ebe 100644
--- a/app/views/layouts/nav/_new_project_sidebar.html.haml
+++ b/app/views/layouts/nav/_new_project_sidebar.html.haml
@@ -1,4 +1,4 @@
-.nav-sidebar
+.nav-sidebar{ class: ("sidebar-icons-only" if collapsed_sidebar?) }
- can_edit = can?(current_user, :admin_project, @project)
.context-header
= link_to project_path(@project), title: @project.name do
@@ -6,16 +6,13 @@
= project_icon(@project, alt: @project.name, class: 'avatar s40 avatar-tile')
.project-title
= @project.name
- = button_tag class: 'close-nav-button', type: 'button' do
- %span.sr-only Close sidebar
- = icon ('times')
%ul.sidebar-top-level-items
= nav_link(path: ['projects#show', 'projects#activity', 'cycle_analytics#show'], html_options: { class: 'home' }) do
- = link_to project_path(@project), title: 'About project', class: 'shortcuts-project' do
+ = link_to project_path(@project), title: 'Project overview', class: 'shortcuts-project' do
.nav-icon-container
= custom_icon('project')
%span.nav-item-name
- About
+ Overview
%ul.sidebar-sub-level-items
= nav_link(path: 'projects#show') do
@@ -76,7 +73,7 @@
= nav_link(controller: %w[projects/registry/repositories]) do
= link_to project_container_registry_index_path(@project), title: 'Container Registry', class: 'shortcuts-container-registry' do
.nav-icon-container
- = custom_icon('mr_bold')
+ = custom_icon('container_registry')
%span.nav-item-name
Registry
@@ -91,34 +88,25 @@
%span.badge.count.issue_counter= number_with_delimiter(IssuesFinder.new(current_user, project_id: @project.id).execute.opened.count)
%ul.sidebar-sub-level-items
- - if project_nav_tab?(:issues) && !current_controller?(:merge_requests)
- = nav_link(controller: :issues) do
- = link_to project_issues_path(@project), title: 'Issues' do
- %span
- List
-
- = nav_link(controller: :boards) do
- = link_to project_boards_path(@project), title: 'Board' do
- %span
- Board
+ = nav_link(controller: :issues) do
+ = link_to project_issues_path(@project), title: 'Issues' do
+ %span
+ List
- - if project_nav_tab?(:merge_requests) && current_controller?(:merge_requests)
- = nav_link(controller: :merge_requests) do
- = link_to project_merge_requests_path(@project), title: 'Merge Requests' do
- %span
- Merge Requests
+ = nav_link(controller: :boards) do
+ = link_to project_boards_path(@project), title: 'Board' do
+ %span
+ Board
- - if project_nav_tab? :labels
- = nav_link(controller: :labels) do
- = link_to project_labels_path(@project), title: 'Labels' do
- %span
- Labels
+ = nav_link(controller: :labels) do
+ = link_to project_labels_path(@project), title: 'Labels' do
+ %span
+ Labels
- - if project_nav_tab? :milestones
- = nav_link(controller: :milestones) do
- = link_to project_milestones_path(@project), title: 'Milestones' do
- %span
- Milestones
+ = nav_link(controller: :milestones) do
+ = link_to project_milestones_path(@project), title: 'Milestones' do
+ %span
+ Milestones
- if project_nav_tab? :merge_requests
= nav_link(controller: @project.issues_enabled? ? :merge_requests : [:merge_requests, :labels, :milestones]) do
@@ -131,11 +119,11 @@
- if project_nav_tab? :pipelines
= nav_link(controller: [:pipelines, :builds, :jobs, :pipeline_schedules, :environments, :artifacts]) do
- = link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-pipelines' do
+ = link_to project_pipelines_path(@project), title: 'CI / CD', class: 'shortcuts-pipelines' do
.nav-icon-container
= custom_icon('pipeline')
%span.nav-item-name
- Pipelines
+ CI / CD
%ul.sidebar-sub-level-items
- if project_nav_tab? :pipelines
@@ -195,7 +183,7 @@
%ul.sidebar-sub-level-items
- can_edit = can?(current_user, :admin_project, @project)
- if can_edit
- = nav_link(controller: :projects) do
+ = nav_link(path: %w[projects#edit]) do
= link_to edit_project_path(@project), title: 'General' do
%span
General
@@ -214,9 +202,9 @@
Repository
- if @project.feature_available?(:builds, current_user)
= nav_link(controller: :ci_cd) do
- = link_to project_settings_ci_cd_path(@project), title: 'Pipelines' do
+ = link_to project_settings_ci_cd_path(@project), title: 'CI / CD' do
%span
- Pipelines
+ CI / CD
- if Gitlab.config.pages.enabled
= nav_link(controller: :pages) do
= link_to project_pages_path(@project), title: 'Pages' do
@@ -228,9 +216,11 @@
= link_to project_settings_members_path(@project), title: 'Members', class: 'shortcuts-tree' do
.nav-icon-container
= custom_icon('members')
- %span
+ %span.nav-item-name
Members
+ = render 'shared/sidebar_toggle_button'
+
-# Shortcut to Project > Activity
%li.hidden
= link_to activity_project_path(@project), title: 'Activity', class: 'shortcuts-project-activity' do
diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml
index 9aed498a8a0..f08dcc0c242 100644
--- a/app/views/profiles/preferences/show.html.haml
+++ b/app/views/profiles/preferences/show.html.haml
@@ -60,9 +60,9 @@
= f.select :dashboard, dashboard_choices, {}, class: 'form-control'
.form-group
= f.label :project_view, class: 'label-light' do
- Project home page content
+ Project overview content
= f.select :project_view, project_view_choices, {}, class: 'form-control'
.help-block
- Choose what content you want to see on a project’s home page
+ Choose what content you want to see on a project’s overview page
.form-group
= f.submit 'Save changes', class: 'btn btn-save'
diff --git a/app/views/projects/_export.html.haml b/app/views/projects/_export.html.haml
new file mode 100644
index 00000000000..623d3bc91c6
--- /dev/null
+++ b/app/views/projects/_export.html.haml
@@ -0,0 +1,41 @@
+- return unless current_application_settings.project_export_enabled?
+
+- project = local_assigns.fetch(:project)
+- expanded = Rails.env.test?
+
+%section.settings
+ .settings-header
+ %h4
+ Export project
+ %button.btn.js-settings-toggle
+ = expanded ? 'Collapse' : 'Expand'
+ %p
+ Export this project with all its related data in order to move your project to a new GitLab instance. Once the export is finished, you can import the file from the "New Project" page.
+ .settings-content.no-animate{ class: ('expanded' if expanded) }
+ .bs-callout.bs-callout-info
+ %p.append-bottom-0
+ %p
+ The following items will be exported:
+ %ul
+ %li Project and wiki repositories
+ %li Project uploads
+ %li Project configuration including web hooks and services
+ %li Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities
+ %p
+ The following items will NOT be exported:
+ %ul
+ %li Job traces and artifacts
+ %li LFS objects
+ %li Container registry images
+ %li CI variables
+ %li Any encrypted tokens
+ %p
+ Once the exported file is ready, you will receive a notification email with a download link.
+ - if project.export_project_path
+ = link_to 'Download export', download_export_project_path(project),
+ rel: 'nofollow', download: '', method: :get, class: "btn btn-default"
+ = link_to 'Generate new export', generate_new_export_project_path(project),
+ method: :post, class: "btn btn-default"
+ - else
+ = link_to 'Export project', export_project_path(project),
+ method: :post, class: "btn btn-default"
diff --git a/app/views/projects/_files.html.haml b/app/views/projects/_files.html.haml
index 426085b3e1c..3a7a99462a6 100644
--- a/app/views/projects/_files.html.haml
+++ b/app/views/projects/_files.html.haml
@@ -1,14 +1,13 @@
- commit = local_assigns.fetch(:commit) { @repository.commit }
- ref = local_assigns.fetch(:ref) { current_ref }
- project = local_assigns.fetch(:project) { @project }
+- content_url = local_assigns.fetch(:content_url) { @tree.readme ? project_blob_path(@project, tree_join(@ref, @tree.readme.path)) : project_tree_path(@project, @ref) }
+
#tree-holder.tree-holder.clearfix
.nav-block
= render 'projects/tree/tree_header', tree: @tree
- - if commit
- .info-well.hidden-xs.project-last-commit.append-bottom-default
- .well-segment
- %ul.blob-commit-info
- = render 'projects/commits/commit', commit: commit, ref: ref, project: project
+ - if !show_new_repo? && commit
+ = render 'shared/commit_well', commit: commit, ref: ref, project: project
- = render 'projects/tree/tree_content', tree: @tree
+ = render 'projects/tree/tree_content', tree: @tree, content_url: content_url
diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml
index d0698285f84..97041b87c48 100644
--- a/app/views/projects/_md_preview.html.haml
+++ b/app/views/projects/_md_preview.html.haml
@@ -1,5 +1,10 @@
- referenced_users = local_assigns.fetch(:referenced_users, nil)
+- if defined?(@issue) && @issue.confidential?
+ .confidential-issue-warning
+ = confidential_icon(@issue)
+ %span This is a confidential issue. Your comment will not be visible to the public.
+
.md-area
.md-header
%ul.nav-links.clearfix
@@ -10,11 +15,6 @@
%a.js-md-preview-button{ href: "#md-preview-holder", tabindex: -1 }
Preview
- - if defined?(@issue) && @issue.confidential?
- %li.confidential-issue-warning
- = icon('warning')
- %span This is a confidential issue. Your comment will not be visible to the public.
-
%li.pull-right
.toolbar-group
= markdown_toolbar_button({ icon: "bold fw", data: { "md-tag" => "**" }, title: "Add bold text" })
diff --git a/app/views/projects/_merge_request_settings.html.haml b/app/views/projects/_merge_request_settings.html.haml
index 818010bc7d3..cc5afa943cf 100644
--- a/app/views/projects/_merge_request_settings.html.haml
+++ b/app/views/projects/_merge_request_settings.html.haml
@@ -1,8 +1,3 @@
- form = local_assigns.fetch(:form)
-%fieldset.features.merge-requests-feature.append-bottom-default
- %hr
- %h5.prepend-top-0
- Merge Requests
-
- = render 'projects/merge_request_merge_settings', form: form
+= render 'projects/merge_request_merge_settings', form: form
diff --git a/app/views/projects/_project_templates.html.haml b/app/views/projects/_project_templates.html.haml
new file mode 100644
index 00000000000..21baf35f2ac
--- /dev/null
+++ b/app/views/projects/_project_templates.html.haml
@@ -0,0 +1,10 @@
+.project-templates-buttons.import-buttons{ data: { toggle: "buttons" } }
+ .btn.blank-option.active
+ %input{ type: "radio", autocomplete: "off", name: "project_templates", id: "blank", checked: "true" }
+ = icon('file-o', class: 'btn-template-icon')
+ Blank
+ - Gitlab::ProjectTemplate.all.each do |template|
+ .btn
+ %input{ type: "radio", autocomplete: "off", name: "project_templates", id: template.name }
+ = custom_icon(template.logo)
+ = template.title
diff --git a/app/views/projects/artifacts/file.html.haml b/app/views/projects/artifacts/file.html.haml
index 18e86ac5a92..b85bbcb980e 100644
--- a/app/views/projects/artifacts/file.html.haml
+++ b/app/views/projects/artifacts/file.html.haml
@@ -3,7 +3,7 @@
= render "projects/jobs/header", show_controls: false
-#tree-holder.tree-holder
+.tree-holder
.nav-block
%ul.breadcrumb.repo-breadcrumb
%li
diff --git a/app/views/projects/blob/_blob.html.haml b/app/views/projects/blob/_blob.html.haml
index 8bd336269ff..849716a679b 100644
--- a/app/views/projects/blob/_blob.html.haml
+++ b/app/views/projects/blob/_blob.html.haml
@@ -9,5 +9,5 @@
#blob-content-holder.blob-content-holder
%article.file-holder
- = render "projects/blob/header", blob: blob
+ = render 'projects/blob/header', blob: blob
= render 'projects/blob/content', blob: blob
diff --git a/app/views/projects/blob/_upload.html.haml b/app/views/projects/blob/_upload.html.haml
index 32dbc1b3417..05b7dfe2872 100644
--- a/app/views/projects/blob/_upload.html.haml
+++ b/app/views/projects/blob/_upload.html.haml
@@ -19,7 +19,9 @@
= render 'shared/new_commit_form', placeholder: placeholder
.form-actions
- = button_tag button_title, class: 'btn btn-small btn-create btn-upload-file', id: 'submit-all'
+ = button_tag class: 'btn btn-create btn-upload-file', id: 'submit-all', type: 'button' do
+ = icon('spin spinner', class: 'js-loading-icon hidden' )
+ = button_title
= link_to _("Cancel"), '#', class: "btn btn-cancel", "data-dismiss" => "modal"
- unless can?(current_user, :push_code, @project)
diff --git a/app/views/projects/blob/_viewer.html.haml b/app/views/projects/blob/_viewer.html.haml
index 013f1c267c8..cc85e5de40f 100644
--- a/app/views/projects/blob/_viewer.html.haml
+++ b/app/views/projects/blob/_viewer.html.haml
@@ -17,3 +17,4 @@
- viewer = BlobViewer::Download.new(viewer.blob) if viewer.binary_detected_after_load?
= render viewer.partial_path, viewer: viewer
+
diff --git a/app/views/projects/blob/show.html.haml b/app/views/projects/blob/show.html.haml
index 7dd834e84b5..240e62d5ac5 100644
--- a/app/views/projects/blob/show.html.haml
+++ b/app/views/projects/blob/show.html.haml
@@ -5,16 +5,23 @@
= render "projects/commits/head"
- content_for :page_specific_javascripts do
- = page_specific_javascript_bundle_tag('blob')
+ = webpack_bundle_tag 'blob'
+
+ - if show_new_repo?
+ = webpack_bundle_tag 'common_vue'
+ = webpack_bundle_tag 'repo'
= render 'projects/last_push'
%div{ class: container_class }
- #tree-holder.tree-holder
- = render 'blob', blob: @blob
+ - if show_new_repo?
+ = render 'projects/files', commit: @last_commit, project: @project, ref: @ref, content_url: project_blob_path(@project, @id)
+ - else
+ #tree-holder.tree-holder
+ = render 'blob', blob: @blob
- - if can_modify_blob?(@blob)
- = render 'projects/blob/remove'
+ - if can_modify_blob?(@blob)
+ = render 'projects/blob/remove'
- - title = "Replace #{@blob.name}"
- = render 'projects/blob/upload', title: title, placeholder: title, button_title: 'Replace file', form_path: project_update_blob_path(@project, @id), method: :put
+ - title = "Replace #{@blob.name}"
+ = render 'projects/blob/upload', title: title, placeholder: title, button_title: 'Replace file', form_path: project_update_blob_path(@project, @id), method: :put
diff --git a/app/views/projects/blob/viewers/_balsamiq.html.haml b/app/views/projects/blob/viewers/_balsamiq.html.haml
index 28670e7de97..1e7c461f02e 100644
--- a/app/views/projects/blob/viewers/_balsamiq.html.haml
+++ b/app/views/projects/blob/viewers/_balsamiq.html.haml
@@ -1,4 +1,4 @@
- content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('balsamiq_viewer')
-.file-content.balsamiq-viewer#js-balsamiq-viewer{ data: { endpoint: blob_raw_url } }
+.file-content.balsamiq-viewer#js-balsamiq-viewer{ data: { endpoint: blob_raw_path } }
diff --git a/app/views/projects/blob/viewers/_download.html.haml b/app/views/projects/blob/viewers/_download.html.haml
index 684240d02c7..6d1138f7959 100644
--- a/app/views/projects/blob/viewers/_download.html.haml
+++ b/app/views/projects/blob/viewers/_download.html.haml
@@ -1,6 +1,6 @@
.file-content.blob_file.blob-no-preview
.center
- = link_to blob_raw_url do
+ = link_to blob_raw_path do
%h1.light
= icon('download')
%h4
diff --git a/app/views/projects/blob/viewers/_image.html.haml b/app/views/projects/blob/viewers/_image.html.haml
index 5fd22a59217..26ea028c5d7 100644
--- a/app/views/projects/blob/viewers/_image.html.haml
+++ b/app/views/projects/blob/viewers/_image.html.haml
@@ -1,2 +1,2 @@
.file-content.image_file
- = image_tag(blob_raw_url, alt: viewer.blob.name)
+ = image_tag(blob_raw_path, alt: viewer.blob.name)
diff --git a/app/views/projects/blob/viewers/_notebook.html.haml b/app/views/projects/blob/viewers/_notebook.html.haml
index 2399fb16265..8a41bc53004 100644
--- a/app/views/projects/blob/viewers/_notebook.html.haml
+++ b/app/views/projects/blob/viewers/_notebook.html.haml
@@ -2,4 +2,4 @@
= page_specific_javascript_bundle_tag('common_vue')
= page_specific_javascript_bundle_tag('notebook_viewer')
-.file-content#js-notebook-viewer{ data: { endpoint: blob_raw_url } }
+.file-content#js-notebook-viewer{ data: { endpoint: blob_raw_path } }
diff --git a/app/views/projects/blob/viewers/_pdf.html.haml b/app/views/projects/blob/viewers/_pdf.html.haml
index 1dd179c4fdc..ec2b18bd4ab 100644
--- a/app/views/projects/blob/viewers/_pdf.html.haml
+++ b/app/views/projects/blob/viewers/_pdf.html.haml
@@ -2,4 +2,4 @@
= page_specific_javascript_bundle_tag('common_vue')
= page_specific_javascript_bundle_tag('pdf_viewer')
-.file-content#js-pdf-viewer{ data: { endpoint: blob_raw_url } }
+.file-content#js-pdf-viewer{ data: { endpoint: blob_raw_path } }
diff --git a/app/views/projects/blob/viewers/_sketch.html.haml b/app/views/projects/blob/viewers/_sketch.html.haml
index 49f716c2c59..775e4584f77 100644
--- a/app/views/projects/blob/viewers/_sketch.html.haml
+++ b/app/views/projects/blob/viewers/_sketch.html.haml
@@ -2,6 +2,6 @@
= page_specific_javascript_bundle_tag('common_vue')
= page_specific_javascript_bundle_tag('sketch_viewer')
-.file-content#js-sketch-viewer{ data: { endpoint: blob_raw_url } }
+.file-content#js-sketch-viewer{ data: { endpoint: blob_raw_path } }
.js-loading-icon.text-center.prepend-top-default.append-bottom-default.js-loading-icon{ 'aria-label' => 'Loading Sketch preview' }
= icon('spinner spin 2x', 'aria-hidden' => 'true');
diff --git a/app/views/projects/blob/viewers/_stl.html.haml b/app/views/projects/blob/viewers/_stl.html.haml
index e4e9d746176..6578d826ace 100644
--- a/app/views/projects/blob/viewers/_stl.html.haml
+++ b/app/views/projects/blob/viewers/_stl.html.haml
@@ -2,7 +2,7 @@
= page_specific_javascript_bundle_tag('stl_viewer')
.file-content.is-stl-loading
- .text-center#js-stl-viewer{ data: { endpoint: blob_raw_url } }
+ .text-center#js-stl-viewer{ data: { endpoint: blob_raw_path } }
= icon('spinner spin 2x', class: 'prepend-top-default append-bottom-default', 'aria-hidden' => 'true', 'aria-label' => 'Loading')
.text-center.prepend-top-default.append-bottom-default.stl-controls
.btn-group
diff --git a/app/views/projects/blob/viewers/_video.html.haml b/app/views/projects/blob/viewers/_video.html.haml
index 595a890a27d..36039c08d52 100644
--- a/app/views/projects/blob/viewers/_video.html.haml
+++ b/app/views/projects/blob/viewers/_video.html.haml
@@ -1,2 +1,2 @@
.file-content.video
- %video{ src: blob_raw_url, controls: true, data: { setup: '{}' } }
+ %video{ src: blob_raw_path, controls: true, data: { setup: '{}' } }
diff --git a/app/views/projects/boards/components/_board.html.haml b/app/views/projects/boards/components/_board.html.haml
index 539ee087b14..64f5f6d7ba0 100644
--- a/app/views/projects/boards/components/_board.html.haml
+++ b/app/views/projects/boards/components/_board.html.haml
@@ -6,8 +6,16 @@
%i.fa.fa-fw.board-title-expandable-toggle{ "v-if": "list.isExpandable",
":class": "{ \"fa-caret-down\": list.isExpanded, \"fa-caret-right\": !list.isExpanded && list.position === -1, \"fa-caret-left\": !list.isExpanded && list.position !== -1 }",
"aria-hidden": "true" }
- %span.has-tooltip{ ":title" => '(list.label ? list.label.description : "")',
- data: { container: "body", placement: "bottom" } }
+
+ %span.has-tooltip{ "v-if": "list.type !== \"label\"",
+ ":title" => '(list.label ? list.label.description : "")' }
+ {{ list.title }}
+
+ %span.has-tooltip{ "v-if": "list.type === \"label\"",
+ ":title" => '(list.label ? list.label.description : "")',
+ data: { container: "body", placement: "bottom" },
+ class: "label color-label title",
+ ":style" => "{ backgroundColor: (list.label && list.label.color ? list.label.color : null), color: (list.label && list.label.color ? list.label.text_color : \"#2e2e2e\") }" }
{{ list.title }}
.issue-count-badge.pull-right.clearfix{ "v-if" => 'list.type !== "blank"' }
%span.issue-count-badge-count.pull-left{ ":class" => '{ "has-btn": list.type !== "closed" && !disabled }' }
diff --git a/app/views/projects/cycle_analytics/show.html.haml b/app/views/projects/cycle_analytics/show.html.haml
index c704635ead3..3467e357c49 100644
--- a/app/views/projects/cycle_analytics/show.html.haml
+++ b/app/views/projects/cycle_analytics/show.html.haml
@@ -40,6 +40,9 @@
%i.fa.fa-chevron-down
%ul.dropdown-menu.dropdown-menu-align-right
%li
+ %a{ "href" => "#", "data-value" => "7" }
+ {{ n__('Last %d day', 'Last %d days', 7) }}
+ %li
%a{ "href" => "#", "data-value" => "30" }
{{ n__('Last %d day', 'Last %d days', 30) }}
%li
diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml
index f9385459a66..178ab3df2e5 100644
--- a/app/views/projects/diffs/_diffs.html.haml
+++ b/app/views/projects/diffs/_diffs.html.haml
@@ -2,22 +2,24 @@
- show_whitespace_toggle = local_assigns.fetch(:show_whitespace_toggle, true)
- can_create_note = !@diff_notes_disabled && can?(current_user, :create_note, diffs.project)
- diff_files = diffs.diff_files
+- merge_request = local_assigns.fetch(:merge_request, false)
-.content-block.oneline-block.files-changed
- .inline-parallel-buttons
- - if !diffs_expanded? && diff_files.any? { |diff_file| diff_file.collapsed? }
- = link_to 'Expand all', url_for(params.merge(expanded: 1, format: nil)), class: 'btn btn-default'
- - if show_whitespace_toggle
- - if current_controller?(:commit)
- = commit_diff_whitespace_link(diffs.project, @commit, class: 'hidden-xs')
- - elsif current_controller?('projects/merge_requests/diffs')
- = diff_merge_request_whitespace_link(diffs.project, @merge_request, class: 'hidden-xs')
- - elsif current_controller?(:compare)
- = diff_compare_whitespace_link(diffs.project, params[:from], params[:to], class: 'hidden-xs')
- .btn-group
- = inline_diff_btn
- = parallel_diff_btn
- = render 'projects/diffs/stats', diff_files: diff_files
+.content-block.oneline-block.files-changed.diff-files-changed.js-diff-files-changed{ class: ("diff-files-changed-merge-request" if merge_request) }
+ .files-changed-inner
+ .inline-parallel-buttons
+ - if !diffs_expanded? && diff_files.any? { |diff_file| diff_file.collapsed? }
+ = link_to 'Expand all', url_for(params.merge(expanded: 1, format: nil)), class: 'btn btn-default'
+ - if show_whitespace_toggle
+ - if current_controller?(:commit)
+ = commit_diff_whitespace_link(diffs.project, @commit, class: 'hidden-xs')
+ - elsif current_controller?('projects/merge_requests/diffs')
+ = diff_merge_request_whitespace_link(diffs.project, @merge_request, class: 'hidden-xs')
+ - elsif current_controller?(:compare)
+ = diff_compare_whitespace_link(diffs.project, params[:from], params[:to], class: 'hidden-xs')
+ .btn-group
+ = inline_diff_btn
+ = parallel_diff_btn
+ = render 'projects/diffs/stats', diff_files: diff_files
- if render_overflow_warning?(diff_files)
= render 'projects/diffs/warning', diff_files: diffs
diff --git a/app/views/projects/diffs/_stats.html.haml b/app/views/projects/diffs/_stats.html.haml
index e69c7f20d49..efc0ea31917 100644
--- a/app/views/projects/diffs/_stats.html.haml
+++ b/app/views/projects/diffs/_stats.html.haml
@@ -1,36 +1,34 @@
-.js-toggle-container
- .commit-stat-summary
- Showing
- %button.diff-stats-summary-toggler.js-toggle-button{ type: "button" }
- %strong= pluralize(diff_files.size, "changed file")
+- sum_added_lines = diff_files.sum(&:added_lines)
+- sum_removed_lines = diff_files.sum(&:removed_lines)
+.commit-stat-summary.dropdown
+ Showing
+ %button.diff-stats-summary-toggler.js-diff-stats-dropdown{ type: "button", data: { toggle: "dropdown" } }<
+ = pluralize(diff_files.size, "changed file")
+ = icon("caret-down", class: "prepend-left-5")
+ %span.diff-stats-additions-deletions-expanded#diff-stats
with
- %strong.cgreen #{diff_files.sum(&:added_lines)} additions
+ %strong.cgreen #{sum_added_lines} additions
and
- %strong.cred #{diff_files.sum(&:removed_lines)} deletions
- .file-stats.js-toggle-content.hide
- %ul
- - diff_files.each do |diff_file|
- - file_hash = hexdigest(diff_file.file_path)
- %li
- - if diff_file.deleted_file?
- %span.deleted-file
- %a{ href: "##{file_hash}" }
- %i.fa.fa-minus
- = diff_file.old_path
- - elsif diff_file.renamed_file?
- %span.renamed-file
- %a{ href: "##{file_hash}" }
- %i.fa.fa-minus
- = diff_file.old_path
- &rarr;
- = diff_file.new_path
- - elsif diff_file.new_file?
- %span.new-file
- %a{ href: "##{file_hash}" }
- %i.fa.fa-plus
- = diff_file.new_path
- - else
- %span.edit-file
- %a{ href: "##{file_hash}" }
- %i.fa.fa-adjust
- = diff_file.new_path
+ %strong.cred #{sum_removed_lines} deletions
+ .diff-stats-additions-deletions-collapsed.pull-right{ "aria-hidden": "true", "aria-describedby": "diff-stats" }
+ %strong.cgreen<
+ +#{sum_added_lines}
+ %strong.cred<
+ \-#{sum_removed_lines}
+ .dropdown-menu.diff-file-changes
+ = dropdown_filter("Search files")
+ .dropdown-content
+ %ul
+ - diff_files.each do |diff_file|
+ %li
+ %a{ href: "##{hexdigest(diff_file.file_path)}", title: diff_file.new_path }
+ = icon("#{diff_file_changed_icon(diff_file)} fw", class: "#{diff_file_changed_icon_color(diff_file)} append-right-5")
+ %span.diff-file-changes-path= diff_file.new_path
+ .pull-right
+ %span.cgreen<
+ +#{diff_file.added_lines}
+ %span.cred<
+ \-#{diff_file.removed_lines}
+ %li.dropdown-menu-empty-link.hidden
+ %a{ href: "#" }
+ No files found.
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index 087cb804449..6178abe9160 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -1,12 +1,19 @@
+- page_title "General"
- @content_class = "limit-container-width" unless fluid_layout
+- expanded = Rails.env.test?
= render "projects/settings/head"
+
.project-edit-container
- .row.prepend-top-default
- .col-lg-4.profile-settings-sidebar
- %h4.prepend-top-0
- Project settings
- .col-lg-8
+ %section.settings.general-settings
+ .settings-header
+ %h4
+ General project settings
+ %button.btn.js-settings-toggle
+ = expanded ? 'Collapse' : 'Expand'
+ %p
+ Update your project name, description, avatar, and other general settings.
+ .settings-content.no-animate{ class: ('expanded' if expanded) }
.project-edit-errors
= form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "edit-project" }, authenticity_token: true do |f|
%fieldset
@@ -35,89 +42,7 @@
= f.label :tag_list, "Tags", class: 'label-light'
= f.text_field :tag_list, value: @project.tag_list.sort.join(', '), maxlength: 2000, class: "form-control"
%p.help-block Separate tags with commas.
- %hr
- %fieldset
- %h5.prepend-top-0
- Sharing &amp; Permissions
- .form_group.prepend-top-20.sharing-and-permissions
- .row.js-visibility-select
- .col-md-8
- .label-light
- = label_tag :project_visibility, 'Project Visibility', class: 'label-light', for: :project_visibility_level
- = link_to icon('question-circle'), help_page_path("public_access/public_access")
- %span.help-block
- .col-md-4.visibility-select-container
- = render('projects/visibility_select', model_method: :visibility_level, form: f, selected_level: @project.visibility_level)
- = f.fields_for :project_feature do |feature_fields|
- %fieldset.features
- .row
- .col-md-8.project-feature
- = feature_fields.label :repository_access_level, "Repository", class: 'label-light'
- %span.help-block View and edit files in this project
- .col-md-4.js-repo-access-level
- = project_feature_access_select(:repository_access_level)
-
- .row
- .col-md-8.project-feature.nested
- = feature_fields.label :merge_requests_access_level, "Merge requests", class: 'label-light'
- %span.help-block Submit changes to be merged upstream
- .col-md-4
- = project_feature_access_select(:merge_requests_access_level)
-
- .row
- .col-md-8.project-feature.nested
- = feature_fields.label :builds_access_level, "Pipelines", class: 'label-light'
- %span.help-block Build, test, and deploy your changes
- .col-md-4
- = project_feature_access_select(:builds_access_level)
-
- .row
- .col-md-8.project-feature
- = feature_fields.label :snippets_access_level, "Snippets", class: 'label-light'
- %span.help-block Share code pastes with others out of Git repository
- .col-md-4
- = project_feature_access_select(:snippets_access_level)
-
- .row
- .col-md-8.project-feature
- = feature_fields.label :issues_access_level, "Issues", class: 'label-light'
- %span.help-block Lightweight issue tracking system for this project
- .col-md-4
- = project_feature_access_select(:issues_access_level)
-
- .row
- .col-md-8.project-feature
- = feature_fields.label :wiki_access_level, "Wiki", class: 'label-light'
- %span.help-block Pages for project documentation
- .col-md-4
- = project_feature_access_select(:wiki_access_level)
- .form-group
- = render 'shared/allow_request_access', form: f
- - if Gitlab.config.lfs.enabled && current_user.admin?
- .row.js-lfs-enabled
- .col-md-8
- = f.label :lfs_enabled, 'LFS', class: 'label-light'
- %span.help-block
- Git Large File Storage
- = link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs')
- .col-md-4
- .select-wrapper
- = f.select :lfs_enabled, [%w(Enabled true), %w(Disabled false)], {}, selected: @project.lfs_enabled?, class: 'pull-right form-control project-repo-select select-control', data: { field: 'lfs_enabled' }
- = icon('chevron-down')
- - if Gitlab.config.registry.enabled
- .form-group.js-container-registry{ style: ("display: none;" if @project.project_feature.send(:repository_access_level) == 0) }
- .checkbox
- = f.label :container_registry_enabled do
- = f.check_box :container_registry_enabled
- %strong Container Registry
- %br
- %span.descr Enable Container Registry for this project
- = link_to icon('question-circle'), help_page_path('user/project/container_registry'), target: '_blank'
-
- = render 'merge_request_settings', form: f
-
- %hr
- %fieldset.features.append-bottom-default
+ %fieldset.features
%h5.prepend-top-0
Project avatar
.form-group
@@ -137,162 +62,207 @@
= link_to 'Remove avatar', project_avatar_path(@project), data: { confirm: "Project avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-avatar"
= f.submit 'Save changes', class: "btn btn-save"
- .row.prepend-top-default
- %hr
- .row.prepend-top-default
- .col-lg-4
- %h4.prepend-top-0
- Housekeeping
- %p.append-bottom-0
- %p
- Runs a number of housekeeping tasks within the current repository,
- such as compressing file revisions and removing unreachable objects.
- .col-lg-8
- = link_to 'Housekeeping', housekeeping_project_path(@project),
- method: :post, class: "btn btn-default"
- %hr
- .row.prepend-top-default
- .col-lg-4
- %h4.prepend-top-0
- Export project
- %p.append-bottom-0
- %p
- Export this project with all its related data in order to move your project to a new GitLab instance. Once the export is finished, you can import the file from the "New Project" page.
- %p
- Once the exported file is ready, you will receive a notification email with a download link.
+ %section.settings.sharing-permissions
+ .settings-header
+ %h4
+ Sharing and permissions
+ %button.btn.js-settings-toggle
+ = expanded ? 'Collapse' : 'Expand'
+ %p
+ Enable or disable certain project features and choose access levels.
+ .settings-content.no-animate{ class: ('expanded' if expanded) }
+ = form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "sharing-permissions-form" }, authenticity_token: true do |f|
+ .form_group.sharing-and-permissions
+ .row.js-visibility-select
+ .col-md-8
+ .label-light
+ = label_tag :project_visibility, 'Project Visibility', class: 'label-light', for: :project_visibility_level
+ = link_to icon('question-circle'), help_page_path("public_access/public_access")
+ %span.help-block
+ .col-md-4.visibility-select-container
+ = render('projects/visibility_select', model_method: :visibility_level, form: f, selected_level: @project.visibility_level)
+ = f.fields_for :project_feature do |feature_fields|
+ %fieldset.features
+ .row
+ .col-md-8.project-feature
+ = feature_fields.label :repository_access_level, "Repository", class: 'label-light'
+ %span.help-block View and edit files in this project
+ .col-md-4.js-repo-access-level
+ = project_feature_access_select(:repository_access_level)
- .col-lg-8
+ .row
+ .col-md-8.project-feature.nested
+ = feature_fields.label :merge_requests_access_level, "Merge requests", class: 'label-light'
+ %span.help-block Submit changes to be merged upstream
+ .col-md-4
+ = project_feature_access_select(:merge_requests_access_level)
- - if @project.export_project_path
- = link_to 'Download export', download_export_project_path(@project),
- rel: 'nofollow', download: '', method: :get, class: "btn btn-default"
- = link_to 'Generate new export', generate_new_export_project_path(@project),
- method: :post, class: "btn btn-default"
- - else
- = link_to 'Export project', export_project_path(@project),
- method: :post, class: "btn btn-default"
+ .row
+ .col-md-8.project-feature.nested
+ = feature_fields.label :builds_access_level, "Pipelines", class: 'label-light'
+ %span.help-block Build, test, and deploy your changes
+ .col-md-4
+ = project_feature_access_select(:builds_access_level)
- .bs-callout.bs-callout-info
- %p.append-bottom-0
- %p
- The following items will be exported:
- %ul
- %li Project and wiki repositories
- %li Project uploads
- %li Project configuration including web hooks and services
- %li Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities
- %p
- The following items will NOT be exported:
- %ul
- %li Job traces and artifacts
- %li LFS objects
- %li Container registry images
- %li CI variables
- %li Any encrypted tokens
- - if can? current_user, :archive_project, @project
- %hr
- .row.prepend-top-default
- .col-lg-4
- %h4.warning-title.prepend-top-0
- - if @project.archived?
- Unarchive project
- - else
- Archive project
- %p.append-bottom-0
+ .row
+ .col-md-8.project-feature
+ = feature_fields.label :snippets_access_level, "Snippets", class: 'label-light'
+ %span.help-block Share code pastes with others out of Git repository
+ .col-md-4
+ = project_feature_access_select(:snippets_access_level)
+
+ .row
+ .col-md-8.project-feature
+ = feature_fields.label :issues_access_level, "Issues", class: 'label-light'
+ %span.help-block Lightweight issue tracking system for this project
+ .col-md-4
+ = project_feature_access_select(:issues_access_level)
+
+ .row
+ .col-md-8.project-feature
+ = feature_fields.label :wiki_access_level, "Wiki", class: 'label-light'
+ %span.help-block Pages for project documentation
+ .col-md-4
+ = project_feature_access_select(:wiki_access_level)
+ .form-group
+ = render 'shared/allow_request_access', form: f
+ - if Gitlab.config.lfs.enabled && current_user.admin?
+ .row.js-lfs-enabled.form-group.sharing-and-permissions
+ .col-md-8
+ = f.label :lfs_enabled, 'Git Large File Storage', class: 'label-light'
+ = link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs')
+ %span.help-block Manages large files such as audio, video and graphics files.
+ .col-md-4
+ .select-wrapper
+ = f.select :lfs_enabled, [%w(Enabled true), %w(Disabled false)], {}, selected: @project.lfs_enabled?, class: 'pull-right form-control project-repo-select select-control', data: { field: 'lfs_enabled' }
+ = icon('chevron-down')
+ - if Gitlab.config.registry.enabled
+ .form-group.js-container-registry{ style: ("display: none;" if @project.project_feature.send(:repository_access_level) == 0) }
+ .checkbox
+ = f.label :container_registry_enabled do
+ = f.check_box :container_registry_enabled
+ %strong Container Registry
+ %br
+ %span.descr Enable Container Registry for this project
+ = link_to icon('question-circle'), help_page_path('user/project/container_registry'), target: '_blank'
+ = f.submit 'Save changes', class: "btn btn-save"
+
+
+ %section.settings.merge-requests-feature{ style: ("display: none;" if @project.project_feature.send(:merge_requests_access_level) == 0) }
+ .settings-header
+ %h4
+ Merge request settings
+ %button.btn.js-settings-toggle
+ = expanded ? 'Collapse' : 'Expand'
+ %p
+ Customize your merge request restrictions.
+ .settings-content.no-animate{ class: ('expanded' if expanded) }
+ = form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "merge-request-settings-form" }, authenticity_token: true do |f|
+ = render 'merge_request_settings', form: f
+ = f.submit 'Save changes', class: "btn btn-save"
+
+ = render 'export', project: @project
+
+ %section.settings.advanced-settings
+ .settings-header
+ %h4
+ Advanced settings
+ %button.btn.js-settings-toggle
+ = expanded ? 'Collapse' : 'Expand'
+ %p
+ Perform advanced options such as housekeeping, exporting, archiving, renaming, transferring, or removing your project.
+ .settings-content.no-animate{ class: ('expanded' if expanded) }
+ .sub-section
+ %h4 Housekeeping
+ %p
+ Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects.
+ = link_to 'Run housekeeping', housekeeping_project_path(@project),
+ method: :post, class: "btn btn-default"
+ - if can? current_user, :archive_project, @project
+ .sub-section
+ %h4.warning-title
+ - if @project.archived?
+ Unarchive project
+ - else
+ Archive project
- if @project.archived?
- Unarchiving the project will mark its repository as active. The project can be committed to.
+ %p
+ Unarchiving the project will mark its repository as active. The project can be committed to.
+ %strong Once active this project shows up in the search and on the dashboard.
+ = link_to 'Unarchive project', unarchive_project_path(@project),
+ data: { confirm: "Are you sure that you want to unarchive this project?\nWhen this project is unarchived it is active and can be committed to again." },
+ method: :post, class: "btn btn-success"
- else
- Archiving the project will mark its repository as read-only. It is hidden from the dashboard and doesn't show up in searches.
- .col-lg-8
- - if @project.archived?
- %p
- %strong Once active this project shows up in the search and on the dashboard.
- = link_to 'Unarchive project', unarchive_project_path(@project),
- data: { confirm: "Are you sure that you want to unarchive this project?\nWhen this project is unarchived it is active and can be committed to again." },
- method: :post, class: "btn btn-success"
- - else
- %p
- %strong Archived projects cannot be committed to!
- = link_to 'Archive project', archive_project_path(@project),
- data: { confirm: "Are you sure that you want to archive this project?\nAn archived project cannot be committed to." },
- method: :post, class: "btn btn-warning"
- %hr
- .row.prepend-top-default
- .col-lg-4
- %h4.prepend-top-0.warning-title
- Rename repository
- .col-lg-8
- = render 'projects/errors'
- = form_for([@project.namespace.becomes(Namespace), @project]) do |f|
- .form-group.project_name_holder
- = f.label :name, class: 'label-light' do
- Project name
- .form-group
- = f.text_field :name, class: "form-control"
- .form-group
- = f.label :path, class: 'label-light' do
- %span Path
- .form-group
- .input-group
- .input-group-addon
- #{URI.join(root_url, @project.namespace.full_path)}/
- = f.text_field :path, class: 'form-control'
- %ul
- %li Be careful. Renaming a project's repository can have unintended side effects.
- %li You will need to update your local repositories to point to the new location.
- - if @project.deployment_services.any?
- %li Your deployment services will be broken, you will need to manually fix the services after renaming.
- = f.submit 'Rename project', class: "btn btn-warning"
- - if can?(current_user, :change_namespace, @project)
- %hr
- .row.prepend-top-default
- .col-lg-4
- %h4.prepend-top-0.danger-title
- Transfer project to new group
- %p.append-bottom-0
- Please select the group you want to transfer this project to in the dropdown to the right.
- .col-lg-8
- = form_for([@project.namespace.becomes(Namespace), @project], url: transfer_project_path(@project), method: :put, remote: true, html: { class: 'js-project-transfer-form' } ) do |f|
+ %p
+ Archiving the project will mark its repository as read-only. It is hidden from the dashboard and doesn't show up in searches.
+ %strong Archived projects cannot be committed to!
+ = link_to 'Archive project', archive_project_path(@project),
+ data: { confirm: "Are you sure that you want to archive this project?\nAn archived project cannot be committed to." },
+ method: :post, class: "btn btn-warning"
+ .sub-section.rename-respository
+ %h4.warning-title
+ Rename repository
+ %p
+ Export this project with all its related data in order to move your project to a new GitLab instance. Once the export is finished, you can import the file from the "New Project" page.
+ = render 'projects/errors'
+ = form_for([@project.namespace.becomes(Namespace), @project]) do |f|
+ .form-group.project_name_holder
+ = f.label :name, class: 'label-light' do
+ Project name
+ .form-group
+ = f.text_field :name, class: "form-control"
.form-group
- = label_tag :new_namespace_id, nil, class: 'label-light' do
- %span Select a new namespace
+ = f.label :path, class: 'label-light' do
+ %span Path
.form-group
- = select_tag :new_namespace_id, namespaces_options(nil), include_blank: true, class: 'select2'
+ .input-group
+ .input-group-addon
+ #{URI.join(root_url, @project.namespace.full_path)}/
+ = f.text_field :path, class: 'form-control'
%ul
- %li Be careful. Changing the project's namespace can have unintended side effects.
- %li You can only transfer the project to namespaces you manage.
+ %li Be careful. Renaming a project's repository can have unintended side effects.
%li You will need to update your local repositories to point to the new location.
- %li Project visibility level will be changed to match namespace rules when transfering to a group.
- = f.submit 'Transfer project', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => transfer_project_message(@project) }
- - if @project.forked? && can?(current_user, :remove_fork_project, @project)
- %hr
- .row.prepend-top-default.append-bottom-default
- .col-lg-4
- %h4.prepend-top-0.danger-title
- Remove fork relationship
- %p.append-bottom-0
+ - if @project.deployment_services.any?
+ %li Your deployment services will be broken, you will need to manually fix the services after renaming.
+ = f.submit 'Rename project', class: "btn btn-warning"
+ - if can?(current_user, :change_namespace, @project)
+ .sub-section
+ %h4.danger-title
+ Transfer project
+ = form_for([@project.namespace.becomes(Namespace), @project], url: transfer_project_path(@project), method: :put, remote: true, html: { class: 'js-project-transfer-form' } ) do |f|
+ .form-group
+ = label_tag :new_namespace_id, nil, class: 'label-light' do
+ %span Select a new namespace
+ .form-group
+ = select_tag :new_namespace_id, namespaces_options(nil), include_blank: true, class: 'select2'
+ %ul
+ %li Be careful. Changing the project's namespace can have unintended side effects.
+ %li You can only transfer the project to namespaces you manage.
+ %li You will need to update your local repositories to point to the new location.
+ %li Project visibility level will be changed to match namespace rules when transferring to a group.
+ = f.submit 'Transfer project', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => transfer_project_message(@project) }
+ - if @project.forked? && can?(current_user, :remove_fork_project, @project)
+ .sub-section
+ %h4.danger-title
+ Remove fork relationship
%p
This will remove the fork relationship to source project
= succeed "." do
= link_to @project.forked_from_project.name_with_namespace, project_path(@project.forked_from_project)
- .col-lg-8
- = form_for([@project.namespace.becomes(Namespace), @project], url: remove_fork_project_path(@project), method: :delete, remote: true, html: { class: 'transfer-project' }) do |f|
- %p
- %strong Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source.
- = button_to 'Remove fork relationship', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_fork_project_message(@project) }
- - if can?(current_user, :remove_project, @project)
- %hr
- .row.prepend-top-default.append-bottom-default
- .col-lg-4
- %h4.prepend-top-0.danger-title
- Remove project
- %p.append-bottom-0
- Removing the project will delete its repository and all related resources including issues, merge requests etc.
- .col-lg-8
- = form_tag(project_path(@project), method: :delete) do
+ = form_for([@project.namespace.becomes(Namespace), @project], url: remove_fork_project_path(@project), method: :delete, remote: true, html: { class: 'transfer-project' }) do |f|
+ %p
+ %strong Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source.
+ = button_to 'Remove fork relationship', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_fork_project_message(@project) }
+ - if can?(current_user, :remove_project, @project)
+ .sub-section
+ %h4.danger-title
+ Remove project
%p
- %strong Removed projects cannot be restored!
- = button_to 'Remove project', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_project_message(@project) }
+ Removing the project will delete its repository and all related resources including issues, merge requests etc.
+ = form_tag(project_path(@project), method: :delete) do
+ %p
+ %strong Removed projects cannot be restored!
+ = button_to 'Remove project', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_project_message(@project) }
.save-project-loader.hide
.center
diff --git a/app/views/projects/graphs/charts.html.haml b/app/views/projects/graphs/charts.html.haml
index 228c8c84792..9f5a1239a82 100644
--- a/app/views/projects/graphs/charts.html.haml
+++ b/app/views/projects/graphs/charts.html.haml
@@ -78,8 +78,8 @@
%script#projectChartData{ type: "application/json" }
- projectChartData = {};
- - projectChartData['hour'] = { 'keys' => @commits_per_time.keys, 'values' => @commits_per_time.values }
- - projectChartData['weekDays'] = { 'keys' => @commits_per_week_days.keys, 'values' => @commits_per_week_days.values }
- - projectChartData['month'] = { 'keys' => @commits_per_month.keys, 'values' => @commits_per_month.values }
+ - projectChartData['hour'] = @commits_per_time
+ - projectChartData['weekDays'] = @commits_per_week_days
+ - projectChartData['month'] = @commits_per_month
- projectChartData['languages'] = @languages
= projectChartData.to_json.html_safe
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index a57844f974e..ad5befc6ee5 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -19,7 +19,8 @@
= icon('angle-double-left')
.issuable-meta
- = confidential_icon(@issue)
+ - if @issue.confidential
+ = icon('eye-slash', class: 'is-confidential')
= issuable_meta(@issue, @project, "Issue")
.issuable-actions
diff --git a/app/views/projects/jobs/_sidebar.html.haml b/app/views/projects/jobs/_sidebar.html.haml
index f2db71e8838..f5d5bc7eda9 100644
--- a/app/views/projects/jobs/_sidebar.html.haml
+++ b/app/views/projects/jobs/_sidebar.html.haml
@@ -1,101 +1,101 @@
- builds = @build.pipeline.builds.to_a
%aside.right-sidebar.right-sidebar-expanded.build-sidebar.js-build-sidebar.js-right-sidebar{ data: { "offset-top" => "101", "spy" => "affix" } }
- .blocks-container
- .block
- %strong
- = @build.name
- %a.gutter-toggle.pull-right.visible-xs-block.visible-sm-block.js-sidebar-build-toggle{ href: "#", 'aria-label': 'Toggle Sidebar', role: 'button' }
- = icon('angle-double-right')
-
- #js-details-block-vue
-
- - if can?(current_user, :read_build, @project) && (@build.artifacts? || @build.artifacts_expired?)
+ .sidebar-container
+ .blocks-container
.block
- .title
- Job artifacts
- - if @build.artifacts_expired?
- %p.build-detail-row
- The artifacts were removed
- #{time_ago_with_tooltip(@build.artifacts_expire_at)}
- - elsif @build.has_expiring_artifacts?
- %p.build-detail-row
- The artifacts will be removed in
- %span.js-artifacts-remove= @build.artifacts_expire_at
+ %strong
+ = @build.name
+ %a.gutter-toggle.pull-right.visible-xs-block.visible-sm-block.js-sidebar-build-toggle{ href: "#", 'aria-label': 'Toggle Sidebar', role: 'button' }
+ = icon('angle-double-right')
- - if @build.artifacts?
- .btn-group.btn-group-justified{ role: :group }
- - if @build.has_expiring_artifacts? && can?(current_user, :update_build, @build)
- = link_to keep_project_job_artifacts_path(@project, @build), class: 'btn btn-sm btn-default', method: :post do
- Keep
+ #js-details-block-vue
- = link_to download_project_job_artifacts_path(@project, @build), rel: 'nofollow', download: '', class: 'btn btn-sm btn-default' do
- Download
+ - if can?(current_user, :read_build, @project) && (@build.artifacts? || @build.artifacts_expired?)
+ .block
+ .title
+ Job artifacts
+ - if @build.artifacts_expired?
+ %p.build-detail-row
+ The artifacts were removed
+ #{time_ago_with_tooltip(@build.artifacts_expire_at)}
+ - elsif @build.has_expiring_artifacts?
+ %p.build-detail-row
+ The artifacts will be removed in
+ %span.js-artifacts-remove= @build.artifacts_expire_at
- - if @build.artifacts_metadata?
- = link_to browse_project_job_artifacts_path(@project, @build), class: 'btn btn-sm btn-default' do
- Browse
+ - if @build.artifacts?
+ .btn-group.btn-group-justified{ role: :group }
+ - if @build.has_expiring_artifacts? && can?(current_user, :update_build, @build)
+ = link_to keep_project_job_artifacts_path(@project, @build), class: 'btn btn-sm btn-default', method: :post do
+ Keep
- - if @build.trigger_request
- .build-widget.block
- %h4.title
- Trigger
+ = link_to download_project_job_artifacts_path(@project, @build), rel: 'nofollow', download: '', class: 'btn btn-sm btn-default' do
+ Download
- %p
- %span.build-light-text Token:
- #{@build.trigger_request.trigger.short_token}
+ - if @build.artifacts_metadata?
+ = link_to browse_project_job_artifacts_path(@project, @build), class: 'btn btn-sm btn-default' do
+ Browse
+
+ - if @build.trigger_request
+ .build-widget.block
+ %h4.title
+ Trigger
- - if @build.trigger_request.variables
%p
- %button.btn.group.btn-group-justified.reveal-variables Reveal Variables
+ %span.build-light-text Token:
+ #{@build.trigger_request.trigger.short_token}
+ - if @build.trigger_request.variables
+ %p
+ %button.btn.group.btn-group-justified.reveal-variables Reveal Variables
- - @build.trigger_request.variables.each do |key, value|
- .hide.js-build
- .js-build-variable.trigger-build-variable= key
- .js-build-value.trigger-build-value= value
+ %dl.js-build-variables.trigger-build-variables.hide
+ - @build.trigger_request.variables.each do |key, value|
+ %dt.js-build-variable.trigger-build-variable= key
+ %dd.js-build-value.trigger-build-value= value
- %div{ class: (@build.pipeline.stages_count > 1 ? "block" : "block-last") }
- %p
- Commit
- = link_to @build.pipeline.short_sha, project_commit_path(@project, @build.pipeline.sha), class: 'commit-sha link-commit'
- = clipboard_button(text: @build.pipeline.short_sha, title: "Copy commit SHA to clipboard")
- - if @build.merge_request
- in
- = link_to "#{@build.merge_request.to_reference}", merge_request_path(@build.merge_request), class: 'link-commit'
+ %div{ class: (@build.pipeline.stages_count > 1 ? "block" : "block-last") }
+ %p
+ Commit
+ = link_to @build.pipeline.short_sha, project_commit_path(@project, @build.pipeline.sha), class: 'commit-sha link-commit'
+ = clipboard_button(text: @build.pipeline.short_sha, title: "Copy commit SHA to clipboard")
+ - if @build.merge_request
+ in
+ = link_to "#{@build.merge_request.to_reference}", merge_request_path(@build.merge_request), class: 'link-commit'
- %p.build-light-text.append-bottom-0
- #{@build.pipeline.git_commit_title}
+ %p.build-light-text.append-bottom-0
+ #{@build.pipeline.git_commit_title}
- - if @build.pipeline.stages_count > 1
- .dropdown.build-dropdown
- %div
- %span{ class: "ci-status-icon-#{@build.pipeline.status}" }
- = ci_icon_for_status(@build.pipeline.status)
- Pipeline
- = link_to "##{@build.pipeline.id}", project_pipeline_path(@project, @build.pipeline), class: 'link-commit'
- from
- = link_to "#{@build.pipeline.ref}", project_branch_path(@project, @build.pipeline.ref), class: 'link-commit'
- %button.dropdown-menu-toggle{ type: 'button', 'data-toggle' => 'dropdown' }
- %span.stage-selection More
- = icon('chevron-down')
- %ul.dropdown-menu
- - @build.pipeline.legacy_stages.each do |stage|
- %li
- %a.stage-item= stage.name
+ - if @build.pipeline.stages_count > 1
+ .block-last.dropdown.build-dropdown
+ %div
+ %span{ class: "ci-status-icon-#{@build.pipeline.status}" }
+ = ci_icon_for_status(@build.pipeline.status)
+ Pipeline
+ = link_to "##{@build.pipeline.id}", project_pipeline_path(@project, @build.pipeline), class: 'link-commit'
+ from
+ = link_to "#{@build.pipeline.ref}", project_ref_path(@project, @build.pipeline.ref), class: 'link-commit ref-name'
+ %button.dropdown-menu-toggle{ type: 'button', 'data-toggle' => 'dropdown' }
+ %span.stage-selection More
+ = icon('chevron-down')
+ %ul.dropdown-menu
+ - @build.pipeline.legacy_stages.each do |stage|
+ %li
+ %a.stage-item= stage.name
- .builds-container
- - HasStatus::ORDERED_STATUSES.each do |build_status|
- - builds.select{|build| build.status == build_status}.each do |build|
- .build-job{ class: sidebar_build_class(build, @build), data: { stage: build.stage } }
- = link_to project_job_path(@project, build) do
- = icon('arrow-right')
- %span{ class: "ci-status-icon-#{build.status}" }
- = ci_icon_for_status(build.status)
- %span
- - if build.name
- = build.name
- - else
- = build.id
- - if build.retried?
- %i.fa.fa-refresh.has-tooltip{ data: { container: 'body', placement: 'bottom' }, title: 'Job was retried' }
+ .builds-container
+ - HasStatus::ORDERED_STATUSES.each do |build_status|
+ - builds.select{|build| build.status == build_status}.each do |build|
+ .build-job{ class: sidebar_build_class(build, @build), data: { stage: build.stage } }
+ = link_to project_job_path(@project, build) do
+ = icon('arrow-right')
+ %span{ class: "ci-status-icon-#{build.status}" }
+ = ci_icon_for_status(build.status)
+ %span
+ - if build.name
+ = build.name
+ - else
+ = build.id
+ - if build.retried?
+ %i.fa.fa-refresh.has-tooltip{ data: { container: 'body', placement: 'bottom' }, title: 'Job was retried' }
diff --git a/app/views/projects/merge_requests/diffs/_diffs.html.haml b/app/views/projects/merge_requests/diffs/_diffs.html.haml
index fb31e2fef00..0d30d6da68f 100644
--- a/app/views/projects/merge_requests/diffs/_diffs.html.haml
+++ b/app/views/projects/merge_requests/diffs/_diffs.html.haml
@@ -1,5 +1,5 @@
- if @merge_request_diff.collected? || @merge_request_diff.overflow?
= render 'projects/merge_requests/diffs/versions'
- = render "projects/diffs/diffs", diffs: @diffs, environment: @environment
+ = render "projects/diffs/diffs", diffs: @diffs, environment: @environment, merge_request: true
- elsif @merge_request_diff.empty?
.nothing-here-block Nothing to merge from #{@merge_request.source_branch} into #{@merge_request.target_branch}
diff --git a/app/views/projects/merge_requests/show.html.haml b/app/views/projects/merge_requests/show.html.haml
index ea6cd16c7ad..d27e121beb4 100644
--- a/app/views/projects/merge_requests/show.html.haml
+++ b/app/views/projects/merge_requests/show.html.haml
@@ -17,6 +17,7 @@
-# haml-lint:disable InlineJavaScript
:javascript
+ window.gl = window.gl || {};
window.gl.mrWidgetData = #{serialize_issuable(@merge_request)}
#js-vue-mr-widget.mr-widget
diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml
index 25109f0f414..e3bbebbcf4c 100644
--- a/app/views/projects/new.html.haml
+++ b/app/views/projects/new.html.haml
@@ -17,8 +17,68 @@
- if import_sources_enabled?
%p
Create or Import your project from popular Git services
- .col-lg-9
+ .col-lg-9.js-toggle-container
= form_for @project, html: { class: 'new_project' } do |f|
+ .create-project-options
+ .first-column
+ .project-template
+ .form-group
+ = f.label :template_project, class: 'label-light' do
+ Create from template
+ = link_to icon('question-circle'), help_page_path("public_access/public_access"), aria: { label: "What’s included in a template?" }, title: "What’s included in a template?", class: 'has-tooltip', data: { placement: 'top'}
+ %div
+ = render 'project_templates', f: f
+ .second-column
+ - if import_sources_enabled?
+ .project-import
+ .form-group.clearfix
+ = f.label :visibility_level, class: 'label-light' do #the label here seems wrong
+ Import project from
+ .col-sm-12.import-buttons
+ %div
+ - if github_import_enabled?
+ = link_to new_import_github_path, class: 'btn import_github' do
+ = icon('github', text: 'GitHub')
+ %div
+ - if bitbucket_import_enabled?
+ = link_to status_import_bitbucket_path, class: "btn import_bitbucket #{'how_to_import_link' unless bitbucket_import_configured?}" do
+ = icon('bitbucket', text: 'Bitbucket')
+ - unless bitbucket_import_configured?
+ = render 'bitbucket_import_modal'
+ %div
+ - if gitlab_import_enabled?
+ = link_to status_import_gitlab_path, class: "btn import_gitlab #{'how_to_import_link' unless gitlab_import_configured?}" do
+ = icon('gitlab', text: 'GitLab.com')
+ - unless gitlab_import_configured?
+ = render 'gitlab_import_modal'
+ %div
+ - if google_code_import_enabled?
+ = link_to new_import_google_code_path, class: 'btn import_google_code' do
+ = icon('google', text: 'Google Code')
+ %div
+ - if fogbugz_import_enabled?
+ = link_to new_import_fogbugz_path, class: 'btn import_fogbugz' do
+ = icon('bug', text: 'Fogbugz')
+ %div
+ - if gitea_import_enabled?
+ = link_to new_import_gitea_url, class: 'btn import_gitea' do
+ = custom_icon('go_logo')
+ Gitea
+ %div
+ - if git_import_enabled?
+ %button.btn.js-toggle-button.import_git{ type: "button" }
+ = icon('git', text: 'Repo by URL')
+ .import_gitlab_project.has-tooltip{ data: { container: 'body' } }
+ = link_to new_import_gitlab_project_path, class: 'btn btn_import_gitlab_project project-submit' do
+ = icon('gitlab', text: 'GitLab export')
+
+ .row
+ .col-lg-12
+ .js-toggle-content.hide
+ %hr
+ = render "shared/import_form", f: f
+ %hr
+
.row
.form-group.col-xs-12.col-sm-6
= f.label :namespace_id, class: 'label-light' do
@@ -45,53 +105,6 @@
Want to house several dependent projects under the same namespace?
= link_to "Create a group", new_group_path
- - if import_sources_enabled?
- .project-import.js-toggle-container
- .form-group.clearfix
- = f.label :visibility_level, class: 'label-light' do
- Import project from
- .col-sm-12.import-buttons
- %div
- - if github_import_enabled?
- = link_to new_import_github_path, class: 'btn import_github' do
- = icon('github', text: 'GitHub')
- %div
- - if bitbucket_import_enabled?
- = link_to status_import_bitbucket_path, class: "btn import_bitbucket #{'how_to_import_link' unless bitbucket_import_configured?}" do
- = icon('bitbucket', text: 'Bitbucket')
- - unless bitbucket_import_configured?
- = render 'bitbucket_import_modal'
- %div
- - if gitlab_import_enabled?
- = link_to status_import_gitlab_path, class: "btn import_gitlab #{'how_to_import_link' unless gitlab_import_configured?}" do
- = icon('gitlab', text: 'GitLab.com')
- - unless gitlab_import_configured?
- = render 'gitlab_import_modal'
- %div
- - if google_code_import_enabled?
- = link_to new_import_google_code_path, class: 'btn import_google_code' do
- = icon('google', text: 'Google Code')
- %div
- - if fogbugz_import_enabled?
- = link_to new_import_fogbugz_path, class: 'btn import_fogbugz' do
- = icon('bug', text: 'FogBugz')
- %div
- - if gitea_import_enabled?
- = link_to new_import_gitea_url, class: 'btn import_gitea' do
- = custom_icon('go_logo')
- Gitea
- %div
- - if git_import_enabled?
- %button.btn.js-toggle-button.import_git{ type: "button" }
- = icon('git', text: 'Repo by URL')
- .import_gitlab_project.has-tooltip{ data: { container: 'body' } }
- - if gitlab_project_import_enabled?
- = link_to new_import_gitlab_project_path, class: 'btn btn_import_gitlab_project project-submit' do
- = icon('gitlab', text: 'GitLab export')
-
- .js-toggle-content.hide
- = render "shared/import_form", f: f
-
.form-group
= f.label :description, class: 'label-light' do
Project description
diff --git a/app/views/projects/notes/_actions.html.haml b/app/views/projects/notes/_actions.html.haml
index 9c42be4e0ff..cb737d129f0 100644
--- a/app/views/projects/notes/_actions.html.haml
+++ b/app/views/projects/notes/_actions.html.haml
@@ -17,24 +17,32 @@
"inline-template" => true,
"ref" => "note_#{note.id}" }
- %button.note-action-button.line-resolve-btn{ type: "button",
- class: ("is-disabled" unless can_resolve),
- ":class" => "{ 'is-active': isResolved }",
- ":aria-label" => "buttonText",
- "@click" => "resolve",
- ":title" => "buttonText",
- ":ref" => "'button'" }
+ .note-actions-item
+ %button.note-action-button.line-resolve-btn{ type: "button",
+ class: ("is-disabled" unless can_resolve),
+ ":class" => "{ 'is-active': isResolved }",
+ ":aria-label" => "buttonText",
+ "@click" => "resolve",
+ ":title" => "buttonText",
+ ":ref" => "'button'" }
- = icon('spin spinner', 'v-show' => 'loading', class: 'loading', 'aria-hidden' => 'true', 'aria-label' => 'Loading')
- %div{ 'v-show' => '!loading' }= render 'shared/icons/icon_status_success.svg'
+ = icon('spin spinner', 'v-show' => 'loading', class: 'loading', 'aria-hidden' => 'true', 'aria-label' => 'Loading')
+ %div{ 'v-show' => '!loading' }= render 'shared/icons/icon_status_success.svg'
- if current_user
- if note.emoji_awardable?
- user_authored = note.user_authored?(current_user)
- = link_to '#', title: 'Add reaction', class: "note-action-button note-emoji-button js-add-award js-note-emoji #{'js-user-authored' if user_authored} has-tooltip", data: { position: 'right' } do
- = icon('spinner spin')
- %span{ class: 'link-highlight award-control-icon-neutral' }= custom_icon('emoji_slightly_smiling_face')
- %span{ class: 'link-highlight award-control-icon-positive' }= custom_icon('emoji_smiley')
- %span{ class: 'link-highlight award-control-icon-super-positive' }= custom_icon('emoji_smile')
+ .note-actions-item
+ = button_tag title: 'Add reaction', class: "note-action-button note-emoji-button js-add-award js-note-emoji #{'js-user-authored' if user_authored} has-tooltip btn btn-transparent", data: { position: 'right', container: 'body' } do
+ = icon('spinner spin')
+ %span{ class: 'link-highlight award-control-icon-neutral' }= custom_icon('emoji_slightly_smiling_face')
+ %span{ class: 'link-highlight award-control-icon-positive' }= custom_icon('emoji_smiley')
+ %span{ class: 'link-highlight award-control-icon-super-positive' }= custom_icon('emoji_smile')
- = render 'projects/notes/more_actions_dropdown', note: note, note_editable: note_editable
+ - if note_editable
+ .note-actions-item
+ = button_tag title: 'Edit comment', class: 'note-action-button js-note-edit has-tooltip btn btn-transparent', data: { container: 'body' } do
+ %span.link-highlight
+ = custom_icon('icon_pencil')
+
+ = render 'projects/notes/more_actions_dropdown', note: note, note_editable: note_editable
diff --git a/app/views/projects/notes/_more_actions_dropdown.html.haml b/app/views/projects/notes/_more_actions_dropdown.html.haml
index 75a4687e1e3..5930209a682 100644
--- a/app/views/projects/notes/_more_actions_dropdown.html.haml
+++ b/app/views/projects/notes/_more_actions_dropdown.html.haml
@@ -1,14 +1,11 @@
- is_current_user = current_user == note.author
- if note_editable || !is_current_user
- .dropdown.more-actions
+ .dropdown.more-actions.note-actions-item
= button_tag title: 'More actions', class: 'note-action-button more-actions-toggle has-tooltip btn btn-transparent', data: { toggle: 'dropdown', container: 'body' } do
- = icon('ellipsis-v', class: 'icon')
+ %span.icon
+ = custom_icon('ellipsis_v')
%ul.dropdown-menu.more-actions-dropdown.dropdown-open-left
- - if note_editable
- %li
- = button_tag 'Edit comment', class: 'js-note-edit btn btn-transparent'
- %li.divider
- unless is_current_user
%li
= link_to new_abuse_report_path(user_id: note.author.id, ref_url: noteable_note_url(note)) do
diff --git a/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml b/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml
index 7343d6e039c..bd8c38292d6 100644
--- a/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml
+++ b/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml
@@ -15,7 +15,7 @@
- else
= s_("PipelineSchedules|None")
%td.next-run-cell
- - if pipeline_schedule.active?
+ - if pipeline_schedule.active? && pipeline_schedule.next_run_at
= time_ago_with_tooltip(pipeline_schedule.real_next_run)
- else
= s_("PipelineSchedules|Inactive")
diff --git a/app/views/projects/runners/edit.html.haml b/app/views/projects/runners/edit.html.haml
index 95706888655..78dc4817ed7 100644
--- a/app/views/projects/runners/edit.html.haml
+++ b/app/views/projects/runners/edit.html.haml
@@ -3,4 +3,4 @@
%h4 Runner ##{@runner.id}
%hr
- = render 'form', runner: @runner, runner_form_url: runner_path(@runner)
+ = render 'form', runner: @runner, runner_form_url: runner_path(@runner)
diff --git a/app/views/projects/tree/_old_tree_content.html.haml b/app/views/projects/tree/_old_tree_content.html.haml
new file mode 100644
index 00000000000..820b947804e
--- /dev/null
+++ b/app/views/projects/tree/_old_tree_content.html.haml
@@ -0,0 +1,24 @@
+.tree-content-holder.js-tree-content{ 'data-logs-path': @logs_path }
+ .table-holder
+ %table.table#tree-slider{ class: "table_#{@hex_path} tree-table" }
+ %thead
+ %tr
+ %th= s_('ProjectFileTree|Name')
+ %th.hidden-xs
+ .pull-left= _('Last commit')
+ %th.text-right= _('Last Update')
+ - if @path.present?
+ %tr.tree-item
+ %td.tree-item-file-name
+ = link_to "..", project_tree_path(@project, up_dir_path), class: 'prepend-left-10'
+ %td
+ %td.hidden-xs
+
+ = render_tree(tree)
+
+ - if tree.readme
+ = render "projects/tree/readme", readme: tree.readme
+
+- if can_edit_tree?
+ = render 'projects/blob/upload', title: _('Upload New File'), placeholder: _('Upload New File'), button_title: _('Upload file'), form_path: project_create_blob_path(@project, @id), method: :post
+ = render 'projects/blob/new_dir'
diff --git a/app/views/projects/tree/_old_tree_header.html.haml b/app/views/projects/tree/_old_tree_header.html.haml
new file mode 100644
index 00000000000..13705ca303b
--- /dev/null
+++ b/app/views/projects/tree/_old_tree_header.html.haml
@@ -0,0 +1,70 @@
+%ul.breadcrumb.repo-breadcrumb
+ %li
+ = link_to project_tree_path(@project, @ref) do
+ = @project.path
+ - path_breadcrumbs do |title, path|
+ %li
+ = link_to truncate(title, length: 40), project_tree_path(@project, tree_join(@ref, path))
+
+ - if current_user
+ %li
+ - if !on_top_of_branch?
+ %span.btn.add-to-tree.disabled.has-tooltip{ title: _("You can only add files when you are on a branch"), data: { container: 'body' } }
+ = icon('plus')
+ - else
+ %span.dropdown
+ %a.dropdown-toggle.btn.add-to-tree{ href: '#', "data-toggle" => "dropdown", "data-target" => ".add-to-tree-dropdown" }
+ = icon('plus')
+ .add-to-tree-dropdown
+ %ul.dropdown-menu
+ - if can_edit_tree?
+ %li
+ = link_to project_new_blob_path(@project, @id) do
+ = icon('pencil fw')
+ #{ _('New file') }
+ %li
+ = link_to '#modal-upload-blob', { 'data-target' => '#modal-upload-blob', 'data-toggle' => 'modal' } do
+ = icon('file fw')
+ #{ _('Upload file') }
+ %li
+ = link_to '#modal-create-new-dir', { 'data-target' => '#modal-create-new-dir', 'data-toggle' => 'modal' } do
+ = icon('folder fw')
+ #{ _('New directory') }
+ - elsif can?(current_user, :fork_project, @project)
+ %li
+ - continue_params = { to: project_new_blob_path(@project, @id),
+ notice: edit_in_new_fork_notice,
+ notice_now: edit_in_new_fork_notice_now }
+ - fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id,
+ continue: continue_params)
+ = link_to fork_path, method: :post do
+ = icon('pencil fw')
+ #{ _('New file') }
+ %li
+ - continue_params = { to: request.fullpath,
+ notice: edit_in_new_fork_notice + " Try to upload a file again.",
+ notice_now: edit_in_new_fork_notice_now }
+ - fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id,
+ continue: continue_params)
+ = link_to fork_path, method: :post do
+ = icon('file fw')
+ #{ _('Upload file') }
+ %li
+ - continue_params = { to: request.fullpath,
+ notice: edit_in_new_fork_notice + " Try to create a new directory again.",
+ notice_now: edit_in_new_fork_notice_now }
+ - fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id,
+ continue: continue_params)
+ = link_to fork_path, method: :post do
+ = icon('folder fw')
+ #{ _('New directory') }
+
+ %li.divider
+ %li
+ = link_to new_project_branch_path(@project) do
+ = icon('code-fork fw')
+ #{ _('New branch') }
+ %li
+ = link_to new_project_tag_path(@project) do
+ = icon('tags fw')
+ #{ _('New tag') }
diff --git a/app/views/projects/tree/_tree_content.html.haml b/app/views/projects/tree/_tree_content.html.haml
index 820b947804e..a4bdd67209d 100644
--- a/app/views/projects/tree/_tree_content.html.haml
+++ b/app/views/projects/tree/_tree_content.html.haml
@@ -1,24 +1,5 @@
-.tree-content-holder.js-tree-content{ 'data-logs-path': @logs_path }
- .table-holder
- %table.table#tree-slider{ class: "table_#{@hex_path} tree-table" }
- %thead
- %tr
- %th= s_('ProjectFileTree|Name')
- %th.hidden-xs
- .pull-left= _('Last commit')
- %th.text-right= _('Last Update')
- - if @path.present?
- %tr.tree-item
- %td.tree-item-file-name
- = link_to "..", project_tree_path(@project, up_dir_path), class: 'prepend-left-10'
- %td
- %td.hidden-xs
-
- = render_tree(tree)
-
- - if tree.readme
- = render "projects/tree/readme", readme: tree.readme
-
-- if can_edit_tree?
- = render 'projects/blob/upload', title: _('Upload New File'), placeholder: _('Upload New File'), button_title: _('Upload file'), form_path: project_create_blob_path(@project, @id), method: :post
- = render 'projects/blob/new_dir'
+- content_url = local_assigns.fetch(:content_url, nil)
+- if show_new_repo?
+ = render 'shared/repo/repo', project: @project, content_url: content_url
+- else
+ = render 'projects/tree/old_tree_content', tree: tree
diff --git a/app/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml
index 858418ff8df..853e2a6e7ec 100644
--- a/app/views/projects/tree/_tree_header.html.haml
+++ b/app/views/projects/tree/_tree_header.html.haml
@@ -1,81 +1,20 @@
.tree-ref-container
.tree-ref-holder
= render 'shared/ref_switcher', destination: 'tree', path: @path
+ - if show_new_repo?
+ .tree-ref-target-holder.js-tree-ref-target-holder
+ = icon('long-arrow-right', title: 'to target branch')
+ = render 'shared/target_switcher', destination: 'tree', path: @path
- %ul.breadcrumb.repo-breadcrumb
- %li
- = link_to project_tree_path(@project, @ref) do
- = @project.path
- - path_breadcrumbs do |title, path|
- %li
- = link_to truncate(title, length: 40), project_tree_path(@project, tree_join(@ref, path))
-
- - if current_user
- %li
- - if !on_top_of_branch?
- %span.btn.add-to-tree.disabled.has-tooltip{ title: _("You can only add files when you are on a branch"), data: { container: 'body' } }
- = icon('plus')
- - else
- %span.dropdown
- %a.dropdown-toggle.btn.add-to-tree{ href: '#', "data-toggle" => "dropdown", "data-target" => ".add-to-tree-dropdown" }
- = icon('plus')
- .add-to-tree-dropdown
- %ul.dropdown-menu
- - if can_edit_tree?
- %li
- = link_to project_new_blob_path(@project, @id) do
- = icon('pencil fw')
- #{ _('New file') }
- %li
- = link_to '#modal-upload-blob', { 'data-target' => '#modal-upload-blob', 'data-toggle' => 'modal' } do
- = icon('file fw')
- #{ _('Upload file') }
- %li
- = link_to '#modal-create-new-dir', { 'data-target' => '#modal-create-new-dir', 'data-toggle' => 'modal' } do
- = icon('folder fw')
- #{ _('New directory') }
- - elsif can?(current_user, :fork_project, @project)
- %li
- - continue_params = { to: project_new_blob_path(@project, @id),
- notice: edit_in_new_fork_notice,
- notice_now: edit_in_new_fork_notice_now }
- - fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id,
- continue: continue_params)
- = link_to fork_path, method: :post do
- = icon('pencil fw')
- #{ _('New file') }
- %li
- - continue_params = { to: request.fullpath,
- notice: edit_in_new_fork_notice + " Try to upload a file again.",
- notice_now: edit_in_new_fork_notice_now }
- - fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id,
- continue: continue_params)
- = link_to fork_path, method: :post do
- = icon('file fw')
- #{ _('Upload file') }
- %li
- - continue_params = { to: request.fullpath,
- notice: edit_in_new_fork_notice + " Try to create a new directory again.",
- notice_now: edit_in_new_fork_notice_now }
- - fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id,
- continue: continue_params)
- = link_to fork_path, method: :post do
- = icon('folder fw')
- #{ _('New directory') }
-
- %li.divider
- %li
- = link_to new_project_branch_path(@project) do
- = icon('code-fork fw')
- #{ _('New branch') }
- %li
- = link_to new_project_tag_path(@project) do
- = icon('tags fw')
- #{ _('New tag') }
+ - unless show_new_repo?
+ = render 'projects/tree/old_tree_header'
.tree-controls
- = render 'projects/find_file_link'
+ - if show_new_repo?
+ = render 'shared/repo/editable_mode'
+ - else
+ = link_to s_('Commits|History'), project_commits_path(@project, @id), class: 'btn'
- = link_to s_('Commits|History'), project_commits_path(@project, @id), class: 'btn'
+ = render 'projects/find_file_link'
= render 'projects/buttons/download', project: @project, ref: @ref
diff --git a/app/views/projects/tree/show.html.haml b/app/views/projects/tree/show.html.haml
index c8587245f88..375e6764add 100644
--- a/app/views/projects/tree/show.html.haml
+++ b/app/views/projects/tree/show.html.haml
@@ -5,8 +5,14 @@
- page_title @path.presence || _("Files"), @ref
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, project_commits_url(@project, @ref, rss_url_options), title: "#{@project.name}:#{@ref} commits")
+
+- if show_new_repo?
+ - content_for :page_specific_javascripts do
+ = webpack_bundle_tag 'common_vue'
+ = webpack_bundle_tag 'repo'
+
= render "projects/commits/head"
%div{ class: [container_class, ("limit-container-width" unless fluid_layout)] }
= render 'projects/last_push'
- = render 'projects/files', commit: @last_commit, project: @project, ref: @ref
+ = render 'projects/files', commit: @last_commit, project: @project, ref: @ref, content_url: project_tree_path(@project, @id)
diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml
index adb8d5aaecb..e5a1fccf9ba 100644
--- a/app/views/projects/wikis/_form.html.haml
+++ b/app/views/projects/wikis/_form.html.haml
@@ -3,9 +3,12 @@
= form_for [@project.namespace.becomes(Namespace), @project, @page], method: @page.persisted? ? :put : :post, html: { class: 'form-horizontal wiki-form common-note-form prepend-top-default js-quick-submit' } do |f|
= form_errors(@page)
- = f.hidden_field :title, value: @page.title
- if @page.persisted?
= f.hidden_field :last_commit_sha, value: @page.last_commit_sha
+
+ .form-group
+ .col-sm-12= f.label :title, class: 'control-label-full-width'
+ .col-sm-12= f.text_field :title, class: 'form-control', value: @page.title
.form-group
.col-sm-12= f.label :format, class: 'control-label-full-width'
.col-sm-12
diff --git a/app/views/projects/wikis/_sidebar.html.haml b/app/views/projects/wikis/_sidebar.html.haml
index e71ce1f357f..f7283ae4739 100644
--- a/app/views/projects/wikis/_sidebar.html.haml
+++ b/app/views/projects/wikis/_sidebar.html.haml
@@ -1,21 +1,22 @@
%aside.right-sidebar.right-sidebar-expanded.wiki-sidebar.js-wiki-sidebar.js-right-sidebar{ data: { "offset-top" => "50", "spy" => "affix" } }
- .block.wiki-sidebar-header.append-bottom-default
- %a.gutter-toggle.pull-right.visible-xs-block.visible-sm-block.js-sidebar-wiki-toggle{ href: "#" }
- = icon('angle-double-right')
+ .sidebar-container
+ .block.wiki-sidebar-header.append-bottom-default
+ %a.gutter-toggle.pull-right.visible-xs-block.visible-sm-block.js-sidebar-wiki-toggle{ href: "#" }
+ = icon('angle-double-right')
- - git_access_url = project_wikis_git_access_path(@project)
- = link_to git_access_url, class: active_nav_link?(path: 'wikis#git_access') ? 'active' : '' do
- = succeed '&nbsp;' do
- = icon('cloud-download')
- Clone repository
+ - git_access_url = project_wikis_git_access_path(@project)
+ = link_to git_access_url, class: active_nav_link?(path: 'wikis#git_access') ? 'active' : '' do
+ = succeed '&nbsp;' do
+ = icon('cloud-download')
+ Clone repository
- .blocks-container
- .block.block-first
- %ul.wiki-pages
- = render @sidebar_wiki_entries, context: 'sidebar'
+ .blocks-container
+ .block.block-first
+ %ul.wiki-pages
+ = render @sidebar_wiki_entries, context: 'sidebar'
- .block
- = link_to project_wikis_pages_path(@project), class: 'btn btn-block' do
- More Pages
+ .block
+ = link_to project_wikis_pages_path(@project), class: 'btn btn-block' do
+ More Pages
= render 'projects/wikis/new'
diff --git a/app/views/shared/_clone_panel.html.haml b/app/views/shared/_clone_panel.html.haml
index b4843eafdb7..3d9c90c38fe 100644
--- a/app/views/shared/_clone_panel.html.haml
+++ b/app/views/shared/_clone_panel.html.haml
@@ -11,7 +11,7 @@
%span
= default_clone_protocol.upcase
= icon('caret-down')
- %ul.dropdown-menu.dropdown-menu-right.clone-options-dropdown
+ %ul.dropdown-menu.dropdown-menu-selectable.dropdown-menu-right.clone-options-dropdown
%li
= ssh_clone_button(project)
%li
diff --git a/app/views/shared/_commit_well.html.haml b/app/views/shared/_commit_well.html.haml
new file mode 100644
index 00000000000..50e3d80a84d
--- /dev/null
+++ b/app/views/shared/_commit_well.html.haml
@@ -0,0 +1,4 @@
+.info-well.hidden-xs.project-last-commit.append-bottom-default
+ .well-segment
+ %ul.blob-commit-info
+ = render 'projects/commits/commit', commit: commit, ref: ref, project: project
diff --git a/app/views/shared/_import_form.html.haml b/app/views/shared/_import_form.html.haml
index 1c7c73be933..873179339dc 100644
--- a/app/views/shared/_import_form.html.haml
+++ b/app/views/shared/_import_form.html.haml
@@ -1,16 +1,16 @@
.form-group.import-url-data
- = f.label :import_url, class: 'control-label' do
+ = f.label :import_url, class: 'label-light' do
%span Git repository URL
- .col-sm-10
- = f.text_field :import_url, autocomplete: 'off', class: 'form-control', placeholder: 'https://username:password@gitlab.company.com/group/project.git'
- .well.prepend-top-20
- %ul
- %li
- The repository must be accessible over <code>http://</code>, <code>https://</code> or <code>git://</code>.
- %li
- If your HTTP repository is not publicly accessible, add authentication information to the URL: <code>https://username:password@gitlab.company.com/group/project.git</code>.
- %li
- The import will time out after 15 minutes. For repositories that take longer, use a clone/push combination.
- %li
- To migrate an SVN repository, check out #{link_to "this document", help_page_path('workflow/importing/migrating_from_svn')}.
+ = f.text_field :import_url, autocomplete: 'off', class: 'form-control', placeholder: 'https://username:password@gitlab.company.com/group/project.git'
+
+ .well.prepend-top-20
+ %ul
+ %li
+ The repository must be accessible over <code>http://</code>, <code>https://</code> or <code>git://</code>.
+ %li
+ If your HTTP repository is not publicly accessible, add authentication information to the URL: <code>https://username:password@gitlab.company.com/group/project.git</code>.
+ %li
+ The import will time out after 15 minutes. For repositories that take longer, use a clone/push combination.
+ %li
+ To migrate an SVN repository, check out #{link_to "this document", help_page_path('workflow/importing/migrating_from_svn')}.
diff --git a/app/views/shared/_new_project_item_select.html.haml b/app/views/shared/_new_project_item_select.html.haml
index 5f3cdaefd54..96502d7ce93 100644
--- a/app/views/shared/_new_project_item_select.html.haml
+++ b/app/views/shared/_new_project_item_select.html.haml
@@ -1,6 +1,7 @@
-- if @projects.any?
- .project-item-select-holder
+- if any_projects?(@projects)
+ .project-item-select-holder.btn-group.pull-right
+ %a.btn.btn-new.new-project-item-link{ href: '', data: { label: local_assigns[:label] } }
+ = icon('spinner spin')
= project_select_tag :project_path, class: "project-item-select", data: { include_groups: local_assigns[:include_groups], order_by: 'last_activity_at', relative_path: local_assigns[:path] }, with_feature_enabled: local_assigns[:with_feature_enabled]
- %a.btn.btn-new.new-project-item-select-button
- = local_assigns[:label]
+ %button.btn.btn-new.new-project-item-select-button
= icon('caret-down')
diff --git a/app/views/shared/_sidebar_toggle_button.html.haml b/app/views/shared/_sidebar_toggle_button.html.haml
new file mode 100644
index 00000000000..eb5ddb0dde4
--- /dev/null
+++ b/app/views/shared/_sidebar_toggle_button.html.haml
@@ -0,0 +1,8 @@
+%a.toggle-sidebar-button.js-toggle-sidebar{ role: "button", type: "button", title: "Toggle sidebar" }
+ = icon('angle-double-left')
+ = icon('angle-double-right')
+ %span.collapse-text Collapse sidebar
+
+= button_tag class: 'close-nav-button', type: 'button' do
+ = icon ('times')
+ %span.collapse-text Close sidebar
diff --git a/app/views/shared/_target_switcher.html.haml b/app/views/shared/_target_switcher.html.haml
new file mode 100644
index 00000000000..3672b552f10
--- /dev/null
+++ b/app/views/shared/_target_switcher.html.haml
@@ -0,0 +1,20 @@
+- dropdown_toggle_text = @ref || @project.default_branch
+= form_tag nil, method: :get, class: "project-refs-target-form" do
+ = hidden_field_tag :destination, destination
+ - if defined?(path)
+ = hidden_field_tag :path, path
+ - @options && @options.each do |key, value|
+ = hidden_field_tag key, value, id: nil
+ .dropdown
+ = dropdown_toggle dropdown_toggle_text, { toggle: "dropdown", selected: dropdown_toggle_text, ref: @ref, refs_url: refs_project_path(@project, find: ['branches']), field_name: 'ref', input_field_name: 'new-branch', submit_form_on_click: true, visit: false }, { toggle_class: "js-project-refs-dropdown" }
+ %ul.dropdown-menu.dropdown-menu-selectable.git-revision-dropdown{ class: ("dropdown-menu-align-right" if local_assigns[:align_right]) }
+ %li
+ = dropdown_title _("Create a new branch")
+ %li
+ = dropdown_input _("Create a new branch")
+ %li
+ = dropdown_title _("Select existing branch"), options: {close: false}
+ %li
+ = dropdown_filter _("Search branches and tags")
+ = dropdown_content
+ = dropdown_loading
diff --git a/app/views/shared/icons/_container_registry.svg b/app/views/shared/icons/_container_registry.svg
new file mode 100644
index 00000000000..56d62aab670
--- /dev/null
+++ b/app/views/shared/icons/_container_registry.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="m16 11.764v-8.764c0-1.657-1.343-3-3-3h-10c-1.657 0-3 1.343-3 3v8.764c.531-.475 1.232-.764 2-.764v-8c0-.552.448-1 1-1h10c.552 0 1 .448 1 1v8c.768 0 1.469.289 2 .764m-14 .236h12c1.105 0 2 .895 2 2 0 1.105-.895 2-2 2h-12c-1.105 0-2-.895-2-2 0-1.105.895-2 2-2m10 1c-.552 0-1 .448-1 1 0 .552.448 1 1 1 .552 0 1-.448 1-1 0-.552-.448-1-1-1"/></svg>
diff --git a/app/views/shared/icons/_ellipsis_v.svg b/app/views/shared/icons/_ellipsis_v.svg
new file mode 100644
index 00000000000..9117a9bb9ec
--- /dev/null
+++ b/app/views/shared/icons/_ellipsis_v.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1600 1600"><path d="M1088 1248v192q0 40-28 68t-68 28H800q-40 0-68-28t-28-68v-192q0-40 28-68t68-28h192q40 0 68 28t28 68zm0-512v192q0 40-28 68t-68 28H800q-40 0-68-28t-28-68V736q0-40 28-68t68-28h192q40 0 68 28t28 68zm0-512v192q0 40-28 68t-68 28H800q-40 0-68-28t-28-68V224q0-40 28-68t68-28h192q40 0 68 28t28 68z"/></svg>
diff --git a/app/views/shared/icons/_java_spring.svg b/app/views/shared/icons/_java_spring.svg
new file mode 100644
index 00000000000..508349aa456
--- /dev/null
+++ b/app/views/shared/icons/_java_spring.svg
@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" class="btn-template-icon icon-java-spring">
+ <g fill="none" fill-rule="evenodd">
+ <rect width="32" height="32"/>
+ <path fill="#70AD51" d="M5.46647617,27.9932117 C6.0517027,28.4658996 6.91159892,28.3777063 7.38425926,27.7914452 C7.85922261,27.2048452 7.76991326,26.3449044 7.18398981,25.8699411 C6.59874295,25.3956543 5.74015536,25.4869934 5.26383884,26.0722403 C4.81393367,26.6267596 4.87238621,27.4284565 5.37913494,27.9159868 L5.11431334,27.6818383 C1.97157151,24.7616933 0,20.5966301 0,15.9782542 C0,7.16842834 7.16775175,0 15.9796074,0 C20.4586065,0 24.5113565,1.8565519 27.4145869,4.8362365 C28.0749348,3.93840692 28.6466499,2.93435335 29.115524,1.82069284 C31.1513712,7.93770658 32.3482517,13.0811131 31.909824,17.1311567 C31.3178113,25.4044499 24.4017495,31.9585382 15.9796074,31.9585382 C12.0682639,31.9585382 8.48438805,30.5444735 5.7042963,28.2034861 L5.46647617,27.9932117 Z M29.0471888,23.0106888 C33.0546075,17.6737787 30.8211972,9.04527781 28.9612624,3.529749 C27.3029502,6.98304378 23.2217836,9.62375882 19.6981239,10.4613722 C16.3950312,11.2482417 13.4715032,10.6021021 10.4153644,11.7780085 C3.44517575,14.457289 3.55613585,22.7698242 7.39373146,24.6365249 C7.39711439,24.6392312 7.62444728,24.7616933 7.62174094,24.7576338 C7.62309411,24.7562806 13.2658211,23.6358542 16.3862356,22.4843049 C20.9450718,20.7996058 25.9524846,16.6494275 27.5986182,11.8273993 C26.723116,16.8415779 22.4179995,21.6669891 18.093262,23.8828081 C15.7908399,25.0648038 14.0005934,25.3279957 10.2123886,26.6385428 C9.74892722,26.798217 9.38492397,26.9538318 9.38492397,26.9538318 C10.3463526,26.7948341 11.301692,26.7420604 11.301692,26.7420604 C16.6954354,26.4869875 25.1087819,28.2582896 29.0471888,23.0106888 Z"/>
+ </g>
+</svg>
diff --git a/app/views/shared/icons/_node_express.svg b/app/views/shared/icons/_node_express.svg
new file mode 100644
index 00000000000..f2c94319f19
--- /dev/null
+++ b/app/views/shared/icons/_node_express.svg
@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="27" height="32" viewBox="0 0 27 32" class="btn-template-icon icon-node-express">
+ <g fill="none" fill-rule="evenodd" transform="translate(-3)">
+ <rect width="32" height="32"/>
+ <path fill="#353535" d="M4.19170065,16.2667139 C4.23142421,18.3323387 4.47969269,20.2489714 4.93651356,22.0166696 C5.39333443,23.7843677 6.09841693,25.3236323 7.05178222,26.6345096 C8.00514751,27.9453869 9.23655921,28.9781838 10.7460543,29.7329313 C12.2555493,30.4876788 14.1026668,30.8650469 16.2874623,30.8650469 C19.5050701,30.8650469 22.1764391,30.0209341 24.3016492,28.3326831 C26.4268593,26.644432 27.7476477,24.1120935 28.2640539,20.7355914 L29.4557545,20.7355914 C29.0187954,24.3107112 27.6086304,27.0813875 25.2252172,29.0477034 C22.841804,31.0140194 19.9023051,31.9971626 16.4066324,31.9971626 C14.0232191,32.0368861 11.9874175,31.659518 10.2991665,30.8650469 C8.61091547,30.0705759 7.23054269,28.9484023 6.15800673,27.4984926 C5.08547078,26.0485829 4.29101162,24.3404957 3.77460543,22.3741798 C3.25819923,20.4078639 3,18.2926164 3,16.0283738 C3,13.4860664 3.3773681,11.2218578 4.13211562,9.23568007 C4.88686314,7.24950238 5.87993709,5.57120741 7.11136726,4.20074481 C8.34279742,2.8302822 9.77282391,1.78755456 11.4014896,1.07253059 C13.0301553,0.357506621 14.6985195,0 16.4066324,0 C18.7900456,0 20.8457087,0.456814016 22.5736832,1.37045575 C24.3016578,2.28409749 25.7118228,3.4956477 26.8042206,5.00514275 C27.8966183,6.51463779 28.6910775,8.24258646 29.1876219,10.1890406 C29.6841663,12.1354947 29.8927118,14.1613656 29.8132647,16.2667139 L4.19170065,16.2667139 Z M28.6215641,15.0750133 C28.6215641,13.2080062 28.3633648,11.4304039 27.8469586,9.74215285 C27.3305524,8.05390181 26.5658855,6.57422163 25.5529349,5.30306791 C24.5399843,4.03191419 23.2787803,3.0289095 21.7692853,2.29402376 C20.2597903,1.55913801 18.5119801,1.19170065 16.5258024,1.19170065 C14.8574132,1.19170065 13.2982871,1.50948432 11.8483774,2.14506118 C10.3984676,2.78063804 9.12733299,3.70419681 8.03493526,4.9157652 C6.94253754,6.12733359 6.05870172,7.58715229 5.38340131,9.2952651 C4.70810089,11.0033779 4.31087132,12.9299414 4.19170065,15.0750133 L28.6215641,15.0750133 Z"/>
+ </g>
+</svg>
diff --git a/app/views/shared/icons/_rails.svg b/app/views/shared/icons/_rails.svg
new file mode 100644
index 00000000000..0bb09a705df
--- /dev/null
+++ b/app/views/shared/icons/_rails.svg
@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="32" height="20" viewBox="0 0 32 20" class="btn-template-icon icon-rails">
+ <g fill="none" fill-rule="evenodd" transform="translate(0 -6)">
+ <rect width="32" height="32"/>
+ <path fill="#C00" fill-rule="nonzero" d="M0.984615385,25.636044 C0.984615385,25.636044 1.40659341,21.4725275 4.36043956,16.5494505 C7.31428571,11.6263736 12.3498901,7.8989011 16.4430769,7.53318681 C24.5872527,6.71736264 31.9015385,14.0175824 31.9015385,14.0175824 C31.9015385,14.0175824 31.6624176,14.1863736 31.4092308,14.3973626 C23.4197802,8.48967033 18.5389011,11.2747253 17.0057143,12.0202198 C9.97274725,15.9446154 12.0967033,25.636044 12.0967033,25.636044 L0.984615385,25.636044 Z M24.1371429,8.32087912 C23.687033,8.13802198 23.2369231,7.96923077 22.7727473,7.81450549 L22.829011,6.88615385 C23.7151648,7.13934066 24.0668132,7.30813187 24.1934066,7.37846154 L24.1371429,8.32087912 Z M22.8008791,11.3028571 C23.250989,11.330989 23.7151648,11.3872527 24.1934066,11.4857143 L24.1371429,12.3578022 C23.672967,12.2593407 23.2087912,12.2030769 22.7446154,12.189011 L22.8008791,11.3028571 Z M17.5964835,6.91428571 C17.1885714,6.91428571 16.7806593,6.92835165 16.3727473,6.97054945 L16.1054945,6.14065934 C16.5696703,6.0843956 17.0197802,6.05626374 17.4558242,6.05626374 L17.7371429,6.91428571 C17.6949451,6.91428571 17.6386813,6.91428571 17.5964835,6.91428571 Z M18.2716484,12.0905495 C18.6232967,11.9358242 19.0312088,11.7810989 19.5094505,11.6404396 L19.8189011,12.5687912 C19.410989,12.6953846 19.0030769,12.8641758 18.5951648,13.0610989 L18.2716484,12.0905495 Z M11.8857143,8.39120879 C11.52,8.57406593 11.1683516,8.78505495 10.8026374,9.01010989 L10.1556044,8.02549451 C10.5353846,7.80043956 10.9010989,7.60351648 11.2527473,7.42065934 L11.8857143,8.39120879 Z M14.7692308,14.7208791 C15.0224176,14.3973626 15.3178022,14.0738462 15.6413187,13.7784615 L16.2742857,14.7349451 C15.9648352,15.0584615 15.6835165,15.381978 15.4443956,15.7336264 L14.7692308,14.7208791 Z M12.7296703,19.2501099 C12.8421978,18.7437363 12.9687912,18.2232967 13.1516484,17.7028571 L14.1643956,18.5046154 C14.0237363,19.0531868 13.9252747,19.6017582 13.869011,20.1503297 L12.7296703,19.2501099 Z M6.56879121,12.5687912 C6.23120879,12.9204396 5.90769231,13.3002198 5.61230769,13.68 L4.52923077,12.7516484 C4.85274725,12.4 5.2043956,12.0483516 5.57010989,11.6967033 L6.56879121,12.5687912 Z M2.32087912,18.8562637 C2.09582418,19.3767033 1.80043956,20.0659341 1.61758242,20.5441758 L0,19.9534066 C0.140659341,19.5736264 0.436043956,18.8703297 0.703296703,18.2654945 L2.32087912,18.8562637 Z M12.5186813,22.8228571 L14.0378022,23.3714286 C14.1221978,24.0325275 14.2487912,24.6514286 14.3753846,25.2 L12.6874725,24.5951648 C12.6171429,24.1731868 12.5468132,23.5683516 12.5186813,22.8228571 Z"/>
+ </g>
+</svg>
diff --git a/app/views/shared/icons/_spam_logs.svg b/app/views/shared/icons/_spam_logs.svg
new file mode 100644
index 00000000000..80ee0eb3856
--- /dev/null
+++ b/app/views/shared/icons/_spam_logs.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M8.75.433l5.428 3.134a1.5 1.5 0 0 1 .75 1.299v6.268a1.5 1.5 0 0 1-.75 1.299L8.75 15.567a1.5 1.5 0 0 1-1.5 0l-5.428-3.134a1.5 1.5 0 0 1-.75-1.299V4.866a1.5 1.5 0 0 1 .75-1.299L7.25.433a1.5 1.5 0 0 1 1.5 0zM3.072 5.155v5.69L8 13.691l4.928-2.846v-5.69L8 2.309 3.072 5.155zM8 4a1 1 0 0 1 1 1v3a1 1 0 1 1-2 0V5a1 1 0 0 1 1-1zm0 8a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/></svg>
diff --git a/app/views/shared/issuable/_label_page_create.html.haml b/app/views/shared/issuable/_label_page_create.html.haml
index bd66f39fa59..0a692d9653f 100644
--- a/app/views/shared/issuable/_label_page_create.html.haml
+++ b/app/views/shared/issuable/_label_page_create.html.haml
@@ -1,5 +1,5 @@
.dropdown-page-two.dropdown-new-label
- = dropdown_title("Create new label", back: true)
+ = dropdown_title("Create new label", options: { back: true })
= dropdown_content do
.dropdown-labels-error.js-label-error
%input#new_label_name.default-dropdown-input{ type: "text", placeholder: "Name new label" }
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index e7510c1d1ec..c3f25c9d255 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -3,7 +3,7 @@
= page_specific_javascript_bundle_tag('common_vue')
= page_specific_javascript_bundle_tag('sidebar')
-%aside.right-sidebar.js-right-sidebar{ data: { "offset-top" => "50", "spy" => "affix", signed: { in: current_user.present? } }, class: sidebar_gutter_collapsed_class, 'aria-live' => 'polite' }
+%aside.right-sidebar.js-right-sidebar.js-issuable-sidebar{ data: { "offset-top" => ("50" unless show_new_nav?), "spy" => ("affix" unless show_new_nav?), signed: { in: current_user.present? } }, class: sidebar_gutter_collapsed_class, 'aria-live' => 'polite' }
.issuable-sidebar{ data: { endpoint: "#{issuable_json_path(issuable)}" } }
- can_edit_issuable = can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
.block.issuable-sidebar-header
@@ -115,6 +115,10 @@
- if can? current_user, :admin_label, @project and @project
= render partial: "shared/issuable/label_page_create"
+ - if issuable.has_attribute?(:confidential)
+ %script#js-confidential-issue-data{ type: "application/json" }= { is_confidential: @issue.confidential, is_editable: can_edit_issuable }.to_json.html_safe
+ #js-confidential-entry-point
+
= render "shared/issuable/participants", participants: issuable.participants(current_user)
- if current_user
- subscribed = issuable.subscribed?(current_user, @project)
diff --git a/app/views/shared/issuable/_user_dropdown_item.html.haml b/app/views/shared/issuable/_user_dropdown_item.html.haml
index a82c01c6dc2..c18e4975bb8 100644
--- a/app/views/shared/issuable/_user_dropdown_item.html.haml
+++ b/app/views/shared/issuable/_user_dropdown_item.html.haml
@@ -3,7 +3,8 @@
%li.filter-dropdown-item{ class: ('js-current-user' if user == current_user) }
%button.btn.btn-link.dropdown-user{ type: :button }
- = user_avatar_without_link(user: user, lazy: avatar[:lazy], url: avatar[:url], size: 30)
+ .avatar-container.s40
+ = user_avatar_without_link(user: user, lazy: avatar[:lazy], url: avatar[:url], size: 40).gsub('/images/{{avatar_url}}','{{avatar_url}}').html_safe
.dropdown-user-details
%span
= user.name
diff --git a/app/views/shared/milestones/_sidebar.html.haml b/app/views/shared/milestones/_sidebar.html.haml
index 895fb8247b5..66ac8196f2f 100644
--- a/app/views/shared/milestones/_sidebar.html.haml
+++ b/app/views/shared/milestones/_sidebar.html.haml
@@ -67,7 +67,7 @@
.block.issues
.sidebar-collapsed-icon
%strong
- = icon('hashtag', 'aria-hidden': 'true')
+ = custom_icon('issues')
%span= milestone.issues_visible_to_user(current_user).count
.title.hide-collapsed
Issues
diff --git a/app/views/shared/projects/_list.html.haml b/app/views/shared/projects/_list.html.haml
index 7ed6c622558..0bedfea3502 100644
--- a/app/views/shared/projects/_list.html.haml
+++ b/app/views/shared/projects/_list.html.haml
@@ -10,7 +10,7 @@
- load_pipeline_status(projects)
.js-projects-list-holder
- - if projects.any?
+ - if any_projects?(projects)
%ul.projects-list
- projects.each_with_index do |project, i|
- css_class = (i >= projects_limit) || project.pending_delete? ? 'hide' : nil
@@ -22,7 +22,7 @@
%li.project-row.private-forks-notice
= icon('lock fw', base: 'circle', class: 'fa-lg private-fork-icon')
%strong= pluralize(@private_forks_count, 'private fork')
- %span you have no access to.
- = paginate(projects, remote: remote, theme: "gitlab") if projects.respond_to? :total_pages
+ %span &nbsp;you have no access to.
+ = paginate_collection(projects, remote: remote)
- else
.nothing-here-block No projects found
diff --git a/app/views/shared/repo/_editable_mode.html.haml b/app/views/shared/repo/_editable_mode.html.haml
new file mode 100644
index 00000000000..73fdb8b523f
--- /dev/null
+++ b/app/views/shared/repo/_editable_mode.html.haml
@@ -0,0 +1,2 @@
+.editable-mode
+ %repo-edit-button
diff --git a/app/views/shared/repo/_repo.html.haml b/app/views/shared/repo/_repo.html.haml
new file mode 100644
index 00000000000..0fc40cf0801
--- /dev/null
+++ b/app/views/shared/repo/_repo.html.haml
@@ -0,0 +1,2 @@
+#repo{ data: { url: content_url, project_name: project.name, refs_url: refs_project_path(project, format: :json), project_url: project_path(project), project_id: project.id, can_commit: (!!can_push_branch?(project, @ref)).to_s } }
+ %repo
diff --git a/app/views/snippets/notes/_actions.html.haml b/app/views/snippets/notes/_actions.html.haml
index 098a88c48c5..3a50324770d 100644
--- a/app/views/snippets/notes/_actions.html.haml
+++ b/app/views/snippets/notes/_actions.html.haml
@@ -1,10 +1,17 @@
- if current_user
- if note.emoji_awardable?
- user_authored = note.user_authored?(current_user)
- = link_to '#', title: 'Add reaction', class: "note-action-button note-emoji-button js-add-award js-note-emoji #{'js-user-authored' if user_authored} has-tooltip", data: { position: 'right' } do
- = icon('spinner spin')
- %span{ class: 'link-highlight award-control-icon-neutral' }= custom_icon('emoji_slightly_smiling_face')
- %span{ class: 'link-highlight award-control-icon-positive' }= custom_icon('emoji_smiley')
- %span{ class: 'link-highlight award-control-icon-super-positive' }= custom_icon('emoji_smile')
+ .note-actions-item
+ = link_to '#', title: 'Add reaction', class: "note-action-button note-emoji-button js-add-award js-note-emoji #{'js-user-authored' if user_authored} has-tooltip", data: { position: 'right' } do
+ = icon('spinner spin')
+ %span{ class: 'link-highlight award-control-icon-neutral' }= custom_icon('emoji_slightly_smiling_face')
+ %span{ class: 'link-highlight award-control-icon-positive' }= custom_icon('emoji_smiley')
+ %span{ class: 'link-highlight award-control-icon-super-positive' }= custom_icon('emoji_smile')
+
+ - if note_editable
+ .note-actions-item
+ = button_tag title: 'Edit comment', class: 'note-action-button js-note-edit has-tooltip btn btn-transparent', data: { container: 'body' } do
+ %span.link-highlight
+ = custom_icon('icon_pencil')
= render 'projects/notes/more_actions_dropdown', note: note, note_editable: note_editable
diff --git a/app/views/users/calendar_activities.html.haml b/app/views/users/calendar_activities.html.haml
index 805a346a85e..6b1d75c6e72 100644
--- a/app/views/users/calendar_activities.html.haml
+++ b/app/views/users/calendar_activities.html.haml
@@ -1,6 +1,6 @@
%h4.prepend-top-20
Contributions for
- %strong= @calendar_date.to_s(:short)
+ %strong= @calendar_date.to_s(:medium)
- if @events.any?
%ul.bordered-list
@@ -8,7 +8,7 @@
%li
%span.light
%i.fa.fa-clock-o
- = event.created_at.to_s(:time)
+ = event.created_at.strftime('%-I:%M%P')
- if event.push?
#{event.action_name} #{event.ref_type}
%strong
@@ -30,4 +30,4 @@
= event.project_name
- else
%p
- No contributions found for #{@calendar_date.to_s(:short)}
+ No contributions found for #{@calendar_date.to_s(:medium)}
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index a449706c567..879e0f99b14 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -104,7 +104,7 @@
.tab-content
#activity.tab-pane
.row-content-block.calender-block.white.second-block.hidden-xs
- .user-calendar{ data: { calendar_path: user_calendar_path(@user, :json), calendar_activities_path: user_calendar_activities_path } }
+ .user-calendar{ data: { calendar_path: user_calendar_path(@user, :json), calendar_activities_path: user_calendar_activities_path, utc_offset: Time.zone.utc_offset } }
%h4.center.light
%i.fa.fa-spinner.fa-spin
.user-calendar-activities
diff --git a/app/workers/concerns/new_issuable.rb b/app/workers/concerns/new_issuable.rb
new file mode 100644
index 00000000000..eb0d6c9c36c
--- /dev/null
+++ b/app/workers/concerns/new_issuable.rb
@@ -0,0 +1,26 @@
+module NewIssuable
+ attr_reader :issuable, :user
+
+ def objects_found?(issuable_id, user_id)
+ set_user(user_id)
+ set_issuable(issuable_id)
+
+ user && issuable
+ end
+
+ def set_user(user_id)
+ @user = User.find_by(id: user_id)
+
+ log_error(User, user_id) unless @user
+ end
+
+ def set_issuable(issuable_id)
+ @issuable = issuable_class.find_by(id: issuable_id)
+
+ log_error(issuable_class, issuable_id) unless @issuable
+ end
+
+ def log_error(record_class, record_id)
+ Rails.logger.error("#{self.class}: couldn't find #{record_class} with ID=#{record_id}, skipping job")
+ end
+end
diff --git a/app/workers/email_receiver_worker.rb b/app/workers/email_receiver_worker.rb
index d3f7e479a8d..1afa24c8e2a 100644
--- a/app/workers/email_receiver_worker.rb
+++ b/app/workers/email_receiver_worker.rb
@@ -31,8 +31,6 @@ class EmailReceiverWorker
when Gitlab::Email::EmptyEmailError
can_retry = true
"It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
- when Gitlab::Email::AutoGeneratedEmailError
- "The email was marked as 'auto generated', which we can't accept. Please create your comment through the web interface."
when Gitlab::Email::UserNotFoundError
"We couldn't figure out what user corresponds to the email. Please create your comment through the web interface."
when Gitlab::Email::UserBlockedError
diff --git a/app/workers/merge_worker.rb b/app/workers/merge_worker.rb
index 48e2da338f6..c3b58df92c1 100644
--- a/app/workers/merge_worker.rb
+++ b/app/workers/merge_worker.rb
@@ -7,6 +7,8 @@ class MergeWorker
current_user = User.find(current_user_id)
merge_request = MergeRequest.find(merge_request_id)
+ merge_request.update_column(:merge_jid, jid)
+
MergeRequests::MergeService.new(merge_request.target_project, current_user, params)
.execute(merge_request)
end
diff --git a/app/workers/new_issue_worker.rb b/app/workers/new_issue_worker.rb
new file mode 100644
index 00000000000..d9a8e892e90
--- /dev/null
+++ b/app/workers/new_issue_worker.rb
@@ -0,0 +1,17 @@
+class NewIssueWorker
+ include Sidekiq::Worker
+ include DedicatedSidekiqQueue
+ include NewIssuable
+
+ def perform(issue_id, user_id)
+ return unless objects_found?(issue_id, user_id)
+
+ EventCreateService.new.open_issue(issuable, user)
+ NotificationService.new.new_issue(issuable, user)
+ issuable.create_cross_references!(user)
+ end
+
+ def issuable_class
+ Issue
+ end
+end
diff --git a/app/workers/new_merge_request_worker.rb b/app/workers/new_merge_request_worker.rb
new file mode 100644
index 00000000000..1910c490159
--- /dev/null
+++ b/app/workers/new_merge_request_worker.rb
@@ -0,0 +1,17 @@
+class NewMergeRequestWorker
+ include Sidekiq::Worker
+ include DedicatedSidekiqQueue
+ include NewIssuable
+
+ def perform(merge_request_id, user_id)
+ return unless objects_found?(merge_request_id, user_id)
+
+ EventCreateService.new.open_mr(issuable, user)
+ NotificationService.new.new_merge_request(issuable, user)
+ issuable.create_cross_references!(user)
+ end
+
+ def issuable_class
+ MergeRequest
+ end
+end
diff --git a/app/workers/pages_worker.rb b/app/workers/pages_worker.rb
index 4eeb9666bb0..64788da7299 100644
--- a/app/workers/pages_worker.rb
+++ b/app/workers/pages_worker.rb
@@ -4,7 +4,7 @@ class PagesWorker
sidekiq_options queue: :pages, retry: false
def perform(action, *arg)
- send(action, *arg)
+ send(action, *arg) # rubocop:disable GitlabSecurity/PublicSend
end
def deploy(build_id)
diff --git a/app/workers/stuck_merge_jobs_worker.rb b/app/workers/stuck_merge_jobs_worker.rb
new file mode 100644
index 00000000000..7843179d77c
--- /dev/null
+++ b/app/workers/stuck_merge_jobs_worker.rb
@@ -0,0 +1,34 @@
+class StuckMergeJobsWorker
+ include Sidekiq::Worker
+ include CronjobQueue
+
+ def perform
+ stuck_merge_requests.find_in_batches(batch_size: 100) do |group|
+ jids = group.map(&:merge_jid)
+
+ # Find the jobs that aren't currently running or that exceeded the threshold.
+ completed_jids = Gitlab::SidekiqStatus.completed_jids(jids)
+
+ if completed_jids.any?
+ completed_ids = group.select { |merge_request| completed_jids.include?(merge_request.merge_jid) }.map(&:id)
+
+ apply_current_state!(completed_jids, completed_ids)
+ end
+ end
+ end
+
+ private
+
+ def apply_current_state!(completed_jids, completed_ids)
+ merge_requests = MergeRequest.where(id: completed_ids)
+
+ merge_requests.where.not(merge_commit_sha: nil).update_all(state: :merged)
+ merge_requests.where(merge_commit_sha: nil).update_all(state: :opened)
+
+ Rails.logger.info("Updated state of locked merge jobs. JIDs: #{completed_jids.join(', ')}")
+ end
+
+ def stuck_merge_requests
+ MergeRequest.select('id, merge_jid').with_state(:locked).where.not(merge_jid: nil).reorder(nil)
+ end
+end
diff --git a/bin/changelog b/bin/changelog
index 4c894f8ff5b..61d4de06e90 100755
--- a/bin/changelog
+++ b/bin/changelog
@@ -14,54 +14,107 @@ Options = Struct.new(
:dry_run,
:force,
:merge_request,
- :title
+ :title,
+ :type
)
+INVALID_TYPE = -1
class ChangelogOptionParser
- def self.parse(argv)
- options = Options.new
+ Type = Struct.new(:name, :description)
+ TYPES = [
+ Type.new('added', 'New feature'),
+ Type.new('fixed', 'Bug fix'),
+ Type.new('changed', 'Feature change'),
+ Type.new('deprecated', 'New deprecation'),
+ Type.new('removed', 'Feature removal'),
+ Type.new('security', 'Security fix'),
+ Type.new('other', 'Other')
+ ].freeze
+ TYPES_OFFSET = 1
+
+ class << self
+ def parse(argv)
+ options = Options.new
+
+ parser = OptionParser.new do |opts|
+ opts.banner = "Usage: #{__FILE__} [options] [title]\n\n"
+
+ # Note: We do not provide a shorthand for this in order to match the `git
+ # commit` interface
+ opts.on('--amend', 'Amend the previous commit') do |value|
+ options.amend = value
+ end
+
+ opts.on('-f', '--force', 'Overwrite an existing entry') do |value|
+ options.force = value
+ end
+
+ opts.on('-m', '--merge-request [integer]', Integer, 'Merge Request ID') do |value|
+ options.merge_request = value
+ end
+
+ opts.on('-n', '--dry-run', "Don't actually write anything, just print") do |value|
+ options.dry_run = value
+ end
+
+ opts.on('-u', '--git-username', 'Use Git user.name configuration as the author') do |value|
+ options.author = git_user_name if value
+ end
+
+ opts.on('-t', '--type [string]', String, "The category of the change, valid options are: #{TYPES.map(&:name).join(', ')}") do |value|
+ options.type = parse_type(value)
+ end
+
+ opts.on('-h', '--help', 'Print help message') do
+ $stdout.puts opts
+ exit
+ end
+ end
- parser = OptionParser.new do |opts|
- opts.banner = "Usage: #{__FILE__} [options] [title]\n\n"
+ parser.parse!(argv)
- # Note: We do not provide a shorthand for this in order to match the `git
- # commit` interface
- opts.on('--amend', 'Amend the previous commit') do |value|
- options.amend = value
- end
+ # Title is everything that remains, but let's clean it up a bit
+ options.title = argv.join(' ').strip.squeeze(' ').tr("\r\n", '')
- opts.on('-f', '--force', 'Overwrite an existing entry') do |value|
- options.force = value
- end
+ options
+ end
- opts.on('-m', '--merge-request [integer]', Integer, 'Merge Request ID') do |value|
- options.merge_request = value
- end
+ def read_type
+ read_type_message
- opts.on('-n', '--dry-run', "Don't actually write anything, just print") do |value|
- options.dry_run = value
- end
+ type = TYPES[$stdin.getc.to_i - TYPES_OFFSET]
+ assert_valid_type!(type)
- opts.on('-u', '--git-username', 'Use Git user.name configuration as the author') do |value|
- options.author = git_user_name if value
- end
+ type.name
+ end
+
+ private
- opts.on('-h', '--help', 'Print help message') do
- $stdout.puts opts
- exit
+ def parse_type(name)
+ type_found = TYPES.find do |type|
+ type.name == name
end
+ type_found ? type_found.name : INVALID_TYPE
end
- parser.parse!(argv)
-
- # Title is everything that remains, but let's clean it up a bit
- options.title = argv.join(' ').strip.squeeze(' ').tr("\r\n", '')
+ def read_type_message
+ $stdout.puts "\n>> Please specify the index for the category of your change:"
+ TYPES.each_with_index do |type, index|
+ $stdout.puts "#{index + TYPES_OFFSET}. #{type.description}"
+ end
+ $stdout.print "\n?> "
+ end
- options
- end
+ def assert_valid_type!(type)
+ unless type
+ $stderr.puts "Invalid category index, please select an index between 1 and #{TYPES.length}"
+ exit 1
+ end
+ end
- def self.git_user_name
- %x{git config user.name}.strip
+ def git_user_name
+ %x{git config user.name}.strip
+ end
end
end
@@ -72,8 +125,12 @@ class ChangelogEntry
@options = options
assert_feature_branch!
- assert_new_file!
assert_title!
+ assert_new_file!
+
+ # Read type from $stdin unless is already set
+ options.type ||= ChangelogOptionParser.read_type
+ assert_valid_type!
$stdout.puts "\e[32mcreate\e[0m #{file_path}"
$stdout.puts contents
@@ -90,7 +147,8 @@ class ChangelogEntry
yaml_content = YAML.dump(
'title' => title,
'merge_request' => options.merge_request,
- 'author' => options.author
+ 'author' => options.author,
+ 'type' => options.type
)
remove_trailing_whitespace(yaml_content)
end
@@ -129,6 +187,12 @@ class ChangelogEntry
" to use the title from the previous commit."
end
+ def assert_valid_type!
+ return unless options.type && options.type == INVALID_TYPE
+
+ fail_with 'Invalid category given!'
+ end
+
def title
if options.title.empty?
last_commit_subject
diff --git a/bin/rspec-stackprof b/bin/rspec-stackprof
index df79feb201d..810863ea4a0 100755
--- a/bin/rspec-stackprof
+++ b/bin/rspec-stackprof
@@ -1,5 +1,6 @@
#!/usr/bin/env ruby
+require 'bundler/setup'
require 'stackprof'
$:.unshift 'spec'
require 'rails_helper'
@@ -13,4 +14,4 @@ StackProf.run(mode: :wall, out: output_file, interval: interval) do
RSpec::Core::Runner.run(ARGV, $stderr, $stdout)
end
-system("stackprof #{output_file} --text --limit #{limit}")
+system("bundle exec stackprof #{output_file} --text --limit #{limit}")
diff --git a/changelogs/unreleased/13265-project_events_noteable_iid.yml b/changelogs/unreleased/13265-project_events_noteable_iid.yml
new file mode 100644
index 00000000000..54d538bb548
--- /dev/null
+++ b/changelogs/unreleased/13265-project_events_noteable_iid.yml
@@ -0,0 +1,4 @@
+---
+title: Expose noteable_iid in Note
+merge_request: 13265
+author: sue445
diff --git a/changelogs/unreleased/13325-bugfix-silence-on-disabled-notifications.yml b/changelogs/unreleased/13325-bugfix-silence-on-disabled-notifications.yml
new file mode 100644
index 00000000000..90b169390d2
--- /dev/null
+++ b/changelogs/unreleased/13325-bugfix-silence-on-disabled-notifications.yml
@@ -0,0 +1,6 @@
+---
+title: disabling notifications globally now properly turns off group/project added
+ emails
+merge_request: 13325
+author: @jneen
+type: fixed
diff --git a/changelogs/unreleased/21949-add-type-to-changelog.yml b/changelogs/unreleased/21949-add-type-to-changelog.yml
new file mode 100644
index 00000000000..a20f6b7ad4e
--- /dev/null
+++ b/changelogs/unreleased/21949-add-type-to-changelog.yml
@@ -0,0 +1,4 @@
+---
+title: Added type to CHANGELOG entries
+merge_request:
+author: Jacopo Beschi @jacopo-beschi
diff --git a/changelogs/unreleased/27616-fix-contributions-graph-utc-offset-mysql.yml b/changelogs/unreleased/27616-fix-contributions-graph-utc-offset-mysql.yml
new file mode 100644
index 00000000000..1b3c3b8538d
--- /dev/null
+++ b/changelogs/unreleased/27616-fix-contributions-graph-utc-offset-mysql.yml
@@ -0,0 +1,4 @@
+---
+title: Fix timezone inconsistencies in user contribution graph
+merge_request: 13208
+author:
diff --git a/changelogs/unreleased/28472-ignore-auto-generated-mails.yml b/changelogs/unreleased/28472-ignore-auto-generated-mails.yml
new file mode 100644
index 00000000000..af63b43e62e
--- /dev/null
+++ b/changelogs/unreleased/28472-ignore-auto-generated-mails.yml
@@ -0,0 +1,4 @@
+---
+title: Don't send rejection mails for all auto-generated mails
+merge_request: 13254
+author:
diff --git a/changelogs/unreleased/29811-fix-line-number-alignment.yml b/changelogs/unreleased/29811-fix-line-number-alignment.yml
new file mode 100644
index 00000000000..94b3328a7f2
--- /dev/null
+++ b/changelogs/unreleased/29811-fix-line-number-alignment.yml
@@ -0,0 +1,4 @@
+---
+title: Fix the alignment of line numbers to lines of code in code viewer
+merge_request: 13403
+author: Trevor Flynn \ No newline at end of file
diff --git a/changelogs/unreleased/31207-clean-locked-merge-requests.yml b/changelogs/unreleased/31207-clean-locked-merge-requests.yml
new file mode 100644
index 00000000000..1f52987baef
--- /dev/null
+++ b/changelogs/unreleased/31207-clean-locked-merge-requests.yml
@@ -0,0 +1,4 @@
+---
+title: Unlock stuck merge request and set the proper state
+merge_request: 13207
+author:
diff --git a/changelogs/unreleased/32844-issuables-performance.yml b/changelogs/unreleased/32844-issuables-performance.yml
new file mode 100644
index 00000000000..e9b21c1aa45
--- /dev/null
+++ b/changelogs/unreleased/32844-issuables-performance.yml
@@ -0,0 +1,4 @@
+---
+title: Move some code from services to workers in order to improve performance
+merge_request: 13326
+author:
diff --git a/changelogs/unreleased/33095-mr-widget-ui.yml b/changelogs/unreleased/33095-mr-widget-ui.yml
new file mode 100644
index 00000000000..9ce3086df27
--- /dev/null
+++ b/changelogs/unreleased/33095-mr-widget-ui.yml
@@ -0,0 +1,4 @@
+---
+title: clean up merge request widget UI
+merge_request:
+author:
diff --git a/changelogs/unreleased/33874_confi.yml b/changelogs/unreleased/33874_confi.yml
new file mode 100644
index 00000000000..940753d9aaa
--- /dev/null
+++ b/changelogs/unreleased/33874_confi.yml
@@ -0,0 +1,5 @@
+---
+title: Update confidential issue UI - add confidential visibility and settings to
+ sidebar
+merge_request:
+author:
diff --git a/changelogs/unreleased/34028-collapse-sidebar.yml b/changelogs/unreleased/34028-collapse-sidebar.yml
new file mode 100644
index 00000000000..468212240ac
--- /dev/null
+++ b/changelogs/unreleased/34028-collapse-sidebar.yml
@@ -0,0 +1,4 @@
+---
+title: Make contextual sidebar collapsible
+merge_request:
+author:
diff --git a/changelogs/unreleased/34339-user_avatar-url-in-push-event-webhook-json-payload-is-relative-should-be-absolute.yml b/changelogs/unreleased/34339-user_avatar-url-in-push-event-webhook-json-payload-is-relative-should-be-absolute.yml
new file mode 100644
index 00000000000..13f28da8577
--- /dev/null
+++ b/changelogs/unreleased/34339-user_avatar-url-in-push-event-webhook-json-payload-is-relative-should-be-absolute.yml
@@ -0,0 +1,4 @@
+---
+title: Use full path of user's avatar in webhooks
+merge_request: 13401
+author: Vitaliy @blackst0ne Klachkov
diff --git a/changelogs/unreleased/34492-firefox-job.yml b/changelogs/unreleased/34492-firefox-job.yml
deleted file mode 100644
index 881b8f649ea..00000000000
--- a/changelogs/unreleased/34492-firefox-job.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Use jQuery to control scroll behavior in job log for cross browser consistency
-merge_request:
-author:
diff --git a/changelogs/unreleased/34527-make-edit-comment-button-always-available-outside-of-dropdown.yml b/changelogs/unreleased/34527-make-edit-comment-button-always-available-outside-of-dropdown.yml
new file mode 100644
index 00000000000..08171f6bcec
--- /dev/null
+++ b/changelogs/unreleased/34527-make-edit-comment-button-always-available-outside-of-dropdown.yml
@@ -0,0 +1,4 @@
+---
+title: move edit comment button outside of dropdown
+merge_request:
+author:
diff --git a/changelogs/unreleased/34764-rename-to-overview.yml b/changelogs/unreleased/34764-rename-to-overview.yml
new file mode 100644
index 00000000000..5b9643285b7
--- /dev/null
+++ b/changelogs/unreleased/34764-rename-to-overview.yml
@@ -0,0 +1,4 @@
+---
+title: Rename about to overview for group and project page
+merge_request:
+author:
diff --git a/changelogs/unreleased/35098-raise-encoding-confidence-threshold.yml b/changelogs/unreleased/35098-raise-encoding-confidence-threshold.yml
new file mode 100644
index 00000000000..3cdb3011f5b
--- /dev/null
+++ b/changelogs/unreleased/35098-raise-encoding-confidence-threshold.yml
@@ -0,0 +1,4 @@
+---
+title: Raise guessed encoding confidence threshold to 50
+merge_request: 12990
+author:
diff --git a/changelogs/unreleased/35136-barchart-not-display-label-at-0-hour.yml b/changelogs/unreleased/35136-barchart-not-display-label-at-0-hour.yml
new file mode 100644
index 00000000000..ea8f31cca9d
--- /dev/null
+++ b/changelogs/unreleased/35136-barchart-not-display-label-at-0-hour.yml
@@ -0,0 +1,4 @@
+---
+title: Fix bar chart does not display label at 0 hour
+merge_request: 35136
+author: Jason Dai
diff --git a/changelogs/unreleased/35483-improve-mobile-sidebar.yml b/changelogs/unreleased/35483-improve-mobile-sidebar.yml
new file mode 100644
index 00000000000..eb3dab1da9e
--- /dev/null
+++ b/changelogs/unreleased/35483-improve-mobile-sidebar.yml
@@ -0,0 +1,4 @@
+---
+title: Improve mobile sidebar
+merge_request:
+author:
diff --git a/changelogs/unreleased/35659-rename-pipeline.yml b/changelogs/unreleased/35659-rename-pipeline.yml
new file mode 100644
index 00000000000..0fe211868e4
--- /dev/null
+++ b/changelogs/unreleased/35659-rename-pipeline.yml
@@ -0,0 +1,4 @@
+---
+title: Rename Pipelines tab to CI / CD in new navigation
+merge_request:
+author:
diff --git a/changelogs/unreleased/35697-allow-logged-in-user-to-read-user-list.yml b/changelogs/unreleased/35697-allow-logged-in-user-to-read-user-list.yml
deleted file mode 100644
index 54b2e71bef9..00000000000
--- a/changelogs/unreleased/35697-allow-logged-in-user-to-read-user-list.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Allow any logged in users to read_users_list even if it's restricted
-merge_request: 13201
-author:
diff --git a/changelogs/unreleased/35761-convdev-perc.yml b/changelogs/unreleased/35761-convdev-perc.yml
new file mode 100644
index 00000000000..319c4d18219
--- /dev/null
+++ b/changelogs/unreleased/35761-convdev-perc.yml
@@ -0,0 +1,4 @@
+---
+title: Store & use ConvDev percentages returned by the Version app
+merge_request:
+author:
diff --git a/changelogs/unreleased/36010-api-v4-allows-setting-a-branch-that-doesn-t-exist-as-the-default-one.yml b/changelogs/unreleased/36010-api-v4-allows-setting-a-branch-that-doesn-t-exist-as-the-default-one.yml
new file mode 100644
index 00000000000..04791e09b84
--- /dev/null
+++ b/changelogs/unreleased/36010-api-v4-allows-setting-a-branch-that-doesn-t-exist-as-the-default-one.yml
@@ -0,0 +1,4 @@
+---
+title: Add checks for branch existence before changing HEAD
+merge_request: 13359
+author: Vitaliy @blackst0ne Klachkov
diff --git a/changelogs/unreleased/36119-issuable-workers.yml b/changelogs/unreleased/36119-issuable-workers.yml
new file mode 100644
index 00000000000..beb01ae5b1a
--- /dev/null
+++ b/changelogs/unreleased/36119-issuable-workers.yml
@@ -0,0 +1,4 @@
+---
+title: Simplify checking if objects exist code in new issaubles workers
+merge_request:
+author:
diff --git a/changelogs/unreleased/36185-or-separator.yml b/changelogs/unreleased/36185-or-separator.yml
new file mode 100644
index 00000000000..4e46e60ea1b
--- /dev/null
+++ b/changelogs/unreleased/36185-or-separator.yml
@@ -0,0 +1,4 @@
+---
+title: Align OR separator to center in new project page
+merge_request:
+author:
diff --git a/changelogs/unreleased/36213-return-is_admin-in-users-api-when-current_user-is-admin.yml b/changelogs/unreleased/36213-return-is_admin-in-users-api-when-current_user-is-admin.yml
new file mode 100644
index 00000000000..b51b5e58b39
--- /dev/null
+++ b/changelogs/unreleased/36213-return-is_admin-in-users-api-when-current_user-is-admin.yml
@@ -0,0 +1,6 @@
+---
+title: Include the `is_admin` field in the `GET /users/:id` API when current user
+ is an admin
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/36385-pipeline-graph-dropdown.yml b/changelogs/unreleased/36385-pipeline-graph-dropdown.yml
new file mode 100644
index 00000000000..1a43c66debd
--- /dev/null
+++ b/changelogs/unreleased/36385-pipeline-graph-dropdown.yml
@@ -0,0 +1,5 @@
+---
+title: Prevents jobs dropdown from closing in pipeline graph
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/3686_make_tarball_download_url.yml b/changelogs/unreleased/3686_make_tarball_download_url.yml
new file mode 100644
index 00000000000..4e75e52e3ac
--- /dev/null
+++ b/changelogs/unreleased/3686_make_tarball_download_url.yml
@@ -0,0 +1,4 @@
+---
+title: repository archive download url now ends with selected file extension
+merge_request: 13178
+author: haseebeqx
diff --git a/changelogs/unreleased/add-star-for-action-scope.yml b/changelogs/unreleased/add-star-for-action-scope.yml
new file mode 100644
index 00000000000..a8119a01ec4
--- /dev/null
+++ b/changelogs/unreleased/add-star-for-action-scope.yml
@@ -0,0 +1,4 @@
+---
+title: Add star for action scope, in order to delete image from registry
+merge_request: 13248
+author: jean
diff --git a/changelogs/unreleased/appearances-caching-and-schema.yml b/changelogs/unreleased/appearances-caching-and-schema.yml
new file mode 100644
index 00000000000..5743f6e0f2d
--- /dev/null
+++ b/changelogs/unreleased/appearances-caching-and-schema.yml
@@ -0,0 +1,4 @@
+---
+title: Cache Appearance instances in Redis
+merge_request:
+author:
diff --git a/changelogs/unreleased/broadcast-messages-cache.yml b/changelogs/unreleased/broadcast-messages-cache.yml
new file mode 100644
index 00000000000..a3c9e1ff465
--- /dev/null
+++ b/changelogs/unreleased/broadcast-messages-cache.yml
@@ -0,0 +1,4 @@
+---
+title: Better caching and indexing of broadcast messages
+merge_request:
+author:
diff --git a/changelogs/unreleased/bump-omniauth-ldap-gem-version-2-0-4.yml b/changelogs/unreleased/bump-omniauth-ldap-gem-version-2-0-4.yml
new file mode 100644
index 00000000000..7571999fa75
--- /dev/null
+++ b/changelogs/unreleased/bump-omniauth-ldap-gem-version-2-0-4.yml
@@ -0,0 +1,4 @@
+---
+title: Bumps omniauth-ldap gem version to 2.0.4
+merge_request: 13465
+author:
diff --git a/changelogs/unreleased/bvl-nfs-circuitbreaker.yml b/changelogs/unreleased/bvl-nfs-circuitbreaker.yml
new file mode 100644
index 00000000000..151854ed31f
--- /dev/null
+++ b/changelogs/unreleased/bvl-nfs-circuitbreaker.yml
@@ -0,0 +1,4 @@
+---
+title: Block access to failing repository storage
+merge_request: 11449
+author:
diff --git a/changelogs/unreleased/bvl-rollback-renamed-system-namespace.yml b/changelogs/unreleased/bvl-rollback-renamed-system-namespace.yml
new file mode 100644
index 00000000000..a24cc7a1c43
--- /dev/null
+++ b/changelogs/unreleased/bvl-rollback-renamed-system-namespace.yml
@@ -0,0 +1,4 @@
+---
+title: Don't rename namespace called system when upgrading from 9.1.x to 9.5
+merge_request: 13228
+author:
diff --git a/changelogs/unreleased/diff-changed-files-dropdown.yml b/changelogs/unreleased/diff-changed-files-dropdown.yml
new file mode 100644
index 00000000000..2d2a26ffea2
--- /dev/null
+++ b/changelogs/unreleased/diff-changed-files-dropdown.yml
@@ -0,0 +1,4 @@
+---
+title: Moved diff changed files into a dropdown
+merge_request:
+author:
diff --git a/changelogs/unreleased/disable-project-export.yml b/changelogs/unreleased/disable-project-export.yml
new file mode 100644
index 00000000000..d7ca9f46193
--- /dev/null
+++ b/changelogs/unreleased/disable-project-export.yml
@@ -0,0 +1,4 @@
+---
+title: Add option to disable project export on instance
+merge_request: 13211
+author: Robin Bobbitt
diff --git a/changelogs/unreleased/dont-use-limit-offset-when-counting-projects.yml b/changelogs/unreleased/dont-use-limit-offset-when-counting-projects.yml
new file mode 100644
index 00000000000..8ecea635ce5
--- /dev/null
+++ b/changelogs/unreleased/dont-use-limit-offset-when-counting-projects.yml
@@ -0,0 +1,4 @@
+---
+title: "Improve performance of checking for projects on the projects dashboard"
+merge_request:
+author:
diff --git a/changelogs/unreleased/eager-load-project-creators-for-project-dashboards.yml b/changelogs/unreleased/eager-load-project-creators-for-project-dashboards.yml
new file mode 100644
index 00000000000..e550e0b2f44
--- /dev/null
+++ b/changelogs/unreleased/eager-load-project-creators-for-project-dashboards.yml
@@ -0,0 +1,4 @@
+---
+title: Eager load project creators for project dashboards
+merge_request:
+author:
diff --git a/changelogs/unreleased/fix-group-milestone-link-in-issuable-sidebar.yml b/changelogs/unreleased/fix-group-milestone-link-in-issuable-sidebar.yml
deleted file mode 100644
index 1558e575e6d..00000000000
--- a/changelogs/unreleased/fix-group-milestone-link-in-issuable-sidebar.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix links to group milestones from issue and merge request sidebar
-merge_request:
-author:
diff --git a/changelogs/unreleased/fix-import-symbolink-links.yml b/changelogs/unreleased/fix-import-symbolink-links.yml
new file mode 100644
index 00000000000..36e73821bdc
--- /dev/null
+++ b/changelogs/unreleased/fix-import-symbolink-links.yml
@@ -0,0 +1,4 @@
+---
+title: Remove hidden symlinks from project import files
+merge_request:
+author:
diff --git a/changelogs/unreleased/fix-thread-safe-gpgme-tmp-directory.yml b/changelogs/unreleased/fix-thread-safe-gpgme-tmp-directory.yml
new file mode 100644
index 00000000000..66b5b6b4f47
--- /dev/null
+++ b/changelogs/unreleased/fix-thread-safe-gpgme-tmp-directory.yml
@@ -0,0 +1,4 @@
+---
+title: Make GPGME temporary directory handling thread safe
+merge_request: 13481
+author: Alexis Reigel
diff --git a/changelogs/unreleased/github.yml b/changelogs/unreleased/github.yml
new file mode 100644
index 00000000000..585b9b13b65
--- /dev/null
+++ b/changelogs/unreleased/github.yml
@@ -0,0 +1,4 @@
+---
+title: Reduce memory usage of the GitHub importer
+merge_request: 12886
+author:
diff --git a/changelogs/unreleased/group-milestone-references-system-notes.yml b/changelogs/unreleased/group-milestone-references-system-notes.yml
new file mode 100644
index 00000000000..58215352305
--- /dev/null
+++ b/changelogs/unreleased/group-milestone-references-system-notes.yml
@@ -0,0 +1,4 @@
+---
+title: Support Markdown references, autocomplete, and quick actions for group milestones
+merge_request:
+author:
diff --git a/changelogs/unreleased/group-new-issue.yml b/changelogs/unreleased/group-new-issue.yml
new file mode 100644
index 00000000000..5480a44526b
--- /dev/null
+++ b/changelogs/unreleased/group-new-issue.yml
@@ -0,0 +1,4 @@
+---
+title: Cache recent projects for group-level new resource creation.
+merge_request: !13058
+author:
diff --git a/changelogs/unreleased/memoize-user-personal-projects-count.yml b/changelogs/unreleased/memoize-user-personal-projects-count.yml
new file mode 100644
index 00000000000..3839a97f185
--- /dev/null
+++ b/changelogs/unreleased/memoize-user-personal-projects-count.yml
@@ -0,0 +1,4 @@
+---
+title: Memoize the number of personal projects a user has to reduce COUNT queries
+merge_request:
+author:
diff --git a/changelogs/unreleased/migrate-events-into-a-new-format.yml b/changelogs/unreleased/migrate-events-into-a-new-format.yml
new file mode 100644
index 00000000000..8a29f75323f
--- /dev/null
+++ b/changelogs/unreleased/migrate-events-into-a-new-format.yml
@@ -0,0 +1,4 @@
+---
+title: Migrate events into a new format to reduce the storage necessary and improve performance
+merge_request:
+author:
diff --git a/changelogs/unreleased/mk-fix-deploy-key-deletion.yml b/changelogs/unreleased/mk-fix-deploy-key-deletion.yml
deleted file mode 100644
index 9ff2e49b14c..00000000000
--- a/changelogs/unreleased/mk-fix-deploy-key-deletion.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix deletion of deploy keys linked to other projects
-merge_request: 13162
-author:
diff --git a/changelogs/unreleased/pagination-projects-explore.yml b/changelogs/unreleased/pagination-projects-explore.yml
new file mode 100644
index 00000000000..dc9c4218793
--- /dev/null
+++ b/changelogs/unreleased/pagination-projects-explore.yml
@@ -0,0 +1,4 @@
+---
+title: Use Prev/Next pagination for exploring projects
+merge_request:
+author:
diff --git a/changelogs/unreleased/pawel-add-sidekiq-metrics-endpoint-32145.yml b/changelogs/unreleased/pawel-add-sidekiq-metrics-endpoint-32145.yml
new file mode 100644
index 00000000000..71eabdc16d2
--- /dev/null
+++ b/changelogs/unreleased/pawel-add-sidekiq-metrics-endpoint-32145.yml
@@ -0,0 +1,4 @@
+---
+title: Add Prometheus metrics exporter to Sidekiq
+merge_request: 13082
+author:
diff --git a/changelogs/unreleased/project-foreign-keys-without-errors.yml b/changelogs/unreleased/project-foreign-keys-without-errors.yml
deleted file mode 100644
index 63c53c8ad8f..00000000000
--- a/changelogs/unreleased/project-foreign-keys-without-errors.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Change project FK migration to skip existing FKs
-merge_request:
-author:
diff --git a/changelogs/unreleased/rc-fix-branches-api-endpoint.yml b/changelogs/unreleased/rc-fix-branches-api-endpoint.yml
index a8f49298258..b36663bbe91 100644
--- a/changelogs/unreleased/rc-fix-branches-api-endpoint.yml
+++ b/changelogs/unreleased/rc-fix-branches-api-endpoint.yml
@@ -1,5 +1,5 @@
---
title: Fix the /projects/:id/repository/branches endpoint to handle dots in the branch
- name when the project full patch contains a `/`
+ name when the project full path contains a `/`
merge_request: 13115
author:
diff --git a/changelogs/unreleased/rc-fix-commits-api.yml b/changelogs/unreleased/rc-fix-commits-api.yml
new file mode 100644
index 00000000000..215429eaf6b
--- /dev/null
+++ b/changelogs/unreleased/rc-fix-commits-api.yml
@@ -0,0 +1,5 @@
+---
+title: Fix the /projects/:id/repository/commits endpoint to handle dots in the ref
+ name when the project full path contains a `/`
+merge_request: 13370
+author:
diff --git a/changelogs/unreleased/rc-fix-tags-api.yml b/changelogs/unreleased/rc-fix-tags-api.yml
new file mode 100644
index 00000000000..0a7dd5ca6ab
--- /dev/null
+++ b/changelogs/unreleased/rc-fix-tags-api.yml
@@ -0,0 +1,5 @@
+---
+title: Fix the /projects/:id/repository/tags endpoint to handle dots in the tag name
+ when the project full path contains a `/`
+merge_request: 13368
+author:
diff --git a/changelogs/unreleased/remove-redundant-query-when-retrieving-recent-pushes.yml b/changelogs/unreleased/remove-redundant-query-when-retrieving-recent-pushes.yml
new file mode 100644
index 00000000000..83934217e6a
--- /dev/null
+++ b/changelogs/unreleased/remove-redundant-query-when-retrieving-recent-pushes.yml
@@ -0,0 +1,4 @@
+---
+title: Remove redundant query when retrieving the most recent push of a user
+merge_request:
+author:
diff --git a/changelogs/unreleased/reorganise-issues-indexes-for-sorting.yml b/changelogs/unreleased/reorganise-issues-indexes-for-sorting.yml
new file mode 100644
index 00000000000..5bfe55e562f
--- /dev/null
+++ b/changelogs/unreleased/reorganise-issues-indexes-for-sorting.yml
@@ -0,0 +1,4 @@
+---
+title: Re-organise "issues" indexes for faster ordering
+merge_request:
+author:
diff --git a/changelogs/unreleased/restrict-haml-javascript.yml b/changelogs/unreleased/restrict-haml-javascript.yml
new file mode 100644
index 00000000000..3d0a52f416d
--- /dev/null
+++ b/changelogs/unreleased/restrict-haml-javascript.yml
@@ -0,0 +1,4 @@
+---
+title: Add custom linter for inline JavaScript to haml_lint
+merge_request: 9742
+author: winniehell
diff --git a/changelogs/unreleased/rs-alphanumeric-ssh-params.yml b/changelogs/unreleased/rs-alphanumeric-ssh-params.yml
new file mode 100644
index 00000000000..426b01cafad
--- /dev/null
+++ b/changelogs/unreleased/rs-alphanumeric-ssh-params.yml
@@ -0,0 +1,5 @@
+---
+title: Disallow Git URLs that include a username or hostname beginning with a non-alphanumeric
+ character
+merge_request:
+author:
diff --git a/changelogs/unreleased/search-flickering.yml b/changelogs/unreleased/search-flickering.yml
deleted file mode 100644
index 951a5a0292a..00000000000
--- a/changelogs/unreleased/search-flickering.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix search box losing focus when typing
-merge_request:
-author:
diff --git a/changelogs/unreleased/seven-days-cycle-analytics.yml b/changelogs/unreleased/seven-days-cycle-analytics.yml
new file mode 100644
index 00000000000..ff660bdd603
--- /dev/null
+++ b/changelogs/unreleased/seven-days-cycle-analytics.yml
@@ -0,0 +1,5 @@
+---
+title: Add a `Last 7 days` option for Cycle Analytics view
+merge_request: 13443
+author: Mehdi Lahmam (@mehlah)
+type: added
diff --git a/changelogs/unreleased/tc-fix-wildcard-protected-delete-merged.yml b/changelogs/unreleased/tc-fix-wildcard-protected-delete-merged.yml
deleted file mode 100644
index 9ca5f81cf79..00000000000
--- a/changelogs/unreleased/tc-fix-wildcard-protected-delete-merged.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Make Delete Merged Branches handle wildcard protected branches correctly
-merge_request: 13251
-author:
diff --git a/changelogs/unreleased/tc-no-todo-service-select.yml b/changelogs/unreleased/tc-no-todo-service-select.yml
new file mode 100644
index 00000000000..ddcae334aa7
--- /dev/null
+++ b/changelogs/unreleased/tc-no-todo-service-select.yml
@@ -0,0 +1,4 @@
+---
+title: Avoid plucking Todo ids in TodoService
+merge_request: 10845
+author:
diff --git a/changelogs/unreleased/use-a-specialized-class-for-querying-events.yml b/changelogs/unreleased/use-a-specialized-class-for-querying-events.yml
new file mode 100644
index 00000000000..6c1ec10aa12
--- /dev/null
+++ b/changelogs/unreleased/use-a-specialized-class-for-querying-events.yml
@@ -0,0 +1,4 @@
+---
+title: Use a specialized class for querying events to improve performance
+merge_request:
+author:
diff --git a/changelogs/unreleased/wiki_title.yml b/changelogs/unreleased/wiki_title.yml
new file mode 100644
index 00000000000..3ef5fa2969b
--- /dev/null
+++ b/changelogs/unreleased/wiki_title.yml
@@ -0,0 +1,4 @@
+---
+title: Allow wiki pages to be renamed in the UI
+merge_request: 10069
+author: wendy0402
diff --git a/changelogs/unreleased/zj-project-templates.yml b/changelogs/unreleased/zj-project-templates.yml
new file mode 100644
index 00000000000..ab6e0f2d5f2
--- /dev/null
+++ b/changelogs/unreleased/zj-project-templates.yml
@@ -0,0 +1,4 @@
+---
+title: Projects can be created from templates
+merge_request: 13108
+author:
diff --git a/config/application.rb b/config/application.rb
index 1c13cc81270..f69dab4de39 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -23,13 +23,13 @@ module Gitlab
# https://github.com/rails/rails/blob/v4.2.6/railties/lib/rails/engine.rb#L687
# This is a nice reference article on autoloading/eager loading:
# http://blog.arkency.com/2014/11/dont-forget-about-eager-load-when-extending-autoload
- config.eager_load_paths.push(*%W(#{config.root}/lib
+ config.eager_load_paths.push(*%W[#{config.root}/lib
#{config.root}/app/models/hooks
#{config.root}/app/models/members
#{config.root}/app/models/project_services
#{config.root}/app/workers/concerns
#{config.root}/app/services/concerns
- #{config.root}/app/finders/concerns))
+ #{config.root}/app/finders/concerns])
config.generators.templates.push("#{config.root}/generator_templates")
@@ -176,12 +176,16 @@ module Gitlab
next unless name.include?('namespace_project')
define_method(name.sub('namespace_project', 'project')) do |project, *args|
- send(name, project&.namespace, project, *args)
+ send(name, project&.namespace, project, *args) # rubocop:disable GitlabSecurity/PublicSend
end
end
end
+ # We add the MilestonesRoutingHelper because we know that this does not
+ # conflict with the methods defined in `project_url_helpers`, and we want
+ # these methods available in the same places.
Gitlab::Routing.add_helpers(project_url_helpers)
+ Gitlab::Routing.add_helpers(MilestonesRoutingHelper)
end
end
end
diff --git a/config/dependency_decisions.yml b/config/dependency_decisions.yml
index 59c7050a14d..ca5b941aebf 100644
--- a/config/dependency_decisions.yml
+++ b/config/dependency_decisions.yml
@@ -398,3 +398,9 @@
:why: https://github.com/remy/undefsafe/blob/master/LICENSE
:versions: []
:when: 2017-04-10 06:30:00.002555000 Z
+- - :approve
+ - thunky
+ - :who: Mike Greiling
+ :why: https://github.com/mafintosh/thunky/blob/master/README.md#license
+ :versions: []
+ :when: 2017-08-07 05:56:09.907045000 Z
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index 73a68c6da1b..e73db08fcac 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -506,6 +506,11 @@ production: &base
path: /home/git/repositories/
gitaly_address: unix:/home/git/gitlab/tmp/sockets/private/gitaly.socket # TCP connections are supported too (e.g. tcp://host:port)
# gitaly_token: 'special token' # Optional: override global gitaly.token for this storage.
+ failure_count_threshold: 10 # number of failures before stopping attempts
+ failure_wait_time: 30 # Seconds after an access failure before allowing access again
+ failure_reset_time: 1800 # Time in seconds to expire failures
+ storage_timeout: 5 # Time in seconds to wait before aborting a storage access attempt
+
## Backup settings
backup:
@@ -585,6 +590,12 @@ production: &base
ip_whitelist:
- 127.0.0.0/8
+ # Sidekiq exporter is webserver built in to Sidekiq to expose Prometheus metrics
+ sidekiq_exporter:
+ # enabled: true
+ # address: localhost
+ # port: 3807
+
#
# 5. Extra customization
# ==========================
@@ -638,6 +649,10 @@ test:
default:
path: tmp/tests/repositories/
gitaly_address: unix:tmp/tests/gitaly/gitaly.socket
+ broken:
+ path: tmp/tests/non-existent-repositories
+ gitaly_address: unix:tmp/tests/gitaly/gitaly.socket
+
gitaly:
enabled: true
token: secret
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 63f4c8c9e0a..5c6578d3531 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -71,7 +71,7 @@ class Settings < Settingslogic
# check that `current` (string or integer) is a contant in `modul`.
def verify_constant(modul, current, default)
- constant = modul.constants.find{ |name| modul.const_get(name) == current }
+ constant = modul.constants.find { |name| modul.const_get(name) == current }
value = constant.nil? ? default : modul.const_get(constant)
if current.is_a? String
value = modul.const_get(current.upcase) rescue default
@@ -395,6 +395,10 @@ Settings.cron_jobs['remove_old_web_hook_logs_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['remove_old_web_hook_logs_worker']['cron'] ||= '40 0 * * *'
Settings.cron_jobs['remove_old_web_hook_logs_worker']['job_class'] = 'RemoveOldWebHookLogsWorker'
+Settings.cron_jobs['stuck_merge_jobs_worker'] ||= Settingslogic.new({})
+Settings.cron_jobs['stuck_merge_jobs_worker']['cron'] ||= '0 */2 * * *'
+Settings.cron_jobs['stuck_merge_jobs_worker']['job_class'] = 'StuckMergeJobsWorker'
+
#
# GitLab Shell
#
@@ -433,6 +437,17 @@ end
Settings.repositories.storages.values.each do |storage|
# Expand relative paths
storage['path'] = Settings.absolute(storage['path'])
+ # Set failure defaults
+ storage['failure_count_threshold'] ||= 10
+ storage['failure_wait_time'] ||= 30
+ storage['failure_reset_time'] ||= 1800
+ storage['storage_timeout'] ||= 5
+ # Set turn strings into numbers
+ storage['failure_count_threshold'] = storage['failure_count_threshold'].to_i
+ storage['failure_wait_time'] = storage['failure_wait_time'].to_i
+ storage['failure_reset_time'] = storage['failure_reset_time'].to_i
+ # We might want to have a timeout shorter than 1 second.
+ storage['storage_timeout'] = storage['storage_timeout'].to_f
end
#
@@ -513,6 +528,10 @@ Settings.webpack.dev_server['port'] ||= 3808
Settings['monitoring'] ||= Settingslogic.new({})
Settings.monitoring['ip_whitelist'] ||= ['127.0.0.1/8']
Settings.monitoring['unicorn_sampler_interval'] ||= 10
+Settings.monitoring['sidekiq_exporter'] ||= Settingslogic.new({})
+Settings.monitoring.sidekiq_exporter['enabled'] ||= false
+Settings.monitoring.sidekiq_exporter['address'] ||= 'localhost'
+Settings.monitoring.sidekiq_exporter['port'] ||= 3807
#
# Testing settings
diff --git a/config/initializers/6_validations.rb b/config/initializers/6_validations.rb
index 9e24f42d284..92ce4dd03cd 100644
--- a/config/initializers/6_validations.rb
+++ b/config/initializers/6_validations.rb
@@ -7,6 +7,13 @@ def find_parent_path(name, path)
Gitlab.config.repositories.storages.detect do |n, rs|
name != n && Pathname.new(rs['path']).realpath == parent
end
+rescue Errno::EIO, Errno::ENOENT => e
+ warning = "WARNING: couldn't verify #{path} (#{name}). "\
+ "If this is an external storage, it might be offline."
+ message = "#{warning}\n#{e.message}"
+ Rails.logger.error("#{message}\n\t" + e.backtrace.join("\n\t"))
+
+ nil
end
def storage_validation_error(message)
@@ -29,6 +36,15 @@ def validate_storages_config
if !repository_storage.is_a?(Hash) || repository_storage['path'].nil?
storage_validation_error("#{name} is not a valid storage, because it has no `path` key. Refer to gitlab.yml.example for an updated example")
end
+
+ %w(failure_count_threshold failure_wait_time failure_reset_time storage_timeout).each do |setting|
+ # Falling back to the defaults is fine!
+ next if repository_storage[setting].nil?
+
+ unless repository_storage[setting].to_f > 0
+ storage_validation_error("#{setting}, for storage `#{name}` needs to be greater than 0")
+ end
+ end
end
end
diff --git a/config/initializers/7_prometheus_metrics.rb b/config/initializers/7_prometheus_metrics.rb
index a2f8421f5d7..54c797e0714 100644
--- a/config/initializers/7_prometheus_metrics.rb
+++ b/config/initializers/7_prometheus_metrics.rb
@@ -10,3 +10,9 @@ Prometheus::Client.configure do |config|
config.multiprocess_files_dir ||= Rails.root.join('tmp/prometheus_multiproc_dir')
end
end
+
+Sidekiq.configure_server do |config|
+ config.on(:startup) do
+ Gitlab::Metrics::SidekiqMetricsExporter.instance.start
+ end
+end
diff --git a/config/initializers/active_record_locking.rb b/config/initializers/active_record_locking.rb
index 9266ff0f615..150aaa2a8c2 100644
--- a/config/initializers/active_record_locking.rb
+++ b/config/initializers/active_record_locking.rb
@@ -18,7 +18,7 @@ module ActiveRecord
lock_col = self.class.locking_column
- previous_lock_value = send(lock_col).to_i
+ previous_lock_value = send(lock_col).to_i # rubocop:disable GitlabSecurity/PublicSend
# This line is added as a patch
previous_lock_value = nil if previous_lock_value == '0' || previous_lock_value == 0
@@ -48,7 +48,7 @@ module ActiveRecord
# If something went wrong, revert the version.
rescue Exception
- send(lock_col + '=', previous_lock_value)
+ send(lock_col + '=', previous_lock_value) # rubocop:disable GitlabSecurity/PublicSend
raise
end
end
diff --git a/config/prometheus/additional_metrics.yml b/config/prometheus/additional_metrics.yml
index 5eb01d62924..0642a0b2fe9 100644
--- a/config/prometheus/additional_metrics.yml
+++ b/config/prometheus/additional_metrics.yml
@@ -1,3 +1,33 @@
+- group: Response metrics (NGINX Ingress)
+ priority: 10
+ metrics:
+ - title: "Throughput"
+ y_label: "Requests / Sec"
+ required_metrics:
+ - nginx_upstream_requests_total
+ weight: 1
+ queries:
+ - query_range: 'sum(rate(nginx_upstream_requests_total{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m]))'
+ label: Total
+ unit: req / sec
+ - title: "Latency"
+ y_label: "Latency (ms)"
+ required_metrics:
+ - nginx_upstream_response_msecs_avg
+ weight: 1
+ queries:
+ - query_range: 'avg(nginx_upstream_response_msecs_avg{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"})'
+ label: Average
+ unit: ms
+ - title: "HTTP Error Rate"
+ y_label: "HTTP 500 Errors / Sec"
+ required_metrics:
+ - nginx_upstream_responses_total
+ weight: 1
+ queries:
+ - query_range: 'sum(rate(nginx_upstream_responses_total{status_code="5xx", upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m]))'
+ label: HTTP Errors
+ unit: "errors / sec"
- group: Response metrics (HA Proxy)
priority: 10
metrics:
@@ -68,18 +98,18 @@
- nginx_upstream_response_msecs_avg
weight: 1
queries:
- - query_range: 'avg(nginx_upstream_response_msecs_avg{%{environment_filter}}) * 1000'
+ - query_range: 'avg(nginx_upstream_response_msecs_avg{%{environment_filter}})'
label: Upstream
unit: ms
- title: "HTTP Error Rate"
- y_label: "Error Rate (%)"
+ y_label: "HTTP 500 Errors / Sec"
required_metrics:
- nginx_responses_total
weight: 1
queries:
- - query_range: 'sum(rate(nginx_responses_total{status_code="5xx", %{environment_filter}}[2m])) / sum(rate(nginx_requests_total{server_zone!="*", server_zone!="_", %{environment_filter}}[2m]))'
+ - query_range: 'sum(rate(nginx_responses_total{status_code="5xx", %{environment_filter}}[2m]))'
label: HTTP Errors
- unit: "%"
+ unit: "errors / sec"
- group: System metrics (Kubernetes)
priority: 5
metrics:
diff --git a/config/routes/admin.rb b/config/routes/admin.rb
index 5427bab93ce..c0748231813 100644
--- a/config/routes/admin.rb
+++ b/config/routes/admin.rb
@@ -67,7 +67,9 @@ namespace :admin do
end
resource :logs, only: [:show]
- resource :health_check, controller: 'health_check', only: [:show]
+ resource :health_check, controller: 'health_check', only: [:show] do
+ post :reset_storage_health
+ end
resource :background_jobs, controller: 'background_jobs', only: [:show]
resource :system_info, controller: 'system_info', only: [:show]
resources :requests_profiles, only: [:index, :show], param: :name, constraints: { name: /.+\.html/ }
diff --git a/config/routes/repository.rb b/config/routes/repository.rb
index edcf3ddf57b..57b7c55423d 100644
--- a/config/routes/repository.rb
+++ b/config/routes/repository.rb
@@ -2,7 +2,10 @@
resource :repository, only: [:create] do
member do
- get 'archive', constraints: { format: Gitlab::PathRegex.archive_formats_regex }
+ get ':ref/archive', constraints: { format: Gitlab::PathRegex.archive_formats_regex, ref: /.+/ }, action: 'archive', as: 'archive'
+
+ # deprecated since GitLab 9.5
+ get 'archive', constraints: { format: Gitlab::PathRegex.archive_formats_regex }, as: 'archive_alternative'
end
end
diff --git a/config/routes/uploads.rb b/config/routes/uploads.rb
index e9c9aa8b2f9..d7bca8310e4 100644
--- a/config/routes/uploads.rb
+++ b/config/routes/uploads.rb
@@ -5,12 +5,12 @@ scope path: :uploads do
constraints: { model: /note|user|group|project/, mounted_as: /avatar|attachment/, filename: /[^\/]+/ }
# show uploads for models, snippets (notes) available for now
- get 'system/:model/:id/:secret/:filename',
+ get '-/system/:model/:id/:secret/:filename',
to: 'uploads#show',
constraints: { model: /personal_snippet/, id: /\d+/, filename: /[^\/]+/ }
# show temporary uploads
- get 'system/temp/:secret/:filename',
+ get '-/system/temp/:secret/:filename',
to: 'uploads#show',
constraints: { filename: /[^\/]+/ }
diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml
index 7496bfa4fbb..83abc83c9f0 100644
--- a/config/sidekiq_queues.yml
+++ b/config/sidekiq_queues.yml
@@ -23,6 +23,8 @@
- [update_merge_requests, 3]
- [process_commit, 3]
- [new_note, 2]
+ - [new_issue, 2]
+ - [new_merge_request, 2]
- [build, 2]
- [pipeline, 2]
- [gitlab_shell, 2]
diff --git a/config/webpack.config.js b/config/webpack.config.js
index 8b0c64f9289..8e1b80cd39f 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -3,7 +3,8 @@
var fs = require('fs');
var path = require('path');
var webpack = require('webpack');
-var StatsPlugin = require('stats-webpack-plugin');
+var StatsWriterPlugin = require('webpack-stats-plugin').StatsWriterPlugin;
+var CopyWebpackPlugin = require('copy-webpack-plugin');
var CompressionPlugin = require('compression-webpack-plugin');
var NameAllModulesPlugin = require('name-all-modules-plugin');
var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
@@ -60,10 +61,12 @@ var config = {
pipelines_details: './pipelines/pipeline_details_bundle.js',
pipelines_times: './pipelines/pipelines_times.js',
profile: './profile/profile_bundle.js',
+ project_import_gl: './projects/project_import_gitlab_project.js',
project_new: './projects/project_new.js',
prometheus_metrics: './prometheus_metrics',
protected_branches: './protected_branches',
protected_tags: './protected_tags',
+ repo: './repo/index.js',
sidebar: './sidebar/sidebar_bundle.js',
schedule_form: './pipeline_schedules/pipeline_schedule_form_bundle.js',
schedules_index: './pipeline_schedules/pipeline_schedules_index_bundle.js',
@@ -110,26 +113,44 @@ var config = {
options: { limit: 2048 },
},
{
- test: /\.(worker\.js|pdf|bmpr)$/,
+ test: /\.(worker(\.min)?\.js|pdf|bmpr)$/,
exclude: /node_modules/,
loader: 'file-loader',
+ options: {
+ name: '[name].[hash].[ext]',
+ }
},
{
test: /locale\/\w+\/(.*)\.js$/,
loader: 'exports-loader?locales',
},
- ]
+ {
+ test: /monaco-editor\/\w+\/vs\/loader\.js$/,
+ use: [
+ { loader: 'exports-loader', options: 'l.global' },
+ { loader: 'imports-loader', options: 'l=>{},this=>l,AMDLoader=>this,module=>undefined' },
+ ],
+ }
+ ],
+
+ noParse: [/monaco-editor\/\w+\/vs\//],
},
plugins: [
// manifest filename must match config.webpack.manifest_filename
// webpack-rails only needs assetsByChunkName to function properly
- new StatsPlugin('manifest.json', {
- chunkModules: false,
- source: false,
- chunks: false,
- modules: false,
- assets: true
+ new StatsWriterPlugin({
+ filename: 'manifest.json',
+ transform: function(data, opts) {
+ var stats = opts.compiler.getStats().toJson({
+ chunkModules: false,
+ source: false,
+ chunks: false,
+ modules: false,
+ assets: true
+ });
+ return JSON.stringify(stats, null, 2);
+ }
}),
// prevent pikaday from including moment.js
@@ -177,6 +198,7 @@ var config = {
'pdf_viewer',
'pipelines',
'pipelines_details',
+ 'repo',
'schedule_form',
'schedules_index',
'sidebar',
@@ -200,6 +222,26 @@ var config = {
new webpack.optimize.CommonsChunkPlugin({
names: ['main', 'locale', 'common', 'webpack_runtime'],
}),
+
+ // copy pre-compiled vendor libraries verbatim
+ new CopyWebpackPlugin([
+ {
+ from: path.join(ROOT_PATH, `node_modules/monaco-editor/${IS_PRODUCTION ? 'min' : 'dev'}/vs`),
+ to: 'monaco-editor/vs',
+ transform: function(content, path) {
+ if (/\.js$/.test(path) && !/worker/i.test(path)) {
+ return (
+ '(function(){\n' +
+ 'var define = this.define, require = this.require;\n' +
+ 'window.define = define; window.require = require;\n' +
+ content +
+ '\n}.call(window.__monaco_context__ || (window.__monaco_context__ = {})));'
+ );
+ }
+ return content;
+ }
+ }
+ ]),
],
resolve: {
@@ -248,6 +290,7 @@ if (IS_DEV_SERVER) {
config.devServer = {
host: DEV_SERVER_HOST,
port: DEV_SERVER_PORT,
+ disableHostCheck: true,
headers: { 'Access-Control-Allow-Origin': '*' },
stats: 'errors-only',
hot: DEV_SERVER_LIVERELOAD,
diff --git a/db/fixtures/development/10_merge_requests.rb b/db/fixtures/development/10_merge_requests.rb
index c304e0706dc..30244ee4431 100644
--- a/db/fixtures/development/10_merge_requests.rb
+++ b/db/fixtures/development/10_merge_requests.rb
@@ -28,6 +28,8 @@ Gitlab::Seeder.quiet do
project = Project.find_by_full_path('gitlab-org/gitlab-test')
+ next if project.empty_repo? # We don't have repository on CI
+
params = {
source_branch: 'feature',
target_branch: 'master',
diff --git a/db/migrate/20160713205315_add_domain_blacklist_to_application_settings.rb b/db/migrate/20160713205315_add_domain_blacklist_to_application_settings.rb
index ecdd1bd7e5e..f64dfa7675f 100644
--- a/db/migrate/20160713205315_add_domain_blacklist_to_application_settings.rb
+++ b/db/migrate/20160713205315_add_domain_blacklist_to_application_settings.rb
@@ -1,6 +1,7 @@
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
+# rubocop:disable Migration/SaferBooleanColumn
class AddDomainBlacklistToApplicationSettings < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
diff --git a/db/migrate/20160817133006_add_koding_to_application_settings.rb b/db/migrate/20160817133006_add_koding_to_application_settings.rb
index 915d3d78e40..46120652d8e 100644
--- a/db/migrate/20160817133006_add_koding_to_application_settings.rb
+++ b/db/migrate/20160817133006_add_koding_to_application_settings.rb
@@ -1,3 +1,4 @@
+# rubocop:disable Migration/SaferBooleanColumn
class AddKodingToApplicationSettings < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
diff --git a/db/migrate/20161017125927_add_unique_index_to_labels.rb b/db/migrate/20161017125927_add_unique_index_to_labels.rb
index b8f6a803a0a..fcdd79d3b02 100644
--- a/db/migrate/20161017125927_add_unique_index_to_labels.rb
+++ b/db/migrate/20161017125927_add_unique_index_to_labels.rb
@@ -10,7 +10,7 @@ class AddUniqueIndexToLabels < ActiveRecord::Migration
def up
select_all('SELECT title, project_id, COUNT(id) as cnt FROM labels GROUP BY project_id, title HAVING COUNT(id) > 1').each do |label|
label_title = quote_string(label['title'])
- duplicated_ids = select_all("SELECT id FROM labels WHERE project_id = #{label['project_id']} AND title = '#{label_title}' ORDER BY id ASC").map{ |label| label['id'] }
+ duplicated_ids = select_all("SELECT id FROM labels WHERE project_id = #{label['project_id']} AND title = '#{label_title}' ORDER BY id ASC").map { |label| label['id'] }
label_id = duplicated_ids.first
duplicated_ids.delete(label_id)
diff --git a/db/migrate/20161103191444_add_sidekiq_throttling_to_application_settings.rb b/db/migrate/20161103191444_add_sidekiq_throttling_to_application_settings.rb
index e644a174964..522437b92b4 100644
--- a/db/migrate/20161103191444_add_sidekiq_throttling_to_application_settings.rb
+++ b/db/migrate/20161103191444_add_sidekiq_throttling_to_application_settings.rb
@@ -1,6 +1,7 @@
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
+# rubocop:disable Migration/SaferBooleanColumn
class AddSidekiqThrottlingToApplicationSettings < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
diff --git a/db/migrate/20161128161412_add_html_emails_enabled_to_application_settings.rb b/db/migrate/20161128161412_add_html_emails_enabled_to_application_settings.rb
index 1c59241d0fe..38f5781745b 100644
--- a/db/migrate/20161128161412_add_html_emails_enabled_to_application_settings.rb
+++ b/db/migrate/20161128161412_add_html_emails_enabled_to_application_settings.rb
@@ -1,6 +1,7 @@
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
+# rubocop:disable Migration/SaferBooleanColumn
class AddHtmlEmailsEnabledToApplicationSettings < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
diff --git a/db/migrate/20161206003819_add_plant_uml_enabled_to_application_settings.rb b/db/migrate/20161206003819_add_plant_uml_enabled_to_application_settings.rb
index 3677f978cc2..7f56ecf4c9e 100644
--- a/db/migrate/20161206003819_add_plant_uml_enabled_to_application_settings.rb
+++ b/db/migrate/20161206003819_add_plant_uml_enabled_to_application_settings.rb
@@ -1,6 +1,7 @@
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
+# rubocop:disable Migration/SaferBooleanColumn
class AddPlantUmlEnabledToApplicationSettings < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
diff --git a/db/migrate/20170316163800_rename_system_namespaces.rb b/db/migrate/20170316163800_rename_system_namespaces.rb
deleted file mode 100644
index 9e9fb5ac225..00000000000
--- a/db/migrate/20170316163800_rename_system_namespaces.rb
+++ /dev/null
@@ -1,231 +0,0 @@
-# See http://doc.gitlab.com/ce/development/migration_style_guide.html
-# for more information on how to write migrations for GitLab.
-class RenameSystemNamespaces < ActiveRecord::Migration
- include Gitlab::Database::MigrationHelpers
- include Gitlab::ShellAdapter
- disable_ddl_transaction!
-
- class User < ActiveRecord::Base
- self.table_name = 'users'
- end
-
- class Namespace < ActiveRecord::Base
- self.table_name = 'namespaces'
- belongs_to :parent, class_name: 'RenameSystemNamespaces::Namespace'
- has_one :route, as: :source
- has_many :children, class_name: 'RenameSystemNamespaces::Namespace', foreign_key: :parent_id
- belongs_to :owner, class_name: 'RenameSystemNamespaces::User'
-
- # Overridden to have the correct `source_type` for the `route` relation
- def self.name
- 'Namespace'
- end
-
- def full_path
- if route && route.path.present?
- @full_path ||= route.path
- else
- update_route if persisted?
-
- build_full_path
- end
- end
-
- def build_full_path
- if parent && path
- parent.full_path + '/' + path
- else
- path
- end
- end
-
- def update_route
- prepare_route
- route.save
- end
-
- def prepare_route
- route || build_route(source: self)
- route.path = build_full_path
- route.name = build_full_name
- @full_path = nil
- @full_name = nil
- end
-
- def build_full_name
- if parent && name
- parent.human_name + ' / ' + name
- else
- name
- end
- end
-
- def human_name
- owner&.name
- end
- end
-
- class Route < ActiveRecord::Base
- self.table_name = 'routes'
- belongs_to :source, polymorphic: true
- end
-
- class Project < ActiveRecord::Base
- self.table_name = 'projects'
-
- def repository_storage_path
- Gitlab.config.repositories.storages[repository_storage]['path']
- end
- end
-
- DOWNTIME = false
-
- def up
- return unless system_namespace
-
- old_path = system_namespace.path
- old_full_path = system_namespace.full_path
- # Only remove the last occurrence of the path name to get the parent namespace path
- namespace_path = remove_last_occurrence(old_full_path, old_path)
- new_path = rename_path(namespace_path, old_path)
- new_full_path = join_namespace_path(namespace_path, new_path)
-
- Namespace.where(id: system_namespace).update_all(path: new_path) # skips callbacks & validations
-
- replace_statement = replace_sql(Route.arel_table[:path], old_full_path, new_full_path)
- route_matches = [old_full_path, "#{old_full_path}/%"]
-
- update_column_in_batches(:routes, :path, replace_statement) do |table, query|
- query.where(Route.arel_table[:path].matches_any(route_matches))
- end
-
- clear_cache_for_namespace(system_namespace)
-
- # tasks here are based on `Namespace#move_dir`
- move_repositories(system_namespace, old_full_path, new_full_path)
- move_namespace_folders(uploads_dir, old_full_path, new_full_path) if file_storage?
- move_namespace_folders(pages_dir, old_full_path, new_full_path)
- end
-
- def down
- # nothing to do
- end
-
- def remove_last_occurrence(string, pattern)
- string.reverse.sub(pattern.reverse, "").reverse
- end
-
- def move_namespace_folders(directory, old_relative_path, new_relative_path)
- old_path = File.join(directory, old_relative_path)
- return unless File.directory?(old_path)
-
- new_path = File.join(directory, new_relative_path)
- FileUtils.mv(old_path, new_path)
- end
-
- def move_repositories(namespace, old_full_path, new_full_path)
- repo_paths_for_namespace(namespace).each do |repository_storage_path|
- # Ensure old directory exists before moving it
- gitlab_shell.add_namespace(repository_storage_path, old_full_path)
-
- unless gitlab_shell.mv_namespace(repository_storage_path, old_full_path, new_full_path)
- say "Exception moving path #{repository_storage_path} from #{old_full_path} to #{new_full_path}"
- end
- end
- end
-
- def rename_path(namespace_path, path_was)
- counter = 0
- path = "#{path_was}#{counter}"
-
- while route_exists?(join_namespace_path(namespace_path, path))
- counter += 1
- path = "#{path_was}#{counter}"
- end
-
- path
- end
-
- def route_exists?(full_path)
- Route.where(Route.arel_table[:path].matches(full_path)).any?
- end
-
- def join_namespace_path(namespace_path, path)
- if namespace_path.present?
- File.join(namespace_path, path)
- else
- path
- end
- end
-
- def system_namespace
- @system_namespace ||= Namespace.where(parent_id: nil)
- .where(arel_table[:path].matches(system_namespace_path))
- .first
- end
-
- def system_namespace_path
- "system"
- end
-
- def clear_cache_for_namespace(namespace)
- project_ids = projects_for_namespace(namespace).pluck(:id)
-
- update_column_in_batches(:projects, :description_html, nil) do |table, query|
- query.where(table[:id].in(project_ids))
- end
-
- update_column_in_batches(:issues, :description_html, nil) do |table, query|
- query.where(table[:project_id].in(project_ids))
- end
-
- update_column_in_batches(:merge_requests, :description_html, nil) do |table, query|
- query.where(table[:target_project_id].in(project_ids))
- end
-
- update_column_in_batches(:notes, :note_html, nil) do |table, query|
- query.where(table[:project_id].in(project_ids))
- end
-
- update_column_in_batches(:milestones, :description_html, nil) do |table, query|
- query.where(table[:project_id].in(project_ids))
- end
- end
-
- def projects_for_namespace(namespace)
- namespace_ids = child_ids_for_parent(namespace, ids: [namespace.id])
- namespace_or_children = Project.arel_table[:namespace_id].in(namespace_ids)
- Project.unscoped.where(namespace_or_children)
- end
-
- # This won't scale to huge trees, but it should do for a handful of namespaces
- # called `system`.
- def child_ids_for_parent(namespace, ids: [])
- namespace.children.each do |child|
- ids << child.id
- child_ids_for_parent(child, ids: ids) if child.children.any?
- end
- ids
- end
-
- def repo_paths_for_namespace(namespace)
- projects_for_namespace(namespace).distinct
- .select(:repository_storage).map(&:repository_storage_path)
- end
-
- def uploads_dir
- File.join(Rails.root, "public", "uploads")
- end
-
- def pages_dir
- Settings.pages.path
- end
-
- def file_storage?
- CarrierWave::Uploader::Base.storage == CarrierWave::Storage::File
- end
-
- def arel_table
- Namespace.arel_table
- end
-end
diff --git a/db/migrate/20170316163845_move_uploads_to_system_dir.rb b/db/migrate/20170316163845_move_uploads_to_system_dir.rb
index 564ee10b5ab..cfcb909ddaf 100644
--- a/db/migrate/20170316163845_move_uploads_to_system_dir.rb
+++ b/db/migrate/20170316163845_move_uploads_to_system_dir.rb
@@ -54,6 +54,6 @@ class MoveUploadsToSystemDir < ActiveRecord::Migration
end
def new_upload_dir
- File.join(base_directory, "public", "uploads", "system")
+ File.join(base_directory, "public", "uploads", "-", "system")
end
end
diff --git a/db/migrate/20170602154736_add_help_page_hide_commercial_content_to_application_settings.rb b/db/migrate/20170602154736_add_help_page_hide_commercial_content_to_application_settings.rb
index 5e8b667b86d..d358020d182 100644
--- a/db/migrate/20170602154736_add_help_page_hide_commercial_content_to_application_settings.rb
+++ b/db/migrate/20170602154736_add_help_page_hide_commercial_content_to_application_settings.rb
@@ -1,3 +1,4 @@
+# rubocop:disable Migration/SaferBooleanColumn
class AddHelpPageHideCommercialContentToApplicationSettings < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
diff --git a/db/migrate/20170608152747_prepare_events_table_for_push_events_migration.rb b/db/migrate/20170608152747_prepare_events_table_for_push_events_migration.rb
new file mode 100644
index 00000000000..f4f03bbabaf
--- /dev/null
+++ b/db/migrate/20170608152747_prepare_events_table_for_push_events_migration.rb
@@ -0,0 +1,51 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class PrepareEventsTableForPushEventsMigration < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ # The order of these columns is deliberate and results in the following
+ # columns and sizes:
+ #
+ # * id (4 bytes)
+ # * project_id (4 bytes)
+ # * author_id (4 bytes)
+ # * target_id (4 bytes)
+ # * created_at (8 bytes)
+ # * updated_at (8 bytes)
+ # * action (2 bytes)
+ # * target_type (variable)
+ #
+ # Unfortunately we can't make the "id" column a bigint/bigserial as Rails 4
+ # does not support this properly.
+ create_table :events_for_migration do |t|
+ t.references :project,
+ index: true,
+ foreign_key: { on_delete: :cascade }
+
+ t.integer :author_id, index: true, null: false
+ t.integer :target_id
+
+ t.timestamps_with_timezone null: false
+
+ t.integer :action, null: false, limit: 2, index: true
+ t.string :target_type
+
+ t.index %i[target_type target_id]
+ end
+
+ # t.references doesn't like it when the column name doesn't make the table
+ # name so we have to add the foreign key separately.
+ add_concurrent_foreign_key(:events_for_migration, :users, column: :author_id)
+ end
+
+ def down
+ drop_table :events_for_migration
+ end
+end
diff --git a/db/migrate/20170608152748_create_push_event_payloads_tables.rb b/db/migrate/20170608152748_create_push_event_payloads_tables.rb
new file mode 100644
index 00000000000..6c55ad1f2f7
--- /dev/null
+++ b/db/migrate/20170608152748_create_push_event_payloads_tables.rb
@@ -0,0 +1,46 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class CreatePushEventPayloadsTables < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ create_table :push_event_payloads, id: false do |t|
+ t.bigint :commit_count, null: false
+
+ t.integer :event_id, null: false
+ t.integer :action, null: false, limit: 2
+ t.integer :ref_type, null: false, limit: 2
+
+ t.binary :commit_from
+ t.binary :commit_to
+
+ t.text :ref
+ t.string :commit_title, limit: 70
+
+ t.index :event_id, unique: true
+ end
+
+ # We're adding a foreign key to the _shadow_ table, and this is deliberate.
+ # By using the shadow table we don't have to recreate/revalidate this
+ # foreign key after swapping the "events_for_migration" and "events" tables.
+ #
+ # The "events_for_migration" table has a foreign key to "projects.id"
+ # ensuring that project removals also remove events from the shadow table
+ # (and thus also from this table).
+ add_concurrent_foreign_key(
+ :push_event_payloads,
+ :events_for_migration,
+ column: :event_id
+ )
+ end
+
+ def down
+ drop_table :push_event_payloads
+ end
+end
diff --git a/db/migrate/20170717074009_move_system_upload_folder.rb b/db/migrate/20170717074009_move_system_upload_folder.rb
index cce31794115..d3caa53a7a4 100644
--- a/db/migrate/20170717074009_move_system_upload_folder.rb
+++ b/db/migrate/20170717074009_move_system_upload_folder.rb
@@ -15,6 +15,11 @@ class MoveSystemUploadFolder < ActiveRecord::Migration
return
end
+ if File.directory?(new_directory)
+ say "#{new_directory} already exists. No need to redo the move."
+ return
+ end
+
FileUtils.mkdir_p(File.join(base_directory, '-'))
say "Moving #{old_directory} -> #{new_directory}"
@@ -33,6 +38,11 @@ class MoveSystemUploadFolder < ActiveRecord::Migration
return
end
+ if !File.symlink?(old_directory) && File.directory?(old_directory)
+ say "#{old_directory} already exists and is not a symlink, no need to revert."
+ return
+ end
+
if File.symlink?(old_directory)
say "Removing #{old_directory} -> #{new_directory} symlink"
FileUtils.rm(old_directory)
diff --git a/db/migrate/20170727123534_add_index_on_events_project_id_id.rb b/db/migrate/20170727123534_add_index_on_events_project_id_id.rb
new file mode 100644
index 00000000000..1c4aaaf9dd6
--- /dev/null
+++ b/db/migrate/20170727123534_add_index_on_events_project_id_id.rb
@@ -0,0 +1,37 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddIndexOnEventsProjectIdId < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ COLUMNS = %i[project_id id].freeze
+ TABLES = %i[events events_for_migration].freeze
+
+ disable_ddl_transaction!
+
+ def up
+ TABLES.each do |table|
+ add_concurrent_index(table, COLUMNS) unless index_exists?(table, COLUMNS)
+
+ # We remove the index _after_ adding the new one since MySQL doesn't let
+ # you remove an index when a foreign key exists for the same column.
+ if index_exists?(table, :project_id)
+ remove_concurrent_index(table, :project_id)
+ end
+ end
+ end
+
+ def down
+ TABLES.each do |table|
+ unless index_exists?(table, :project_id)
+ add_concurrent_index(table, :project_id)
+ end
+
+ unless index_exists?(table, COLUMNS)
+ remove_concurrent_index(table, COLUMNS)
+ end
+ end
+ end
+end
diff --git a/db/migrate/20170731175128_add_percentages_to_conv_dev.rb b/db/migrate/20170731175128_add_percentages_to_conv_dev.rb
new file mode 100644
index 00000000000..1819bfc96bb
--- /dev/null
+++ b/db/migrate/20170731175128_add_percentages_to_conv_dev.rb
@@ -0,0 +1,32 @@
+class AddPercentagesToConvDev < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+ disable_ddl_transaction!
+
+ DOWNTIME = false
+
+ def up
+ add_column_with_default :conversational_development_index_metrics, :percentage_boards, :float, allow_null: false, default: 0
+ add_column_with_default :conversational_development_index_metrics, :percentage_ci_pipelines, :float, allow_null: false, default: 0
+ add_column_with_default :conversational_development_index_metrics, :percentage_deployments, :float, allow_null: false, default: 0
+ add_column_with_default :conversational_development_index_metrics, :percentage_environments, :float, allow_null: false, default: 0
+ add_column_with_default :conversational_development_index_metrics, :percentage_issues, :float, allow_null: false, default: 0
+ add_column_with_default :conversational_development_index_metrics, :percentage_merge_requests, :float, allow_null: false, default: 0
+ add_column_with_default :conversational_development_index_metrics, :percentage_milestones, :float, allow_null: false, default: 0
+ add_column_with_default :conversational_development_index_metrics, :percentage_notes, :float, allow_null: false, default: 0
+ add_column_with_default :conversational_development_index_metrics, :percentage_projects_prometheus_active, :float, allow_null: false, default: 0
+ add_column_with_default :conversational_development_index_metrics, :percentage_service_desk_issues, :float, allow_null: false, default: 0
+ end
+
+ def down
+ remove_column :conversational_development_index_metrics, :percentage_boards
+ remove_column :conversational_development_index_metrics, :percentage_ci_pipelines
+ remove_column :conversational_development_index_metrics, :percentage_deployments
+ remove_column :conversational_development_index_metrics, :percentage_environments
+ remove_column :conversational_development_index_metrics, :percentage_issues
+ remove_column :conversational_development_index_metrics, :percentage_merge_requests
+ remove_column :conversational_development_index_metrics, :percentage_milestones
+ remove_column :conversational_development_index_metrics, :percentage_notes
+ remove_column :conversational_development_index_metrics, :percentage_projects_prometheus_active
+ remove_column :conversational_development_index_metrics, :percentage_service_desk_issues
+ end
+end
diff --git a/db/migrate/20170731183033_add_merge_jid_to_merge_requests.rb b/db/migrate/20170731183033_add_merge_jid_to_merge_requests.rb
new file mode 100644
index 00000000000..a7d8f2f3604
--- /dev/null
+++ b/db/migrate/20170731183033_add_merge_jid_to_merge_requests.rb
@@ -0,0 +1,7 @@
+class AddMergeJidToMergeRequests < ActiveRecord::Migration
+ DOWNTIME = false
+
+ def change
+ add_column :merge_requests, :merge_jid, :string
+ end
+end
diff --git a/db/migrate/20170803130232_reorganise_issues_indexes_for_faster_sorting.rb b/db/migrate/20170803130232_reorganise_issues_indexes_for_faster_sorting.rb
new file mode 100644
index 00000000000..eb7d1be1732
--- /dev/null
+++ b/db/migrate/20170803130232_reorganise_issues_indexes_for_faster_sorting.rb
@@ -0,0 +1,43 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class ReorganiseIssuesIndexesForFasterSorting < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ REMOVE_INDEX_COLUMNS = %i[project_id created_at due_date updated_at].freeze
+
+ ADD_INDEX_COLUMNS = [
+ %i[project_id created_at id state],
+ %i[project_id due_date id state],
+ %i[project_id updated_at id state]
+ ].freeze
+
+ TABLE = :issues
+
+ def up
+ add_indexes(ADD_INDEX_COLUMNS)
+ remove_indexes(REMOVE_INDEX_COLUMNS)
+ end
+
+ def down
+ add_indexes(REMOVE_INDEX_COLUMNS)
+ remove_indexes(ADD_INDEX_COLUMNS)
+ end
+
+ def add_indexes(columns)
+ columns.each do |column|
+ add_concurrent_index(TABLE, column) unless index_exists?(TABLE, column)
+ end
+ end
+
+ def remove_indexes(columns)
+ columns.each do |column|
+ remove_concurrent_index(TABLE, column) if index_exists?(TABLE, column)
+ end
+ end
+end
diff --git a/db/migrate/20170809133343_add_broadcast_messages_index.rb b/db/migrate/20170809133343_add_broadcast_messages_index.rb
new file mode 100644
index 00000000000..4ab2ddb059d
--- /dev/null
+++ b/db/migrate/20170809133343_add_broadcast_messages_index.rb
@@ -0,0 +1,21 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddBroadcastMessagesIndex < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ COLUMNS = %i[starts_at ends_at id].freeze
+
+ def up
+ add_concurrent_index :broadcast_messages, COLUMNS
+ end
+
+ def down
+ remove_concurrent_index :broadcast_messages, COLUMNS
+ end
+end
diff --git a/db/migrate/20170809134534_add_broadcast_message_not_null_constraints.rb b/db/migrate/20170809134534_add_broadcast_message_not_null_constraints.rb
new file mode 100644
index 00000000000..13e8ef52f22
--- /dev/null
+++ b/db/migrate/20170809134534_add_broadcast_message_not_null_constraints.rb
@@ -0,0 +1,17 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddBroadcastMessageNotNullConstraints < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ COLUMNS = %i[starts_at ends_at created_at updated_at message_html]
+
+ def change
+ COLUMNS.each do |column|
+ change_column_null :broadcast_messages, column, false
+ end
+ end
+end
diff --git a/db/migrate/20170809142252_cleanup_appearances_schema.rb b/db/migrate/20170809142252_cleanup_appearances_schema.rb
new file mode 100644
index 00000000000..90d12925ba2
--- /dev/null
+++ b/db/migrate/20170809142252_cleanup_appearances_schema.rb
@@ -0,0 +1,33 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class CleanupAppearancesSchema < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ NOT_NULL_COLUMNS = %i[title description description_html created_at updated_at]
+
+ TIME_COLUMNS = %i[created_at updated_at]
+
+ def up
+ NOT_NULL_COLUMNS.each do |column|
+ change_column_null :appearances, column, false
+ end
+
+ TIME_COLUMNS.each do |column|
+ change_column :appearances, column, :datetime_with_timezone
+ end
+ end
+
+ def down
+ NOT_NULL_COLUMNS.each do |column|
+ change_column_null :appearances, column, true
+ end
+
+ TIME_COLUMNS.each do |column|
+ change_column :appearances, column, :datetime # rubocop: disable Migration/Datetime
+ end
+ end
+end
diff --git a/db/migrate/20170809161910_add_project_export_enabled_to_application_settings.rb b/db/migrate/20170809161910_add_project_export_enabled_to_application_settings.rb
new file mode 100644
index 00000000000..4baba1ade6d
--- /dev/null
+++ b/db/migrate/20170809161910_add_project_export_enabled_to_application_settings.rb
@@ -0,0 +1,14 @@
+class AddProjectExportEnabledToApplicationSettings < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+ disable_ddl_transaction!
+
+ DOWNTIME = false
+
+ def up
+ add_column_with_default(:application_settings, :project_export_enabled, :boolean, default: true)
+ end
+
+ def down
+ remove_column(:application_settings, :project_export_enabled)
+ end
+end
diff --git a/db/post_migrate/20170317162059_update_upload_paths_to_system.rb b/db/post_migrate/20170317162059_update_upload_paths_to_system.rb
index ca2912f8dce..92e33848bf0 100644
--- a/db/post_migrate/20170317162059_update_upload_paths_to_system.rb
+++ b/db/post_migrate/20170317162059_update_upload_paths_to_system.rb
@@ -48,7 +48,7 @@ class UpdateUploadPathsToSystem < ActiveRecord::Migration
end
def new_upload_dir
- File.join(base_directory, "system")
+ File.join(base_directory, "-", "system")
end
def arel_table
diff --git a/db/post_migrate/20170406111121_clean_upload_symlinks.rb b/db/post_migrate/20170406111121_clean_upload_symlinks.rb
index fc3a4acc0bb..f2ce25d4524 100644
--- a/db/post_migrate/20170406111121_clean_upload_symlinks.rb
+++ b/db/post_migrate/20170406111121_clean_upload_symlinks.rb
@@ -47,6 +47,6 @@ class CleanUploadSymlinks < ActiveRecord::Migration
end
def new_upload_dir
- File.join(base_directory, "public", "uploads", "system")
+ File.join(base_directory, "public", "uploads", "-", "system")
end
end
diff --git a/db/post_migrate/20170502101023_cleanup_namespaceless_pending_delete_projects.rb b/db/post_migrate/20170502101023_cleanup_namespaceless_pending_delete_projects.rb
index c1e64f20109..5238a2ba1b7 100644
--- a/db/post_migrate/20170502101023_cleanup_namespaceless_pending_delete_projects.rb
+++ b/db/post_migrate/20170502101023_cleanup_namespaceless_pending_delete_projects.rb
@@ -30,7 +30,7 @@ class CleanupNamespacelessPendingDeleteProjects < ActiveRecord::Migration
private
def pending_delete_batch
- connection.exec_query(find_batch).map{ |row| row['id'].to_i }
+ connection.exec_query(find_batch).map { |row| row['id'].to_i }
end
BATCH_SIZE = 5000
diff --git a/db/post_migrate/20170606202615_move_appearance_to_system_dir.rb b/db/post_migrate/20170606202615_move_appearance_to_system_dir.rb
index 561de59ec69..07935ab8a52 100644
--- a/db/post_migrate/20170606202615_move_appearance_to_system_dir.rb
+++ b/db/post_migrate/20170606202615_move_appearance_to_system_dir.rb
@@ -52,6 +52,6 @@ class MoveAppearanceToSystemDir < ActiveRecord::Migration
end
def new_upload_dir
- File.join(base_directory, "public", "uploads", "system")
+ File.join(base_directory, "public", "uploads", "-", "system")
end
end
diff --git a/db/post_migrate/20170612071012_move_personal_snippets_files.rb b/db/post_migrate/20170612071012_move_personal_snippets_files.rb
index 33043364bde..2b79a87ccd8 100644
--- a/db/post_migrate/20170612071012_move_personal_snippets_files.rb
+++ b/db/post_migrate/20170612071012_move_personal_snippets_files.rb
@@ -10,7 +10,7 @@ class MovePersonalSnippetsFiles < ActiveRecord::Migration
return unless file_storage?
@source_relative_location = File.join('/uploads', 'personal_snippet')
- @destination_relative_location = File.join('/uploads', 'system', 'personal_snippet')
+ @destination_relative_location = File.join('/uploads', '-', 'system', 'personal_snippet')
move_personal_snippet_files
end
@@ -18,7 +18,7 @@ class MovePersonalSnippetsFiles < ActiveRecord::Migration
def down
return unless file_storage?
- @source_relative_location = File.join('/uploads', 'system', 'personal_snippet')
+ @source_relative_location = File.join('/uploads', '-', 'system', 'personal_snippet')
@destination_relative_location = File.join('/uploads', 'personal_snippet')
move_personal_snippet_files
diff --git a/db/post_migrate/20170627101016_schedule_event_migrations.rb b/db/post_migrate/20170627101016_schedule_event_migrations.rb
new file mode 100644
index 00000000000..1f34375ff0d
--- /dev/null
+++ b/db/post_migrate/20170627101016_schedule_event_migrations.rb
@@ -0,0 +1,40 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class ScheduleEventMigrations < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ BUFFER_SIZE = 1000
+
+ disable_ddl_transaction!
+
+ class Event < ActiveRecord::Base
+ include EachBatch
+
+ self.table_name = 'events'
+ end
+
+ def up
+ jobs = []
+
+ Event.each_batch(of: 1000) do |relation|
+ min, max = relation.pluck('MIN(id), MAX(id)').first
+
+ if jobs.length == BUFFER_SIZE
+ # We push multiple jobs at a time to reduce the time spent in
+ # Sidekiq/Redis operations. We're using this buffer based approach so we
+ # don't need to run additional queries for every range.
+ BackgroundMigrationWorker.perform_bulk(jobs)
+ jobs.clear
+ end
+
+ jobs << ['MigrateEventsToPushEventPayloads', [min, max]]
+ end
+
+ BackgroundMigrationWorker.perform_bulk(jobs) unless jobs.empty?
+ end
+
+ def down
+ end
+end
diff --git a/db/post_migrate/20170703130158_schedule_merge_request_diff_migrations.rb b/db/post_migrate/20170703130158_schedule_merge_request_diff_migrations.rb
new file mode 100644
index 00000000000..17a9dc293f1
--- /dev/null
+++ b/db/post_migrate/20170703130158_schedule_merge_request_diff_migrations.rb
@@ -0,0 +1,33 @@
+class ScheduleMergeRequestDiffMigrations < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ BATCH_SIZE = 2500
+ MIGRATION = 'DeserializeMergeRequestDiffsAndCommits'
+
+ disable_ddl_transaction!
+
+ class MergeRequestDiff < ActiveRecord::Base
+ self.table_name = 'merge_request_diffs'
+
+ include ::EachBatch
+ end
+
+ # Assuming that there are 5 million rows affected (which is more than on
+ # GitLab.com), and that each batch of 2,500 rows takes up to 5 minutes, then
+ # we can migrate all the rows in 7 days.
+ #
+ # On staging, plucking the IDs themselves takes 5 seconds.
+ def up
+ non_empty = 'st_commits IS NOT NULL OR st_diffs IS NOT NULL'
+
+ MergeRequestDiff.where(non_empty).each_batch(of: BATCH_SIZE) do |relation, index|
+ range = relation.pluck('MIN(id)', 'MAX(id)').first
+
+ BackgroundMigrationWorker.perform_in(index * 5.minutes, MIGRATION, range)
+ end
+ end
+
+ def down
+ end
+end
diff --git a/db/post_migrate/20170803090603_calculate_conv_dev_index_percentages.rb b/db/post_migrate/20170803090603_calculate_conv_dev_index_percentages.rb
new file mode 100644
index 00000000000..9af76c94bf3
--- /dev/null
+++ b/db/post_migrate/20170803090603_calculate_conv_dev_index_percentages.rb
@@ -0,0 +1,30 @@
+class CalculateConvDevIndexPercentages < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+ DOWNTIME = false
+
+ class ConversationalDevelopmentIndexMetric < ActiveRecord::Base
+ self.table_name = 'conversational_development_index_metrics'
+
+ METRICS = %w[boards ci_pipelines deployments environments issues merge_requests milestones notes
+ projects_prometheus_active service_desk_issues]
+ end
+
+ def up
+ ConversationalDevelopmentIndexMetric.find_each do |conv_dev_index|
+ update = []
+
+ ConversationalDevelopmentIndexMetric::METRICS.each do |metric|
+ instance_score = conv_dev_index["instance_#{metric}"].to_f
+ leader_score = conv_dev_index["leader_#{metric}"].to_f
+
+ percentage = leader_score.zero? ? 0.0 : (instance_score / leader_score) * 100
+ update << "percentage_#{metric} = '#{percentage}'"
+ end
+
+ execute("UPDATE conversational_development_index_metrics SET #{update.join(',')} WHERE id = #{conv_dev_index.id}")
+ end
+ end
+
+ def down
+ end
+end
diff --git a/db/post_migrate/20170807160457_remove_locked_at_column_from_merge_requests.rb b/db/post_migrate/20170807160457_remove_locked_at_column_from_merge_requests.rb
new file mode 100644
index 00000000000..ea3d1fb3e02
--- /dev/null
+++ b/db/post_migrate/20170807160457_remove_locked_at_column_from_merge_requests.rb
@@ -0,0 +1,11 @@
+class RemoveLockedAtColumnFromMergeRequests < ActiveRecord::Migration
+ DOWNTIME = false
+
+ def up
+ remove_column :merge_requests, :locked_at
+ end
+
+ def down
+ add_column :merge_requests, :locked_at, :datetime_with_timezone
+ end
+end
diff --git a/db/post_migrate/20170807190736_move_personal_snippet_files_into_correct_folder.rb b/db/post_migrate/20170807190736_move_personal_snippet_files_into_correct_folder.rb
new file mode 100644
index 00000000000..e3d2446b897
--- /dev/null
+++ b/db/post_migrate/20170807190736_move_personal_snippet_files_into_correct_folder.rb
@@ -0,0 +1,29 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class MovePersonalSnippetFilesIntoCorrectFolder < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+ disable_ddl_transaction!
+
+ DOWNTIME = false
+ NEW_DIRECTORY = File.join('/uploads', '-', 'system', 'personal_snippet')
+ OLD_DIRECTORY = File.join('/uploads', 'system', 'personal_snippet')
+
+ def up
+ return unless file_storage?
+
+ BackgroundMigrationWorker.perform_async('MovePersonalSnippetFiles',
+ [OLD_DIRECTORY, NEW_DIRECTORY])
+ end
+
+ def down
+ return unless file_storage?
+
+ BackgroundMigrationWorker.perform_async('MovePersonalSnippetFiles',
+ [NEW_DIRECTORY, OLD_DIRECTORY])
+ end
+
+ def file_storage?
+ CarrierWave::Uploader::Base.storage == CarrierWave::Storage::File
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 4ba218e1e0d..3206e106552 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20170728101014) do
+ActiveRecord::Schema.define(version: 20170809161910) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -28,13 +28,13 @@ ActiveRecord::Schema.define(version: 20170728101014) do
end
create_table "appearances", force: :cascade do |t|
- t.string "title"
- t.text "description"
+ t.string "title", null: false
+ t.text "description", null: false
t.string "header_logo"
t.string "logo"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
- t.text "description_html"
+ t.text "description_html", null: false
t.integer "cached_markdown_version"
end
@@ -127,6 +127,7 @@ ActiveRecord::Schema.define(version: 20170728101014) do
t.string "help_page_support_url"
t.integer "performance_bar_allowed_group_id"
t.boolean "password_authentication_enabled"
+ t.boolean "project_export_enabled", default: true, null: false
end
create_table "audit_events", force: :cascade do |t|
@@ -163,16 +164,18 @@ ActiveRecord::Schema.define(version: 20170728101014) do
create_table "broadcast_messages", force: :cascade do |t|
t.text "message", null: false
- t.datetime "starts_at"
- t.datetime "ends_at"
- t.datetime "created_at"
- t.datetime "updated_at"
+ t.datetime "starts_at", null: false
+ t.datetime "ends_at", null: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
t.string "color"
t.string "font"
- t.text "message_html"
+ t.text "message_html", null: false
t.integer "cached_markdown_version"
end
+ add_index "broadcast_messages", ["starts_at", "ends_at", "id"], name: "index_broadcast_messages_on_starts_at_and_ends_at_and_id", using: :btree
+
create_table "chat_names", force: :cascade do |t|
t.integer "user_id", null: false
t.integer "service_id", null: false
@@ -451,6 +454,16 @@ ActiveRecord::Schema.define(version: 20170728101014) do
t.float "instance_service_desk_issues", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
+ t.float "percentage_boards", default: 0.0, null: false
+ t.float "percentage_ci_pipelines", default: 0.0, null: false
+ t.float "percentage_deployments", default: 0.0, null: false
+ t.float "percentage_environments", default: 0.0, null: false
+ t.float "percentage_issues", default: 0.0, null: false
+ t.float "percentage_merge_requests", default: 0.0, null: false
+ t.float "percentage_milestones", default: 0.0, null: false
+ t.float "percentage_notes", default: 0.0, null: false
+ t.float "percentage_projects_prometheus_active", default: 0.0, null: false
+ t.float "percentage_service_desk_issues", default: 0.0, null: false
end
create_table "deploy_keys_projects", force: :cascade do |t|
@@ -520,10 +533,25 @@ ActiveRecord::Schema.define(version: 20170728101014) do
add_index "events", ["action"], name: "index_events_on_action", using: :btree
add_index "events", ["author_id"], name: "index_events_on_author_id", using: :btree
add_index "events", ["created_at"], name: "index_events_on_created_at", using: :btree
- add_index "events", ["project_id"], name: "index_events_on_project_id", using: :btree
+ add_index "events", ["project_id", "id"], name: "index_events_on_project_id_and_id", using: :btree
add_index "events", ["target_id"], name: "index_events_on_target_id", using: :btree
add_index "events", ["target_type"], name: "index_events_on_target_type", using: :btree
+ create_table "events_for_migration", force: :cascade do |t|
+ t.integer "project_id"
+ t.integer "author_id", null: false
+ t.integer "target_id"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.integer "action", limit: 2, null: false
+ t.string "target_type"
+ end
+
+ add_index "events_for_migration", ["action"], name: "index_events_for_migration_on_action", using: :btree
+ add_index "events_for_migration", ["author_id"], name: "index_events_for_migration_on_author_id", using: :btree
+ add_index "events_for_migration", ["project_id", "id"], name: "index_events_for_migration_on_project_id_and_id", using: :btree
+ add_index "events_for_migration", ["target_type", "target_id"], name: "index_events_for_migration_on_target_type_and_target_id", using: :btree
+
create_table "feature_gates", force: :cascade do |t|
t.string "feature_key", null: false
t.string "key", null: false
@@ -641,12 +669,13 @@ ActiveRecord::Schema.define(version: 20170728101014) do
add_index "issues", ["assignee_id"], name: "index_issues_on_assignee_id", using: :btree
add_index "issues", ["author_id"], name: "index_issues_on_author_id", using: :btree
add_index "issues", ["confidential"], name: "index_issues_on_confidential", using: :btree
- add_index "issues", ["created_at"], name: "index_issues_on_created_at", using: :btree
add_index "issues", ["deleted_at"], name: "index_issues_on_deleted_at", using: :btree
add_index "issues", ["description"], name: "index_issues_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"}
- add_index "issues", ["due_date"], name: "index_issues_on_due_date", using: :btree
add_index "issues", ["milestone_id"], name: "index_issues_on_milestone_id", using: :btree
+ add_index "issues", ["project_id", "created_at", "id", "state"], name: "index_issues_on_project_id_and_created_at_and_id_and_state", using: :btree
+ add_index "issues", ["project_id", "due_date", "id", "state"], name: "index_issues_on_project_id_and_due_date_and_id_and_state", using: :btree
add_index "issues", ["project_id", "iid"], name: "index_issues_on_project_id_and_iid", unique: true, using: :btree
+ add_index "issues", ["project_id", "updated_at", "id", "state"], name: "index_issues_on_project_id_and_updated_at_and_id_and_state", using: :btree
add_index "issues", ["relative_position"], name: "index_issues_on_relative_position", using: :btree
add_index "issues", ["state"], name: "index_issues_on_state", using: :btree
add_index "issues", ["title"], name: "index_issues_on_title_trigram", using: :gin, opclasses: {"title"=>"gin_trgm_ops"}
@@ -839,7 +868,6 @@ ActiveRecord::Schema.define(version: 20170728101014) do
t.integer "target_project_id", null: false
t.integer "iid"
t.text "description"
- t.datetime "locked_at"
t.integer "updated_by_id"
t.text "merge_error"
t.text "merge_params"
@@ -857,6 +885,7 @@ ActiveRecord::Schema.define(version: 20170728101014) do
t.integer "last_edited_by_id"
t.integer "head_pipeline_id"
t.boolean "ref_fetched"
+ t.string "merge_jid"
end
add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree
@@ -1243,6 +1272,19 @@ ActiveRecord::Schema.define(version: 20170728101014) do
add_index "protected_tags", ["project_id"], name: "index_protected_tags_on_project_id", using: :btree
+ create_table "push_event_payloads", id: false, force: :cascade do |t|
+ t.integer "commit_count", limit: 8, null: false
+ t.integer "event_id", null: false
+ t.integer "action", limit: 2, null: false
+ t.integer "ref_type", limit: 2, null: false
+ t.binary "commit_from"
+ t.binary "commit_to"
+ t.text "ref"
+ t.string "commit_title", limit: 70
+ end
+
+ add_index "push_event_payloads", ["event_id"], name: "index_push_event_payloads_on_event_id", unique: true, using: :btree
+
create_table "redirect_routes", force: :cascade do |t|
t.integer "source_id", null: false
t.string "source_type", null: false
@@ -1643,6 +1685,8 @@ ActiveRecord::Schema.define(version: 20170728101014) do
add_foreign_key "deployments", "projects", name: "fk_b9a3851b82", on_delete: :cascade
add_foreign_key "environments", "projects", name: "fk_d1c8c1da6a", on_delete: :cascade
add_foreign_key "events", "projects", name: "fk_0434b48643", on_delete: :cascade
+ add_foreign_key "events_for_migration", "projects", on_delete: :cascade
+ add_foreign_key "events_for_migration", "users", column: "author_id", name: "fk_edfd187b6f", on_delete: :cascade
add_foreign_key "forked_project_links", "projects", column: "forked_to_project_id", name: "fk_434510edb0", on_delete: :cascade
add_foreign_key "gpg_keys", "users", on_delete: :cascade
add_foreign_key "gpg_signatures", "gpg_keys", on_delete: :nullify
@@ -1685,6 +1729,7 @@ ActiveRecord::Schema.define(version: 20170728101014) do
add_foreign_key "protected_tag_create_access_levels", "protected_tags"
add_foreign_key "protected_tag_create_access_levels", "users"
add_foreign_key "protected_tags", "projects", name: "fk_8e4af87648", on_delete: :cascade
+ add_foreign_key "push_event_payloads", "events_for_migration", column: "event_id", name: "fk_36c74129da", on_delete: :cascade
add_foreign_key "releases", "projects", name: "fk_47fe2a0596", on_delete: :cascade
add_foreign_key "services", "projects", name: "fk_71cce407f9", on_delete: :cascade
add_foreign_key "snippets", "projects", name: "fk_be41fd4bb7", on_delete: :cascade
diff --git a/doc/README.md b/doc/README.md
index 8bb8e147cd1..4175750d497 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -1,9 +1,13 @@
+---
+toc: false
+---
+
# GitLab Documentation
Welcome to [GitLab](https://about.gitlab.com/), a Git-based fully featured
platform for software development!
-We offer four different products for you and your company:
+GitLab offers the most scalable Git-based fully integrated platform for software development, with flexible products and subscription plans:
- **GitLab Community Edition (CE)** is an [opensource product](https://gitlab.com/gitlab-org/gitlab-ce/),
self-hosted, free to use. Every feature available in GitLab CE is also available on GitLab Enterprise Edition (Starter and Premium) and GitLab.com.
@@ -26,6 +30,9 @@ Shortcuts to GitLab's most visited docs:
| [Configuring `.gitlab-ci.yml`](ci/yaml/README.md) | [SSH authentication](ssh/README.md) |
| [Using Docker images](ci/docker/using_docker_images.md) | [GitLab Pages](user/project/pages/index.md) |
+- [User documentation](user/index.md)
+- [Administrator documentation](#administrator-documentation)
+
## Getting started with GitLab
- [GitLab Basics](gitlab-basics/README.md): Start working on your command line and on GitLab.
@@ -36,7 +43,6 @@ Shortcuts to GitLab's most visited docs:
### User account
-- [User documentation](user/index.md): Learn how to use GitLab and explore its features
- [User account](user/profile/index.md): Manage your account
- [Authentication](topics/authentication/index.md): Account security with two-factor authentication, setup your ssh keys and deploy keys for secure access to your projects.
- [Profile settings](user/profile/index.md#profile-settings): Manage your profile settings, two factor authentication and more.
diff --git a/doc/administration/high_availability/nfs.md b/doc/administration/high_availability/nfs.md
index 90a2e9298bf..e09ccaba08c 100644
--- a/doc/administration/high_availability/nfs.md
+++ b/doc/administration/high_availability/nfs.md
@@ -42,6 +42,10 @@ GitLab does not recommend using EFS with GitLab.
are allocated. For smaller volumes, users may experience decent performance
for a period of time due to 'Burst Credits'. Over a period of weeks to months
credits may run out and performance will bottom out.
+- To keep "Burst Credits" available, it may be necessary to provision more space
+ with 'dummy data'. However, this may get expensive.
+- Another option to maintain "Burst Credits" is to use FS Cache on the server so
+ that AWS doesn't always have to go into EFS to access files.
- For larger volumes, allocated IOPS may not be the problem. Workloads where
many small files are written in a serialized manner are not well-suited for EFS.
EBS with an NFS server on top will perform much better.
diff --git a/doc/administration/img/failing_storage.png b/doc/administration/img/failing_storage.png
new file mode 100644
index 00000000000..82b393a58b2
--- /dev/null
+++ b/doc/administration/img/failing_storage.png
Binary files differ
diff --git a/doc/administration/logs.md b/doc/administration/logs.md
index 4b8d5c5cc87..76e071dc673 100644
--- a/doc/administration/logs.md
+++ b/doc/administration/logs.md
@@ -9,6 +9,33 @@ documentation](http://docs.gitlab.com/ee/administration/audit_events.html)
System log files are typically plain text in a standard log file format.
This guide talks about how to read and use these system log files.
+## `production_json.log`
+
+This file lives in `/var/log/gitlab/gitlab-rails/production_json.log` for
+Omnibus GitLab packages or in `/home/git/gitlab/log/production_json.log` for
+installations from source. (When Gitlab is running in an environment
+other than production, the corresponding logfile is shown here.)
+
+It contains a structured log for Rails controller requests received from
+GitLab, thanks to [Lograge](https://github.com/roidrage/lograge/). Note that
+requests from the API [are not yet logged to this
+file](https://gitlab.com/gitlab-org/gitlab-ce/issues/36189).
+
+Each line contains a JSON line that can be ingested by Elasticsearch, Splunk, etc. For example:
+
+```json
+{"method":"GET","path":"/gitlab/gitlab-ce/issues/1234","format":"html","controller":"Projects::IssuesController","action":"show","status":200,"duration":229.03,"view":174.07,"db":13.24,"time":"2017-08-08T20:15:54.821Z","params":{"namespace_id":"gitlab","project_id":"gitlab-ce","id":"1234"},"remote_ip":"18.245.0.1","user_id":1,"username":"admin"}
+```
+
+In this example, you can see this was a GET request for a specific issue. Notice each line also contains performance data:
+
+1. `duration`: the total time taken to retrieve the request
+2. `view`: total time taken inside the Rails views
+3. `db`: total time to retrieve data from the database
+
+In addition, the log contains the IP address from which the request originated
+(`remote_ip`) as well as the user's ID (`user_id`), and username (`username`).
+
## `production.log`
This file lives in `/var/log/gitlab/gitlab-rails/production.log` for
diff --git a/doc/administration/reply_by_email_postfix_setup.md b/doc/administration/reply_by_email_postfix_setup.md
index 3b8c716eff5..a1bb3851951 100644
--- a/doc/administration/reply_by_email_postfix_setup.md
+++ b/doc/administration/reply_by_email_postfix_setup.md
@@ -177,6 +177,20 @@ Courier, which we will install later to add IMAP authentication, requires mailbo
```sh
sudo apt-get install courier-imap
```
+
+ And start `imapd`:
+ ```sh
+ imapd start
+ ```
+
+1. The courier-authdaemon isn't started after installation. Without it, imap authentication will fail:
+ ```sh
+ sudo service courier-authdaemon start
+ ```
+ You can also configure courier-authdaemon to start on boot:
+ ```sh
+ sudo systemctl enable courier-authdaemon
+ ```
## Configure Postfix to receive email from the internet
diff --git a/doc/administration/repository_storage_paths.md b/doc/administration/repository_storage_paths.md
index 55a45119525..624a908b3a3 100644
--- a/doc/administration/repository_storage_paths.md
+++ b/doc/administration/repository_storage_paths.md
@@ -60,7 +60,7 @@ respectively.
path: /mnt/cephfs/repositories
```
-1. [Restart GitLab] for the changes to take effect.
+1. [Restart GitLab][restart-gitlab] for the changes to take effect.
>**Note:**
The [`gitlab_shell: repos_path` entry][repospath] in `gitlab.yml` will be
@@ -97,9 +97,80 @@ be stored via the **Application Settings** in the Admin area.
Beginning with GitLab 8.13.4, multiple paths can be chosen. New projects will be
randomly placed on one of the selected paths.
+## Handling failing repository storage
+
+> [Introduced][ce-11449] in GitLab 9.5.
+
+When GitLab detects access to the repositories storage fails repeatedly, it can
+gracefully prevent attempts to access the storage. This might be useful when
+the repositories are stored somewhere on the network.
+
+The configuration could look as follows:
+
+**For Omnibus installations**
+
+1. Edit `/etc/gitlab/gitlab.rb`:
+
+ ```ruby
+ git_data_dirs({
+ "default" => {
+ "path" => "/mnt/nfs-01/git-data",
+ "failure_count_threshold" => 10,
+ "failure_wait_time" => 30,
+ "failure_reset_time" => 1800,
+ "storage_timeout" => 5
+ }
+ })
+ ```
+
+1. Save the file and [reconfigure GitLab][reconfigure-gitlab] for the changes to take effect.
+
+---
+
+**For installations from source**
+
+1. Edit `config/gitlab.yml`:
+
+ ```yaml
+ repositories:
+ storages: # You must have at least a `default` storage path.
+ default:
+ path: /home/git/repositories/
+ failure_count_threshold: 10 # number of failures before stopping attempts
+ failure_wait_time: 30 # Seconds after last access failure before trying again
+ failure_reset_time: 1800 # Time in seconds to expire failures
+ storage_timeout: 5 # Time in seconds to wait before aborting a storage access attempt
+ ```
+
+1. Save the file and [restart GitLab][restart-gitlab] for the changes to take effect.
+
+
+**`failure_count_threshold`:** The number of failures of after which GitLab will
+completely prevent access to the storage. The number of failures can be reset in
+the admin interface: `https://gitlab.example.com/admin/health_check` or using the
+[api](../api/repository_storage_health.md) to allow access to the storage again.
+
+**`failure_wait_time`:** When access to a storage fails. GitLab will prevent
+access to the storage for the time specified here. This allows the filesystem to
+recover without.
+
+**`failure_reset_time`:** The time in seconds GitLab will keep failure
+information. When no failures occur during this time, information about the
+mount is reset.
+
+**`storage_timeout`:** The time in seconds GitLab will try to access storage.
+After this time a timeout error will be raised.
+
+When storage failures occur, this will be visible in the admin interface like this:
+
+![failing storage](img/failing_storage.png)
+
+To allow access to all storages, click the `Reset git storage health information` button.
+
[ce-4578]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4578
-[restart gitlab]: restart_gitlab.md#installations-from-source
-[reconfigure gitlab]: restart_gitlab.md#omnibus-gitlab-reconfigure
+[restart-gitlab]: restart_gitlab.md#installations-from-source
+[reconfigure-gitlab]: restart_gitlab.md#omnibus-gitlab-reconfigure
[backups]: ../raketasks/backup_restore.md
[raketask]: https://gitlab.com/gitlab-org/gitlab-ce/blob/033e5423a2594e08a7ebcd2379bd2331f4c39032/lib/backup/repository.rb#L54-56
[repospath]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-9-stable/config/gitlab.yml.example#L457
+[ce-11449]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11449
diff --git a/doc/api/README.md b/doc/api/README.md
index f63d395b10e..8acb2145f1a 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -77,6 +77,38 @@ controller-specific endpoints. GraphQL has a number of benefits:
It will co-exist with the current v4 REST API. If we have a v5 API, this should
be a compatibility layer on top of GraphQL.
+## Basic usage
+
+API requests should be prefixed with `api` and the API version. The API version
+is defined in [`lib/api.rb`][lib-api-url]. For example, the root of the v4 API
+is at `/api/v4`.
+
+For endpoints that require [authentication](#authentication), you need to pass
+a `private_token` parameter via query string or header. If passed as a header,
+the header name must be `PRIVATE-TOKEN` (uppercase and with a dash instead of
+an underscore).
+
+Example of a valid API request:
+
+```
+GET /projects?private_token=9koXpg98eAheJpvBs5tK
+```
+
+Example of a valid API request using cURL and authentication via header:
+
+```shell
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects"
+```
+
+Example of a valid API request using cURL and authentication via a query string:
+
+```shell
+curl "https://gitlab.example.com/api/v4/projects?private_token=9koXpg98eAheJpvBs5tK"
+```
+
+The API uses JSON to serialize data. You don't need to specify `.json` at the
+end of an API URL.
+
## Authentication
Most API requests require authentication via a session cookie or token. For
@@ -207,37 +239,6 @@ GET /projects?private_token=9koXpg98eAheJpvBs5tK&sudo=23
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --header "SUDO: 23" "https://gitlab.example.com/api/v4/projects"
```
-## Basic usage
-
-API requests should be prefixed with `api` and the API version. The API version
-is defined in [`lib/api.rb`][lib-api-url].
-
-For endpoints that require [authentication](#authentication), you need to pass
-a `private_token` parameter via query string or header. If passed as a header,
-the header name must be `PRIVATE-TOKEN` (uppercase and with a dash instead of
-an underscore).
-
-Example of a valid API request:
-
-```
-GET /projects?private_token=9koXpg98eAheJpvBs5tK
-```
-
-Example of a valid API request using cURL and authentication via header:
-
-```shell
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects"
-```
-
-Example of a valid API request using cURL and authentication via a query string:
-
-```shell
-curl "https://gitlab.example.com/api/v4/projects?private_token=9koXpg98eAheJpvBs5tK"
-```
-
-The API uses JSON to serialize data. You don't need to specify `.json` at the
-end of an API URL.
-
## Status codes
The API is designed to return different status codes according to context and
diff --git a/doc/api/commits.md b/doc/api/commits.md
index c91f9ecbdaf..2a78553782f 100644
--- a/doc/api/commits.md
+++ b/doc/api/commits.md
@@ -69,8 +69,9 @@ POST /projects/:id/repository/commits
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
-| `branch` | string | yes | The name of a branch |
+| `branch` | string | yes | Name of the branch to commit into. To create a new branch, also provide `start_branch`. |
| `commit_message` | string | yes | Commit message |
+| `start_branch` | string | no | Name of the branch to start the new commit from |
| `actions[]` | array | yes | An array of action hashes to commit as a batch. See the next table for what attributes it can take. |
| `author_email` | string | no | Specify the commit author's email address |
| `author_name` | string | no | Specify the commit author's name |
diff --git a/doc/api/events.md b/doc/api/events.md
index 6e530317f6c..129af0afa35 100644
--- a/doc/api/events.md
+++ b/doc/api/events.md
@@ -79,7 +79,6 @@ Example response:
"target_id":160,
"target_type":"Issue",
"author_id":25,
- "data":null,
"target_title":"Qui natus eos odio tempore et quaerat consequuntur ducimus cupiditate quis.",
"created_at":"2017-02-09T10:43:19.667Z",
"author":{
@@ -99,7 +98,6 @@ Example response:
"target_id":159,
"target_type":"Issue",
"author_id":21,
- "data":null,
"target_title":"Nostrum enim non et sed optio illo deleniti non.",
"created_at":"2017-02-09T10:43:19.426Z",
"author":{
@@ -151,7 +149,6 @@ Example response:
"target_id": 830,
"target_type": "Issue",
"author_id": 1,
- "data": null,
"target_title": "Public project search field",
"author": {
"name": "Dmitriy Zaporozhets",
@@ -166,7 +163,7 @@ Example response:
{
"title": null,
"project_id": 15,
- "action_name": "opened",
+ "action_name": "pushed",
"target_id": null,
"target_type": null,
"author_id": 1,
@@ -179,31 +176,14 @@ Example response:
"web_url": "http://localhost:3000/root"
},
"author_username": "john",
- "data": {
- "before": "50d4420237a9de7be1304607147aec22e4a14af7",
- "after": "c5feabde2d8cd023215af4d2ceeb7a64839fc428",
- "ref": "refs/heads/master",
- "user_id": 1,
- "user_name": "Dmitriy Zaporozhets",
- "repository": {
- "name": "gitlabhq",
- "url": "git@dev.gitlab.org:gitlab/gitlabhq.git",
- "description": "GitLab: self hosted Git management software. \r\nDistributed under the MIT License.",
- "homepage": "https://dev.gitlab.org/gitlab/gitlabhq"
- },
- "commits": [
- {
- "id": "c5feabde2d8cd023215af4d2ceeb7a64839fc428",
- "message": "Add simple search to projects in public area",
- "timestamp": "2013-05-13T18:18:08+00:00",
- "url": "https://dev.gitlab.org/gitlab/gitlabhq/commit/c5feabde2d8cd023215af4d2ceeb7a64839fc428",
- "author": {
- "name": "Dmitriy Zaporozhets",
- "email": "dmitriy.zaporozhets@gmail.com"
- }
- }
- ],
- "total_commits_count": 1
+ "push_data": {
+ "commit_count": 1,
+ "action": "pushed",
+ "ref_type": "branch",
+ "commit_from": "50d4420237a9de7be1304607147aec22e4a14af7",
+ "commit_to": "c5feabde2d8cd023215af4d2ceeb7a64839fc428",
+ "ref": "master",
+ "commit_title": "Add simple search to projects in public area"
},
"target_title": null
},
@@ -214,7 +194,6 @@ Example response:
"target_id": 840,
"target_type": "Issue",
"author_id": 1,
- "data": null,
"target_title": "Finish & merge Code search PR",
"author": {
"name": "Dmitriy Zaporozhets",
@@ -233,7 +212,6 @@ Example response:
"target_id": 1312,
"target_type": "Note",
"author_id": 1,
- "data": null,
"target_title": null,
"created_at": "2015-12-04T10:33:58.089Z",
"note": {
@@ -305,7 +283,6 @@ Example response:
"target_iid":160,
"target_type":"Issue",
"author_id":25,
- "data":null,
"target_title":"Qui natus eos odio tempore et quaerat consequuntur ducimus cupiditate quis.",
"created_at":"2017-02-09T10:43:19.667Z",
"author":{
@@ -326,7 +303,6 @@ Example response:
"target_iid":159,
"target_type":"Issue",
"author_id":21,
- "data":null,
"target_title":"Nostrum enim non et sed optio illo deleniti non.",
"created_at":"2017-02-09T10:43:19.426Z",
"author":{
@@ -338,6 +314,45 @@ Example response:
"web_url":"https://gitlab.example.com/ted"
},
"author_username":"ted"
+ },
+ {
+ "title": null,
+ "project_id": 1,
+ "action_name": "commented on",
+ "target_id": 1312,
+ "target_iid": 1312,
+ "target_type": "Note",
+ "author_id": 1,
+ "data": null,
+ "target_title": null,
+ "created_at": "2015-12-04T10:33:58.089Z",
+ "note": {
+ "id": 1312,
+ "body": "What an awesome day!",
+ "attachment": null,
+ "author": {
+ "name": "Dmitriy Zaporozhets",
+ "username": "root",
+ "id": 1,
+ "state": "active",
+ "avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png",
+ "web_url": "http://localhost:3000/root"
+ },
+ "created_at": "2015-12-04T10:33:56.698Z",
+ "system": false,
+ "noteable_id": 377,
+ "noteable_type": "Issue",
+ "noteable_iid": 377
+ },
+ "author": {
+ "name": "Dmitriy Zaporozhets",
+ "username": "root",
+ "id": 1,
+ "state": "active",
+ "avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png",
+ "web_url": "http://localhost:3000/root"
+ },
+ "author_username": "root"
}
]
```
diff --git a/doc/api/group_milestones.md b/doc/api/group_milestones.md
index 086fba7e91d..dbfc7529125 100644
--- a/doc/api/group_milestones.md
+++ b/doc/api/group_milestones.md
@@ -6,7 +6,7 @@ Returns a list of group milestones.
```
GET /groups/:id/milestones
-GET /groups/:id/milestones?iids=42
+GET /groups/:id/milestones?iids[]=42
GET /groups/:id/milestones?iids[]=42&iids[]=43
GET /groups/:id/milestones?state=active
GET /groups/:id/milestones?state=closed
@@ -18,7 +18,7 @@ Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
-| `iids` | Array[integer] | optional | Return only the milestones having the given `iids` |
+| `iids[]` | Array[integer] | optional | Return only the milestones having the given `iid` |
| `state` | string | optional | Return only `active` or `closed` milestones` |
| `search` | string | optional | Return only milestones with a title or description matching the provided string |
diff --git a/doc/api/issues.md b/doc/api/issues.md
index 6bac2927339..f30ed08d0fa 100644
--- a/doc/api/issues.md
+++ b/doc/api/issues.md
@@ -40,7 +40,7 @@ GET /issues?assignee_id=5
| `scope` | string | no | Return issues for the given scope: `created-by-me`, `assigned-to-me` or `all`. Defaults to `created-by-me` _([Introduced][ce-13004] in GitLab 9.5)_ |
| `author_id` | integer | no | Return issues created by the given user `id`. Combine with `scope=all` or `scope=assigned-to-me`. _([Introduced][ce-13004] in GitLab 9.5)_ |
| `assignee_id` | integer | no | Return issues assigned to the given user `id` _([Introduced][ce-13004] in GitLab 9.5)_ |
-| `iids` | Array[integer] | no | Return only the issues having the given `iid` |
+| `iids[]` | Array[integer] | no | Return only the issues having the given `iid` |
| `order_by` | string | no | Return issues ordered by `created_at` or `updated_at` fields. Default is `created_at` |
| `sort` | string | no | Return issues sorted in `asc` or `desc` order. Default is `desc` |
| `search` | string | no | Search issues against their `title` and `description` |
@@ -132,7 +132,7 @@ GET /groups/:id/issues?assignee_id=5
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
| `state` | string | no | Return all issues or just those that are `opened` or `closed` |
| `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned. `No+Label` lists all issues with no labels |
-| `iids` | Array[integer] | no | Return only the issues having the given `iid` |
+| `iids[]` | Array[integer] | no | Return only the issues having the given `iid` |
| `milestone` | string | no | The milestone title |
| `scope` | string | no | Return issues for the given scope: `created-by-me`, `assigned-to-me` or `all` _([Introduced][ce-13004] in GitLab 9.5)_ |
| `author_id` | integer | no | Return issues created by the given user `id` _([Introduced][ce-13004] in GitLab 9.5)_ |
@@ -227,7 +227,7 @@ GET /projects/:id/issues?assignee_id=5
| Attribute | Type | Required | Description |
|-------------|----------------|----------|-----------------------------------------------------------------------------------------------------------------------------|
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
-| `iids` | Array[integer] | no | Return only the milestone having the given `iid` |
+| `iids[]` | Array[integer] | no | Return only the milestone having the given `iid` |
| `state` | string | no | Return all issues or just those that are `opened` or `closed` |
| `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned. `No+Label` lists all issues with no labels |
| `milestone` | string | no | The milestone title |
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index d0725b5e06e..802e5362d70 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -117,7 +117,7 @@ Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project |
-| `iids` | Array[integer] | no | Return the request having the given `iid` |
+| `iids[]` | Array[integer] | no | Return the request having the given `iid` |
| `state` | string | no | Return all merge requests or just those that are `opened`, `closed`, or `merged`|
| `order_by`| string | no | Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` |
| `sort` | string | no | Return requests sorted in `asc` or `desc` order. Default is `desc` |
diff --git a/doc/api/milestones.md b/doc/api/milestones.md
index a082d548499..84930f0bdc9 100644
--- a/doc/api/milestones.md
+++ b/doc/api/milestones.md
@@ -6,7 +6,7 @@ Returns a list of project milestones.
```
GET /projects/:id/milestones
-GET /projects/:id/milestones?iids=42
+GET /projects/:id/milestones?iids[]=42
GET /projects/:id/milestones?iids[]=42&iids[]=43
GET /projects/:id/milestones?state=active
GET /projects/:id/milestones?state=closed
@@ -18,7 +18,7 @@ Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
-| `iids` | Array[integer] | optional | Return only the milestones having the given `iids` |
+| `iids[]` | Array[integer] | optional | Return only the milestones having the given `iid` |
| `state` | string | optional | Return only `active` or `closed` milestones` |
| `search` | string | optional | Return only milestones with a title or description matching the provided string |
diff --git a/doc/api/notes.md b/doc/api/notes.md
index 388e6989df2..e627369e17b 100644
--- a/doc/api/notes.md
+++ b/doc/api/notes.md
@@ -35,7 +35,8 @@ Parameters:
"updated_at": "2013-10-02T10:22:45Z",
"system": true,
"noteable_id": 377,
- "noteable_type": "Issue"
+ "noteable_type": "Issue",
+ "noteable_iid": 377
},
{
"id": 305,
@@ -53,7 +54,8 @@ Parameters:
"updated_at": "2013-10-02T09:56:03Z",
"system": true,
"noteable_id": 121,
- "noteable_type": "Issue"
+ "noteable_type": "Issue",
+ "noteable_iid": 121
}
]
```
@@ -267,7 +269,8 @@ Parameters:
"updated_at": "2013-10-02T08:57:14Z",
"system": false,
"noteable_id": 2,
- "noteable_type": "MergeRequest"
+ "noteable_type": "MergeRequest",
+ "noteable_iid": 2
}
```
diff --git a/doc/api/repository_files.md b/doc/api/repository_files.md
index 1fc577561a0..c517a38a8ba 100644
--- a/doc/api/repository_files.md
+++ b/doc/api/repository_files.md
@@ -76,7 +76,8 @@ Example response:
Parameters:
- `file_path` (required) - Url encoded full path to new file. Ex. lib%2Fclass%2Erb
-- `branch` (required) - The name of branch
+- `branch` (required) - Name of the branch
+- `start_branch` (optional) - Name of the branch to start the new commit from
- `encoding` (optional) - Change encoding to 'base64'. Default is text.
- `author_email` (optional) - Specify the commit author's email address
- `author_name` (optional) - Specify the commit author's name
@@ -105,7 +106,8 @@ Example response:
Parameters:
- `file_path` (required) - Url encoded full path to new file. Ex. lib%2Fclass%2Erb
-- `branch` (required) - The name of branch
+- `branch` (required) - Name of the branch
+- `start_branch` (optional) - Name of the branch to start the new commit from
- `encoding` (optional) - Change encoding to 'base64'. Default is text.
- `author_email` (optional) - Specify the commit author's email address
- `author_name` (optional) - Specify the commit author's name
@@ -144,7 +146,8 @@ Example response:
Parameters:
- `file_path` (required) - Url encoded full path to new file. Ex. lib%2Fclass%2Erb
-- `branch` (required) - The name of branch
+- `branch` (required) - Name of the branch
+- `start_branch` (optional) - Name of the branch to start the new commit from
- `author_email` (optional) - Specify the commit author's email address
- `author_name` (optional) - Specify the commit author's name
- `commit_message` (required) - Commit message
diff --git a/doc/api/repository_storage_health.md b/doc/api/repository_storage_health.md
new file mode 100644
index 00000000000..e0c0315c2d7
--- /dev/null
+++ b/doc/api/repository_storage_health.md
@@ -0,0 +1,74 @@
+# Circuitbreaker API
+
+> [Introduced][ce-11449] in GitLab 9.5.
+
+The Circuitbreaker API is only accessible to administrators. All requests by
+guests will respond with `401 Unauthorized`, and all requests by normal users
+will respond with `403 Forbidden`.
+
+## Repository Storages
+
+### Get all storage information
+
+Returns of all currently configured storages and their health information.
+
+```
+GET /circuit_breakers/repository_storage
+```
+
+```bash
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/circuit_breakers/repository_storage
+```
+
+```json
+[
+ {
+ "storage_name": "default",
+ "failing_on_hosts": [],
+ "total_failures": 0
+ },
+ {
+ "storage_name": "broken",
+ "failing_on_hosts": [
+ "web01", "worker01"
+ ],
+ "total_failures": 1
+ }
+]
+```
+
+### Get failing storages
+
+This returns a list of all currently failing storages.
+
+```
+GET /circuit_breakers/repository_storage/failing
+```
+
+```bash
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/circuit_breakers/repository_storage/failing
+```
+
+```json
+[
+ {
+ "storage_name":"broken",
+ "failing_on_hosts":["web01", "worker01"],
+ "total_failures":2
+ }
+]
+```
+
+## Reset failing storage information
+
+Use this remove all failing storage information and allow access to the storage again.
+
+```
+DELETE /circuit_breakers/repository_storage
+```
+
+```bash
+curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/circuit_breakers/repository_storage
+```
+
+[ce-11449]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11449
diff --git a/doc/api/tags.md b/doc/api/tags.md
index 54f092d1d30..32fe5eea692 100644
--- a/doc/api/tags.md
+++ b/doc/api/tags.md
@@ -18,17 +18,20 @@ Parameters:
[
{
"commit": {
+ "id": "2695effb5807a22ff3d138d593fd856244e155e7",
+ "short_id": "2695effb",
+ "title": "Initial commit",
+ "created_at": "2017-07-26T11:08:53.000+02:00",
+ "parent_ids": [
+ "2a4b78934375d7f53875269ffd4f45fd83a84ebe"
+ ],
+ "message": "Initial commit",
"author_name": "John Smith",
"author_email": "john@example.com",
"authored_date": "2012-05-28T04:42:42-07:00",
- "committed_date": "2012-05-28T04:42:42-07:00",
"committer_name": "Jack Smith",
"committer_email": "jack@example.com",
- "id": "2695effb5807a22ff3d138d593fd856244e155e7",
- "message": "Initial commit",
- "parent_ids": [
- "2a4b78934375d7f53875269ffd4f45fd83a84ebe"
- ]
+ "committed_date": "2012-05-28T04:42:42-07:00"
},
"release": {
"tag_name": "1.0.0",
@@ -68,16 +71,19 @@ Example Response:
"message": null,
"commit": {
"id": "60a8ff033665e1207714d6670fcd7b65304ec02f",
- "message": "v5.0.0\n",
+ "short_id": "60a8ff03",
+ "title": "Initial commit",
+ "created_at": "2017-07-26T11:08:53.000+02:00",
"parent_ids": [
"f61c062ff8bcbdb00e0a1b3317a91aed6ceee06b"
],
- "authored_date": "2015-02-01T21:56:31.000+01:00",
+ "message": "v5.0.0\n",
"author_name": "Arthur Verschaeve",
"author_email": "contact@arthurverschaeve.be",
- "committed_date": "2015-02-01T21:56:31.000+01:00",
+ "authored_date": "2015-02-01T21:56:31.000+01:00",
"committer_name": "Arthur Verschaeve",
- "committer_email": "contact@arthurverschaeve.be"
+ "committer_email": "contact@arthurverschaeve.be",
+ "committed_date": "2015-02-01T21:56:31.000+01:00"
},
"release": null
}
@@ -102,17 +108,20 @@ Parameters:
```json
{
"commit": {
+ "id": "2695effb5807a22ff3d138d593fd856244e155e7",
+ "short_id": "2695effb",
+ "title": "Initial commit",
+ "created_at": "2017-07-26T11:08:53.000+02:00",
+ "parent_ids": [
+ "2a4b78934375d7f53875269ffd4f45fd83a84ebe"
+ ],
+ "message": "Initial commit",
"author_name": "John Smith",
"author_email": "john@example.com",
"authored_date": "2012-05-28T04:42:42-07:00",
- "committed_date": "2012-05-28T04:42:42-07:00",
"committer_name": "Jack Smith",
"committer_email": "jack@example.com",
- "id": "2695effb5807a22ff3d138d593fd856244e155e7",
- "message": "Initial commit",
- "parent_ids": [
- "2a4b78934375d7f53875269ffd4f45fd83a84ebe"
- ]
+ "committed_date": "2012-05-28T04:42:42-07:00"
},
"release": {
"tag_name": "1.0.0",
diff --git a/doc/articles/index.md b/doc/articles/index.md
index 9d2e5956029..558c624fe39 100644
--- a/doc/articles/index.md
+++ b/doc/articles/index.md
@@ -80,7 +80,7 @@ Install, upgrade, integrate, migrate to GitLab:
| :------------ | :------: | --------------: |
| [Video Tutorial: Idea to Production on Google Container Engine (GKE)](https://about.gitlab.com/2017/01/23/video-tutorial-idea-to-production-on-google-container-engine-gke/) | Tutorial | 2017/01/23 |
| [How to Setup a GitLab Instance on Microsoft Azure](https://about.gitlab.com/2016/07/13/how-to-setup-a-gitlab-instance-on-microsoft-azure/) | Tutorial | 2016/07/13 |
-| [Get started with OpenShift Origin 3 and GitLab](https://about.gitlab.com/2016/06/28/get-started-with-openshift-origin-3-and-gitlab/) | Tutorial | 2016/06/28 |
+| [Get started with OpenShift Origin 3 and GitLab](openshift_and_gitlab/index.md) | Tutorial | 2016/06/28 |
| [Getting started with GitLab and DigitalOcean](https://about.gitlab.com/2016/04/27/getting-started-with-gitlab-and-digitalocean/) | Tutorial | 2016/04/27 |
## Software development
diff --git a/doc/articles/openshift_and_gitlab/img/add-gitlab-to-project.png b/doc/articles/openshift_and_gitlab/img/add-gitlab-to-project.png
new file mode 100644
index 00000000000..fcad4e59ae3
--- /dev/null
+++ b/doc/articles/openshift_and_gitlab/img/add-gitlab-to-project.png
Binary files differ
diff --git a/doc/articles/openshift_and_gitlab/img/add-to-project.png b/doc/articles/openshift_and_gitlab/img/add-to-project.png
new file mode 100644
index 00000000000..bd915a229f6
--- /dev/null
+++ b/doc/articles/openshift_and_gitlab/img/add-to-project.png
Binary files differ
diff --git a/doc/articles/openshift_and_gitlab/img/create-project-ui.png b/doc/articles/openshift_and_gitlab/img/create-project-ui.png
new file mode 100644
index 00000000000..e72866f252a
--- /dev/null
+++ b/doc/articles/openshift_and_gitlab/img/create-project-ui.png
Binary files differ
diff --git a/doc/articles/openshift_and_gitlab/img/gitlab-logs.png b/doc/articles/openshift_and_gitlab/img/gitlab-logs.png
new file mode 100644
index 00000000000..1e24080c7df
--- /dev/null
+++ b/doc/articles/openshift_and_gitlab/img/gitlab-logs.png
Binary files differ
diff --git a/doc/articles/openshift_and_gitlab/img/gitlab-overview.png b/doc/articles/openshift_and_gitlab/img/gitlab-overview.png
new file mode 100644
index 00000000000..3c5df0ea101
--- /dev/null
+++ b/doc/articles/openshift_and_gitlab/img/gitlab-overview.png
Binary files differ
diff --git a/doc/articles/openshift_and_gitlab/img/gitlab-running.png b/doc/articles/openshift_and_gitlab/img/gitlab-running.png
new file mode 100644
index 00000000000..c7db691cb30
--- /dev/null
+++ b/doc/articles/openshift_and_gitlab/img/gitlab-running.png
Binary files differ
diff --git a/doc/articles/openshift_and_gitlab/img/gitlab-scale.png b/doc/articles/openshift_and_gitlab/img/gitlab-scale.png
new file mode 100644
index 00000000000..4903c7d7498
--- /dev/null
+++ b/doc/articles/openshift_and_gitlab/img/gitlab-scale.png
Binary files differ
diff --git a/doc/articles/openshift_and_gitlab/img/gitlab-settings.png b/doc/articles/openshift_and_gitlab/img/gitlab-settings.png
new file mode 100644
index 00000000000..db4360ffef0
--- /dev/null
+++ b/doc/articles/openshift_and_gitlab/img/gitlab-settings.png
Binary files differ
diff --git a/doc/articles/openshift_and_gitlab/img/no-resources.png b/doc/articles/openshift_and_gitlab/img/no-resources.png
new file mode 100644
index 00000000000..480fb766468
--- /dev/null
+++ b/doc/articles/openshift_and_gitlab/img/no-resources.png
Binary files differ
diff --git a/doc/articles/openshift_and_gitlab/img/openshift-infra-project.png b/doc/articles/openshift_and_gitlab/img/openshift-infra-project.png
new file mode 100644
index 00000000000..8b9f85aa341
--- /dev/null
+++ b/doc/articles/openshift_and_gitlab/img/openshift-infra-project.png
Binary files differ
diff --git a/doc/articles/openshift_and_gitlab/img/pods-overview.png b/doc/articles/openshift_and_gitlab/img/pods-overview.png
new file mode 100644
index 00000000000..e1cf08bd217
--- /dev/null
+++ b/doc/articles/openshift_and_gitlab/img/pods-overview.png
Binary files differ
diff --git a/doc/articles/openshift_and_gitlab/img/rc-name.png b/doc/articles/openshift_and_gitlab/img/rc-name.png
new file mode 100644
index 00000000000..889e34adbec
--- /dev/null
+++ b/doc/articles/openshift_and_gitlab/img/rc-name.png
Binary files differ
diff --git a/doc/articles/openshift_and_gitlab/img/running-pods.png b/doc/articles/openshift_and_gitlab/img/running-pods.png
new file mode 100644
index 00000000000..3fd4e56662f
--- /dev/null
+++ b/doc/articles/openshift_and_gitlab/img/running-pods.png
Binary files differ
diff --git a/doc/articles/openshift_and_gitlab/img/storage-volumes.png b/doc/articles/openshift_and_gitlab/img/storage-volumes.png
new file mode 100644
index 00000000000..ae1e5381faa
--- /dev/null
+++ b/doc/articles/openshift_and_gitlab/img/storage-volumes.png
Binary files differ
diff --git a/doc/articles/openshift_and_gitlab/img/web-console.png b/doc/articles/openshift_and_gitlab/img/web-console.png
new file mode 100644
index 00000000000..aa1425d4f94
--- /dev/null
+++ b/doc/articles/openshift_and_gitlab/img/web-console.png
Binary files differ
diff --git a/doc/articles/openshift_and_gitlab/index.md b/doc/articles/openshift_and_gitlab/index.md
new file mode 100644
index 00000000000..7f76e577efa
--- /dev/null
+++ b/doc/articles/openshift_and_gitlab/index.md
@@ -0,0 +1,510 @@
+# Getting started with OpenShift Origin 3 and GitLab
+
+> **Article [Type](../../development/writing_documentation.html#types-of-technical-articles):** tutorial ||
+> **Level:** intermediary ||
+> **Author:** [Achilleas Pipinellis](https://gitlab.com/axil) ||
+> **Publication date:** 2016/06/28
+
+## Introduction
+
+[OpenShift Origin][openshift] is an open source container application
+platform created by [RedHat], based on [kubernetes] and [Docker]. That means
+you can host your own PaaS for free and almost with no hassle.
+
+In this tutorial, we will see how to deploy GitLab in OpenShift using GitLab's
+official Docker image while getting familiar with the web interface and CLI
+tools that will help us achieve our goal.
+
+---
+
+## Prerequisites
+
+OpenShift 3 is not yet deployed on RedHat's offered Online platform ([openshift.com]),
+so in order to test it, we will use an [all-in-one Virtualbox image][vm] that is
+offered by the OpenShift developers and managed by Vagrant. If you haven't done
+already, go ahead and install the following components as they are essential to
+test OpenShift easily:
+
+- [VirtualBox]
+- [Vagrant]
+- [OpenShift Client][oc] (`oc` for short)
+
+It is also important to mention that for the purposes of this tutorial, the
+latest Origin release is used:
+
+- **oc** `v1.3.0` (must be [installed][oc-gh] locally on your computer)
+- **openshift** `v1.3.0` (is pre-installed in the [VM image][vm-new])
+- **kubernetes** `v1.3.0` (is pre-installed in the [VM image][vm-new])
+
+>**Note:**
+If you intend to deploy GitLab on a production OpenShift cluster, there are some
+limitations to bare in mind. Read on the [limitations](#current-limitations)
+section for more information and follow the linked links for the relevant
+discussions.
+
+Now that you have all batteries, let's see how easy it is to test OpenShift
+on your computer.
+
+## Getting familiar with OpenShift Origin
+
+The environment we are about to use is based on CentOS 7 which comes with all
+the tools needed pre-installed: Docker, kubernetes, OpenShift, etcd.
+
+### Test OpenShift using Vagrant
+
+As of this writing, the all-in-one VM is at version 1.3, and that's
+what we will use in this tutorial.
+
+In short:
+
+1. Open a terminal and in a new directory run:
+ ```sh
+ vagrant init openshift/origin-all-in-one
+ ```
+1. This will generate a Vagrantfile based on the all-in-one VM image
+1. In the same directory where you generated the Vagrantfile
+ enter:
+
+ ```sh
+ vagrant up
+ ```
+
+This will download the VirtualBox image and fire up the VM with some preconfigured
+values as you can see in the Vagrantfile. As you may have noticed, you need
+plenty of RAM (5GB in our example), so make sure you have enough.
+
+Now that OpenShift is setup, let's see how the web console looks like.
+
+### Explore the OpenShift web console
+
+Once Vagrant finishes its thing with the VM, you will be presented with a
+message which has some important information. One of them is the IP address
+of the deployed OpenShift platform and in particular <https://10.2.2.2:8443/console/>.
+Open this link with your browser and accept the self-signed certificate in
+order to proceed.
+
+Let's login as admin with username/password `admin/admin`. This is what the
+landing page looks like:
+
+![openshift web console](img/web-console.png)
+
+You can see that a number of [projects] are already created for testing purposes.
+
+If you head over the `openshift-infra` project, a number of services with their
+respective pods are there to explore.
+
+![openshift web console](img/openshift-infra-project.png)
+
+We are not going to explore the whole interface, but if you want to learn about
+the key concepts of OpenShift, read the [core concepts reference][core] in the
+official documentation.
+
+### Explore the OpenShift CLI
+
+OpenShift Client (`oc`), is a powerful CLI tool that talks to the OpenShift API
+and performs pretty much everything you can do from the web UI and much more.
+
+Assuming you have [installed][oc] it, let's explore some of its main
+functionalities.
+
+Let's first see the version of `oc`:
+
+```sh
+$ oc version
+
+oc v1.3.0
+kubernetes v1.3.0+52492b4
+```
+
+With `oc help` you can see the top level arguments you can run with `oc` and
+interact with your cluster, kubernetes, run applications, create projects and
+much more.
+
+Let's login to the all-in-one VM and see how to achieve the same results like
+when we visited the web console earlier. The username/password for the
+administrator user is `admin/admin`. There is also a test user with username/
+password `user/user`, with limited access. Let's login as admin for the moment:
+
+```sh
+$ oc login https://10.2.2.2:8443
+
+Authentication required for https://10.2.2.2:8443 (openshift)
+Username: admin
+Password:
+Login successful.
+
+You have access to the following projects and can switch between them with 'oc project <projectname>':
+
+ * cockpit
+ * default (current)
+ * delete
+ * openshift
+ * openshift-infra
+ * sample
+
+Using project "default".
+```
+
+Switch to the `openshift-infra` project with:
+
+```sh
+oc project openshift-infra
+```
+
+And finally, see its status:
+
+```sh
+oc status
+```
+
+The last command should spit a bunch of information about the statuses of the
+pods and the services, which if you look closely is what we encountered in the
+second image when we explored the web console.
+
+You can always read more about `oc` in the [OpenShift CLI documentation][oc].
+
+### Troubleshooting the all-in-one VM
+
+Using the all-in-one VM gives you the ability to test OpenShift whenever you
+want. That means you get to play with it, shutdown the VM, and pick up where
+you left off.
+
+Sometimes though, you may encounter some issues, like OpenShift not running
+when booting up the VM. The web UI may not responding or you may see issues
+when trying to login with `oc`, like:
+
+```
+The connection to the server 10.2.2.2:8443 was refused - did you specify the right host or port?
+```
+
+In that case, the OpenShift service might not be running, so in order to fix it:
+
+1. SSH into the VM by going to the directory where the Vagrantfile is and then
+ run:
+
+ ```sh
+ vagrant ssh
+ ```
+
+1. Run `systemctl` and verify by the output that the `openshift` service is not
+ running (it will be in red color). If that's the case start the service with:
+
+ ```sh
+ sudo systemctl start openshift
+ ```
+
+1. Verify the service is up with:
+
+ ```sh
+ systemctl status openshift -l
+ ```
+
+Now you will be able to login using `oc` (like we did before) and visit the web
+console.
+
+## Deploy GitLab
+
+Now that you got a taste of what OpenShift looks like, let's deploy GitLab!
+
+### Create a new project
+
+First, we will create a new project to host our application. You can do this
+either by running the CLI client:
+
+```bash
+$ oc new-project gitlab
+```
+
+or by using the web interface:
+
+![Create a new project from the UI](img/create-project-ui.png)
+
+If you used the command line, `oc` automatically uses the new project and you
+can see its status with:
+
+```sh
+$ oc status
+
+In project gitlab on server https://10.2.2.2:8443
+
+You have no services, deployment configs, or build configs.
+Run 'oc new-app' to create an application.
+```
+
+If you visit the web console, you can now see `gitlab` listed in the projects list.
+
+The next step is to import the OpenShift template for GitLab.
+
+### Import the template
+
+The [template][templates] is basically a JSON file which describes a set of
+related object definitions to be created together, as well as a set of
+parameters for those objects.
+
+The template for GitLab resides in the Omnibus GitLab repository under the
+docker directory. Let's download it locally with `wget`:
+
+```bash
+wget https://gitlab.com/gitlab-org/omnibus-gitlab/raw/master/docker/openshift-template.json
+```
+
+And then let's import it in OpenShift:
+
+```bash
+oc create -f openshift-template.json -n openshift
+```
+
+>**Note:**
+The `-n openshift` namespace flag is a trick to make the template available to all
+projects. If you recall from when we created the `gitlab` project, `oc` switched
+to it automatically, and that can be verified by the `oc status` command. If
+you omit the namespace flag, the application will be available only to the
+current project, in our case `gitlab`. The `openshift` namespace is a global
+one that the administrators should use if they want the application to be
+available to all users.
+
+We are now ready to finally deploy GitLab!
+
+### Create a new application
+
+The next step is to use the template we previously imported. Head over to the
+`gitlab` project and hit the **Add to Project** button.
+
+![Add to project](img/add-to-project.png)
+
+This will bring you to the catalog where you can find all the pre-defined
+applications ready to deploy with the click of a button. Search for `gitlab`
+and you will see the previously imported template:
+
+![Add GitLab to project](img/add-gitlab-to-project.png)
+
+Select it, and in the following screen you will be presented with the predefined
+values used with the GitLab template:
+
+![GitLab settings](img/gitlab-settings.png)
+
+Notice at the top that there are three resources to be created with this
+template:
+
+- `gitlab-ce`
+- `gitlab-ce-redis`
+- `gitlab-ce-postgresql`
+
+While PostgreSQL and Redis are bundled in Omnibus GitLab, the template is using
+separate images as you can see from [this line][line] in the template.
+
+The predefined values have been calculated for the purposes of testing out
+GitLab in the all-in-one VM. You don't need to change anything here, hit
+**Create** to start the deployment.
+
+If you are deploying to production you will want to change the **GitLab instance
+hostname** and use greater values for the volume sizes. If you don't provide a
+password for PostgreSQL, it will be created automatically.
+
+>**Note:**
+The `gitlab.apps.10.2.2.2.xip.io` hostname that is used by default will
+resolve to the host with IP `10.2.2.2` which is the IP our VM uses. It is a
+trick to have distinct FQDNs pointing to services that are on our local network.
+Read more on how this works in <http://xip.io>.
+
+Now that we configured this, let's see how to manage and scale GitLab.
+
+## Manage and scale GitLab
+
+Setting up GitLab for the first time might take a while depending on your
+internet connection and the resources you have attached to the all-in-one VM.
+GitLab's docker image is quite big (~500MB), so you'll have to wait until
+it's downloaded and configured before you use it.
+
+### Watch while GitLab gets deployed
+
+Navigate to the `gitlab` project at **Overview**. You can notice that the
+deployment is in progress by the orange color. The Docker images are being
+downloaded and soon they will be up and running.
+
+![GitLab overview](img/gitlab-overview.png)
+
+Switch to the **Browse > Pods** and you will eventually see all 3 pods in a
+running status. Remember the 3 resources that were to be created when we first
+created the GitLab app? This is where you can see them in action.
+
+![Running pods](img/running-pods.png)
+
+You can see GitLab being reconfigured by taking look at the logs in realtime.
+Click on `gitlab-ce-2-j7ioe` (your ID will be different) and go to the **Logs**
+tab.
+
+![GitLab logs](img/gitlab-logs.png)
+
+At a point you should see a _**gitlab Reconfigured!**_ message in the logs.
+Navigate back to the **Overview** and hopefully all pods will be up and running.
+
+![GitLab running](img/gitlab-running.png)
+
+Congratulations! You can now navigate to your new shinny GitLab instance by
+visiting <http://gitlab.apps.10.2.2.2.xip.io> where you will be asked to
+change the root user password. Login using `root` as username and providing the
+password you just set, and start using GitLab!
+
+### Scale GitLab with the push of a button
+
+If you reach to a point where your GitLab instance could benefit from a boost
+of resources, you'd be happy to know that you can scale up with the push of a
+button.
+
+In the **Overview** page just click the up arrow button in the pod where
+GitLab is. The change is instant and you can see the number of [replicas] now
+running scaled to 2.
+
+![GitLab scale](img/gitlab-scale.png)
+
+Upping the GitLab pods is actually like adding new application servers to your
+cluster. You can see how that would work if you didn't use GitLab with
+OpenShift by following the [HA documentation][ha] for the application servers.
+
+Bare in mind that you may need more resources (CPU, RAM, disk space) when you
+scale up. If a pod is in pending state for too long, you can navigate to
+**Browse > Events** and see the reason and message of the state.
+
+![No resources](img/no-resources.png)
+
+### Scale GitLab using the `oc` CLI
+
+Using `oc` is super easy to scale up the replicas of a pod. You may want to
+skim through the [basic CLI operations][basic-cli] to get a taste how the CLI
+commands are used. Pay extra attention to the object types as we will use some
+of them and their abbreviated versions below.
+
+In order to scale up, we need to find out the name of the replication controller.
+Let's see how to do that using the following steps.
+
+1. Make sure you are in the `gitlab` project:
+
+ ```sh
+ oc project gitlab
+ ```
+
+1. See what services are used for this project:
+
+ ```sh
+ oc get svc
+ ```
+
+ The output will be similar to:
+
+ ```
+ NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
+ gitlab-ce 172.30.243.177 <none> 22/TCP,80/TCP 5d
+ gitlab-ce-postgresql 172.30.116.75 <none> 5432/TCP 5d
+ gitlab-ce-redis 172.30.105.88 <none> 6379/TCP 5d
+ ```
+
+1. We need to see the replication controllers of the `gitlab-ce` service.
+ Get a detailed view of the current ones:
+
+ ```sh
+ oc describe rc gitlab-ce
+ ```
+
+ This will return a large detailed list of the current replication controllers.
+ Search for the name of the GitLab controller, usually `gitlab-ce-1` or if
+ that failed at some point and you spawned another one, it will be named
+ `gitlab-ce-2`.
+
+1. Scale GitLab using the previous information:
+
+ ```sh
+ oc scale --replicas=2 replicationcontrollers gitlab-ce-2
+ ```
+
+1. Get the new replicas number to make sure scaling worked:
+
+ ```sh
+ oc get rc gitlab-ce-2
+ ```
+
+ which will return something like:
+
+ ```
+ NAME DESIRED CURRENT AGE
+ gitlab-ce-2 2 2 5d
+ ```
+
+And that's it! We successfully scaled the replicas to 2 using the CLI.
+
+As always, you can find the name of the controller using the web console. Just
+click on the service you are interested in and you will see the details in the
+right sidebar.
+
+![Replication controller name](img/rc-name.png)
+
+### Autoscaling GitLab
+
+In case you were wondering whether there is an option to autoscale a pod based
+on the resources of your server, the answer is yes, of course there is.
+
+We will not expand on this matter, but feel free to read the documentation on
+OpenShift's website about [autoscaling].
+
+## Current limitations
+
+As stated in the [all-in-one VM][vm] page:
+
+> By default, OpenShift will not allow a container to run as root or even a
+non-random container assigned userid. Most Docker images in the Dockerhub do not
+follow this best practice and instead run as root.
+
+The all-in-one VM we are using has this security turned off so it will not
+bother us. In any case, it is something to keep in mind when deploying GitLab
+on a production cluster.
+
+In order to deploy GitLab on a production cluster, you will need to assign the
+GitLab service account to the `anyuid` Security Context.
+
+1. Edit the Security Context:
+ ```sh
+ oc edit scc anyuid
+ ```
+
+1. Add `system:serviceaccount:<project>:gitlab-ce-user` to the `users` section.
+ If you changed the Application Name from the default the user will
+ will be `<app-name>-user` instead of `gitlab-ce-user`
+
+1. Save and exit the editor
+
+## Conclusion
+
+By now, you should have an understanding of the basic OpenShift Origin concepts
+and a sense of how things work using the web console or the CLI.
+
+GitLab was hard to install in previous versions of OpenShift,
+but now that belongs to the past. Upload a template, create a project, add an
+application and you are done. You are ready to login to your new GitLab instance.
+
+And remember that in this tutorial we just scratched the surface of what Origin
+is capable of. As always, you can refer to the detailed
+[documentation][openshift-docs] to learn more about deploying your own OpenShift
+PaaS and managing your applications with the ease of containers.
+
+[RedHat]: https://www.redhat.com/en "RedHat website"
+[openshift]: https://www.openshift.org "OpenShift Origin website"
+[vm]: https://www.openshift.org/vm/ "OpenShift All-in-one VM"
+[vm-new]: https://atlas.hashicorp.com/openshift/boxes/origin-all-in-one "Official OpenShift Vagrant box on Atlas"
+[template]: https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/docker/openshift-template.json "OpenShift template for GitLab"
+[openshift.com]: https://openshift.com "OpenShift Online"
+[kubernetes]: http://kubernetes.io/ "Kubernetes website"
+[Docker]: https://www.docker.com "Docker website"
+[oc]: https://docs.openshift.org/latest/cli_reference/get_started_cli.html "Documentation - oc CLI documentation"
+[VirtualBox]: https://www.virtualbox.org/wiki/Downloads "VirtualBox downloads"
+[Vagrant]: https://www.vagrantup.com/downloads.html "Vagrant downloads"
+[projects]: https://docs.openshift.org/latest/dev_guide/projects.html "Documentation - Projects overview"
+[core]: https://docs.openshift.org/latest/architecture/core_concepts/index.html "Documentation - Core concepts of OpenShift Origin"
+[templates]: https://docs.openshift.org/latest/architecture/core_concepts/templates.html "Documentation - OpenShift templates"
+[old-post]: https://blog.openshift.com/deploy-gitlab-openshift/ "Old post - Deploy GitLab on OpenShift"
+[line]: https://gitlab.com/gitlab-org/omnibus-gitlab/blob/658c065c8d022ce858dd63eaeeadb0b2ddc8deea/docker/openshift-template.json#L239 "GitLab - OpenShift template"
+[oc-gh]: https://github.com/openshift/origin/releases/tag/v1.3.0 "Openshift 1.3.0 release on GitHub"
+[ha]: http://docs.gitlab.com/ce/administration/high_availability/gitlab.html "Documentation - GitLab High Availability"
+[replicas]: https://docs.openshift.org/latest/architecture/core_concepts/deployments.html#replication-controllers "Documentation - Replication controller"
+[autoscaling]: https://docs.openshift.org/latest/dev_guide/pod_autoscaling.html "Documentation - Autoscale"
+[basic-cli]: https://docs.openshift.org/latest/cli_reference/basic_cli_operations.html "Documentation - Basic CLI operations"
+[openshift-docs]: https://docs.openshift.org "OpenShift documentation"
diff --git a/doc/ci/environments.md b/doc/ci/environments.md
index 6a7f694d705..28b27921f8b 100644
--- a/doc/ci/environments.md
+++ b/doc/ci/environments.md
@@ -607,10 +607,9 @@ exist, you should see something like:
- With GitLab 9.2, all deployments to an environment are shown directly on the
monitoring dashboard
-If you have enabled Prometheus for collecting metrics, you can monitor the performance behavior of your app
-through the environments.
+If you have enabled [Prometheus for monitoring system and response metrics](https://docs.gitlab.com/ee/user/project/integrations/prometheus.html), you can monitor the performance behavior of your app running in each environment.
-Once configured, GitLab will attempt to retrieve performance metrics for any
+Once configured, GitLab will attempt to retrieve [supported performance metrics](https://docs.gitlab.com/ee/user/project/integrations/prometheus_library/metrics.html) for any
environment which has had a successful deployment. If monitoring data was
successfully retrieved, a Monitoring button will appear on the environment's
detail page.
diff --git a/doc/development/background_migrations.md b/doc/development/background_migrations.md
index e67db9ff142..f83a60e49e8 100644
--- a/doc/development/background_migrations.md
+++ b/doc/development/background_migrations.md
@@ -7,6 +7,11 @@ storing data in a single JSON column the data is stored in a separate table.
## When To Use Background Migrations
+>**Note:**
+When adding background migrations _you must_ make sure they are announced in the
+monthly release post along with an estimate of how long it will take to complete
+the migrations.
+
In the vast majority of cases you will want to use a regular Rails migration
instead. Background migrations should _only_ be used when migrating _data_ in
tables that have so many rows this process would take hours when performed in a
@@ -91,6 +96,10 @@ BackgroundMigrationWorker.perform_bulk_in(5.minutes, jobs)
## Cleaning Up
+>**Note:**
+Cleaning up any remaining background migrations _must_ be done in either a major
+or minor release, you _must not_ do this in a patch release.
+
Because background migrations can take a long time you can't immediately clean
things up after scheduling them. For example, you can't drop a column that's
used in the migration process as this would cause jobs to fail. This means that
diff --git a/doc/development/changelog.md b/doc/development/changelog.md
index ce39a379a0e..f869938fe11 100644
--- a/doc/development/changelog.md
+++ b/doc/development/changelog.md
@@ -15,11 +15,14 @@ following format:
title: "Going through change[log]s"
merge_request: 1972
author: Ozzy Osbourne
+type: added
```
The `merge_request` value is a reference to a merge request that adds this
entry, and the `author` key is used to give attribution to community
contributors. **Both are optional**.
+The `type` field maps the category of the change,
+valid options are: added, fixed, changed, deprecated, removed, security, other. **Type field is mandatory**.
Community contributors and core team members are encouraged to add their name to
the `author` field. GitLab team members **should not**.
@@ -94,6 +97,19 @@ Its simplest usage is to provide the value for `title`:
$ bin/changelog 'Hey DZ, I added a feature to GitLab!'
```
+At this point the script would ask you to select the category of the change (mapped to the `type` field in the entry):
+
+```text
+>> Please specify the category of your change:
+1. New feature
+2. Bug fix
+3. Feature change
+4. New deprecation
+5. Feature removal
+6. Security fix
+7. Other
+```
+
The entry filename is based on the name of the current Git branch. If you run
the command above on a branch called `feature/hey-dz`, it will generate a
`changelogs/unreleased/feature-hey-dz.yml` file.
@@ -106,26 +122,29 @@ create changelogs/unreleased/my-feature.yml
title: Hey DZ, I added a feature to GitLab!
merge_request:
author:
+type:
```
If you're working on the GitLab EE repository, the entry will be added to
`changelogs/unreleased-ee/` instead.
#### Arguments
-| Argument | Shorthand | Purpose |
-| ----------------- | --------- | --------------------------------------------- |
-| [`--amend`] | | Amend the previous commit |
-| [`--force`] | `-f` | Overwrite an existing entry |
-| [`--merge-request`] | `-m` | Set merge request ID |
-| [`--dry-run`] | `-n` | Don't actually write anything, just print |
-| [`--git-username`] | `-u` | Use Git user.name configuration as the author |
-| [`--help`] | `-h` | Print help message |
+| Argument | Shorthand | Purpose |
+| ----------------- | --------- | ---------------------------------------------------------------------------------------------------------- |
+| [`--amend`] | | Amend the previous commit |
+| [`--force`] | `-f` | Overwrite an existing entry |
+| [`--merge-request`] | `-m` | Set merge request ID |
+| [`--dry-run`] | `-n` | Don't actually write anything, just print |
+| [`--git-username`] | `-u` | Use Git user.name configuration as the author |
+| [`--type`] | `-t` | The category of the change, valid options are: added, fixed, changed, deprecated, removed, security, other |
+| [`--help`] | `-h` | Print help message |
[`--amend`]: #-amend
[`--force`]: #-force-or-f
[`--merge-request`]: #-merge-request-or-m
[`--dry-run`]: #-dry-run-or-n
[`--git-username`]: #-git-username-or-u
+[`--type`]: #-type-or-t
[`--help`]: #-help
##### `--amend`
@@ -147,6 +166,7 @@ create changelogs/unreleased/feature-hey-dz.yml
title: Added an awesome new feature to GitLab
merge_request:
author:
+type:
```
##### `--force` or `-f`
@@ -164,6 +184,7 @@ create changelogs/unreleased/feature-hey-dz.yml
title: Hey DZ, I added a feature to GitLab!
merge_request: 1983
author:
+type:
```
##### `--merge-request` or `-m`
@@ -178,6 +199,7 @@ create changelogs/unreleased/feature-hey-dz.yml
title: Hey DZ, I added a feature to GitLab!
merge_request: 1983
author:
+type:
```
##### `--dry-run` or `-n`
@@ -192,6 +214,7 @@ create changelogs/unreleased/feature-hey-dz.yml
title: Added an awesome new feature to GitLab
merge_request:
author:
+type:
$ ls changelogs/unreleased/
```
@@ -211,6 +234,21 @@ create changelogs/unreleased/feature-hey-dz.yml
title: Hey DZ, I added a feature to GitLab!
merge_request:
author: Jane Doe
+type:
+```
+
+##### `--type` or `-t`
+
+Use the **`--type`** or **`-t`** argument to provide the `type` value:
+
+```text
+$ bin/changelog 'Hey DZ, I added a feature to GitLab!' -t added
+create changelogs/unreleased/feature-hey-dz.yml
+---
+title: Hey DZ, I added a feature to GitLab!
+merge_request:
+author:
+type: added
```
### History and Reasoning
diff --git a/doc/development/fe_guide/style_guide_js.md b/doc/development/fe_guide/style_guide_js.md
index 149a0159680..9c72fda0229 100644
--- a/doc/development/fe_guide/style_guide_js.md
+++ b/doc/development/fe_guide/style_guide_js.md
@@ -11,7 +11,7 @@ See [our current .eslintrc][eslintrc] for specific rules and patterns.
#### ESlint
-1. **Never** disable eslint rules unless you have a good reason.
+1. **Never** disable eslint rules unless you have a good reason.
You may see a lot of legacy files with `/* eslint-disable some-rule, some-other-rule */`
at the top, but legacy files are a special case. Any time you develop a new feature or
refactor an existing one, you should abide by the eslint rules.
@@ -100,26 +100,44 @@ followed by any global declarations, then a blank newline prior to any imports o
export default Foo;
```
-1. Relative paths: Unless you are writing a test, always reference other scripts using
-relative paths instead of `~`
- * In **app/assets/javascripts**:
+1. Relative paths: when importing a module in the same directory, a child
+directory, or an immediate parent directory prefer relative paths. When
+importing a module which is two or more levels up, prefer either `~/` or `ee/`
+.
- ```javascript
- // bad
- import Foo from '~/foo'
+In **app/assets/javascripts/my-feature/subdir**:
- // good
- import Foo from '../foo';
- ```
- * In **spec/javascripts**:
+``` javascript
+// bad
+import Foo from '~/my-feature/foo';
+import Bar from '~/my-feature/subdir/bar';
+import Bin from '~/my-feature/subdir/lib/bin';
- ```javascript
- // bad
- import Foo from '../../app/assets/javascripts/foo'
+// good
+import Foo from '../foo';
+import Bar from './bar';
+import Bin from './lib/bin';
+```
- // good
- import Foo from '~/foo';
- ```
+In **spec/javascripts**:
+
+``` javascript
+// bad
+import Foo from '../../app/assets/javascripts/my-feature/foo';
+
+// good
+import Foo from '~/my-feature/foo';
+```
+
+When referencing an **EE component**:
+
+``` javascript
+// bad
+import Foo from '../../../../../ee/app/assets/javascripts/my-feature/ee-foo';
+
+// good
+import Foo from 'ee/my-feature/foo';
+```
1. Avoid using IIFE. Although we have a lot of examples of files which wrap their
contents in IIFEs (immediately-invoked function expressions),
@@ -465,7 +483,7 @@ A forEach will cause side effects, it will be mutating the array being iterated.
#### Vue and Boostrap
1. Tooltips: Do not rely on `has-tooltip` class name for Vue components
- ```javascript
+ ```javascript
// bad
<span
class="has-tooltip"
@@ -493,7 +511,24 @@ A forEach will cause side effects, it will be mutating the array being iterated.
$('span').tooltip('fixTitle');
```
+### The Javascript/Vue Accord
+The goal of this accord is to make sure we are all on the same page.
+
+1. When writing Vue, you may not use jQuery in your application.
+1.1 If you need to grab data from the DOM, you may query the DOM 1 time while bootstrapping your application to grab data attributes using `dataset`. You can do this without jQuery.
+1.2 You may use a jQuery dependency in Vue.js following [this example from the docs](https://vuejs.org/v2/examples/select2.html).
+1.3 If an outside jQuery Event needs to be listen to inside the Vue application, you may use jQuery event listeners.
+1.4 We will avoid adding new jQuery events when they are not required. Instead of adding new jQuery events take a look at [different methods to do the same task](https://vuejs.org/v2/api/#vm-emit).
+
+1. You may query the `window` object 1 time, while bootstrapping your application for application specific data (e.g. `scrollTo` is ok to access anytime). Do this access during the bootstrapping of your application.
+
+1. You may have a temporary but immediate need to create technical debt by writing code that does not follow our standards, to be refactored later. Maintainers need to be ok with the tech debt in the first place. An issue should be created for that tech debt to evaluate it further and discuss. In the coming months you should fix that tech debt, with it's priority to be determined by maintainers.
+
+1. When creating tech debt you must write the tests for that code before hand and those tests may not be rewritten. e.g. jQuery tests rewritten to Vue tests.
+
+1. You may choose to use VueX as a centralized state management. If you choose not to use VueX, you must use the *store pattern* which can be found in the [Vue.js documentation](https://vuejs.org/v2/guide/state-management.html#Simple-State-Management-from-Scratch).
+1. Once you have chosen a centralized state management solution you must use it for your entire application. i.e. Don't mix and match your state management solutions.
## SCSS
- [SCSS](style_guide_scss.md)
diff --git a/doc/development/migration_style_guide.md b/doc/development/migration_style_guide.md
index 161d2544169..9b8ab5da74e 100644
--- a/doc/development/migration_style_guide.md
+++ b/doc/development/migration_style_guide.md
@@ -35,12 +35,11 @@ Please don't depend on GitLab-specific code since it can change in future
versions. If needed copy-paste GitLab code into the migration to make it forward
compatible.
-## Commit Guidelines
+## Schema Changes
-Each migration **must** be added in its own commit with a descriptive commit
-message. If a commit adds a migration it _should only_ include the migration and
-any corresponding changes to `db/schema.rb`. This makes it easy to revert a
-database migration without accidentally reverting other changes.
+Migrations that make changes to the database schema (e.g. adding a column) can
+only be added in the monthly release, patch releases may only contain data
+migrations _unless_ schema changes are absolutely required to solve a problem.
## Downtime Tagging
@@ -224,9 +223,9 @@ add_column(:projects, :foo, :integer, default: 10, limit: 8)
## Timestamp column type
-By default, Rails uses the `timestamp` data type that stores timestamp data without timezone information.
-The `timestamp` data type is used by calling either the `add_timestamps` or the `timestamps` method.
-Also Rails converts the `:datetime` data type to the `timestamp` one.
+By default, Rails uses the `timestamp` data type that stores timestamp data without timezone information.
+The `timestamp` data type is used by calling either the `add_timestamps` or the `timestamps` method.
+Also Rails converts the `:datetime` data type to the `timestamp` one.
Example:
diff --git a/doc/development/rake_tasks.md b/doc/development/rake_tasks.md
index 42bb5e8619c..bfd80aab6a4 100644
--- a/doc/development/rake_tasks.md
+++ b/doc/development/rake_tasks.md
@@ -146,3 +146,20 @@ If new emoji are added, the spritesheet may change size. To compensate for
such changes, first generate the `emoji.png` spritesheet with the above Rake
task, then check the dimensions of the new spritesheet and update the
`SPRITESHEET_WIDTH` and `SPRITESHEET_HEIGHT` constants accordingly.
+
+## Updating project templates
+
+Starting a project from a template needs this project to be exported. On a
+up to date master branch with run:
+
+```
+gdk run db
+# In a new terminal window
+bundle exec rake gitlab:update_project_templates
+git checkout -b update-project-templates
+git add vendor/project_templates
+git commit
+git push -u origin update-project-templates
+```
+
+Now create a merge request and merge that to master.
diff --git a/doc/development/testing.md b/doc/development/testing.md
index 3d5aa3d45e9..efd56484b12 100644
--- a/doc/development/testing.md
+++ b/doc/development/testing.md
@@ -157,8 +157,9 @@ trade-off:
- Unit tests are usually cheap, and you should consider them like the basement
of your house: you need them to be confident that your code is behaving
- correctly. However if you run only unit tests without integration / system tests, you might [miss] the [big] [picture]!
-- Integration tests are a bit more expensive, but don't abuse them. A feature test
+ correctly. However if you run only unit tests without integration / system
+ tests, you might [miss] the [big] [picture]!
+- Integration tests are a bit more expensive, but don't abuse them. A system test
is often better than an integration test that is stubbing a lot of internals.
- System tests are expensive (compared to unit tests), even more if they require
a JavaScript driver. Make sure to follow the guidelines in the [Speed](#test-speed)
@@ -188,24 +189,34 @@ Please consult the [dedicated "Frontend testing" guide](./fe_guide/testing.md).
### General Guidelines
- Use a single, top-level `describe ClassName` block.
-- Use `described_class` instead of repeating the class name being described
- (_this is enforced by RuboCop_).
- Use `.method` to describe class methods and `#method` to describe instance
methods.
- Use `context` to test branching logic.
-- Use multi-line `do...end` blocks for `before` and `after`, even when it would
- fit on a single line.
- Don't assert against the absolute value of a sequence-generated attribute (see [Gotchas](gotchas.md#dont-assert-against-the-absolute-value-of-a-sequence-generated-attribute)).
-- Don't supply the `:each` argument to hooks since it's the default.
-- Prefer `not_to` to `to_not` (_this is enforced by RuboCop_).
- Try to match the ordering of tests to the ordering within the class.
- Try to follow the [Four-Phase Test][four-phase-test] pattern, using newlines
to separate phases.
-- Try to use `Gitlab.config.gitlab.host` rather than hard coding `'localhost'`
+- Use `Gitlab.config.gitlab.host` rather than hard coding `'localhost'`
+- Don't assert against the absolute value of a sequence-generated attribute (see
+ [Gotchas](gotchas.md#dont-assert-against-the-absolute-value-of-a-sequence-generated-attribute)).
+- Don't supply the `:each` argument to hooks since it's the default.
- On `before` and `after` hooks, prefer it scoped to `:context` over `:all`
[four-phase-test]: https://robots.thoughtbot.com/four-phase-test
+### Automatic retries and flaky tests detection
+
+On our CI, we use [rspec-retry] to automatically retry a failing example a few
+times (see [`spec/spec_helper.rb`] for the precise retries count).
+
+We also use a home-made `RspecFlaky::Listener` listener which records flaky
+examples in a JSON report file on `master` (`retrieve-tests-metadata` and `update-tests-metadata` jobs), and warns when a new flaky example
+is detected in any other branch (`flaky-examples-check` job). In the future, the
+`flaky-examples-check` job will not be allowed to fail.
+
+[rspec-retry]: https://github.com/NoRedInk/rspec-retry
+[`spec/spec_helper.rb`]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/spec/spec_helper.rb
+
### `let` variables
GitLab's RSpec suite has made extensive use of `let` variables to reduce
@@ -268,6 +279,43 @@ end
- Avoid scenario titles that add no information, such as "successfully".
- Avoid scenario titles that repeat the feature title.
+### Table-based / Parameterized tests
+
+This style of testing is used to exercise one piece of code with a comprehensive
+range of inputs. By specifying the test case once, alongside a table of inputs
+and the expected output for each, your tests can be made easier to read and more
+compact.
+
+We use the [rspec-parameterized](https://github.com/tomykaira/rspec-parameterized)
+gem. A short example, using the table syntax and checking Ruby equality for a
+range of inputs, might look like this:
+
+```ruby
+describe "#==" do
+ using Rspec::Parameterized::TableSyntax
+
+ let(:project1) { create(:project) }
+ let(:project2) { create(:project) }
+ where(:a, :b, :result) do
+ 1 | 1 | true
+ 1 | 2 | false
+ true | true | true
+ true | false | false
+ project1 | project1 | true
+ project2 | project2 | true
+ project 1 | project2 | false
+ end
+
+ with_them do
+ it { expect(a == b).to eq(result) }
+
+ it 'is isomorphic' do
+ expect(b == a).to eq(result)
+ end
+ end
+end
+```
+
### Matchers
Custom matchers should be created to clarify the intent and/or hide the
@@ -276,6 +324,15 @@ complexity of RSpec expectations.They should be placed under
a certain type of specs only (e.g. features, requests etc.) but shouldn't be if
they apply to multiple type of specs.
+#### have_gitlab_http_status
+
+Prefer `have_gitlab_http_status` over `have_http_status` because the former
+could also show the response body whenever the status mismatched. This would
+be very useful whenever some tests start breaking and we would love to know
+why without editing the source and rerun the tests.
+
+This is especially useful whenever it's showing 500 internal server error.
+
### Shared contexts
All shared contexts should be be placed under `spec/support/shared_contexts/`.
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 8ded607bcab..b14cb2d44c4 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -66,6 +66,9 @@ Install the required packages (needed to compile Ruby and native extensions to R
sudo apt-get install -y build-essential zlib1g-dev libyaml-dev libssl-dev libgdbm-dev libre2-dev libreadline-dev libncurses5-dev libffi-dev curl openssh-server checkinstall libxml2-dev libxslt-dev libcurl4-openssl-dev libicu-dev logrotate python-docutils pkg-config cmake
+Ubuntu 14.04 (Trusty Tahr) doesn't have the `libre2-dev` package available, but
+you can [install re2 manually](https://github.com/google/re2/wiki/Install).
+
If you want to use Kerberos for user authentication, then install libkrb5-dev:
sudo apt-get install libkrb5-dev
@@ -168,8 +171,10 @@ are out of date, so we'll need to install through the following commands:
curl --location https://deb.nodesource.com/setup_7.x | sudo bash -
sudo apt-get install -y nodejs
- # install yarn
- curl --location https://yarnpkg.com/install.sh | bash -
+ curl --silent --show-error https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
+ echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
+ sudo apt-get update
+ sudo apt-get install yarn
Visit the official websites for [node](https://nodejs.org/en/download/package-manager/) and [yarn](https://yarnpkg.com/en/docs/install/) if you have any trouble with these steps.
@@ -294,9 +299,9 @@ sudo usermod -aG redis git
### Clone the Source
# Clone GitLab repository
- sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 9-4-stable gitlab
+ sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 9-5-stable gitlab
-**Note:** You can change `9-4-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
+**Note:** You can change `9-5-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
### Configure It
@@ -505,15 +510,17 @@ Check if GitLab and its environment are configured correctly:
sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
+
+### Compile GetText PO files
+
+ sudo -u git -H bundle exec rake gettext:pack RAILS_ENV=production
+ sudo -u git -H bundle exec rake gettext:po_to_json RAILS_ENV=production
+
### Compile Assets
sudo -u git -H yarn install --production --pure-lockfile
sudo -u git -H bundle exec rake gitlab:assets:compile RAILS_ENV=production NODE_ENV=production
-### Compile GetText PO files
-
- sudo -u git -H bundle exec rake gettext:compile RAILS_ENV=production
-
### Start Your GitLab Instance
sudo service gitlab start
diff --git a/doc/install/kubernetes/gitlab_chart.md b/doc/install/kubernetes/gitlab_chart.md
index d2442a4fbde..0fad181f59e 100644
--- a/doc/install/kubernetes/gitlab_chart.md
+++ b/doc/install/kubernetes/gitlab_chart.md
@@ -1,7 +1,7 @@
# GitLab Helm Chart
-> Officially supported cloud providers are Google Container Service and Azure Container Service.
+> These Helm charts are in beta. GitLab is working on a [cloud-native](http://docs.gitlab.com/omnibus/package-information/cloud_native.html) set of [Charts](https://gitlab.com/charts/helm.gitlab.io) which will replace these.
-> Officially supported schedulers are Kubernetes and Terraform.
+> Officially supported cloud providers are Google Container Service and Azure Container Service.
The `gitlab` Helm chart deploys GitLab into your Kubernetes cluster.
@@ -207,7 +207,7 @@ its class in an annotation.
>**Note:**
The Ingress alone doesn't expose GitLab externally. You need to have a Ingress controller setup to do that.
Setting up an Ingress controller can be done by installing the `nginx-ingress` helm chart. But be sure
-to read the [documentation](https://github.com/kubernetes/charts/blob/master/stable/nginx-ingress/README.md).
+to read the [documentation](https://github.com/kubernetes/charts/blob/master/stable/nginx-ingress/README.md).
>**Note:**
If you would like to use the Registry, you will also need to ensure your Ingress supports a [sufficiently large request size](http://nginx.org/en/docs/http/ngx_http_core_module.html#client_max_body_size).
diff --git a/doc/install/kubernetes/gitlab_omnibus.md b/doc/install/kubernetes/gitlab_omnibus.md
new file mode 100644
index 00000000000..b7e86ea7c81
--- /dev/null
+++ b/doc/install/kubernetes/gitlab_omnibus.md
@@ -0,0 +1,171 @@
+# GitLab-Omnibus Helm Chart
+> These Helm charts are in beta. GitLab is working on a [cloud-native](http://docs.gitlab.com/omnibus/package-information/cloud_native.html) set of [Charts](https://gitlab.com/charts/helm.gitlab.io) which will replace these.
+
+> Officially supported cloud providers are Google Container Service and Azure Container Service.
+
+This work is based partially on: https://github.com/lwolf/kubernetes-gitlab/. GitLab would like to thank Sergey Nuzhdin for his work.
+
+## Introduction
+
+This chart provides an easy way to get started with GitLab, provisioning an installation with nearly all functionality enabled. SSL is automatically provisioned as well via [Let's Encrypt](https://letsencrypt.org/).
+
+The deployment includes:
+
+- A [GitLab Omnibus](https://docs.gitlab.com/omnibus/) Pod, including Mattermost, Container Registry, and Prometheus
+- An auto-scaling [GitLab Runner](https://docs.gitlab.com/runner/) using the Kubernetes executor
+- [Redis](https://github.com/kubernetes/charts/tree/master/stable/redis)
+- [PostgreSQL](https://github.com/kubernetes/charts/tree/master/stable/postgresql)
+- [NGINX Ingress](https://github.com/kubernetes/charts/tree/master/stable/nginx-ingress)
+- Persistent Volume Claims for Data, Registry, Postgres, and Redis
+
+A video demonstration of GitLab utilizing this chart [is available](https://about.gitlab.com/handbook/sales/demo/).
+
+Terms:
+
+- Google Cloud Platform (**GCP**)
+- Google Container Engine (**GKE**)
+- Azure Container Service (**ACS**)
+- Kubernetes (**k8s**)
+
+## Prerequisites
+
+- _At least_ 4 GB of RAM available on your cluster, in chunks of 1 GB. 41GB of storage and 2 CPU are also required.
+- Kubernetes 1.4+ with Beta APIs enabled
+- [Persistent Volume](https://kubernetes.io/docs/concepts/storage/persistent-volumes/) provisioner support in the underlying infrastructure
+- An [external IP address](#networking-prerequisites)
+- A [wildcard DNS entry](#networking-prerequisites), which resolves to the external IP address
+- The `kubectl` CLI installed locally and authenticated for the cluster
+- The Helm Client installed locally
+- The Helm Server (Tiller) already installed and running in the cluster, by running `helm init`
+- The GitLab Helm Repo [added to your Helm Client](index.md#add-the-gitlab-helm-repository)
+
+### Networking Prerequisites
+
+This chart configures a GitLab server and Kubernetes cluster which can support dynamic [Review Apps](https://docs.gitlab.com/ee/ci/review_apps/index.html), as well as services like the integrated [Container Registry](https://docs.gitlab.com/ee/user/project/container_registry.html) and [Mattermost](https://docs.gitlab.com/omnibus/gitlab-mattermost/).
+
+To support the GitLab services and dynamic environments, a wildcard DNS entry is required which resolves to the external Load Balancer IP.
+
+To provision an external IP on GCP and Azure, simply request a new address from the Networking section. Ensure that the region matches the region your container cluster is created in. Note, it is important that the IP is not assigned at this point in time. It will be automatically assigned once the Helm chart is installed, and assigned to the Load Balancer.
+
+Now that an external IP address has been allocated, ensure that the wildcard DNS entry you would like to use resolves to this IP. Please consult the documentation for your DNS service for more information on creating DNS records.
+
+## Configuring and Installing GitLab
+
+For most installations, only two parameters are required:
+- `baseIP`: the desired [external IP address](#networking-prerequisites)
+- `baseDomain`: the [base domain](#networking-prerequisites) with the wildcard host entry resolving to the `baseIP`. For example, `mycompany.io`.
+
+Other common configuration options:
+- `gitlab`: Choose the [desired edition](https://about.gitlab.com/products), either `ee` or `ce`. `ce` is the default.
+- `gitlabEELicense`: For Enterprise Edition, the [license](https://docs.gitlab.com/ee/user/admin_area/license.html) can be installed directly via the Chart
+- `provider`: Optimizes the deployment for a cloud provider. The default is `gke` for GCP, with `acs` also supported for Azure.
+- `legoEmail`: Email address to use when requesting new SSL certificates from Let's Encrypt
+
+For additional configuration options, consult the [values.yaml](https://gitlab.com/charts/charts.gitlab.io/blob/master/charts/gitlab-omnibus/values.yaml).
+
+These settings can either be passed directly on the command line:
+```bash
+helm install --name gitlab --set baseDomain=gitlab.io,baseIP=1.1.1.1,gitlab=ee,gitlabEELicense=$LICENSE,legoEmail=email@gitlab.com gitlab/gitlab-omnibus
+```
+
+or within a YAML file:
+```bash
+helm install --name gitlab -f values.yaml gitlab/gitlab-omnibus
+```
+
+> **Note:**
+If you are using a machine type with support for less than 4 attached disks, like an Azure trial, you should disable dedicated storage for [Postgres and Redis](#persistent-storage).
+
+### Choosing a different GitLab release version
+
+The version of GitLab installed is based on the `gitlab` setting (see [section](#choosing-gitlab-edition) above), and
+the value of the corresponding helm setting: `gitlabCEImage` or `gitabEEImage`.
+
+```yaml
+gitlab: CE
+gitlabCEImage: gitlab/gitlab-ce:9.1.2-ce.0
+gitlabEEImage: gitlab/gitlab-ee:9.1.2-ee.0
+```
+
+The different images can be found in the [gitlab-ce](https://hub.docker.com/r/gitlab/gitlab-ce/tags/) and [gitlab-ee](https://hub.docker.com/r/gitlab/gitlab-ee/tags/)
+repositories on Docker Hub.
+
+> **Note:**
+There is no guarantee that other release versions of GitLab, other than what are
+used by default in the chart, will be supported by a chart install.
+
+### Persistent storage
+
+By default, persistent storage is enabled for GitLab and the charts it depends
+on (Redis and PostgreSQL).
+
+Components can have their claim size set from your `values.yaml`, along with whether to provision separate storage for Postgres and Redis.
+
+Basic configuration:
+
+```yaml
+redisImage: redis:3.2.10
+redisDedicatedStorage: true
+redisStorageSize: 5Gi
+postgresImage: postgres:9.6.3
+# If you disable postgresDedicatedStorage, you should consider bumping up gitlabRailsStorageSize
+postgresDedicatedStorage: true
+postgresStorageSize: 30Gi
+gitlabRailsStorageSize: 30Gi
+gitlabRegistryStorageSize: 30Gi
+gitlabConfigStorageSize: 1Gi
+```
+
+### Routing and SSL
+
+Ingress routing and SSL are automatically configured within this Chart. An NGINX ingress is provisioned and configured, and will route traffic to any service. SSL certificates are automatically created and configured by [kube-lego](https://github.com/kubernetes/charts/tree/master/stable/kube-lego).
+
+> **Note:**
+Let's Encrypt limits a single TLD to five certificate requests within a single week. This means that common DNS wildcard services like [xip.io](http://xip.io) and [nip.io](http://nip.io) are unlikely to work.
+
+## Installing GitLab using the Helm Chart
+> You may see a temporary error message `SchedulerPredicates failed due to PersistentVolumeClaim is not bound` while storage provisions. Once the storage provisions, the pods will automatically restart. This may take a couple minutes depending on your cloud provider. If the error persists, please review the [prerequisites](#prerequisites) to ensure you have enough RAM, CPU, and storage.
+
+Once you have reviewed the [configuration settings](#configuring-and-installing-gitlab), you can install the chart. We recommending saving your configuration options in a `values.yaml` file for easier upgrades in the future.
+
+For example:
+```bash
+helm install --name gitlab -f values.yaml gitlab/gitlab-omnibus
+```
+
+or passing them on the command line:
+```bash
+helm install --name gitlab --set baseDomain=gitlab.io,baseIP=1.1.1.1,gitlab=ee,gitlabEELicense=$LICENSE,legoEmail=email@gitlab.com gitlab/gitlab-omnibus
+```
+
+## Updating GitLab using the Helm Chart
+
+Once your GitLab Chart is installed, configuration changes and chart updates
+should we done using `helm upgrade`
+
+```bash
+helm upgrade -f <CONFIG_VALUES_FILE> <RELEASE-NAME> gitlab/gitlab
+```
+
+where:
+
+- `<CONFIG_VALUES_FILE>` is the path to values file containing your custom
+ [configuration] (#configuring-and-installing-gitlab).
+- `<RELEASE-NAME>` is the name you gave the chart when installing it.
+ In the [Install section](#installing-gitlab-using-the-helm-chart) we called it `gitlab`.
+
+## Uninstalling GitLab using the Helm Chart
+
+To uninstall the GitLab Chart, run the following:
+
+```bash
+helm delete <RELEASE-NAME>
+```
+
+where:
+
+- `<RELEASE-NAME>` is the name you gave the chart when installing it.
+ In the [Install section](#installing) we called it `gitlab`.
+
+[kube-srv]: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services---service-types
+[storageclass]: https://kubernetes.io/docs/concepts/storage/persistent-volumes/#storageclasses
diff --git a/doc/install/kubernetes/gitlab_runner_chart.md b/doc/install/kubernetes/gitlab_runner_chart.md
index 515b2841d08..b0fe91d6337 100644
--- a/doc/install/kubernetes/gitlab_runner_chart.md
+++ b/doc/install/kubernetes/gitlab_runner_chart.md
@@ -1,7 +1,7 @@
# GitLab Runner Helm Chart
-> Officially supported cloud providers are Google Container Service and Azure Container Service.
+> These Helm charts are in beta. GitLab is working on a [cloud-native](http://docs.gitlab.com/omnibus/package-information/cloud_native.html) set of [Charts](https://gitlab.com/charts/helm.gitlab.io) which will replace these.
-> Officially supported schedulers are Kubernetes and Terraform.
+> Officially supported cloud providers are Google Container Service and Azure Container Service.
The `gitlab-runner` Helm chart deploys a GitLab Runner instance into your
Kubernetes cluster.
diff --git a/doc/install/kubernetes/index.md b/doc/install/kubernetes/index.md
index 5ea08869a9b..3608aa6b2d6 100644
--- a/doc/install/kubernetes/index.md
+++ b/doc/install/kubernetes/index.md
@@ -1,7 +1,7 @@
# Installing GitLab on Kubernetes
-> Officially supported cloud providers are Google Container Service and Azure Container Service.
+> These Helm charts are in beta. GitLab is working on a [cloud-native](http://docs.gitlab.com/omnibus/package-information/cloud_native.html) set of [Charts](https://gitlab.com/charts/helm.gitlab.io) which will replace these.
-> Officially supported schedulers are Kubernetes, Terraform and Tectonic.
+> Officially supported cloud providers are Google Container Service and Azure Container Service.
The easiest method to deploy GitLab in [Kubernetes](https://kubernetes.io/) is
to take advantage of the official GitLab Helm charts. [Helm] is a package
@@ -35,12 +35,11 @@ helm init
## Using the GitLab Helm Charts
-GitLab makes available two Helm Charts, one for the GitLab server and another
-for the Runner. More detailed information on installing and configuring each
-Chart can be found below:
+GitLab makes available three Helm Charts: an easy to use bundled chart, and a specific chart for GitLab itself and the Runner.
-- [Install GitLab](gitlab_chart.md)
-- [Install GitLab Runner](gitlab_runner_chart.md)
+- [gitlab-omnibus](gitlab_omnibus.md): The easiest way to get started. Includes everything needed to run GitLab, including: a Runner, Container Registry, automatic SSL, and an Ingress.
+- [gitlab](gitlab_chart.md): Just the GitLab service, with optional Postgres and Redis.
+- [gitlab-runner](gitlab_runner_chart.md): GitLab Runner, to process CI jobs.
[chart]: https://github.com/kubernetes/charts
[helm-quick]: https://github.com/kubernetes/helm/blob/master/docs/quickstart.md
diff --git a/doc/install/requirements.md b/doc/install/requirements.md
index 141df55f6bc..175dfc62096 100644
--- a/doc/install/requirements.md
+++ b/doc/install/requirements.md
@@ -104,6 +104,10 @@ features of GitLab work with MySQL/MariaDB:
See [issue #30472][30472] for more information.
1. GitLab Geo does [not support MySQL](https://docs.gitlab.com/ee/gitlab-geo/database.html#mysql-replication).
1. [Zero downtime migrations][zero] do not work with MySQL
+1. GitLab [optimizes the loading of dashboard events](https://gitlab.com/gitlab-org/gitlab-ce/issues/31806) using [PostgreSQL LATERAL JOINs](https://blog.heapanalytics.com/postgresqls-powerful-new-join-type-lateral/).
+1. In general, SQL optimized for PostgreSQL may run much slower in MySQL due to
+ differences in query planners. For example, subqueries that work well in PostgreSQL
+ may not be [performant in MySQL](https://dev.mysql.com/doc/refman/5.7/en/optimizing-subqueries.html)
1. We expect this list to grow over time.
Existing users using GitLab with MySQL/MariaDB are advised to
diff --git a/doc/system_hooks/system_hooks.md b/doc/system_hooks/system_hooks.md
index 583ec5522fd..0399ebec86a 100644
--- a/doc/system_hooks/system_hooks.md
+++ b/doc/system_hooks/system_hooks.md
@@ -31,7 +31,7 @@ X-Gitlab-Event: System Hook
"path": "storecloud",
"path_with_namespace": "jsmith/storecloud",
"project_id": 74,
- "project_visibility": "private",
+ "project_visibility": "private"
}
```
@@ -48,7 +48,7 @@ X-Gitlab-Event: System Hook
"path": "underscore",
"path_with_namespace": "jsmith/underscore",
"project_id": 73,
- "project_visibility": "internal",
+ "project_visibility": "internal"
}
```
@@ -66,7 +66,7 @@ X-Gitlab-Event: System Hook
"owner_name": "John Smith",
"owner_email": "johnsmith@gmail.com",
"project_visibility": "internal",
- "old_path_with_namespace": "jsmith/overscore",
+ "old_path_with_namespace": "jsmith/overscore"
}
```
@@ -84,7 +84,7 @@ X-Gitlab-Event: System Hook
"owner_name": "John Smith",
"owner_email": "johnsmith@gmail.com",
"project_visibility": "internal",
- "old_path_with_namespace": "jsmith/overscore",
+ "old_path_with_namespace": "jsmith/overscore"
}
```
@@ -101,7 +101,7 @@ X-Gitlab-Event: System Hook
"path": "storecloud",
"path_with_namespace": "jsmith/storecloud",
"project_id": 74,
- "project_visibility": "private",
+ "project_visibility": "private"
}
```
@@ -121,7 +121,7 @@ X-Gitlab-Event: System Hook
"user_name": "John Smith",
"user_username": "johnsmith",
"user_id": 41,
- "project_visibility": "private",
+ "project_visibility": "private"
}
```
@@ -141,7 +141,7 @@ X-Gitlab-Event: System Hook
"user_name": "John Smith",
"user_username": "johnsmith",
"user_id": 41,
- "project_visibility": "private",
+ "project_visibility": "private"
}
```
diff --git a/doc/update/8.17-to-9.0.md b/doc/update/8.17-to-9.0.md
index 6308317b1f2..2abc57da1a0 100644
--- a/doc/update/8.17-to-9.0.md
+++ b/doc/update/8.17-to-9.0.md
@@ -65,7 +65,10 @@ Since 8.17, GitLab requires the use of yarn `>= v0.17.0` to manage
JavaScript dependencies.
```bash
-curl --location https://yarnpkg.com/install.sh | bash -
+curl --silent --show-error https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
+echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
+sudo apt-get update
+sudo apt-get install yarn
```
More information can be found on the [yarn website](https://yarnpkg.com/en/docs/install).
@@ -261,6 +264,16 @@ sudo systemctl daemon-reload
### 9. Install libs, migrations, etc.
+GitLab 9.0.11 [introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/24570)
+a dependency on on the `re2` regular expression library. To install this dependency:
+
+```bash
+sudo apt-get install libre2-dev
+```
+
+Ubuntu 14.04 (Trusty Tahr) doesn't have the `libre2-dev` package available, but
+you can [install re2 manually](https://github.com/google/re2/wiki/Install).
+
```bash
cd /home/git/gitlab
diff --git a/doc/update/9.0-to-9.1.md b/doc/update/9.0-to-9.1.md
index 2d597894517..3fd1d023d2a 100644
--- a/doc/update/9.0-to-9.1.md
+++ b/doc/update/9.0-to-9.1.md
@@ -65,7 +65,10 @@ Since 8.17, GitLab requires the use of yarn `>= v0.17.0` to manage
JavaScript dependencies.
```bash
-curl --location https://yarnpkg.com/install.sh | bash -
+curl --silent --show-error https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
+echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
+sudo apt-get update
+sudo apt-get install yarn
```
More information can be found on the [yarn website](https://yarnpkg.com/en/docs/install).
@@ -261,6 +264,16 @@ sudo systemctl daemon-reload
### 9. Install libs, migrations, etc.
+GitLab 9.1.8 [introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/24570)
+a dependency on on the `re2` regular expression library. To install this dependency:
+
+```bash
+sudo apt-get install libre2-dev
+```
+
+Ubuntu 14.04 (Trusty Tahr) doesn't have the `libre2-dev` package available, but
+you can [install re2 manually](https://github.com/google/re2/wiki/Install).
+
```bash
cd /home/git/gitlab
diff --git a/doc/update/9.1-to-9.2.md b/doc/update/9.1-to-9.2.md
index 225a4dcc924..5f7a616cc7d 100644
--- a/doc/update/9.1-to-9.2.md
+++ b/doc/update/9.1-to-9.2.md
@@ -65,7 +65,10 @@ Since 8.17, GitLab requires the use of yarn `>= v0.17.0` to manage
JavaScript dependencies.
```bash
-curl --location https://yarnpkg.com/install.sh | bash -
+curl --silent --show-error https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
+echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
+sudo apt-get update
+sudo apt-get install yarn
```
More information can be found on the [yarn website](https://yarnpkg.com/en/docs/install).
@@ -97,6 +100,7 @@ cd /home/git/gitlab
sudo -u git -H git fetch --all
sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
+sudo -u git -H git checkout -- locale
```
For GitLab Community Edition:
@@ -218,6 +222,16 @@ sudo systemctl daemon-reload
### 10. Install libs, migrations, etc.
+GitLab 9.2.8 [introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/24570)
+a dependency on on the `re2` regular expression library. To install this dependency:
+
+```bash
+sudo apt-get install libre2-dev
+```
+
+Ubuntu 14.04 (Trusty Tahr) doesn't have the `libre2-dev` package available, but
+you can [install re2 manually](https://github.com/google/re2/wiki/Install).
+
```bash
cd /home/git/gitlab
@@ -233,6 +247,11 @@ sudo -u git -H bundle clean
# Run database migrations
sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+# Compile GetText PO files
+
+sudo -u git -H bundle exec rake gettext:pack RAILS_ENV=production
+sudo -u git -H bundle exec rake gettext:po_to_json RAILS_ENV=production
+
# Update node dependencies and recompile assets
sudo -u git -H bundle exec rake yarn:install gitlab:assets:clean gitlab:assets:compile RAILS_ENV=production NODE_ENV=production
diff --git a/doc/update/9.2-to-9.3.md b/doc/update/9.2-to-9.3.md
index 910539acc70..9d0b0da7edb 100644
--- a/doc/update/9.2-to-9.3.md
+++ b/doc/update/9.2-to-9.3.md
@@ -65,7 +65,10 @@ Since 8.17, GitLab requires the use of yarn `>= v0.17.0` to manage
JavaScript dependencies.
```bash
-curl --location https://yarnpkg.com/install.sh | bash -
+curl --silent --show-error https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
+echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
+sudo apt-get update
+sudo apt-get install yarn
```
More information can be found on the [yarn website](https://yarnpkg.com/en/docs/install).
@@ -97,6 +100,7 @@ cd /home/git/gitlab
sudo -u git -H git fetch --all
sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
+sudo -u git -H git checkout -- locale
```
For GitLab Community Edition:
@@ -254,6 +258,16 @@ sudo systemctl daemon-reload
### 12. Install libs, migrations, etc.
+GitLab 9.3.8 [introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/24570)
+a dependency on on the `re2` regular expression library. To install this dependency:
+
+```bash
+sudo apt-get install libre2-dev
+```
+
+Ubuntu 14.04 (Trusty Tahr) doesn't have the `libre2-dev` package available, but
+you can [install re2 manually](https://github.com/google/re2/wiki/Install).
+
```bash
cd /home/git/gitlab
@@ -269,6 +283,10 @@ sudo -u git -H bundle clean
# Run database migrations
sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+# Compile GetText PO files
+
+sudo -u git -H bundle exec rake gettext:compile RAILS_ENV=production
+
# Update node dependencies and recompile assets
sudo -u git -H bundle exec rake yarn:install gitlab:assets:clean gitlab:assets:compile RAILS_ENV=production NODE_ENV=production
diff --git a/doc/update/9.3-to-9.4.md b/doc/update/9.3-to-9.4.md
index 9540c36e7d0..9ee01bc9c51 100644
--- a/doc/update/9.3-to-9.4.md
+++ b/doc/update/9.3-to-9.4.md
@@ -65,7 +65,10 @@ Since 8.17, GitLab requires the use of yarn `>= v0.17.0` to manage
JavaScript dependencies.
```bash
-curl --location https://yarnpkg.com/install.sh | bash -
+curl --silent --show-error https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
+echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
+sudo apt-get update
+sudo apt-get install yarn
```
More information can be found on the [yarn website](https://yarnpkg.com/en/docs/install).
@@ -97,6 +100,7 @@ cd /home/git/gitlab
sudo -u git -H git fetch --all
sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
+sudo -u git -H git checkout -- locale
```
For GitLab Community Edition:
@@ -267,6 +271,16 @@ sudo systemctl daemon-reload
### 12. Install libs, migrations, etc.
+GitLab 9.4 [introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/24570)
+a dependency on on the `re2` regular expression library. To install this dependency:
+
+```bash
+sudo apt-get install libre2-dev
+```
+
+Ubuntu 14.04 (Trusty Tahr) doesn't have the `libre2-dev` package available, but
+you can [install re2 manually](https://github.com/google/re2/wiki/Install).
+
```bash
cd /home/git/gitlab
@@ -282,6 +296,10 @@ sudo -u git -H bundle clean
# Run database migrations
sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+# Compile GetText PO files
+
+sudo -u git -H bundle exec rake gettext:compile RAILS_ENV=production
+
# Update node dependencies and recompile assets
sudo -u git -H bundle exec rake yarn:install gitlab:assets:clean gitlab:assets:compile RAILS_ENV=production NODE_ENV=production
diff --git a/doc/update/9.4-to-9.5.md b/doc/update/9.4-to-9.5.md
new file mode 100644
index 00000000000..1b5a15589af
--- /dev/null
+++ b/doc/update/9.4-to-9.5.md
@@ -0,0 +1,356 @@
+# From 9.4 to 9.5
+
+Make sure you view this update guide from the tag (version) of GitLab you would
+like to install. In most cases this should be the highest numbered production
+tag (without rc in it). You can select the tag in the version dropdown at the
+top left corner of GitLab (below the menu bar).
+
+If the highest number stable branch is unclear please check the
+[GitLab Blog](https://about.gitlab.com/blog/archives.html) for installation
+guide links by version.
+
+### 1. Stop server
+
+```bash
+sudo service gitlab stop
+```
+
+### 2. Backup
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
+```
+
+### 3. Update Ruby
+
+NOTE: GitLab 9.0 and higher only support Ruby 2.3.x and dropped support for Ruby 2.1.x. Be
+sure to upgrade your interpreter if necessary.
+
+You can check which version you are running with `ruby -v`.
+
+Download and compile Ruby:
+
+```bash
+mkdir /tmp/ruby && cd /tmp/ruby
+curl --remote-name --progress https://cache.ruby-lang.org/pub/ruby/2.3/ruby-2.3.3.tar.gz
+echo '1014ee699071aa2ddd501907d18cbe15399c997d ruby-2.3.3.tar.gz' | shasum -c - && tar xzf ruby-2.3.3.tar.gz
+cd ruby-2.3.3
+./configure --disable-install-rdoc
+make
+sudo make install
+```
+
+Install Bundler:
+
+```bash
+sudo gem install bundler --no-ri --no-rdoc
+```
+
+### 4. Update Node
+
+GitLab now runs [webpack](http://webpack.js.org) to compile frontend assets and
+it has a minimum requirement of node v4.3.0.
+
+You can check which version you are running with `node -v`. If you are running
+a version older than `v4.3.0` you will need to update to a newer version. You
+can find instructions to install from community maintained packages or compile
+from source at the nodejs.org website.
+
+<https://nodejs.org/en/download/>
+
+
+Since 8.17, GitLab requires the use of yarn `>= v0.17.0` to manage
+JavaScript dependencies.
+
+```bash
+curl --silent --show-error https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
+echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
+sudo apt-get update
+sudo apt-get install yarn
+```
+
+More information can be found on the [yarn website](https://yarnpkg.com/en/docs/install).
+
+### 5. Update Go
+
+NOTE: GitLab 9.2 and higher only supports Go 1.8.3 and dropped support for Go
+1.5.x through 1.7.x. Be sure to upgrade your installation if necessary.
+
+You can check which version you are running with `go version`.
+
+Download and install Go:
+
+```bash
+# Remove former Go installation folder
+sudo rm -rf /usr/local/go
+
+curl --remote-name --progress https://storage.googleapis.com/golang/go1.8.3.linux-amd64.tar.gz
+echo '1862f4c3d3907e59b04a757cfda0ea7aa9ef39274af99a784f5be843c80c6772 go1.8.3.linux-amd64.tar.gz' | shasum -a256 -c - && \
+ sudo tar -C /usr/local -xzf go1.8.3.linux-amd64.tar.gz
+sudo ln -sf /usr/local/go/bin/{go,godoc,gofmt} /usr/local/bin/
+rm go1.8.3.linux-amd64.tar.gz
+```
+
+### 6. Get latest code
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H git fetch --all
+sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
+sudo -u git -H git checkout -- locale
+```
+
+For GitLab Community Edition:
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H git checkout 9-5-stable
+```
+
+OR
+
+For GitLab Enterprise Edition:
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H git checkout 9-5-stable-ee
+```
+
+### 7. Update gitlab-shell
+
+```bash
+cd /home/git/gitlab-shell
+
+sudo -u git -H git fetch --all --tags
+sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_SHELL_VERSION)
+sudo -u git -H bin/compile
+```
+
+### 8. Update gitlab-workhorse
+
+Install and compile gitlab-workhorse. GitLab-Workhorse uses
+[GNU Make](https://www.gnu.org/software/make/).
+If you are not using Linux you may have to run `gmake` instead of
+`make` below.
+
+```bash
+cd /home/git/gitlab-workhorse
+
+sudo -u git -H git fetch --all --tags
+sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_WORKHORSE_VERSION)
+sudo -u git -H make
+```
+
+### 9. Update Gitaly
+
+#### New Gitaly configuration options required
+
+In order to function Gitaly needs some additional configuration information. Below we assume you installed Gitaly in `/home/git/gitaly` and GitLab Shell in `/home/git/gitlab-shell'.
+
+```shell
+echo '
+[gitaly-ruby]
+dir = "/home/git/gitaly/ruby"
+
+[gitlab-shell]
+dir = "/home/git/gitlab-shell"
+' | sudo -u git tee -a /home/git/gitaly/config.toml
+```
+
+#### Check Gitaly configuration
+
+Due to a bug in the `rake gitlab:gitaly:install` script your Gitaly
+configuration file may contain syntax errors. The block name
+`[[storages]]`, which may occur more than once in your `config.toml`
+file, should be `[[storage]]` instead.
+
+```shell
+sudo -u git -H sed -i.pre-9.5 's/\[\[storages\]\]/[[storage]]/' /home/git/gitaly/config.toml
+```
+
+#### Compile Gitaly
+
+```shell
+cd /home/git/gitaly
+sudo -u git -H git fetch --all --tags
+sudo -u git -H git checkout v$(</home/git/gitlab/GITALY_SERVER_VERSION)
+sudo -u git -H make
+```
+
+### 10. Update MySQL permissions
+
+If you are using MySQL you need to grant the GitLab user the necessary
+permissions on the database:
+
+```bash
+mysql -u root -p -e "GRANT TRIGGER ON \`gitlabhq_production\`.* TO 'git'@'localhost';"
+```
+
+If you use MySQL with replication, or just have MySQL configured with binary logging,
+you will need to also run the following on all of your MySQL servers:
+
+```bash
+mysql -u root -p -e "SET GLOBAL log_bin_trust_function_creators = 1;"
+```
+
+You can make this setting permanent by adding it to your `my.cnf`:
+
+```
+log_bin_trust_function_creators=1
+```
+
+### 11. Update configuration files
+
+#### New configuration options for `gitlab.yml`
+
+There might be configuration options available for [`gitlab.yml`][yaml]. View them with the command below and apply them manually to your current `gitlab.yml`:
+
+```sh
+cd /home/git/gitlab
+
+git diff origin/9-4-stable:config/gitlab.yml.example origin/9-5-stable:config/gitlab.yml.example
+```
+
+#### Nginx configuration
+
+Ensure you're still up-to-date with the latest NGINX configuration changes:
+
+```sh
+cd /home/git/gitlab
+
+# For HTTPS configurations
+git diff origin/9-4-stable:lib/support/nginx/gitlab-ssl origin/9-5-stable:lib/support/nginx/gitlab-ssl
+
+# For HTTP configurations
+git diff origin/9-4-stable:lib/support/nginx/gitlab origin/9-5-stable:lib/support/nginx/gitlab
+```
+
+If you are using Strict-Transport-Security in your installation to continue using it you must enable it in your Nginx
+configuration as GitLab application no longer handles setting it.
+
+If you are using Apache instead of NGINX please see the updated [Apache templates].
+Also note that because Apache does not support upstreams behind Unix sockets you
+will need to let gitlab-workhorse listen on a TCP port. You can do this
+via [/etc/default/gitlab].
+
+[Apache templates]: https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/web-server/apache
+[/etc/default/gitlab]: https://gitlab.com/gitlab-org/gitlab-ce/blob/9-5-stable/lib/support/init.d/gitlab.default.example#L38
+
+#### SMTP configuration
+
+If you're installing from source and use SMTP to deliver mail, you will need to add the following line
+to config/initializers/smtp_settings.rb:
+
+```ruby
+ActionMailer::Base.delivery_method = :smtp
+```
+
+See [smtp_settings.rb.sample] as an example.
+
+[smtp_settings.rb.sample]: https://gitlab.com/gitlab-org/gitlab-ce/blob/9-4-stable/config/initializers/smtp_settings.rb.sample#L13
+
+#### Init script
+
+There might be new configuration options available for [`gitlab.default.example`][gl-example]. View them with the command below and apply them manually to your current `/etc/default/gitlab`:
+
+```sh
+cd /home/git/gitlab
+
+git diff origin/9-4-stable:lib/support/init.d/gitlab.default.example origin/9-5-stable:lib/support/init.d/gitlab.default.example
+```
+
+Ensure you're still up-to-date with the latest init script changes:
+
+```bash
+cd /home/git/gitlab
+
+sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
+```
+
+For Ubuntu 16.04.1 LTS:
+
+```bash
+sudo systemctl daemon-reload
+```
+
+### 12. Install libs, migrations, etc.
+
+```bash
+cd /home/git/gitlab
+
+# MySQL installations (note: the line below states '--without postgres')
+sudo -u git -H bundle install --without postgres development test --deployment
+
+# PostgreSQL installations (note: the line below states '--without mysql')
+sudo -u git -H bundle install --without mysql development test --deployment
+
+# Optional: clean up old gems
+sudo -u git -H bundle clean
+
+# Run database migrations
+sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+
+# Compile GetText PO files
+
+sudo -u git -H bundle exec rake gettext:compile RAILS_ENV=production
+
+# Update node dependencies and recompile assets
+sudo -u git -H bundle exec rake yarn:install gitlab:assets:clean gitlab:assets:compile RAILS_ENV=production NODE_ENV=production
+
+# Clean up cache
+sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production
+```
+
+**MySQL installations**: Run through the `MySQL strings limits` and `Tables and data conversion to utf8mb4` [tasks](../install/database_mysql.md).
+
+### 13. Start application
+
+```bash
+sudo service gitlab start
+sudo service nginx restart
+```
+
+### 14. Check application status
+
+Check if GitLab and its environment are configured correctly:
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
+```
+
+To make sure you didn't miss anything run a more thorough check:
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+```
+
+If all items are green, then congratulations, the upgrade is complete!
+
+## Things went south? Revert to previous version (9.4)
+
+### 1. Revert the code to the previous version
+
+Follow the [upgrade guide from 9.3 to 9.4](9.3-to-9.4.md), except for the
+database migration (the backup is already migrated to the previous version).
+
+### 2. Restore from the backup
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
+```
+
+If you have more than one backup `*.tar` file(s) please add `BACKUP=timestamp_of_backup` to the command above.
+
+[yaml]: https://gitlab.com/gitlab-org/gitlab-ce/blob/9-5-stable/config/gitlab.yml.example
+[gl-example]: https://gitlab.com/gitlab-org/gitlab-ce/blob/9-5-stable/lib/support/init.d/gitlab.default.example
diff --git a/doc/update/README.md b/doc/update/README.md
index 22dbc7c750f..c98e20686e0 100644
--- a/doc/update/README.md
+++ b/doc/update/README.md
@@ -34,17 +34,67 @@ update them are in [a separate document][omnidocker].
## Upgrading without downtime
-Starting with GitLab 9.1.0 it's possible to upgrade to a newer major, minor, or patch version of GitLab
-without having to take your GitLab instance offline. However, for this to work
-there are the following requirements:
-
-1. You can only upgrade 1 minor release at a time. So from 9.1 to 9.2, not to 9.3.
-2. You have to be on the most recent patch release. For example, if 9.1.15 is the last
- release of 9.1 then you can safely upgrade from that version to any 9.2.x version.
- However, if you are running 9.1.14 you first need to upgrade to 9.1.15.
+Starting with GitLab 9.1.0 it's possible to upgrade to a newer major, minor, or
+patch version of GitLab without having to take your GitLab instance offline.
+However, for this to work there are the following requirements:
+
+1. You can only upgrade 1 minor release at a time. So from 9.1 to 9.2, not to
+ 9.3.
2. You have to use [post-deployment
migrations](../development/post_deployment_migrations.md).
-3. You are using PostgreSQL. If you are using MySQL please look at the release post to see if downtime is required.
+3. You are using PostgreSQL. If you are using MySQL please look at the release
+ post to see if downtime is required.
+
+Most of the time you can safely upgrade from a patch release to the next minor
+release if the patch release is not the latest. For example, upgrading from
+9.1.1 to 9.2.0 should be safe even if 9.1.2 has been released. We do recommend
+you check the release posts of any releases between your current and target
+version just in case they include any migrations that may require you to upgrade
+1 release at a time.
+
+Some releases may also include so called "background migrations". These
+migrations are performed in the background by Sidekiq and are often used for
+migrating data. Background migrations are only added in the monthly releases.
+
+Certain major/minor releases may require a set of background migrations to be
+finished. To guarantee this such a release will process any remaining jobs
+before continuing the upgrading procedure. While this won't require downtime
+(if the above conditions are met) we recommend users to keep at least 1 week
+between upgrading major/minor releases, allowing the background migrations to
+finish. The time necessary to complete these migrations can be reduced by
+increasing the number of Sidekiq workers that can process jobs in the
+`background_migration` queue.
+
+As a rule of thumb, any database smaller than 10 GB won't take too much time to
+upgrade; perhaps an hour at most per minor release. Larger databases however may
+require more time, but this is highly dependent on the size of the database and
+the migrations that are being performed.
+
+### Examples
+
+To help explain this, let's look at some examples.
+
+**Example 1:** You are running a large GitLab installation using version 9.4.2,
+which is the latest patch release of 9.4. When GitLab 9.5.0 is released this
+installation can be safely upgraded to 9.5.0 without requiring downtime if the
+requirements mentioned above are met. You can also skip 9.5.0 and upgrade to
+9.5.1 once it's released, but you **can not** upgrade straight to 9.6.0; you
+_have_ to first upgrade to a 9.5.x release.
+
+**Example 2:** You are running a large GitLab installation using version 9.4.2,
+which is the latest patch release of 9.4. GitLab 9.5 includes some background
+migrations, and 10.0 will require these to be completed (processing any
+remaining jobs for you). Skipping 9.5 is not possible without downtime, and due
+to the background migrations would require potentially hours of downtime
+depending on how long it takes for the background migrations to complete. To
+work around this you will have to upgrade to 9.5.x first, then wait at least a
+week before upgrading to 10.0.
+
+**Example 3:** You use MySQL as the database for GitLab. Any upgrade to a new
+major/minor release will require downtime. If a release includes any background
+migrations this could potentially lead to hours of downtime, depending on the
+size of your database. To work around this you will have to use PostgreSQL and
+meet the other online upgrade requirements mentioned above.
## Upgrading between editions
diff --git a/doc/update/patch_versions.md b/doc/update/patch_versions.md
index ac1bcb8f241..30107360446 100644
--- a/doc/update/patch_versions.md
+++ b/doc/update/patch_versions.md
@@ -35,7 +35,7 @@ current version with `cat VERSION`).
cd /home/git/gitlab
sudo -u git -H git fetch --all
-sudo -u git -H git checkout -- Gemfile.lock db/schema.rb
+sudo -u git -H git checkout -- Gemfile.lock db/schema.rb locale
sudo -u git -H git checkout LATEST_TAG -b LATEST_TAG
```
@@ -56,6 +56,12 @@ sudo -u git -H bundle clean
# Run database migrations
sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+# Compile GetText PO files
+# Internationalization was added in `v9.2.0` so these commands are only
+# required for versions equal or major to it.
+sudo -u git -H bundle exec rake gettext:pack RAILS_ENV=production
+sudo -u git -H bundle exec rake gettext:po_to_json RAILS_ENV=production
+
# Clean up assets and cache
sudo -u git -H bundle exec rake yarn:install gitlab:assets:clean gitlab:assets:compile cache:clear RAILS_ENV=production NODE_ENV=production
```
diff --git a/doc/user/group/index.md b/doc/user/group/index.md
index 08da721c71d..ceec8b74373 100644
--- a/doc/user/group/index.md
+++ b/doc/user/group/index.md
@@ -153,6 +153,14 @@ Find this option under your project's settings.
GitLab administrators can use the admin interface to move any project to any namespace if needed.
+## Sharing a project with a group
+
+You can [share your projects with a group](../project/members/share_project_with_groups.md)
+and give your group members access to the project all at once.
+
+Alternatively, with [GitLab Enterprise Edition Starter](https://about.gitlab.com/gitlab-ee/),
+you can [lock the sharing with group feature](#share-with-group-lock-ees-eep).
+
## Manage group memberships via LDAP
In GitLab Enterprise Edition it is possible to manage GitLab group memberships using LDAP groups.
@@ -189,7 +197,7 @@ Learn more about [Member Lock](https://docs.gitlab.com/ee/user/group/index.html#
In [GitLab Enterprise Edition Starter](https://about.gitlab.com/gitlab-ee/)
it is possible to prevent projects in a group from [sharing
-a project with another group](../../workflow/share_projects_with_other_groups.md).
+a project with another group](../project/members/share_project_with_groups.md).
This allows for tighter control over project access.
Learn more about [Share with group lock](https://docs.gitlab.com/ee/user/group/index.html#share-with-group-lock-ees-eep).
diff --git a/doc/user/index.md b/doc/user/index.md
index 1281cc6e4f0..d664fd62754 100644
--- a/doc/user/index.md
+++ b/doc/user/index.md
@@ -71,80 +71,44 @@ your code, use it as an issue tracker, collaborate on code, and continuously
build, test, and deploy your app with built-in GitLab CI/CD. Or, you can do
it all at once, from one single project.
-### Repository
-
-Host your codebase in [GitLab repositories](project/repository/index.md) with version control
-and as part of a fully integrated platform.
-
-### Issues
-
-Explore the best of GitLab [Issues](project/issues/index.md).
-
-### Merge Requests
-
-Collanorate on code, gather reviews, live preview changes per branch, and
-request approvals with [Merge Requests](project/merge_requests/index.md).
-
-### Milestones
-
-Work on multiple issues and merge requests towards the same target date
-with [Milestones](project/milestones/index.md).
-
-### GitLab Pages
-
-Publish your static site directly from GitLab with [GitLab Pages](project/pages/index.md). You
-can [build, test, and deploy any Static Site Generator](https://about.gitlab.com/2016/06/17/ssg-overview-gitlab-pages-part-3-examples-ci/) with Pages.
-
-### Container Registry
-
-Build and deploy Docker images with [GitLab Container Registry](project/container_registry.md).
+- [Repositories](project/repository/index.md): Host your codebase in
+repositories with version control and as part of a fully integrated platform.
+- [Issues](project/issues/index.md): Explore the best of GitLab Issues' features.
+- [Merge Requests](project/merge_requests/index.md): Collaborate on code,
+reviews, live preview changes per branch, and request approvals with Merge Requests.
+- [Milestones](project/milestones/index.md): Work on multiple issues and merge
+requests towards the same target date with Milestones.
## GitLab CI/CD
Use built-in [GitLab CI/CD](../ci/README.md) to test, build, and deploy your applications
directly from GitLab. No third-party integrations needed.
-### Auto Deploy
-
-Deploy your application out-of-the-box with [GitLab Auto Deploy](../ci/autodeploy/index.md).
-
-### Review Apps
-
-Live-preview the changes introduced by a merge request with [Review Apps](../ci/review_apps/index.md).
-
-## Groups
-
-With GitLab [Groups](group/index.md) you can assemble related projects together
-and grant members access to several projects at once.
-
-### Subgroups
-
-Groups can also be nested in [subgroups](group/subgroups/index.md).
+- [GitLab Auto Deploy](../ci/autodeploy/index.md): Deploy your application out-of-the-box with GitLab Auto Deploy.
+- [Review Apps](../ci/review_apps/index.md): Live-preview the changes introduced by a merge request with Review Apps.
+- [GitLab Pages](project/pages/index.md): Publish your static site directly from
+GitLab with Gitlab Pages. You can build, test, and deploy any Static Site Generator with Pages.
+- [GitLab Container Registry](project/container_registry.md): Build and deploy Docker
+images with Container Registry.
## Account
There is a lot you can customize and configure
to enjoy the best of GitLab.
-[Manage your user settings](profile/index.md) to change your personal info,
+- [Settings](profile/index.md): Manage your user settings to change your personal info,
personal access tokens, authorized applications, etc.
+- [Authentication](../topics/authentication/index.md): Read through the authentication
+methods available in GitLab.
+- [Permissions](permissions.md): Learn the different set of permissions levels for each
+user type (guest, reporter, developer, master, owner).
-### Authentication
-
-Read through the [authentication](../topics/authentication/index.md) methods available in GitLab.
-
-### Permissions
-
-Learn the different set of [permissions](permissions.md) for user type (guest, reporter, developer, master, owner).
-
-## Integrations
-
-[Integrate GitLab](../integration/README.md) with your preferred tool,
-such as Trello, JIRA, etc.
+## Groups
-## Git and GitLab
+With GitLab [Groups](group/index.md) you can assemble related projects together
+and grant members access to several projects at once.
-Learn what is [Git](../topics/git/index.md) and its best practices.
+Groups can also be nested in [subgroups](group/subgroups/index.md).
## Discussions
@@ -168,6 +132,11 @@ requests you're assigned to.
you have quick access to. You can also gather feedback on them through
[discussions](#discussions).
+## Integrations
+
+[Integrate GitLab](../integration/README.md) with your preferred tool,
+such as Trello, JIRA, etc.
+
## Webhooks
Configure [webhooks](project/integrations/webhooks.html) to listen for
@@ -178,3 +147,6 @@ POST request with data to the webhook URL.
Automate GitLab via [API](../api/README.html).
+## Git and GitLab
+
+Learn what is [Git](../topics/git/index.md) and its best practices.
diff --git a/doc/user/markdown.md b/doc/user/markdown.md
index 0d29b471d52..b42b8f0a525 100644
--- a/doc/user/markdown.md
+++ b/doc/user/markdown.md
@@ -248,7 +248,7 @@ GFM will recognize the following:
| `~123` | label by ID |
| `~bug` | one-word label by name |
| `~"feature request"` | multi-word label by name |
-| `%123` | milestone by ID |
+| `%123` | project milestone by ID |
| `%v1.23` | one-word milestone by name |
| `%"release candidate"` | multi-word milestone by name |
| `9ba12248` | specific commit |
@@ -262,7 +262,7 @@ GFM also recognizes certain cross-project references:
|:----------------------------------------|:------------------------|
| `namespace/project#123` | issue |
| `namespace/project!123` | merge request |
-| `namespace/project%123` | milestone |
+| `namespace/project%123` | project milestone |
| `namespace/project$123` | snippet |
| `namespace/project@9ba12248` | specific commit |
| `namespace/project@9ba12248...b19a04f5` | commit range comparison |
@@ -274,7 +274,7 @@ It also has a shorthand version to reference other projects from the same namesp
|:------------------------------|:------------------------|
| `project#123` | issue |
| `project!123` | merge request |
-| `project%123` | milestone |
+| `project%123` | project milestone |
| `project$123` | snippet |
| `project@9ba12248` | specific commit |
| `project@9ba12248...b19a04f5` | commit range comparison |
diff --git a/doc/user/permissions.md b/doc/user/permissions.md
index 3d47e644ad2..555b0cf77ea 100644
--- a/doc/user/permissions.md
+++ b/doc/user/permissions.md
@@ -12,8 +12,8 @@ will be unassigned automatically.
GitLab administrators receive all permissions.
-To add or import a user, you can follow the [project users and members
-documentation](../workflow/add-user/add-user.md).
+To add or import a user, you can follow the
+[project members documentation](../user/project/members/index.md).
## Project
diff --git a/doc/user/project/index.md b/doc/user/project/index.md
index 91a19600951..0dd0faf35e9 100644
--- a/doc/user/project/index.md
+++ b/doc/user/project/index.md
@@ -98,7 +98,11 @@ from your fork to the upstream project
- [Export a project from GitLab](settings/import_export.md#exporting-a-project-and-its-data)
- [Importing and exporting projects between GitLab instances](settings/import_export.md)
-## Leave a project
+## Project's members
+
+Learn how to [add members to your projects](members/index.md).
+
+### Leave a project
**Leave project** will only display on the project's dashboard
when a project is part of a group (under a
diff --git a/doc/user/project/integrations/prometheus.md b/doc/user/project/integrations/prometheus.md
index 6f15765751c..5fefb3b69c4 100644
--- a/doc/user/project/integrations/prometheus.md
+++ b/doc/user/project/integrations/prometheus.md
@@ -40,7 +40,7 @@ Installing and configuring Prometheus to monitor applications is fairly straight
### Configuring Omnibus GitLab Prometheus to monitor Kubernetes deployments
With Omnibus GitLab running inside of Kubernetes, you can leverage the bundled
-version of Prometheus to collect the supported metrics. Once enabled, Prometheus will automatically begin monitoring Kubernetes Nodes and any [annotated Pods](https://prometheus.io/docs/operating/configuration/#<kubernetes_sd_config>).
+version of Prometheus to collect the supported metrics. Once enabled, Prometheus will automatically begin monitoring Kubernetes Nodes and any [annotated Pods](https://prometheus.io/docs/operating/configuration/#<kubernetes_sd_config>).
1. Read how to configure the bundled Prometheus server in the
[Administration guide][gitlab-prometheus-k8s-monitor].
@@ -133,6 +133,8 @@ to integrate with.
Once configured, GitLab will attempt to retrieve performance metrics for any
environment which has had a successful deployment.
+GitLab will automatically scan the Prometheus server for known metrics and attempt to identify the metrics for a particular environment. The supported metrics and scan process is detailed in our [Prometheus Metric Library documentation](prometheus_library/metrics.html).
+
[Learn more about monitoring environments.](../../../ci/environments.md#monitoring-environments)
## Determining the performance impact of a merge
@@ -174,7 +176,7 @@ If the "Attempting to load performance data" screen continues to appear, it coul
[prometheus-docker-image]: https://hub.docker.com/r/prom/prometheus/
[prometheus-yml]:samples/prometheus.yml
[gitlab.com-ip-range]: https://gitlab.com/gitlab-com/infrastructure/issues/434
-[ci-environment-slug]: https://docs.gitlab.com/ce/ci/variables/#predefined-variables-environment-variables
+[ci-environment-slug]: ../../../ci/variables/#predefined-variables-environment-variables
[ce-8935]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8935
[ce-10408]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10408
[promgldocs]: ../../../administration/monitoring/prometheus/index.md
diff --git a/doc/user/project/integrations/prometheus_library/metrics.md b/doc/user/project/integrations/prometheus_library/metrics.md
index 6bdffce9c55..f09ecf9ff2d 100644
--- a/doc/user/project/integrations/prometheus_library/metrics.md
+++ b/doc/user/project/integrations/prometheus_library/metrics.md
@@ -4,6 +4,7 @@
GitLab offers automatic detection of select [Prometheus exporters](https://prometheus.io/docs/instrumenting/exporters/). Currently supported exporters are:
* [Kubernetes](kubernetes.md)
* [NGINX](nginx.md)
+* [NGINX Ingress Controller](nginx_ingress.md)
* [HAProxy](haproxy.md)
* [Amazon Cloud Watch](cloudwatch.md)
@@ -14,10 +15,7 @@ We have tried to surface the most important metrics for each exporter, and will
GitLab retrieves performance data from the configured Prometheus server, and attempts to identifying the presence of known metrics. Once identified, GitLab then needs to be able to map the data to a particular environment.
In order to isolate and only display relevant metrics for a given environment, GitLab needs a method to detect which labels are associated. To do that,
-GitLab will look for the required metrics which have a label that
-matches the [$CI_ENVIRONMENT_SLUG][ci-environment-slug].
-
-For example if you are deploying to an environment named `production`, there must be a label for the metric with the value of `production`.
+GitLab uses the defined queries and fills in the environment specific variables. Typically this involves looking for the [$CI_ENVIRONMENT_SLUG](https://docs.gitlab.com/ee/ci/variables/#predefined-variables-environment-variables), but may also include other information such as the project's Kubernetes namespace. Each search query is defined in the [exporter specific documentation](#prometheus-metrics-library).
## Adding to the library
diff --git a/doc/user/project/integrations/prometheus_library/nginx.md b/doc/user/project/integrations/prometheus_library/nginx.md
index b3470773996..12e3321f5f3 100644
--- a/doc/user/project/integrations/prometheus_library/nginx.md
+++ b/doc/user/project/integrations/prometheus_library/nginx.md
@@ -8,8 +8,8 @@ GitLab has support for automatically detecting and monitoring NGINX. This is pro
| Name | Query |
| ---- | ----- |
| Throughput (req/sec) | sum(rate(nginx_requests_total{server_zone!="*", server_zone!="_", %{environment_filter}}[2m])) |
-| Latency (ms) | avg(nginx_upstream_response_msecs_avg{%{environment_filter}}) * 1000 |
-| HTTP Error Rate (%) | sum(rate(haproxy_frontend_http_responses_total{code="5xx",%{environment_filter}}[2m])) / sum(rate(haproxy_frontend_http_responses_total{%{environment_filter}}[2m])) |
+| Latency (ms) | avg(nginx_upstream_response_msecs_avg{%{environment_filter}}) |
+| HTTP Error Rate (HTTP Errors / sec) | rate(nginx_responses_total{status_code="5xx", %{environment_filter}}[2m])) |
## Configuring Prometheus to monitor for NGINX metrics
diff --git a/doc/user/project/integrations/prometheus_library/nginx_ingress.md b/doc/user/project/integrations/prometheus_library/nginx_ingress.md
new file mode 100644
index 00000000000..84ee8bc45e5
--- /dev/null
+++ b/doc/user/project/integrations/prometheus_library/nginx_ingress.md
@@ -0,0 +1,25 @@
+# Monitoring NGINX Ingress Controller
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/13438) in GitLab 9.5
+
+GitLab has support for automatically detecting and monitoring the Kubernetes NGINX ingress controller. This is provided by leveraging the built in Prometheus metrics included in [version 0.9.0](https://github.com/kubernetes/ingress/blob/master/controllers/nginx/Changelog.md#09-beta1) of the ingress.
+
+## Metrics supported
+
+| Name | Query |
+| ---- | ----- |
+| Throughput (req/sec) | sum(rate(nginx_upstream_requests_total{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m])) |
+| Latency (ms) | avg(nginx_upstream_response_msecs_avg{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}) |
+| HTTP Error Rate (HTTP Errors / sec) | sum(rate(nginx_upstream_responses_total{status_code="5xx", upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m])) |
+
+## Configuring Prometheus to monitor for NGINX ingress metrics
+
+The easiest way to get started is to use at least version 0.9.0 of [NGINX ingress](https://github.com/kubernetes/ingress/tree/master/controllers/nginx). If you are using NGINX as your Kubernetes ingress, there is [direct support](https://github.com/kubernetes/ingress/pull/423) for enabling Prometheus monitoring in the 0.9.0 release.
+
+If you have deployed with the [gitlab-omnibus](https://docs.gitlab.com/ee/install/kubernetes/gitlab_omnibus.md) Helm chart, these metrics will be automatically enabled and annotated for Prometheus monitoring.
+
+## Specifying the Environment label
+
+In order to isolate and only display relevant metrics for a given environment
+however, GitLab needs a method to detect which labels are associated. To do this, GitLab will search metrics with appropriate labels. In this case, the `upstream` label must be of the form `<Kubernetes Namespace>-<CI_ENVIRONMENT_SLUG>-*`.
+
+If you have used [Auto Deploy](https://docs.gitlab.com/ee/ci/autodeploy/index.html) to deploy your app, this format will be used automatically and metrics will be detected with no action on your part.
diff --git a/doc/user/project/integrations/webhooks.md b/doc/user/project/integrations/webhooks.md
index c03a2df9a72..47eb0b34f66 100644
--- a/doc/user/project/integrations/webhooks.md
+++ b/doc/user/project/integrations/webhooks.md
@@ -438,7 +438,6 @@ X-Gitlab-Event: Note Hook
"iid": 1,
"description": "Et voluptas corrupti assumenda temporibus. Architecto cum animi eveniet amet asperiores. Vitae numquam voluptate est natus sit et ad id.",
"position": 0,
- "locked_at": null,
"source":{
"name":"Gitlab Test",
"description":"Aut reprehenderit ut est.",
diff --git a/doc/workflow/add-user/img/access_requests_management.png b/doc/user/project/members/img/access_requests_management.png
index 3693bed869b..3693bed869b 100644
--- a/doc/workflow/add-user/img/access_requests_management.png
+++ b/doc/user/project/members/img/access_requests_management.png
Binary files differ
diff --git a/doc/workflow/add-user/img/add_new_user_to_project_settings.png b/doc/user/project/members/img/add_new_user_to_project_settings.png
index 40db600455f..40db600455f 100644
--- a/doc/workflow/add-user/img/add_new_user_to_project_settings.png
+++ b/doc/user/project/members/img/add_new_user_to_project_settings.png
Binary files differ
diff --git a/doc/workflow/add-user/img/add_user_email_accept.png b/doc/user/project/members/img/add_user_email_accept.png
index 763b3ff463d..763b3ff463d 100644
--- a/doc/workflow/add-user/img/add_user_email_accept.png
+++ b/doc/user/project/members/img/add_user_email_accept.png
Binary files differ
diff --git a/doc/workflow/add-user/img/add_user_email_ready.png b/doc/user/project/members/img/add_user_email_ready.png
index 0066eb3427b..0066eb3427b 100644
--- a/doc/workflow/add-user/img/add_user_email_ready.png
+++ b/doc/user/project/members/img/add_user_email_ready.png
Binary files differ
diff --git a/doc/workflow/add-user/img/add_user_email_search.png b/doc/user/project/members/img/add_user_email_search.png
index 66bcd6aad80..66bcd6aad80 100644
--- a/doc/workflow/add-user/img/add_user_email_search.png
+++ b/doc/user/project/members/img/add_user_email_search.png
Binary files differ
diff --git a/doc/workflow/add-user/img/add_user_give_permissions.png b/doc/user/project/members/img/add_user_give_permissions.png
index 376a3eefccc..376a3eefccc 100644
--- a/doc/workflow/add-user/img/add_user_give_permissions.png
+++ b/doc/user/project/members/img/add_user_give_permissions.png
Binary files differ
diff --git a/doc/workflow/add-user/img/add_user_import_members_from_another_project.png b/doc/user/project/members/img/add_user_import_members_from_another_project.png
index 0c32001098e..0c32001098e 100644
--- a/doc/workflow/add-user/img/add_user_import_members_from_another_project.png
+++ b/doc/user/project/members/img/add_user_import_members_from_another_project.png
Binary files differ
diff --git a/doc/workflow/add-user/img/add_user_imported_members.png b/doc/user/project/members/img/add_user_imported_members.png
index 51fd7688890..51fd7688890 100644
--- a/doc/workflow/add-user/img/add_user_imported_members.png
+++ b/doc/user/project/members/img/add_user_imported_members.png
Binary files differ
diff --git a/doc/workflow/add-user/img/add_user_list_members.png b/doc/user/project/members/img/add_user_list_members.png
index e0fa404288d..e0fa404288d 100644
--- a/doc/workflow/add-user/img/add_user_list_members.png
+++ b/doc/user/project/members/img/add_user_list_members.png
Binary files differ
diff --git a/doc/workflow/add-user/img/add_user_members_menu.png b/doc/user/project/members/img/add_user_members_menu.png
index 8e61d15fe65..8e61d15fe65 100644
--- a/doc/workflow/add-user/img/add_user_members_menu.png
+++ b/doc/user/project/members/img/add_user_members_menu.png
Binary files differ
diff --git a/doc/workflow/add-user/img/add_user_search_people.png b/doc/user/project/members/img/add_user_search_people.png
index 41767a9167c..41767a9167c 100644
--- a/doc/workflow/add-user/img/add_user_search_people.png
+++ b/doc/user/project/members/img/add_user_search_people.png
Binary files differ
diff --git a/doc/workflow/groups/max_access_level.png b/doc/user/project/members/img/max_access_level.png
index 63f33f9d91d..63f33f9d91d 100644
--- a/doc/workflow/groups/max_access_level.png
+++ b/doc/user/project/members/img/max_access_level.png
Binary files differ
diff --git a/doc/workflow/groups/other_group_sees_shared_project.png b/doc/user/project/members/img/other_group_sees_shared_project.png
index 67af27043eb..67af27043eb 100644
--- a/doc/workflow/groups/other_group_sees_shared_project.png
+++ b/doc/user/project/members/img/other_group_sees_shared_project.png
Binary files differ
diff --git a/doc/workflow/add-user/img/request_access_button.png b/doc/user/project/members/img/request_access_button.png
index 608baccb0ca..608baccb0ca 100644
--- a/doc/workflow/add-user/img/request_access_button.png
+++ b/doc/user/project/members/img/request_access_button.png
Binary files differ
diff --git a/doc/workflow/groups/share_project_with_groups.png b/doc/user/project/members/img/share_project_with_groups.png
index 3cb4796f9f7..3cb4796f9f7 100644
--- a/doc/workflow/groups/share_project_with_groups.png
+++ b/doc/user/project/members/img/share_project_with_groups.png
Binary files differ
diff --git a/doc/workflow/add-user/img/withdraw_access_request_button.png b/doc/user/project/members/img/withdraw_access_request_button.png
index 6edd786b151..6edd786b151 100644
--- a/doc/workflow/add-user/img/withdraw_access_request_button.png
+++ b/doc/user/project/members/img/withdraw_access_request_button.png
Binary files differ
diff --git a/doc/user/project/members/index.md b/doc/user/project/members/index.md
new file mode 100644
index 00000000000..b8dd96087f1
--- /dev/null
+++ b/doc/user/project/members/index.md
@@ -0,0 +1,116 @@
+# Project's members
+
+You can manage the groups and users and their access levels in all of your
+projects. You can also personalize the access level you give each user,
+per-project.
+
+You should have `master` or `owner` [permissions](../../permissions.md) to add
+or import a new user to your project.
+
+To view, edit, add, and remove project's members, go to your
+project's **Settings > Members**.
+
+---
+
+## Add a user
+
+Right next to **People**, start typing the name or username of the user you
+want to add.
+
+![Search for people](img/add_user_search_people.png)
+
+---
+
+Select the user and the [permission level](../../user/permissions.md)
+that you'd like to give the user. Note that you can select more than one user.
+
+![Give user permissions](img/add_user_give_permissions.png)
+
+---
+
+Once done, hit **Add users to project** and they will be immediately added to
+your project with the permissions you gave them above.
+
+![List members](img/add_user_list_members.png)
+
+---
+
+From there on, you can either remove an existing user or change their access
+level to the project.
+
+## Import users from another project
+
+You can import another project's users in your own project by hitting the
+**Import members** button on the upper right corner of the **Members** menu.
+
+In the dropdown menu, you can see only the projects you are Master on.
+
+![Import members from another project](img/add_user_import_members_from_another_project.png)
+
+---
+
+Select the one you want and hit **Import project members**. A flash message
+notifying you that the import was successful will appear, and the new members
+are now in the project's members list. Notice that the permissions that they
+had on the project you imported from are retained.
+
+![Members list of new members](img/add_user_imported_members.png)
+
+---
+
+## Invite people using their e-mail address
+
+If a user you want to give access to doesn't have an account on your GitLab
+instance, you can invite them just by typing their e-mail address in the
+user search field.
+
+![Invite user by mail](img/add_user_email_search.png)
+
+---
+
+As you can imagine, you can mix inviting multiple people and adding existing
+GitLab users to the project.
+
+![Invite user by mail ready to submit](img/add_user_email_ready.png)
+
+---
+
+Once done, hit **Add users to project** and watch that there is a new member
+with the e-mail address we used above. From there on, you can resend the
+invitation, change their access level or even delete them.
+
+![Invite user members list](img/add_user_email_accept.png)
+
+---
+
+Once the user accepts the invitation, they will be prompted to create a new
+GitLab account using the same e-mail address the invitation was sent to.
+
+## Request access to a project
+
+As a project owner you can enable or disable non members to request access to
+your project. Go to the project settings and click on **Allow users to request access**.
+
+As a user, you can request to be a member of a project. Go to the project you'd
+like to be a member of, and click the **Request Access** button on the right
+side of your screen.
+
+![Request access button](img/request_access_button.png)
+
+---
+
+Project owners & masters will be notified of your request and will be able to approve or
+decline it on the members page.
+
+![Manage access requests](img/access_requests_management.png)
+
+---
+
+If you change your mind before your request is approved, just click the
+**Withdraw Access Request** button.
+
+![Withdraw access request button](img/withdraw_access_request_button.png)
+
+## Share project with group
+
+Alternatively, you can [share a project with an entire group](share_project_with_groups.md) instead of adding users one by one.
diff --git a/doc/user/project/members/share_project_with_groups.md b/doc/user/project/members/share_project_with_groups.md
new file mode 100644
index 00000000000..4c1ddcdcba8
--- /dev/null
+++ b/doc/user/project/members/share_project_with_groups.md
@@ -0,0 +1,41 @@
+# Share Projects with other Groups
+
+You can share projects with other [groups](../../group/index.md). This makes it
+possible to add a group of users to a project with a single action.
+
+## Groups as collections of users
+
+Groups are used primarily to [create collections of projects](../user/group/index.md), but you can also
+take advantage of the fact that groups define collections of _users_, namely the group
+members.
+
+## Sharing a project with a group of users
+
+The primary mechanism to give a group of users, say 'Engineering', access to a project,
+say 'Project Acme', in GitLab is to make the 'Engineering' group the owner of 'Project
+Acme'. But what if 'Project Acme' already belongs to another group, say 'Open Source'?
+This is where the group sharing feature can be of use.
+
+To share 'Project Acme' with the 'Engineering' group, go to the project settings page for 'Project Acme' and use the left navigation menu to go to the 'Groups' section.
+
+![The 'Groups' section in the project settings screen](img/share_project_with_groups.png)
+
+Now you can add the 'Engineering' group with the maximum access level of your choice.
+After sharing 'Project Acme' with 'Engineering', the project is listed on the group dashboard.
+
+!['Project Acme' is listed as a shared project for 'Engineering'](img/other_group_sees_shared_project.png)
+
+## Maximum access level
+
+!['Project Acme' is shared with 'Engineering' with a maximum access level of 'Developer'](img/max_access_level.png)
+
+In the screenshot above, the maximum access level of 'Developer' for members from 'Engineering' means that users with higher access levels in 'Engineering' ('Master' or 'Owner') will only have 'Developer' access to 'Project Acme'.
+
+## Share project with group lock (EES/EEP)
+
+In [GitLab Enterprise Edition Starter](https://about.gitlab.com/gitlab-ee/)
+it is possible to prevent projects in a group from [sharing
+a project with another group](../members/share_project_with_groups.md).
+This allows for tighter control over project access.
+
+Learn more about [Share with group lock](https://docs.gitlab.com/ee/user/group/index.html#share-with-group-lock-ees-eep).
diff --git a/doc/user/project/milestones/index.md b/doc/user/project/milestones/index.md
index 23ffde4e8bd..876b98a4dc5 100644
--- a/doc/user/project/milestones/index.md
+++ b/doc/user/project/milestones/index.md
@@ -56,4 +56,5 @@ total merge requests and issues.
## Quick actions
-[Quick actions](../quick_actions.md) are available for assigning and removing project milestones only. [In the future](https://gitlab.com/gitlab-org/gitlab-ce/issues/34778), this will also apply to group milestones.
+[Quick actions](../quick_actions.md) are available for assigning and removing
+project and group milestones.
diff --git a/doc/user/project/repository/index.md b/doc/user/project/repository/index.md
index 4b2c435a120..5e5ae880518 100644
--- a/doc/user/project/repository/index.md
+++ b/doc/user/project/repository/index.md
@@ -20,6 +20,8 @@ documentation.
For security reasons, when using the command line, we strongly recommend
you to [connect with GitLab via SSH](../../../ssh/README.md).
+## Files
+
## Create and edit files
Host your codebase in GitLab repositories by pushing your files to GitLab.
@@ -47,6 +49,10 @@ it's easier to do so [via GitLab UI](web_editor.md):
To get started with the command line, please read through the
[command line basics documentation](../../../gitlab-basics/command-line-commands.md).
+### Find files
+
+Use GitLab's [file finder](../../../workflow/file_finder.md) to search for files in a repository.
+
## Branches
When you submit changes in a new branch, you create a new version
diff --git a/doc/user/project/settings/import_export.md b/doc/user/project/settings/import_export.md
index 35960ade3d4..97cca3007b1 100644
--- a/doc/user/project/settings/import_export.md
+++ b/doc/user/project/settings/import_export.md
@@ -9,6 +9,9 @@
> application settings (`/admin/application_settings`) under 'Import sources'.
> Ask your administrator if you don't see the **GitLab export** button when
> creating a new project.
+> - Starting with GitLab 10.0, administrators can disable the project export option
+> on the GitLab instance in application settings (`/admin/application_settings`)
+> under 'Visibility and Access Controls'.
> - You can find some useful raketasks if you are an administrator in the
> [import_export](../../../administration/raketasks/project_import_export.md)
> raketask.
diff --git a/doc/user/snippets.md b/doc/user/snippets.md
index 78861625f8a..2170b079f62 100644
--- a/doc/user/snippets.md
+++ b/doc/user/snippets.md
@@ -16,7 +16,7 @@ Comments on snippets was [introduced](https://gitlab.com/gitlab-org/gitlab-ce/is
## Project snippets
-Project snippets are always related to a specific project - see [Project features](../workflow/project_features.md) for more information.
+Project snippets are always related to a specific project - see [Project's features](project/index.md#project-39-s-features) for more information.
## Personal snippets
diff --git a/doc/workflow/README.md b/doc/workflow/README.md
index 925bbf76d49..673e08287a3 100644
--- a/doc/workflow/README.md
+++ b/doc/workflow/README.md
@@ -16,14 +16,13 @@
- [File finder](file_finder.md)
- [Labels](../user/project/labels.md)
- [Notification emails](notifications.md)
-- [Project Features](project_features.md)
+- [Projects](../user/project/index.md)
- [Project forking workflow](forking_workflow.md)
-- [Project users](add-user/add-user.md)
+- [Project users](../user/project/members/index.md)
- [Protected branches](../user/project/protected_branches.md)
- [Protected tags](../user/project/protected_tags.md)
- [Quick Actions](../user/project/quick_actions.md)
-- [Sharing a project with a group](share_with_group.md)
-- [Share projects with other groups](share_projects_with_other_groups.md)
+- [Sharing projects with groups](../user/project/members/share_project_with_groups.md)
- [Time tracking](time_tracking.md)
- [Web Editor](../user/project/repository/web_editor.md)
- [Releases](releases.md)
diff --git a/doc/workflow/add-user/add-user.md b/doc/workflow/add-user/add-user.md
index e541111d7b3..35cc080d2b7 100644
--- a/doc/workflow/add-user/add-user.md
+++ b/doc/workflow/add-user/add-user.md
@@ -1,114 +1 @@
-# Project users
-
-You can manage the groups and users and their access levels in all of your
-projects. You can also personalize the access level you give each user,
-per-project.
-
-You should have `master` or `owner` permissions to add or import a new user
-to your project.
-
-The first step to add or import a user, go to your project and click on
-**Members** in the drop-down menu on the right side of your screen.
-
-![Members](img/add_user_members_menu.png)
-
----
-
-## Add a user
-
-Right next to **People**, start typing the name or username of the user you
-want to add.
-
-![Search for people](img/add_user_search_people.png)
-
----
-
-Select the user and the [permission level](../../user/permissions.md)
-that you'd like to give the user. Note that you can select more than one user.
-
-![Give user permissions](img/add_user_give_permissions.png)
-
----
-
-Once done, hit **Add users to project** and they will be immediately added to
-your project with the permissions you gave them above.
-
-![List members](img/add_user_list_members.png)
-
----
-
-From there on, you can either remove an existing user or change their access
-level to the project.
-
-## Import users from another project
-
-You can import another project's users in your own project by hitting the
-**Import members** button on the upper right corner of the **Members** menu.
-
-In the dropdown menu, you can see only the projects you are Master on.
-
-![Import members from another project](img/add_user_import_members_from_another_project.png)
-
----
-
-Select the one you want and hit **Import project members**. A flash message
-notifying you that the import was successful will appear, and the new members
-are now in the project's members list. Notice that the permissions that they
-had on the project you imported from are retained.
-
-![Members list of new members](img/add_user_imported_members.png)
-
----
-
-## Invite people using their e-mail address
-
-If a user you want to give access to doesn't have an account on your GitLab
-instance, you can invite them just by typing their e-mail address in the
-user search field.
-
-![Invite user by mail](img/add_user_email_search.png)
-
----
-
-As you can imagine, you can mix inviting multiple people and adding existing
-GitLab users to the project.
-
-![Invite user by mail ready to submit](img/add_user_email_ready.png)
-
----
-
-Once done, hit **Add users to project** and watch that there is a new member
-with the e-mail address we used above. From there on, you can resend the
-invitation, change their access level or even delete them.
-
-![Invite user members list](img/add_user_email_accept.png)
-
----
-
-Once the user accepts the invitation, they will be prompted to create a new
-GitLab account using the same e-mail address the invitation was sent to.
-
-## Request access to a project
-
-As a project owner you can enable or disable non members to request access to
-your project. Go to the project settings and click on **Allow users to request access**.
-
-As a user, you can request to be a member of a project. Go to the project you'd
-like to be a member of, and click the **Request Access** button on the right
-side of your screen.
-
-![Request access button](img/request_access_button.png)
-
----
-
-Project owners & masters will be notified of your request and will be able to approve or
-decline it on the members page.
-
-![Manage access requests](img/access_requests_management.png)
-
----
-
-If you change your mind before your request is approved, just click the
-**Withdraw Access Request** button.
-
-![Withdraw access request button](img/withdraw_access_request_button.png)
+This document was moved to [../../user/project/members/index.md](../../user/project/members/index.md)
diff --git a/doc/workflow/project_features.md b/doc/workflow/project_features.md
index 3f5de2bd4b1..feb88712f5a 100644
--- a/doc/workflow/project_features.md
+++ b/doc/workflow/project_features.md
@@ -1,45 +1 @@
-# Project features
-
-When in a Project -> Settings, you will find Features on the bottom of the page that you can toggle.
-
-Below you will find a more elaborate explanation of each of these.
-
-## Issues
-
-Issues is a really powerful, but lightweight issue tracking system.
-
-You can make tickets, assign them to people, file them under milestones, order them with labels and have discussion in them.
-
-They integrate deeply into GitLab and are easily referenced from anywhere by using `#` and the issue number.
-
-## Merge Requests
-
-Using a merge request, you can review and discuss code before it is merged in the branch of your code.
-
-As with issues, it can be assigned; people, issues, etc. can be referenced; milestones attached.
-
-We see it as an integral part of working together on code and couldn't work without it.
-
-## Wiki
-
-This is a separate system for documentation, built right into GitLab.
-
-It is source controlled and is very convenient if you don't want to keep you documentation in your source code, but you do want to keep it in your GitLab project.
-
-[Read more about Wikis.](../user/project/wiki/index.md)
-
-## Snippets
-
-Snippets are little bits of code or text.
-
-This is a nice place to put code or text that is used semi-regularly within the project, but does not belong in source control.
-
-For example, a specific config file that is used by the team that is only valid for the people that work on the code.
-
-## Git LFS
-
->**Note:** Project-specific LFS setting was added on 8.12 and is available only to admins.
-
-Git Large File Storage allows you to easily manage large binary files with Git.
-With this setting admins can better control which projects are allowed to use
-LFS.
+This document was moved to [../user/project/index.md](../user/project/index.md)
diff --git a/doc/workflow/share_projects_with_other_groups.md b/doc/workflow/share_projects_with_other_groups.md
index 40d756bc199..2eb4d24958a 100644
--- a/doc/workflow/share_projects_with_other_groups.md
+++ b/doc/workflow/share_projects_with_other_groups.md
@@ -1,32 +1 @@
-# Share Projects with other Groups
-
-You can share projects with other groups. This makes it possible to add a group of users
-to a project with a single action.
-
-## Groups as collections of users
-
-Groups are used primarily to [create collections of projects](../user/group/index.md), but you can also
-take advantage of the fact that groups define collections of _users_, namely the group
-members.
-
-## Sharing a project with a group of users
-
-The primary mechanism to give a group of users, say 'Engineering', access to a project,
-say 'Project Acme', in GitLab is to make the 'Engineering' group the owner of 'Project
-Acme'. But what if 'Project Acme' already belongs to another group, say 'Open Source'?
-This is where the group sharing feature can be of use.
-
-To share 'Project Acme' with the 'Engineering' group, go to the project settings page for 'Project Acme' and use the left navigation menu to go to the 'Groups' section.
-
-![The 'Groups' section in the project settings screen](groups/share_project_with_groups.png)
-
-Now you can add the 'Engineering' group with the maximum access level of your choice.
-After sharing 'Project Acme' with 'Engineering', the project is listed on the group dashboard.
-
-!['Project Acme' is listed as a shared project for 'Engineering'](groups/other_group_sees_shared_project.png)
-
-## Maximum access level
-
-!['Project Acme' is shared with 'Engineering' with a maximum access level of 'Developer'](groups/max_access_level.png)
-
-In the screenshot above, the maximum access level of 'Developer' for members from 'Engineering' means that users with higher access levels in 'Engineering' ('Master' or 'Owner') will only have 'Developer' access to 'Project Acme'.
+This document was moved to [../user/project/members/share_project_with_groups.md](../user/project/members/share_project_with_groups.md)
diff --git a/doc/workflow/share_with_group.md b/doc/workflow/share_with_group.md
index 3b7690973cb..2eb4d24958a 100644
--- a/doc/workflow/share_with_group.md
+++ b/doc/workflow/share_with_group.md
@@ -1,13 +1 @@
-# Sharing a project with a group
-
-If you want to share a single project in a group with another group,
-you can do so easily. By setting the permission you can quickly
-give a select group of users access to a project in a restricted manner.
-
-In a project go to the project settings -> groups.
-
-Now you can select a group that you want to share this project with and with
-which maximum access level. Users in that group are able to access this project
-with their set group access level, up to the maximum level that you've set.
-
-![Share a project with a group](share_with_group.png)
+This document was moved to [../user/project/members/share_project_with_groups.md](../user/project/members/share_project_with_groups.md)
diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb
index 810cd75591b..7254fbc2e4e 100644
--- a/features/steps/project/merge_requests.rb
+++ b/features/steps/project/merge_requests.rb
@@ -299,9 +299,6 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
step 'I change the comment "Line is wrong" to "Typo, please fix" on diff' do
page.within('.diff-file:nth-of-type(5) .note') do
- find('.more-actions').click
- find('.more-actions .dropdown-menu li', match: :first)
-
find('.js-note-edit').click
page.within('.current-note-edit-form', visible: true) do
diff --git a/features/steps/project/project.rb b/features/steps/project/project.rb
index 170e2f16c80..0a89c1baf20 100644
--- a/features/steps/project/project.rb
+++ b/features/steps/project/project.rb
@@ -10,7 +10,9 @@ class Spinach::Features::Project < Spinach::FeatureSteps
end
step 'I save project' do
- click_button 'Save changes'
+ page.within '.general-settings' do
+ click_button 'Save changes'
+ end
end
step 'I should see project with new settings' do
@@ -31,7 +33,9 @@ class Spinach::Features::Project < Spinach::FeatureSteps
:project_avatar,
File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif')
)
- click_button 'Save changes'
+ page.within '.general-settings' do
+ click_button 'Save changes'
+ end
@project.reload
end
@@ -50,7 +54,9 @@ class Spinach::Features::Project < Spinach::FeatureSteps
:project_avatar,
File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif')
)
- click_button 'Save changes'
+ page.within '.general-settings' do
+ click_button 'Save changes'
+ end
@project.reload
end
@@ -69,7 +75,9 @@ class Spinach::Features::Project < Spinach::FeatureSteps
step 'change project default branch' do
select 'fix', from: 'project_default_branch'
- click_button 'Save changes'
+ page.within '.general-settings' do
+ click_button 'Save changes'
+ end
end
step 'I should see project default branch changed' do
diff --git a/features/steps/project/wiki.rb b/features/steps/project/wiki.rb
index 2b8da2a6f19..855757e34b3 100644
--- a/features/steps/project/wiki.rb
+++ b/features/steps/project/wiki.rb
@@ -63,7 +63,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
end
step 'That page has two revisions' do
- @page.update("new content", message: "second commit")
+ @page.update(content: "new content", message: "second commit")
end
step 'I click the History button' do
diff --git a/features/steps/shared/note.rb b/features/steps/shared/note.rb
index 80187b83fee..492da38355c 100644
--- a/features/steps/shared/note.rb
+++ b/features/steps/shared/note.rb
@@ -11,8 +11,8 @@ module SharedNote
note = find('.note')
note.hover
- note.find('.more-actions').click
- note.find('.more-actions .dropdown-menu li', match: :first)
+ find('.more-actions').click
+ find('.more-actions .dropdown-menu li', match: :first)
find(".js-note-delete").click
end
@@ -147,9 +147,6 @@ module SharedNote
note = find('.note')
note.hover
- note.find('.more-actions').click
- note.find('.more-actions .dropdown-menu li', match: :first)
-
note.find('.js-note-edit').click
end
diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb
index f6edf93b311..605c9a3ab71 100644
--- a/features/steps/shared/project.rb
+++ b/features/steps/shared/project.rb
@@ -71,28 +71,14 @@ module SharedProject
step 'project "Shop" has push event' do
@project = Project.find_by(name: "Shop")
+ @event = create(:push_event, project: @project, author: @user)
- data = {
- before: Gitlab::Git::BLANK_SHA,
- after: "6d394385cf567f80a8fd85055db1ab4c5295806f",
- ref: "refs/heads/fix",
- user_id: @user.id,
- user_name: @user.name,
- repository: {
- name: @project.name,
- url: "localhost/rubinius",
- description: "",
- homepage: "localhost/rubinius",
- private: true
- }
- }
-
- @event = Event.create(
- project: @project,
- action: Event::PUSHED,
- data: data,
- author_id: @user.id
- )
+ create(:push_event_payload,
+ event: @event,
+ action: :created,
+ commit_to: '6d394385cf567f80a8fd85055db1ab4c5295806f',
+ ref: 'fix',
+ commit_count: 1)
end
step 'I should see project "Shop" activity feed' do
@@ -103,7 +89,7 @@ module SharedProject
step 'I should see project settings' do
expect(current_path).to eq edit_project_path(@project)
expect(page).to have_content("Project name")
- expect(page).to have_content("Sharing & Permissions")
+ expect(page).to have_content("Sharing and permissions")
end
def current_project
diff --git a/lib/api/api.rb b/lib/api/api.rb
index e08f4e2995f..94df543853b 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -48,8 +48,8 @@ module API
end
before { header['X-Frame-Options'] = 'SAMEORIGIN' }
- before { Gitlab::I18n.locale = current_user&.preferred_language }
+ # The locale is set to the current user's locale when `current_user` is loaded
after { Gitlab::I18n.use_default_locale }
rescue_from Gitlab::Access::AccessDeniedError do
@@ -95,6 +95,7 @@ module API
mount ::API::Boards
mount ::API::Branches
mount ::API::BroadcastMessages
+ mount ::API::CircuitBreakers
mount ::API::Commits
mount ::API::CommitStatuses
mount ::API::DeployKeys
diff --git a/lib/api/circuit_breakers.rb b/lib/api/circuit_breakers.rb
new file mode 100644
index 00000000000..118883f5ea5
--- /dev/null
+++ b/lib/api/circuit_breakers.rb
@@ -0,0 +1,50 @@
+module API
+ class CircuitBreakers < Grape::API
+ before { authenticated_as_admin! }
+
+ resource :circuit_breakers do
+ params do
+ requires :type,
+ type: String,
+ desc: "The type of circuitbreaker",
+ values: ['repository_storage']
+ end
+ resource ':type' do
+ namespace '', requirements: { type: 'repository_storage' } do
+ helpers do
+ def failing_storage_health
+ @failing_storage_health ||= Gitlab::Git::Storage::Health.for_failing_storages
+ end
+
+ def storage_health
+ @failing_storage_health ||= Gitlab::Git::Storage::Health.for_all_storages
+ end
+ end
+
+ desc 'Get all failing git storages' do
+ detail 'This feature was introduced in GitLab 9.5'
+ success Entities::RepositoryStorageHealth
+ end
+ get do
+ present storage_health, with: Entities::RepositoryStorageHealth
+ end
+
+ desc 'Get all failing git storages' do
+ detail 'This feature was introduced in GitLab 9.5'
+ success Entities::RepositoryStorageHealth
+ end
+ get 'failing' do
+ present failing_storage_health, with: Entities::RepositoryStorageHealth
+ end
+
+ desc 'Reset all storage failures and open circuitbreaker' do
+ detail 'This feature was introduced in GitLab 9.5'
+ end
+ delete do
+ Gitlab::Git::Storage::CircuitBreaker.reset_all!
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/commits.rb b/lib/api/commits.rb
index bcb842b9211..ea78737288a 100644
--- a/lib/api/commits.rb
+++ b/lib/api/commits.rb
@@ -4,13 +4,14 @@ module API
class Commits < Grape::API
include PaginationParams
- before { authenticate! }
+ COMMIT_ENDPOINT_REQUIREMENTS = API::PROJECT_ENDPOINT_REQUIREMENTS.merge(sha: API::NO_SLASH_URL_PART_REGEX)
+
before { authorize! :download_code, user_project }
params do
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects, requirements: { id: %r{[^/]+} } do
+ resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
desc 'Get a project repository commits' do
success Entities::RepoCommit
end
@@ -21,7 +22,7 @@ module API
optional :path, type: String, desc: 'The file path'
use :pagination
end
- get ":id/repository/commits" do
+ get ':id/repository/commits' do
path = params[:path]
before = params[:until]
after = params[:since]
@@ -53,16 +54,19 @@ module API
detail 'This feature was introduced in GitLab 8.13'
end
params do
- requires :branch, type: String, desc: 'The name of branch'
+ requires :branch, type: String, desc: 'Name of the branch to commit into. To create a new branch, also provide `start_branch`.'
requires :commit_message, type: String, desc: 'Commit message'
requires :actions, type: Array[Hash], desc: 'Actions to perform in commit'
+ optional :start_branch, type: String, desc: 'Name of the branch to start the new commit from'
optional :author_email, type: String, desc: 'Author email for commit'
optional :author_name, type: String, desc: 'Author name for commit'
end
- post ":id/repository/commits" do
+ post ':id/repository/commits' do
authorize! :push_code, user_project
- attrs = declared_params.merge(start_branch: declared_params[:branch], branch_name: declared_params[:branch])
+ attrs = declared_params
+ attrs[:branch_name] = attrs.delete(:branch)
+ attrs[:start_branch] ||= attrs[:branch_name]
result = ::Files::MultiService.new(user_project, current_user, attrs).execute
@@ -76,42 +80,42 @@ module API
desc 'Get a specific commit of a project' do
success Entities::RepoCommitDetail
- failure [[404, 'Not Found']]
+ failure [[404, 'Commit Not Found']]
end
params do
requires :sha, type: String, desc: 'A commit sha, or the name of a branch or tag'
end
- get ":id/repository/commits/:sha" do
+ get ':id/repository/commits/:sha', requirements: COMMIT_ENDPOINT_REQUIREMENTS do
commit = user_project.commit(params[:sha])
- not_found! "Commit" unless commit
+ not_found! 'Commit' unless commit
present commit, with: Entities::RepoCommitDetail
end
desc 'Get the diff for a specific commit of a project' do
- failure [[404, 'Not Found']]
+ failure [[404, 'Commit Not Found']]
end
params do
requires :sha, type: String, desc: 'A commit sha, or the name of a branch or tag'
end
- get ":id/repository/commits/:sha/diff" do
+ get ':id/repository/commits/:sha/diff', requirements: COMMIT_ENDPOINT_REQUIREMENTS do
commit = user_project.commit(params[:sha])
- not_found! "Commit" unless commit
+ not_found! 'Commit' unless commit
commit.raw_diffs.to_a
end
desc "Get a commit's comments" do
success Entities::CommitNote
- failure [[404, 'Not Found']]
+ failure [[404, 'Commit Not Found']]
end
params do
use :pagination
requires :sha, type: String, desc: 'A commit sha, or the name of a branch or tag'
end
- get ':id/repository/commits/:sha/comments' do
+ get ':id/repository/commits/:sha/comments', requirements: COMMIT_ENDPOINT_REQUIREMENTS do
commit = user_project.commit(params[:sha])
not_found! 'Commit' unless commit
@@ -125,10 +129,10 @@ module API
success Entities::RepoCommit
end
params do
- requires :sha, type: String, desc: 'A commit sha to be cherry picked'
+ requires :sha, type: String, desc: 'A commit sha, or the name of a branch or tag to be cherry picked'
requires :branch, type: String, desc: 'The name of the branch'
end
- post ':id/repository/commits/:sha/cherry_pick' do
+ post ':id/repository/commits/:sha/cherry_pick', requirements: COMMIT_ENDPOINT_REQUIREMENTS do
authorize! :push_code, user_project
commit = user_project.commit(params[:sha])
@@ -157,7 +161,7 @@ module API
success Entities::CommitNote
end
params do
- requires :sha, type: String, regexp: /\A\h{6,40}\z/, desc: "The commit's SHA"
+ requires :sha, type: String, desc: 'A commit sha, or the name of a branch or tag on which to post a comment'
requires :note, type: String, desc: 'The text of the comment'
optional :path, type: String, desc: 'The file path'
given :path do
@@ -165,7 +169,7 @@ module API
requires :line_type, type: String, values: %w(new old), default: 'new', desc: 'The type of the line'
end
end
- post ':id/repository/commits/:sha/comments' do
+ post ':id/repository/commits/:sha/comments', requirements: COMMIT_ENDPOINT_REQUIREMENTS do
commit = user_project.commit(params[:sha])
not_found! 'Commit' unless commit
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 298831a8fdb..18cd604a216 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -66,13 +66,6 @@ module API
expose :job_events
end
- class BasicProjectDetails < Grape::Entity
- expose :id
- expose :http_url_to_repo, :web_url
- expose :name, :name_with_namespace
- expose :path, :path_with_namespace
- end
-
class SharedGroup < Grape::Entity
expose :group_id
expose :group_name do |group_link, options|
@@ -81,7 +74,16 @@ module API
expose :group_access, as: :group_access_level
end
- class Project < Grape::Entity
+ class BasicProjectDetails < Grape::Entity
+ expose :id, :description, :default_branch, :tag_list
+ expose :ssh_url_to_repo, :http_url_to_repo, :web_url
+ expose :name, :name_with_namespace
+ expose :path, :path_with_namespace
+ expose :star_count, :forks_count
+ expose :created_at, :last_activity_at
+ end
+
+ class Project < BasicProjectDetails
include ::API::Helpers::RelatedResourcesHelpers
expose :_links do
@@ -114,12 +116,9 @@ module API
end
end
- expose :id, :description, :default_branch, :tag_list
expose :archived?, as: :archived
- expose :visibility, :ssh_url_to_repo, :http_url_to_repo, :web_url
+ expose :visibility
expose :owner, using: Entities::UserBasic, unless: ->(project, options) { project.group }
- expose :name, :name_with_namespace
- expose :path, :path_with_namespace
expose :container_registry_enabled
# Expose old field names with the new permissions methods to keep API compatible
@@ -129,18 +128,16 @@ module API
expose(:jobs_enabled) { |project, options| project.feature_available?(:builds, options[:current_user]) }
expose(:snippets_enabled) { |project, options| project.feature_available?(:snippets, options[:current_user]) }
- expose :created_at, :last_activity_at
expose :shared_runners_enabled
expose :lfs_enabled?, as: :lfs_enabled
expose :creator_id
expose :namespace, using: 'API::Entities::Namespace'
- expose :forked_from_project, using: Entities::BasicProjectDetails, if: lambda{ |project, options| project.forked? }
+ expose :forked_from_project, using: Entities::BasicProjectDetails, if: lambda { |project, options| project.forked? }
expose :import_status
expose :import_error, if: lambda { |_project, options| options[:user_can_admin_project] }
expose :avatar_url do |user, options|
user.avatar_url(only_path: false)
end
- expose :star_count, :forks_count
expose :open_issues_count, if: lambda { |project, options| project.feature_available?(:issues, options[:current_user]) }
expose :runners_token, if: lambda { |_project, options| options[:user_can_admin_project] }
expose :public_builds, as: :public_jobs
@@ -457,6 +454,9 @@ module API
end
class Note < Grape::Entity
+ # Only Issue and MergeRequest have iid
+ NOTEABLE_TYPES_WITH_IID = %w(Issue MergeRequest).freeze
+
expose :id
expose :note, as: :body
expose :attachment_identifier, as: :attachment
@@ -464,6 +464,9 @@ module API
expose :created_at, :updated_at
expose :system?, as: :system
expose :noteable_id, :noteable_type
+
+ # Avoid N+1 queries as much as possible
+ expose(:noteable_iid) { |note| note.noteable.iid if NOTEABLE_TYPES_WITH_IID.include?(note.noteable_type) }
end
class AwardEmoji < Grape::Entity
@@ -494,14 +497,24 @@ module API
expose :author, using: Entities::UserBasic
end
+ class PushEventPayload < Grape::Entity
+ expose :commit_count, :action, :ref_type, :commit_from, :commit_to
+ expose :ref, :commit_title
+ end
+
class Event < Grape::Entity
expose :title, :project_id, :action_name
expose :target_id, :target_iid, :target_type, :author_id
- expose :data, :target_title
+ expose :target_title
expose :created_at
expose :note, using: Entities::Note, if: ->(event, options) { event.note? }
expose :author, using: Entities::UserBasic, if: ->(event, options) { event.author }
+ expose :push_event_payload,
+ as: :push_data,
+ using: PushEventPayload,
+ if: -> (event, _) { event.push? }
+
expose :author_username do |event, options|
event.author&.username
end
@@ -702,7 +715,7 @@ module API
class RepoTag < Grape::Entity
expose :name, :message
- expose :commit do |repo_tag, options|
+ expose :commit, using: Entities::RepoCommit do |repo_tag, options|
options[:project].repository.commit(repo_tag.dereferenced_target)
end
@@ -954,5 +967,11 @@ module API
expose :ip_address
expose :submitted, as: :akismet_submitted
end
+
+ class RepositoryStorageHealth < Grape::Entity
+ expose :storage_name
+ expose :failing_on_hosts
+ expose :total_failures
+ end
end
end
diff --git a/lib/api/files.rb b/lib/api/files.rb
index 521287ee2b4..450334fee84 100644
--- a/lib/api/files.rb
+++ b/lib/api/files.rb
@@ -4,7 +4,7 @@ module API
def commit_params(attrs)
{
file_path: attrs[:file_path],
- start_branch: attrs[:branch],
+ start_branch: attrs[:start_branch] || attrs[:branch],
branch_name: attrs[:branch],
commit_message: attrs[:commit_message],
file_content: attrs[:content],
@@ -37,8 +37,9 @@ module API
params :simple_file_params do
requires :file_path, type: String, desc: 'The url encoded path to the file. Ex. lib%2Fclass%2Erb'
- requires :branch, type: String, desc: 'The name of branch'
- requires :commit_message, type: String, desc: 'Commit Message'
+ requires :branch, type: String, desc: 'Name of the branch to commit into. To create a new branch, also provide `start_branch`.'
+ requires :commit_message, type: String, desc: 'Commit message'
+ optional :start_branch, type: String, desc: 'Name of the branch to start the new commit from'
optional :author_email, type: String, desc: 'The email of the author'
optional :author_name, type: String, desc: 'The name of the author'
end
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 234825480f2..3582ed81b0f 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -16,6 +16,8 @@ module API
@current_user = initial_current_user
+ Gitlab::I18n.locale = @current_user&.preferred_language
+
sudo!
@current_user
@@ -255,7 +257,15 @@ module API
message << " " << trace.join("\n ")
API.logger.add Logger::FATAL, message
- rack_response({ 'message' => '500 Internal Server Error' }.to_json, 500)
+
+ response_message =
+ if Rails.env.test?
+ message
+ else
+ '500 Internal Server Error'
+ end
+
+ rack_response({ 'message' => response_message }.to_json, 500)
end
# project helpers
diff --git a/lib/api/helpers/members_helpers.rb b/lib/api/helpers/members_helpers.rb
index d9cae1501f8..a50ea0b52aa 100644
--- a/lib/api/helpers/members_helpers.rb
+++ b/lib/api/helpers/members_helpers.rb
@@ -1,8 +1,10 @@
+# rubocop:disable GitlabSecurity/PublicSend
+
module API
module Helpers
module MembersHelpers
def find_source(source_type, id)
- public_send("find_#{source_type}!", id)
+ public_send("find_#{source_type}!", id) # rubocop:disable GitlabSecurity/PublicSend
end
def authorize_admin_source!(source_type, source)
diff --git a/lib/api/notes.rb b/lib/api/notes.rb
index 65ff89edf65..4e4e473994b 100644
--- a/lib/api/notes.rb
+++ b/lib/api/notes.rb
@@ -139,7 +139,7 @@ module API
helpers do
def find_project_noteable(noteables_str, noteable_id)
- public_send("find_project_#{noteables_str.singularize}", noteable_id)
+ public_send("find_project_#{noteables_str.singularize}", noteable_id) # rubocop:disable GitlabSecurity/PublicSend
end
def noteable_read_ability_name(noteable)
diff --git a/lib/api/settings.rb b/lib/api/settings.rb
index d55a61fa638..667ba468ce6 100644
--- a/lib/api/settings.rb
+++ b/lib/api/settings.rb
@@ -29,6 +29,7 @@ module API
desc: 'Enabled sources for code import during project creation. OmniAuth must be configured for GitHub, Bitbucket, and GitLab.com'
optional :disabled_oauth_sign_in_sources, type: Array[String], desc: 'Disable certain OAuth sign-in sources'
optional :enabled_git_access_protocol, type: String, values: %w[ssh http nil], desc: 'Allow only the selected protocols to be used for Git access.'
+ optional :project_export_enabled, type: Boolean, desc: 'Enable project export'
optional :gravatar_enabled, type: Boolean, desc: 'Flag indicating if the Gravatar service is enabled'
optional :default_projects_limit, type: Integer, desc: 'The maximum number of personal projects'
optional :max_attachment_size, type: Integer, desc: 'Maximum attachment size in MB'
diff --git a/lib/api/tags.rb b/lib/api/tags.rb
index 633a858f8c7..1333747cced 100644
--- a/lib/api/tags.rb
+++ b/lib/api/tags.rb
@@ -2,19 +2,21 @@ module API
class Tags < Grape::API
include PaginationParams
+ TAG_ENDPOINT_REQUIREMENTS = API::PROJECT_ENDPOINT_REQUIREMENTS.merge(tag_name: API::NO_SLASH_URL_PART_REGEX)
+
before { authorize! :download_code, user_project }
params do
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects, requirements: { id: %r{[^/]+} } do
+ resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
desc 'Get a project repository tags' do
success Entities::RepoTag
end
params do
use :pagination
end
- get ":id/repository/tags" do
+ get ':id/repository/tags' do
tags = ::Kaminari.paginate_array(user_project.repository.tags.sort_by(&:name).reverse)
present paginate(tags), with: Entities::RepoTag, project: user_project
end
@@ -25,7 +27,7 @@ module API
params do
requires :tag_name, type: String, desc: 'The name of the tag'
end
- get ":id/repository/tags/:tag_name", requirements: { tag_name: /.+/ } do
+ get ':id/repository/tags/:tag_name', requirements: TAG_ENDPOINT_REQUIREMENTS do
tag = user_project.repository.find_tag(params[:tag_name])
not_found!('Tag') unless tag
@@ -60,7 +62,7 @@ module API
params do
requires :tag_name, type: String, desc: 'The name of the tag'
end
- delete ":id/repository/tags/:tag_name", requirements: { tag_name: /.+/ } do
+ delete ':id/repository/tags/:tag_name', requirements: TAG_ENDPOINT_REQUIREMENTS do
authorize_push_project
result = ::Tags::DestroyService.new(user_project, current_user)
@@ -78,7 +80,7 @@ module API
requires :tag_name, type: String, desc: 'The name of the tag'
requires :description, type: String, desc: 'Release notes with markdown support'
end
- post ':id/repository/tags/:tag_name/release', requirements: { tag_name: /.+/ } do
+ post ':id/repository/tags/:tag_name/release', requirements: TAG_ENDPOINT_REQUIREMENTS do
authorize_push_project
result = CreateReleaseService.new(user_project, current_user)
@@ -98,7 +100,7 @@ module API
requires :tag_name, type: String, desc: 'The name of the tag'
requires :description, type: String, desc: 'Release notes with markdown support'
end
- put ':id/repository/tags/:tag_name/release', requirements: { tag_name: /.+/ } do
+ put ':id/repository/tags/:tag_name/release', requirements: TAG_ENDPOINT_REQUIREMENTS do
authorize_push_project
result = UpdateReleaseService.new(user_project, current_user)
diff --git a/lib/api/todos.rb b/lib/api/todos.rb
index d1f7e364029..55191169dd4 100644
--- a/lib/api/todos.rb
+++ b/lib/api/todos.rb
@@ -59,10 +59,10 @@ module API
requires :id, type: Integer, desc: 'The ID of the todo being marked as done'
end
post ':id/mark_as_done' do
- todo = current_user.todos.find(params[:id])
- TodoService.new.mark_todos_as_done([todo], current_user)
+ TodoService.new.mark_todos_as_done_by_ids(params[:id], current_user)
+ todo = Todo.find(params[:id])
- present todo.reload, with: Entities::Todo, current_user: current_user
+ present todo, with: Entities::Todo, current_user: current_user
end
desc 'Mark all todos as done'
diff --git a/lib/api/users.rb b/lib/api/users.rb
index a590f2692a2..e2019d6d512 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -79,22 +79,17 @@ module API
end
desc 'Get a single user' do
- success Entities::UserBasic
+ success Entities::User
end
params do
requires :id, type: Integer, desc: 'The ID of the user'
end
get ":id" do
user = User.find_by(id: params[:id])
- not_found!('User') unless user
+ not_found!('User') unless user && can?(current_user, :read_user, user)
- if current_user && current_user.admin?
- present user, with: Entities::UserPublic
- elsif can?(current_user, :read_user, user)
- present user, with: Entities::User
- else
- render_api_error!("User not found.", 404)
- end
+ opts = current_user&.admin? ? { with: Entities::UserWithAdmin } : {}
+ present user, opts
end
desc 'Create a user. Available only for admins.' do
diff --git a/lib/api/v3/entities.rb b/lib/api/v3/entities.rb
index 773f667abe0..a9a35f2a4bd 100644
--- a/lib/api/v3/entities.rb
+++ b/lib/api/v3/entities.rb
@@ -25,14 +25,24 @@ module API
expose(:downvote?) { |note| false }
end
+ class PushEventPayload < Grape::Entity
+ expose :commit_count, :action, :ref_type, :commit_from, :commit_to
+ expose :ref, :commit_title
+ end
+
class Event < Grape::Entity
expose :title, :project_id, :action_name
expose :target_id, :target_type, :author_id
- expose :data, :target_title
+ expose :target_title
expose :created_at
expose :note, using: Entities::Note, if: ->(event, options) { event.note? }
expose :author, using: ::API::Entities::UserBasic, if: ->(event, options) { event.author }
+ expose :push_event_payload,
+ as: :push_data,
+ using: PushEventPayload,
+ if: -> (event, _) { event.push? }
+
expose :author_username do |event, options|
event.author&.username
end
@@ -68,7 +78,7 @@ module API
expose :lfs_enabled?, as: :lfs_enabled
expose :creator_id
expose :namespace, using: 'API::Entities::Namespace'
- expose :forked_from_project, using: ::API::Entities::BasicProjectDetails, if: lambda{ |project, options| project.forked? }
+ expose :forked_from_project, using: ::API::Entities::BasicProjectDetails, if: lambda { |project, options| project.forked? }
expose :avatar_url do |user, options|
user.avatar_url(only_path: false)
end
diff --git a/lib/api/v3/todos.rb b/lib/api/v3/todos.rb
index e3b311d61cd..2f2cf259987 100644
--- a/lib/api/v3/todos.rb
+++ b/lib/api/v3/todos.rb
@@ -11,10 +11,10 @@ module API
requires :id, type: Integer, desc: 'The ID of the todo being marked as done'
end
delete ':id' do
- todo = current_user.todos.find(params[:id])
- TodoService.new.mark_todos_as_done([todo], current_user)
+ TodoService.new.mark_todos_as_done_by_ids(params[:id], current_user)
+ todo = Todo.find(params[:id])
- present todo.reload, with: ::API::Entities::Todo, current_user: current_user
+ present todo, with: ::API::Entities::Todo, current_user: current_user
end
desc 'Mark all todos as done'
diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb
index ca6d6848d41..b9a573d3542 100644
--- a/lib/backup/manager.rb
+++ b/lib/backup/manager.rb
@@ -198,11 +198,11 @@ module Backup
end
def archives_to_backup
- ARCHIVES_TO_BACKUP.map{ |name| (name + ".tar.gz") unless skipped?(name) }.compact
+ ARCHIVES_TO_BACKUP.map { |name| (name + ".tar.gz") unless skipped?(name) }.compact
end
def folders_to_backup
- FOLDERS_TO_BACKUP.reject{ |name| skipped?(name) }
+ FOLDERS_TO_BACKUP.reject { |name| skipped?(name) }
end
def disabled_features
diff --git a/lib/banzai/filter/abstract_reference_filter.rb b/lib/banzai/filter/abstract_reference_filter.rb
index 685b43605ae..ef4578aabd6 100644
--- a/lib/banzai/filter/abstract_reference_filter.rb
+++ b/lib/banzai/filter/abstract_reference_filter.rb
@@ -54,42 +54,42 @@ module Banzai
self.class.references_in(*args, &block)
end
+ # Implement in child class
+ # Example: project.merge_requests.find
def find_object(project, id)
- # Implement in child class
- # Example: project.merge_requests.find
end
- def find_object_cached(project, id)
- if RequestStore.active?
- cache = find_objects_cache[object_class][project.id]
+ # Override if the link reference pattern produces a different ID (global
+ # ID vs internal ID, for instance) to the regular reference pattern.
+ def find_object_from_link(project, id)
+ find_object(project, id)
+ end
- get_or_set_cache(cache, id) { find_object(project, id) }
- else
+ # Implement in child class
+ # Example: project_merge_request_url
+ def url_for_object(object, project)
+ end
+
+ def find_object_cached(project, id)
+ cached_call(:banzai_find_object, id, path: [object_class, project.id]) do
find_object(project, id)
end
end
- def project_from_ref_cached(ref)
- if RequestStore.active?
- cache = project_refs_cache
-
- get_or_set_cache(cache, ref) { project_from_ref(ref) }
- else
- project_from_ref(ref)
+ def find_object_from_link_cached(project, id)
+ cached_call(:banzai_find_object_from_link, id, path: [object_class, project.id]) do
+ find_object_from_link(project, id)
end
end
- def url_for_object(object, project)
- # Implement in child class
- # Example: project_merge_request_url
+ def project_from_ref_cached(ref)
+ cached_call(:banzai_project_refs, ref) do
+ project_from_ref(ref)
+ end
end
def url_for_object_cached(object, project)
- if RequestStore.active?
- cache = url_for_object_cache[object_class][project.id]
-
- get_or_set_cache(cache, object) { url_for_object(object, project) }
- else
+ cached_call(:banzai_url_for_object, object, path: [object_class, project.id]) do
url_for_object(object, project)
end
end
@@ -120,7 +120,7 @@ module Banzai
if link == inner_html && inner_html =~ /\A#{link_pattern}/
replace_link_node_with_text(node, link) do
- object_link_filter(inner_html, link_pattern)
+ object_link_filter(inner_html, link_pattern, link_reference: true)
end
next
@@ -128,7 +128,7 @@ module Banzai
if link =~ /\A#{link_pattern}\z/
replace_link_node_with_href(node, link) do
- object_link_filter(link, link_pattern, link_content: inner_html)
+ object_link_filter(link, link_pattern, link_content: inner_html, link_reference: true)
end
next
@@ -146,15 +146,26 @@ module Banzai
# text - String text to replace references in.
# pattern - Reference pattern to match against.
# link_content - Original content of the link being replaced.
+ # link_reference - True if this was using the link reference pattern,
+ # false otherwise.
#
# Returns a String with references replaced with links. All links
# have `gfm` and `gfm-OBJECT_NAME` class names attached for styling.
- def object_link_filter(text, pattern, link_content: nil)
+ def object_link_filter(text, pattern, link_content: nil, link_reference: false)
references_in(text, pattern) do |match, id, project_ref, namespace_ref, matches|
project_path = full_project_path(namespace_ref, project_ref)
project = project_from_ref_cached(project_path)
- if project && object = find_object_cached(project, id)
+ if project
+ object =
+ if link_reference
+ find_object_from_link_cached(project, id)
+ else
+ find_object_cached(project, id)
+ end
+ end
+
+ if object
title = object_link_title(object)
klass = reference_class(object_sym)
@@ -297,15 +308,17 @@ module Banzai
RequestStore[:banzai_project_refs] ||= {}
end
- def find_objects_cache
- RequestStore[:banzai_find_objects_cache] ||= Hash.new do |hash, key|
- hash[key] = Hash.new { |h, k| h[k] = {} }
- end
- end
+ def cached_call(request_store_key, cache_key, path: [])
+ if RequestStore.active?
+ cache = RequestStore[request_store_key] ||= Hash.new do |hash, key|
+ hash[key] = Hash.new { |h, k| h[k] = {} }
+ end
- def url_for_object_cache
- RequestStore[:banzai_url_for_object] ||= Hash.new do |hash, key|
- hash[key] = Hash.new { |h, k| h[k] = {} }
+ cache = cache.dig(*path) if path.any?
+
+ get_or_set_cache(cache, cache_key) { yield }
+ else
+ yield
end
end
diff --git a/lib/banzai/filter/milestone_reference_filter.rb b/lib/banzai/filter/milestone_reference_filter.rb
index 45c033d32a8..4fc5f211e84 100644
--- a/lib/banzai/filter/milestone_reference_filter.rb
+++ b/lib/banzai/filter/milestone_reference_filter.rb
@@ -8,8 +8,15 @@ module Banzai
Milestone
end
+ # Links to project milestones contain the IID, but when we're handling
+ # 'regular' references, we need to use the global ID to disambiguate
+ # between group and project milestones.
def find_object(project, id)
- project.milestones.find_by(iid: id)
+ find_milestone_with_finder(project, id: id)
+ end
+
+ def find_object_from_link(project, iid)
+ find_milestone_with_finder(project, iid: iid)
end
def references_in(text, pattern = Milestone.reference_pattern)
@@ -22,7 +29,7 @@ module Banzai
milestone = find_milestone($~[:project], $~[:namespace], $~[:milestone_iid], $~[:milestone_name])
if milestone
- yield match, milestone.iid, $~[:project], $~[:namespace], $~
+ yield match, milestone.id, $~[:project], $~[:namespace], $~
else
match
end
@@ -36,7 +43,8 @@ module Banzai
return unless project
milestone_params = milestone_params(milestone_id, milestone_name)
- project.milestones.find_by(milestone_params)
+
+ find_milestone_with_finder(project, milestone_params)
end
def milestone_params(iid, name)
@@ -47,15 +55,27 @@ module Banzai
end
end
+ def find_milestone_with_finder(project, params)
+ finder_params = { project_ids: [project.id], order: nil }
+
+ # We don't support IID lookups for group milestones, because IIDs can
+ # clash between group and project milestones.
+ if project.group && !params[:iid]
+ finder_params[:group_ids] = [project.group.id]
+ end
+
+ MilestonesFinder.new(finder_params).execute.find_by(params)
+ end
+
def url_for_object(milestone, project)
- h = Gitlab::Routing.url_helpers
- h.project_milestone_url(project, milestone,
- only_path: context[:only_path])
+ Gitlab::Routing
+ .url_helpers
+ .milestone_url(milestone, only_path: context[:only_path])
end
def object_link_text(object, matches)
milestone_link = escape_once(super)
- reference = object.project.to_reference(project)
+ reference = object.project&.to_reference(project)
if reference.present?
"#{milestone_link} <i>in #{reference}</i>".html_safe
diff --git a/lib/banzai/renderer.rb b/lib/banzai/renderer.rb
index c7801cb5baf..ad08c0905e2 100644
--- a/lib/banzai/renderer.rb
+++ b/lib/banzai/renderer.rb
@@ -132,6 +132,8 @@ module Banzai
end
def self.cacheless_render(text, context = {})
+ return text.to_s unless text.present?
+
Gitlab::Metrics.measure(:banzai_cacheless_render) do
result = render_result(text, context)
diff --git a/lib/ci/ansi2html.rb b/lib/ci/ansi2html.rb
index 55402101e43..8354fc8d595 100644
--- a/lib/ci/ansi2html.rb
+++ b/lib/ci/ansi2html.rb
@@ -254,7 +254,7 @@ module Ci
def state
state = STATE_PARAMS.inject({}) do |h, param|
- h[param] = send(param)
+ h[param] = send(param) # rubocop:disable GitlabSecurity/PublicSend
h
end
Base64.urlsafe_encode64(state.to_json)
@@ -266,7 +266,7 @@ module Ci
return if state[:offset].to_i > stream.size
STATE_PARAMS.each do |param|
- send("#{param}=".to_sym, state[param])
+ send("#{param}=".to_sym, state[param]) # rubocop:disable GitlabSecurity/PublicSend
end
end
diff --git a/lib/ci/charts.rb b/lib/ci/charts.rb
index 872e418c788..76a69bf8a83 100644
--- a/lib/ci/charts.rb
+++ b/lib/ci/charts.rb
@@ -47,7 +47,7 @@ module Ci
def collect
query = project.pipelines
- .where("? > #{Ci::Pipeline.table_name}.created_at AND #{Ci::Pipeline.table_name}.created_at > ?", @to, @from)
+ .where("? > #{Ci::Pipeline.table_name}.created_at AND #{Ci::Pipeline.table_name}.created_at > ?", @to, @from) # rubocop:disable GitlabSecurity/SqlInjection
totals_count = grouped_count(query)
success_count = grouped_count(query.success)
diff --git a/lib/constraints/project_url_constrainer.rb b/lib/constraints/project_url_constrainer.rb
index 4c0aee6c48f..fd7b97d3167 100644
--- a/lib/constraints/project_url_constrainer.rb
+++ b/lib/constraints/project_url_constrainer.rb
@@ -6,6 +6,8 @@ class ProjectUrlConstrainer
return false unless DynamicPathValidator.valid_project_path?(full_path)
+ # We intentionally allow SELECT(*) here so result of this query can be used
+ # as cache for further Project.find_by_full_path calls within request
Project.find_by_full_path(full_path, follow_redirects: request.get?).present?
end
end
diff --git a/lib/declarative_policy.rb b/lib/declarative_policy.rb
index b1eb1a6cef1..ae65653645b 100644
--- a/lib/declarative_policy.rb
+++ b/lib/declarative_policy.rb
@@ -28,7 +28,13 @@ module DeclarativePolicy
subject = find_delegate(subject)
- class_for_class(subject.class)
+ policy_class = class_for_class(subject.class)
+ raise "no policy for #{subject.class.name}" if policy_class.nil?
+ policy_class
+ end
+
+ def has_policy?(subject)
+ !class_for_class(subject.class).nil?
end
private
@@ -51,9 +57,7 @@ module DeclarativePolicy
end
end
- policy_class = subject_class.instance_variable_get(CLASS_CACHE_IVAR)
- raise "no policy for #{subject.class.name}" if policy_class.nil?
- policy_class
+ subject_class.instance_variable_get(CLASS_CACHE_IVAR)
end
def compute_class_for_class(subject_class)
@@ -71,6 +75,8 @@ module DeclarativePolicy
nil
end
end
+
+ nil
end
def find_delegate(subject)
diff --git a/lib/declarative_policy/runner.rb b/lib/declarative_policy/runner.rb
index b5c615da4e3..56afd1f1392 100644
--- a/lib/declarative_policy/runner.rb
+++ b/lib/declarative_policy/runner.rb
@@ -76,6 +76,8 @@ module DeclarativePolicy
@state = State.new
steps_by_score do |step, score|
+ return if !debug && @state.prevented?
+
passed = nil
case step.action
when :enable then
@@ -93,10 +95,7 @@ module DeclarativePolicy
# been prevented.
unless @state.prevented?
passed = step.pass?
- if passed
- @state.prevent!
- return unless debug
- end
+ @state.prevent! if passed
end
debug << inspect_step(step, score, passed) if debug
@@ -141,13 +140,14 @@ module DeclarativePolicy
end
steps = Set.new(@steps)
+ remaining_enablers = steps.count { |s| s.enable? }
loop do
return if steps.empty?
# if the permission hasn't yet been enabled and we only have
# prevent steps left, we short-circuit the state here
- @state.prevent! if !@state.enabled? && steps.all?(&:prevent?)
+ @state.prevent! if !@state.enabled? && remaining_enablers == 0
lowest_score = Float::INFINITY
next_step = nil
@@ -162,6 +162,8 @@ module DeclarativePolicy
steps.delete(next_step)
+ remaining_enablers -= 1 if next_step.enable?
+
yield next_step, lowest_score
end
end
diff --git a/lib/file_streamer.rb b/lib/file_streamer.rb
deleted file mode 100644
index 4e3c6d3c773..00000000000
--- a/lib/file_streamer.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-class FileStreamer #:nodoc:
- attr_reader :to_path
-
- def initialize(path)
- @to_path = path
- end
-
- # Stream the file's contents if Rack::Sendfile isn't present.
- def each
- File.open(to_path, 'rb') do |file|
- while chunk = file.read(16384)
- yield chunk
- end
- end
- end
-end
diff --git a/lib/github/client.rb b/lib/github/client.rb
index e65d908d232..9c476df7d46 100644
--- a/lib/github/client.rb
+++ b/lib/github/client.rb
@@ -1,13 +1,16 @@
module Github
class Client
+ TIMEOUT = 60
+
attr_reader :connection, :rate_limit
def initialize(options)
- @connection = Faraday.new(url: options.fetch(:url)) do |faraday|
- faraday.options.open_timeout = options.fetch(:timeout, 60)
- faraday.options.timeout = options.fetch(:timeout, 60)
+ @connection = Faraday.new(url: options.fetch(:url, root_endpoint)) do |faraday|
+ faraday.options.open_timeout = options.fetch(:timeout, TIMEOUT)
+ faraday.options.timeout = options.fetch(:timeout, TIMEOUT)
faraday.authorization 'token', options.fetch(:token)
faraday.adapter :net_http
+ faraday.ssl.verify = verify_ssl
end
@rate_limit = RateLimit.new(connection)
@@ -19,5 +22,32 @@ module Github
Github::Response.new(connection.get(url, query))
end
+
+ private
+
+ def root_endpoint
+ custom_endpoint || github_endpoint
+ end
+
+ def custom_endpoint
+ github_omniauth_provider.dig('args', 'client_options', 'site')
+ end
+
+ def verify_ssl
+ # If there is no config, we're connecting to github.com
+ # and we should verify ssl.
+ github_omniauth_provider.fetch('verify_ssl', true)
+ end
+
+ def github_endpoint
+ OmniAuth::Strategies::GitHub.default_options[:client_options][:site]
+ end
+
+ def github_omniauth_provider
+ @github_omniauth_provider ||=
+ Gitlab.config.omniauth.providers
+ .find { |provider| provider.name == 'github' }
+ .to_h
+ end
end
end
diff --git a/lib/github/import.rb b/lib/github/import.rb
index cea4be5460b..4cc01593ef4 100644
--- a/lib/github/import.rb
+++ b/lib/github/import.rb
@@ -41,13 +41,16 @@ module Github
self.reset_callbacks :validate
end
- attr_reader :project, :repository, :repo, :options, :errors, :cached, :verbose
+ attr_reader :project, :repository, :repo, :repo_url, :wiki_url,
+ :options, :errors, :cached, :verbose
- def initialize(project, options)
+ def initialize(project, options = {})
@project = project
@repository = project.repository
@repo = project.import_source
- @options = options
+ @repo_url = project.import_url
+ @wiki_url = project.import_url.sub(/\.git\z/, '.wiki.git')
+ @options = options.reverse_merge(token: project.import_data&.credentials&.fetch(:user))
@verbose = options.fetch(:verbose, false)
@cached = Hash.new { |hash, key| hash[key] = Hash.new }
@errors = []
@@ -65,6 +68,8 @@ module Github
fetch_pull_requests
puts 'Fetching issues...'.color(:aqua) if verbose
fetch_issues
+ puts 'Fetching releases...'.color(:aqua) if verbose
+ fetch_releases
puts 'Cloning wiki repository...'.color(:aqua) if verbose
fetch_wiki_repository
puts 'Expiring repository cache...'.color(:aqua) if verbose
@@ -72,6 +77,7 @@ module Github
true
rescue Github::RepositoryFetchError
+ expire_repository_cache
false
ensure
keep_track_of_errors
@@ -81,23 +87,21 @@ module Github
def fetch_repository
begin
- project.create_repository unless project.repository.exists?
- project.repository.add_remote('github', "https://#{options.fetch(:token)}@github.com/#{repo}.git")
+ project.ensure_repository
+ project.repository.add_remote('github', repo_url)
project.repository.set_remote_as_mirror('github')
project.repository.fetch_remote('github', forced: true)
- rescue Gitlab::Shell::Error => e
- error(:project, "https://github.com/#{repo}.git", e.message)
+ rescue Gitlab::Git::Repository::NoRepository, Gitlab::Shell::Error => e
+ error(:project, repo_url, e.message)
raise Github::RepositoryFetchError
end
end
def fetch_wiki_repository
- wiki_url = "https://#{options.fetch(:token)}@github.com/#{repo}.wiki.git"
- wiki_path = "#{project.full_path}.wiki"
+ return if project.wiki.repository_exists?
- unless project.wiki.repository_exists?
- gitlab_shell.import_repository(project.repository_storage_path, wiki_path, wiki_url)
- end
+ wiki_path = "#{project.disk_path}.wiki"
+ gitlab_shell.import_repository(project.repository_storage_path, wiki_path, wiki_url)
rescue Gitlab::Shell::Error => e
# GitHub error message when the wiki repo has not been created,
# this means that repo has wiki enabled, but have no pages. So,
@@ -309,7 +313,7 @@ module Github
next unless representation.valid?
release = ::Release.find_or_initialize_by(project_id: project.id, tag: representation.tag)
- next unless relese.new_record?
+ next unless release.new_record?
begin
release.description = representation.description
@@ -337,7 +341,7 @@ module Github
def user_id(user, fallback_id = nil)
return unless user.present?
- return cached[:user_ids][user.id] if cached[:user_ids].key?(user.id)
+ return cached[:user_ids][user.id] if cached[:user_ids][user.id].present?
gitlab_user_id = user_id_by_external_uid(user.id) || user_id_by_email(user.email)
@@ -367,7 +371,7 @@ module Github
end
def expire_repository_cache
- repository.expire_content_cache
+ repository.expire_content_cache if project.repository_exists?
end
def keep_track_of_errors
diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb
index 9bed81e7327..7d3aa532750 100644
--- a/lib/gitlab/auth.rb
+++ b/lib/gitlab/auth.rb
@@ -218,7 +218,8 @@ module Gitlab
def full_authentication_abilities
read_authentication_abilities + [
:push_code,
- :create_container_image
+ :create_container_image,
+ :admin_container_image
]
end
alias_method :api_scope_authentication_abilities, :full_authentication_abilities
diff --git a/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits.rb b/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits.rb
new file mode 100644
index 00000000000..310a69a4bd4
--- /dev/null
+++ b/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits.rb
@@ -0,0 +1,109 @@
+module Gitlab
+ module BackgroundMigration
+ class DeserializeMergeRequestDiffsAndCommits
+ attr_reader :diff_ids, :commit_rows, :file_rows
+
+ class MergeRequestDiff < ActiveRecord::Base
+ self.table_name = 'merge_request_diffs'
+ end
+
+ BUFFER_ROWS = 1000
+
+ def perform(start_id, stop_id)
+ merge_request_diffs = MergeRequestDiff
+ .select(:id, :st_commits, :st_diffs)
+ .where('st_commits IS NOT NULL OR st_diffs IS NOT NULL')
+ .where(id: start_id..stop_id)
+
+ reset_buffers!
+
+ merge_request_diffs.each do |merge_request_diff|
+ commits, files = single_diff_rows(merge_request_diff)
+
+ diff_ids << merge_request_diff.id
+ commit_rows.concat(commits)
+ file_rows.concat(files)
+
+ if diff_ids.length > BUFFER_ROWS ||
+ commit_rows.length > BUFFER_ROWS ||
+ file_rows.length > BUFFER_ROWS
+
+ flush_buffers!
+ end
+ end
+
+ flush_buffers!
+ end
+
+ private
+
+ def reset_buffers!
+ @diff_ids = []
+ @commit_rows = []
+ @file_rows = []
+ end
+
+ def flush_buffers!
+ if diff_ids.any?
+ MergeRequestDiff.transaction do
+ Gitlab::Database.bulk_insert('merge_request_diff_commits', commit_rows)
+ Gitlab::Database.bulk_insert('merge_request_diff_files', file_rows)
+
+ MergeRequestDiff.where(id: diff_ids).update_all(st_commits: nil, st_diffs: nil)
+ end
+ end
+
+ reset_buffers!
+ end
+
+ def single_diff_rows(merge_request_diff)
+ sha_attribute = Gitlab::Database::ShaAttribute.new
+ commits = YAML.load(merge_request_diff.st_commits) rescue []
+
+ commit_rows = commits.map.with_index do |commit, index|
+ commit_hash = commit.to_hash.with_indifferent_access.except(:parent_ids)
+ sha = commit_hash.delete(:id)
+
+ commit_hash.merge(
+ merge_request_diff_id: merge_request_diff.id,
+ relative_order: index,
+ sha: sha_attribute.type_cast_for_database(sha)
+ )
+ end
+
+ diffs = YAML.load(merge_request_diff.st_diffs) rescue []
+ diffs = [] unless valid_raw_diffs?(diffs)
+
+ file_rows = diffs.map.with_index do |diff, index|
+ diff_hash = diff.to_hash.with_indifferent_access.merge(
+ binary: false,
+ merge_request_diff_id: merge_request_diff.id,
+ relative_order: index
+ )
+
+ # Compatibility with old diffs created with Psych.
+ diff_hash.tap do |hash|
+ diff_text = hash[:diff]
+
+ hash[:too_large] = !!hash[:too_large]
+
+ if diff_text.encoding == Encoding::BINARY && !diff_text.ascii_only?
+ hash[:binary] = true
+ hash[:diff] = [diff_text].pack('m0')
+ end
+ end
+ end
+
+ [commit_rows, file_rows]
+ end
+
+ # Unlike MergeRequestDiff#valid_raw_diff?, don't count Rugged objects as
+ # valid, because we don't render them usefully anyway.
+ def valid_raw_diffs?(diffs)
+ return false unless diffs.respond_to?(:each)
+
+ diffs.all? { |diff| diff.is_a?(Hash) }
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/background_migration/migrate_events_to_push_event_payloads.rb b/lib/gitlab/background_migration/migrate_events_to_push_event_payloads.rb
new file mode 100644
index 00000000000..432f7c3e706
--- /dev/null
+++ b/lib/gitlab/background_migration/migrate_events_to_push_event_payloads.rb
@@ -0,0 +1,176 @@
+module Gitlab
+ module BackgroundMigration
+ # Class that migrates events for the new push event payloads setup. All
+ # events are copied to a shadow table, and push events will also have a row
+ # created in the push_event_payloads table.
+ class MigrateEventsToPushEventPayloads
+ class Event < ActiveRecord::Base
+ self.table_name = 'events'
+
+ serialize :data
+
+ BLANK_REF = ('0' * 40).freeze
+ TAG_REF_PREFIX = 'refs/tags/'.freeze
+ MAX_INDEX = 69
+ PUSHED = 5
+
+ def push_event?
+ action == PUSHED && data.present?
+ end
+
+ def commit_title
+ commit = commits.last
+
+ return nil unless commit && commit[:message]
+
+ index = commit[:message].index("\n")
+ message = index ? commit[:message][0..index] : commit[:message]
+
+ message.strip.truncate(70)
+ end
+
+ def commit_from_sha
+ if create?
+ nil
+ else
+ data[:before]
+ end
+ end
+
+ def commit_to_sha
+ if remove?
+ nil
+ else
+ data[:after]
+ end
+ end
+
+ def data
+ super || {}
+ end
+
+ def commits
+ data[:commits] || []
+ end
+
+ def commit_count
+ data[:total_commits_count] || 0
+ end
+
+ def ref
+ data[:ref]
+ end
+
+ def trimmed_ref_name
+ if ref_type == :tag
+ ref[10..-1]
+ else
+ ref[11..-1]
+ end
+ end
+
+ def create?
+ data[:before] == BLANK_REF
+ end
+
+ def remove?
+ data[:after] == BLANK_REF
+ end
+
+ def push_action
+ if create?
+ :created
+ elsif remove?
+ :removed
+ else
+ :pushed
+ end
+ end
+
+ def ref_type
+ if ref.start_with?(TAG_REF_PREFIX)
+ :tag
+ else
+ :branch
+ end
+ end
+ end
+
+ class EventForMigration < ActiveRecord::Base
+ self.table_name = 'events_for_migration'
+ end
+
+ class PushEventPayload < ActiveRecord::Base
+ self.table_name = 'push_event_payloads'
+
+ enum action: {
+ created: 0,
+ removed: 1,
+ pushed: 2
+ }
+
+ enum ref_type: {
+ branch: 0,
+ tag: 1
+ }
+ end
+
+ # start_id - The start ID of the range of events to process
+ # end_id - The end ID of the range to process.
+ def perform(start_id, end_id)
+ return unless migrate?
+
+ find_events(start_id, end_id).each { |event| process_event(event) }
+ end
+
+ def process_event(event)
+ replicate_event(event)
+ create_push_event_payload(event) if event.push_event?
+ end
+
+ def replicate_event(event)
+ new_attributes = event.attributes
+ .with_indifferent_access.except(:title, :data)
+
+ EventForMigration.create!(new_attributes)
+ rescue ActiveRecord::InvalidForeignKey
+ # A foreign key error means the associated event was removed. In this
+ # case we'll just skip migrating the event.
+ end
+
+ def create_push_event_payload(event)
+ commit_from = pack(event.commit_from_sha)
+ commit_to = pack(event.commit_to_sha)
+
+ PushEventPayload.create!(
+ event_id: event.id,
+ commit_count: event.commit_count,
+ ref_type: event.ref_type,
+ action: event.push_action,
+ commit_from: commit_from,
+ commit_to: commit_to,
+ ref: event.trimmed_ref_name,
+ commit_title: event.commit_title
+ )
+ rescue ActiveRecord::InvalidForeignKey
+ # A foreign key error means the associated event was removed. In this
+ # case we'll just skip migrating the event.
+ end
+
+ def find_events(start_id, end_id)
+ Event
+ .where('NOT EXISTS (SELECT true FROM events_for_migration WHERE events_for_migration.id = events.id)')
+ .where(id: start_id..end_id)
+ end
+
+ def migrate?
+ Event.table_exists? && PushEventPayload.table_exists? &&
+ EventForMigration.table_exists?
+ end
+
+ def pack(value)
+ value ? [value].pack('H*') : nil
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/background_migration/move_personal_snippet_files.rb b/lib/gitlab/background_migration/move_personal_snippet_files.rb
new file mode 100644
index 00000000000..07cec96bcc3
--- /dev/null
+++ b/lib/gitlab/background_migration/move_personal_snippet_files.rb
@@ -0,0 +1,79 @@
+module Gitlab
+ module BackgroundMigration
+ class MovePersonalSnippetFiles
+ delegate :select_all, :execute, :quote_string, to: :connection
+
+ def perform(relative_source, relative_destination)
+ @source_relative_location = relative_source
+ @destination_relative_location = relative_destination
+
+ move_personal_snippet_files
+ end
+
+ def move_personal_snippet_files
+ query = "SELECT uploads.path, uploads.model_id FROM uploads "\
+ "INNER JOIN snippets ON snippets.id = uploads.model_id WHERE uploader = 'PersonalFileUploader'"
+ select_all(query).each do |upload|
+ secret = upload['path'].split('/')[0]
+ file_name = upload['path'].split('/')[1]
+
+ move_file(upload['model_id'], secret, file_name)
+ update_markdown(upload['model_id'], secret, file_name)
+ end
+ end
+
+ def move_file(snippet_id, secret, file_name)
+ source_dir = File.join(base_directory, @source_relative_location, snippet_id.to_s, secret)
+ destination_dir = File.join(base_directory, @destination_relative_location, snippet_id.to_s, secret)
+
+ source_file_path = File.join(source_dir, file_name)
+ destination_file_path = File.join(destination_dir, file_name)
+
+ unless File.exist?(source_file_path)
+ say "Source file `#{source_file_path}` doesn't exist. Skipping."
+ return
+ end
+
+ say "Moving file #{source_file_path} -> #{destination_file_path}"
+
+ FileUtils.mkdir_p(destination_dir)
+ FileUtils.move(source_file_path, destination_file_path)
+ end
+
+ def update_markdown(snippet_id, secret, file_name)
+ source_markdown_path = File.join(@source_relative_location, snippet_id.to_s, secret, file_name)
+ destination_markdown_path = File.join(@destination_relative_location, snippet_id.to_s, secret, file_name)
+
+ source_markdown = "](#{source_markdown_path})"
+ destination_markdown = "](#{destination_markdown_path})"
+ quoted_source = quote_string(source_markdown)
+ quoted_destination = quote_string(destination_markdown)
+
+ execute("UPDATE snippets "\
+ "SET description = replace(snippets.description, '#{quoted_source}', '#{quoted_destination}'), description_html = NULL "\
+ "WHERE id = #{snippet_id}")
+
+ query = "SELECT id, note FROM notes WHERE noteable_id = #{snippet_id} "\
+ "AND noteable_type = 'Snippet' AND note IS NOT NULL"
+ select_all(query).each do |note|
+ text = note['note'].gsub(source_markdown, destination_markdown)
+ quoted_text = quote_string(text)
+
+ execute("UPDATE notes SET note = '#{quoted_text}', note_html = NULL WHERE id = #{note['id']}")
+ end
+ end
+
+ def base_directory
+ File.join(Rails.root, 'public')
+ end
+
+ def connection
+ ActiveRecord::Base.connection
+ end
+
+ def say(message)
+ Rails.logger.debug(message)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/checks/force_push.rb b/lib/gitlab/checks/force_push.rb
index 1e73f89158d..714464fd5e7 100644
--- a/lib/gitlab/checks/force_push.rb
+++ b/lib/gitlab/checks/force_push.rb
@@ -5,12 +5,19 @@ module Gitlab
return false if project.empty_repo?
# Created or deleted branch
- if Gitlab::Git.blank_ref?(oldrev) || Gitlab::Git.blank_ref?(newrev)
- false
- else
- Gitlab::Git::RevList.new(
- path_to_repo: project.repository.path_to_repo,
- oldrev: oldrev, newrev: newrev).missed_ref.present?
+ return false if Gitlab::Git.blank_ref?(oldrev) || Gitlab::Git.blank_ref?(newrev)
+
+ GitalyClient.migrate(:force_push) do |is_enabled|
+ if is_enabled
+ !project
+ .repository
+ .gitaly_commit_client
+ .is_ancestor(oldrev, newrev)
+ else
+ Gitlab::Git::RevList.new(
+ path_to_repo: project.repository.path_to_repo,
+ oldrev: oldrev, newrev: newrev).missed_ref.present?
+ end
end
end
end
diff --git a/lib/gitlab/conflict/file_collection.rb b/lib/gitlab/conflict/file_collection.rb
index 1611eba31da..d671867e7c7 100644
--- a/lib/gitlab/conflict/file_collection.rb
+++ b/lib/gitlab/conflict/file_collection.rb
@@ -77,8 +77,8 @@ EOM
def initialize(merge_request, project)
@merge_request = merge_request
- @our_commit = merge_request.source_branch_head.raw.raw_commit
- @their_commit = merge_request.target_branch_head.raw.raw_commit
+ @our_commit = merge_request.source_branch_head.raw.rugged_commit
+ @their_commit = merge_request.target_branch_head.raw.rugged_commit
@project = project
end
end
diff --git a/lib/gitlab/contributions_calendar.rb b/lib/gitlab/contributions_calendar.rb
index bf557103cfd..0735243e021 100644
--- a/lib/gitlab/contributions_calendar.rb
+++ b/lib/gitlab/contributions_calendar.rb
@@ -48,7 +48,7 @@ module Gitlab
end
def starting_month
- Date.today.month
+ Date.current.month
end
private
@@ -66,12 +66,18 @@ module Gitlab
.select(:id)
conditions = t[:created_at].gteq(date_from.beginning_of_day)
- .and(t[:created_at].lteq(Date.today.end_of_day))
+ .and(t[:created_at].lteq(Date.current.end_of_day))
.and(t[:author_id].eq(contributor.id))
+ date_interval = if Gitlab::Database.postgresql?
+ "INTERVAL '#{Time.zone.now.utc_offset} seconds'"
+ else
+ "INTERVAL #{Time.zone.now.utc_offset} SECOND"
+ end
+
Event.reorder(nil)
- .select(t[:project_id], t[:target_type], t[:action], 'date(created_at) AS date', 'count(id) as total_amount')
- .group(t[:project_id], t[:target_type], t[:action], 'date(created_at)')
+ .select(t[:project_id], t[:target_type], t[:action], "date(created_at + #{date_interval}) AS date", 'count(id) as total_amount')
+ .group(t[:project_id], t[:target_type], t[:action], "date(created_at + #{date_interval})")
.where(conditions)
.having(t[:project_id].in(Arel::Nodes::SqlLiteral.new(authed_projects.to_sql)))
end
diff --git a/lib/gitlab/cycle_analytics/plan_event_fetcher.rb b/lib/gitlab/cycle_analytics/plan_event_fetcher.rb
index b260822788d..2479b4a7706 100644
--- a/lib/gitlab/cycle_analytics/plan_event_fetcher.rb
+++ b/lib/gitlab/cycle_analytics/plan_event_fetcher.rb
@@ -54,7 +54,7 @@ module Gitlab
end
def serialize_commit(event, commit, query)
- commit = Commit.new(Gitlab::Git::Commit.new(commit.to_hash), @project)
+ commit = Commit.from_hash(commit.to_hash, @project)
AnalyticsCommitSerializer.new(project: @project, total_time: event['total_time']).represent(commit)
end
diff --git a/lib/gitlab/daemon.rb b/lib/gitlab/daemon.rb
new file mode 100644
index 00000000000..dfd17e35707
--- /dev/null
+++ b/lib/gitlab/daemon.rb
@@ -0,0 +1,62 @@
+module Gitlab
+ class Daemon
+ def self.initialize_instance(*args)
+ raise "#{name} singleton instance already initialized" if @instance
+ @instance = new(*args)
+ Kernel.at_exit(&@instance.method(:stop))
+ @instance
+ end
+
+ def self.instance
+ @instance ||= initialize_instance
+ end
+
+ attr_reader :thread
+
+ def thread?
+ !thread.nil?
+ end
+
+ def initialize
+ @mutex = Mutex.new
+ end
+
+ def enabled?
+ true
+ end
+
+ def start
+ return unless enabled?
+
+ @mutex.synchronize do
+ return thread if thread?
+
+ @thread = Thread.new { start_working }
+ end
+ end
+
+ def stop
+ @mutex.synchronize do
+ return unless thread?
+
+ stop_working
+
+ if thread
+ thread.wakeup if thread.alive?
+ thread.join
+ @thread = nil
+ end
+ end
+ end
+
+ private
+
+ def start_working
+ raise NotImplementedError
+ end
+
+ def stop_working
+ # no-ops
+ end
+ end
+end
diff --git a/lib/gitlab/database.rb b/lib/gitlab/database.rb
index d7dab584a44..e001d25e7b7 100644
--- a/lib/gitlab/database.rb
+++ b/lib/gitlab/database.rb
@@ -25,6 +25,10 @@ module Gitlab
database_version.match(/\A(?:PostgreSQL |)([^\s]+).*\z/)[1]
end
+ def self.join_lateral_supported?
+ postgresql? && version.to_f >= 9.3
+ end
+
def self.nulls_last_order(field, direction = 'ASC')
order = "#{field} #{direction}"
diff --git a/lib/gitlab/diff/line.rb b/lib/gitlab/diff/line.rb
index 2d89ccfc354..0603141e441 100644
--- a/lib/gitlab/diff/line.rb
+++ b/lib/gitlab/diff/line.rb
@@ -21,7 +21,7 @@ module Gitlab
def to_hash
hash = {}
- serialize_keys.each { |key| hash[key] = send(key) }
+ serialize_keys.each { |key| hash[key] = send(key) } # rubocop:disable GitlabSecurity/PublicSend
hash
end
diff --git a/lib/gitlab/ee_compat_check.rb b/lib/gitlab/ee_compat_check.rb
index 72d7d4f84d1..abd401224d8 100644
--- a/lib/gitlab/ee_compat_check.rb
+++ b/lib/gitlab/ee_compat_check.rb
@@ -98,10 +98,11 @@ module Gitlab
if status.zero?
@ee_branch_found = ee_branch_prefix
- else
- _, status = step("Fetching origin/#{ee_branch_suffix}", %W[git fetch origin #{ee_branch_suffix}])
+ return
end
+ _, status = step("Fetching origin/#{ee_branch_suffix}", %W[git fetch origin #{ee_branch_suffix}])
+
if status.zero?
@ee_branch_found = ee_branch_suffix
else
diff --git a/lib/gitlab/email/handler/create_note_handler.rb b/lib/gitlab/email/handler/create_note_handler.rb
index 31579e94a87..8eea33b9ab5 100644
--- a/lib/gitlab/email/handler/create_note_handler.rb
+++ b/lib/gitlab/email/handler/create_note_handler.rb
@@ -15,7 +15,6 @@ module Gitlab
def execute
raise SentNotificationNotFoundError unless sent_notification
- raise AutoGeneratedEmailError if mail.header.to_s =~ /auto-(generated|replied)/
validate_permission!(:create_note)
diff --git a/lib/gitlab/email/receiver.rb b/lib/gitlab/email/receiver.rb
index 0d6b08b5d29..c8f4591d060 100644
--- a/lib/gitlab/email/receiver.rb
+++ b/lib/gitlab/email/receiver.rb
@@ -26,6 +26,9 @@ module Gitlab
raise EmptyEmailError if @raw.blank?
mail = build_mail
+
+ ignore_auto_submitted!(mail)
+
mail_key = extract_mail_key(mail)
handler = Handler.for(mail, mail_key)
@@ -87,6 +90,16 @@ module Gitlab
break key if key
end
end
+
+ def ignore_auto_submitted!(mail)
+ # Mail::Header#[] is case-insensitive
+ auto_submitted = mail.header['Auto-Submitted']&.value
+
+ # Mail::Field#value would strip leading and trailing whitespace
+ raise AutoGeneratedEmailError if
+ # See also https://tools.ietf.org/html/rfc3834
+ auto_submitted && auto_submitted != 'no'
+ end
end
end
end
diff --git a/lib/gitlab/encoding_helper.rb b/lib/gitlab/encoding_helper.rb
index 781f9c56a42..8ddc91e341d 100644
--- a/lib/gitlab/encoding_helper.rb
+++ b/lib/gitlab/encoding_helper.rb
@@ -11,7 +11,7 @@ module Gitlab
# obscure encoding with low confidence.
# There is a lot more info with this merge request:
# https://gitlab.com/gitlab-org/gitlab_git/merge_requests/77#note_4754193
- ENCODING_CONFIDENCE_THRESHOLD = 40
+ ENCODING_CONFIDENCE_THRESHOLD = 50
def encode!(message)
return nil unless message.respond_to? :force_encoding
diff --git a/lib/gitlab/environment.rb b/lib/gitlab/environment.rb
new file mode 100644
index 00000000000..5e0dd6e7859
--- /dev/null
+++ b/lib/gitlab/environment.rb
@@ -0,0 +1,7 @@
+module Gitlab
+ module Environment
+ def self.hostname
+ @hostname ||= ENV['HOSTNAME'] || Socket.gethostname
+ end
+ end
+end
diff --git a/lib/gitlab/git/blame.rb b/lib/gitlab/git/blame.rb
index 0deaab01b5b..31effdba292 100644
--- a/lib/gitlab/git/blame.rb
+++ b/lib/gitlab/git/blame.rb
@@ -1,5 +1,3 @@
-# Gitaly note: JV: needs 1 RPC for #load_blame.
-
module Gitlab
module Git
class Blame
@@ -18,7 +16,7 @@ module Gitlab
def each
@blames.each do |blame|
yield(
- Gitlab::Git::Commit.new(blame.commit),
+ Gitlab::Git::Commit.new(@repo, blame.commit),
blame.line
)
end
@@ -26,15 +24,29 @@ module Gitlab
private
- # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/376
def load_blame
- cmd = %W(#{Gitlab.config.git.bin_path} --git-dir=#{@repo.path} blame -p #{@sha} -- #{@path})
- # Read in binary mode to ensure ASCII-8BIT
- raw_output = IO.popen(cmd, 'rb') {|io| io.read }
+ raw_output = @repo.gitaly_migrate(:blame) do |is_enabled|
+ if is_enabled
+ load_blame_by_gitaly
+ else
+ load_blame_by_shelling_out
+ end
+ end
+
output = encode_utf8(raw_output)
process_raw_blame output
end
+ def load_blame_by_gitaly
+ @repo.gitaly_commit_client.raw_blame(@sha, @path)
+ end
+
+ def load_blame_by_shelling_out
+ cmd = %W(#{Gitlab.config.git.bin_path} --git-dir=#{@repo.path} blame -p #{@sha} -- #{@path})
+ # Read in binary mode to ensure ASCII-8BIT
+ IO.popen(cmd, 'rb') {|io| io.read }
+ end
+
def process_raw_blame(output)
lines, final = [], []
info, commits = {}, {}
diff --git a/lib/gitlab/git/blob.rb b/lib/gitlab/git/blob.rb
index db6cfc9671f..77b81d2d437 100644
--- a/lib/gitlab/git/blob.rb
+++ b/lib/gitlab/git/blob.rb
@@ -20,66 +20,7 @@ module Gitlab
if is_enabled
find_by_gitaly(repository, sha, path)
else
- find_by_rugged(repository, sha, path)
- end
- end
- end
-
- def find_by_gitaly(repository, sha, path)
- path = path.sub(/\A\/*/, '')
- path = '/' if path.empty?
- name = File.basename(path)
- entry = Gitlab::GitalyClient::CommitService.new(repository).tree_entry(sha, path, MAX_DATA_DISPLAY_SIZE)
- return unless entry
-
- case entry.type
- when :COMMIT
- new(
- id: entry.oid,
- name: name,
- size: 0,
- data: '',
- path: path,
- commit_id: sha
- )
- when :BLOB
- new(
- id: entry.oid,
- name: name,
- size: entry.size,
- data: entry.data.dup,
- mode: entry.mode.to_s(8),
- path: path,
- commit_id: sha,
- binary: binary?(entry.data)
- )
- end
- end
-
- def find_by_rugged(repository, sha, path)
- commit = repository.lookup(sha)
- root_tree = commit.tree
-
- blob_entry = find_entry_by_path(repository, root_tree.oid, path)
-
- return nil unless blob_entry
-
- if blob_entry[:type] == :commit
- submodule_blob(blob_entry, path, sha)
- else
- blob = repository.lookup(blob_entry[:oid])
-
- if blob
- new(
- id: blob.oid,
- name: blob_entry[:name],
- size: blob.size,
- data: blob.content(MAX_DATA_DISPLAY_SIZE),
- mode: blob_entry[:filemode].to_s(8),
- path: path,
- commit_id: sha,
- binary: blob.binary?
- )
+ find_by_rugged(repository, sha, path, limit: MAX_DATA_DISPLAY_SIZE)
end
end
end
@@ -109,6 +50,21 @@ module Gitlab
detect && detect[:type] == :binary
end
+ # Returns an array of Blob instances, specified in blob_references as
+ # [[commit_sha, path], [commit_sha, path], ...]. If blob_size_limit < 0 then the
+ # full blob contents are returned. If blob_size_limit >= 0 then each blob will
+ # contain no more than limit bytes in its data attribute.
+ #
+ # Keep in mind that this method may allocate a lot of memory. It is up
+ # to the caller to limit the number of blobs and blob_size_limit.
+ #
+ def batch(repository, blob_references, blob_size_limit: nil)
+ blob_size_limit ||= MAX_DATA_DISPLAY_SIZE
+ blob_references.map do |sha, path|
+ find_by_rugged(repository, sha, path, limit: blob_size_limit)
+ end
+ end
+
private
# Recursive search of blob id by path
@@ -153,6 +109,66 @@ module Gitlab
commit_id: sha
)
end
+
+ def find_by_gitaly(repository, sha, path)
+ path = path.sub(/\A\/*/, '')
+ path = '/' if path.empty?
+ name = File.basename(path)
+ entry = Gitlab::GitalyClient::CommitService.new(repository).tree_entry(sha, path, MAX_DATA_DISPLAY_SIZE)
+ return unless entry
+
+ case entry.type
+ when :COMMIT
+ new(
+ id: entry.oid,
+ name: name,
+ size: 0,
+ data: '',
+ path: path,
+ commit_id: sha
+ )
+ when :BLOB
+ new(
+ id: entry.oid,
+ name: name,
+ size: entry.size,
+ data: entry.data.dup,
+ mode: entry.mode.to_s(8),
+ path: path,
+ commit_id: sha,
+ binary: binary?(entry.data)
+ )
+ end
+ end
+
+ def find_by_rugged(repository, sha, path, limit:)
+ commit = repository.lookup(sha)
+ root_tree = commit.tree
+
+ blob_entry = find_entry_by_path(repository, root_tree.oid, path)
+
+ return nil unless blob_entry
+
+ if blob_entry[:type] == :commit
+ submodule_blob(blob_entry, path, sha)
+ else
+ blob = repository.lookup(blob_entry[:oid])
+
+ if blob
+ new(
+ id: blob.oid,
+ name: blob_entry[:name],
+ size: blob.size,
+ # Rugged::Blob#content is expensive; don't call it if we don't have to.
+ data: limit.zero? ? '' : blob.content(limit),
+ mode: blob_entry[:filemode].to_s(8),
+ path: path,
+ commit_id: sha,
+ binary: blob.binary?
+ )
+ end
+ end
+ end
end
def initialize(options)
diff --git a/lib/gitlab/git/commit.rb b/lib/gitlab/git/commit.rb
index ca7e3a7c4be..fd4dfdb09a2 100644
--- a/lib/gitlab/git/commit.rb
+++ b/lib/gitlab/git/commit.rb
@@ -14,7 +14,7 @@ module Gitlab
attr_accessor *SERIALIZE_KEYS # rubocop:disable Lint/AmbiguousOperator
- delegate :tree, to: :raw_commit
+ delegate :tree, to: :rugged_commit
def ==(other)
return false unless other.is_a?(Gitlab::Git::Commit)
@@ -50,19 +50,29 @@ module Gitlab
#
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/321
def find(repo, commit_id = "HEAD")
+ # Already a commit?
return commit_id if commit_id.is_a?(Gitlab::Git::Commit)
- return decorate(commit_id) if commit_id.is_a?(Rugged::Commit)
- obj = if commit_id.is_a?(String)
- repo.rev_parse_target(commit_id)
- else
- Gitlab::Git::Ref.dereference_object(commit_id)
- end
+ # A rugged reference?
+ commit_id = Gitlab::Git::Ref.dereference_object(commit_id)
+ return decorate(repo, commit_id) if commit_id.is_a?(Rugged::Commit)
- return nil unless obj.is_a?(Rugged::Commit)
+ # Some weird thing?
+ return nil unless commit_id.is_a?(String)
- decorate(obj)
- rescue Rugged::ReferenceError, Rugged::InvalidError, Rugged::ObjectError, Gitlab::Git::Repository::NoRepository
+ commit = repo.gitaly_migrate(:find_commit) do |is_enabled|
+ if is_enabled
+ repo.gitaly_commit_client.find_commit(commit_id)
+ else
+ obj = repo.rev_parse_target(commit_id)
+
+ obj.is_a?(Rugged::Commit) ? obj : nil
+ end
+ end
+
+ decorate(repo, commit) if commit
+ rescue Rugged::ReferenceError, Rugged::InvalidError, Rugged::ObjectError,
+ Gitlab::Git::CommandError, Gitlab::Git::Repository::NoRepository
nil
end
@@ -102,7 +112,7 @@ module Gitlab
if is_enabled
repo.gitaly_commit_client.between(base, head)
else
- repo.commits_between(base, head).map { |c| decorate(c) }
+ repo.rugged_commits_between(base, head).map { |c| decorate(repo, c) }
end
end
rescue Rugged::ReferenceError
@@ -169,7 +179,7 @@ module Gitlab
offset = actual_options[:skip]
limit = actual_options[:max_count]
walker.each(offset: offset, limit: limit) do |commit|
- commits.push(decorate(commit))
+ commits.push(decorate(repo, commit))
end
walker.reset
@@ -183,27 +193,8 @@ module Gitlab
Gitlab::GitalyClient::CommitService.new(repo).find_all_commits(options)
end
- def decorate(commit, ref = nil)
- Gitlab::Git::Commit.new(commit, ref)
- end
-
- # Returns a diff object for the changes introduced by +rugged_commit+.
- # If +rugged_commit+ doesn't have a parent, then the diff is between
- # this commit and an empty repo. See Repository#diff for the keys
- # allowed in the +options+ hash.
- def diff_from_parent(rugged_commit, options = {})
- options ||= {}
- break_rewrites = options[:break_rewrites]
- actual_options = Gitlab::Git::Diff.filter_diff_options(options)
-
- diff = if rugged_commit.parents.empty?
- rugged_commit.diff(actual_options.merge(reverse: true))
- else
- rugged_commit.parents[0].diff(rugged_commit, actual_options)
- end
-
- diff.find_similar!(break_rewrites: break_rewrites)
- diff
+ def decorate(repository, commit, ref = nil)
+ Gitlab::Git::Commit.new(repository, commit, ref)
end
# Returns the `Rugged` sorting type constant for one or more given
@@ -221,7 +212,7 @@ module Gitlab
end
end
- def initialize(raw_commit, head = nil)
+ def initialize(repository, raw_commit, head = nil)
raise "Nil as raw commit passed" unless raw_commit
case raw_commit
@@ -229,12 +220,13 @@ module Gitlab
init_from_hash(raw_commit)
when Rugged::Commit
init_from_rugged(raw_commit)
- when Gitlab::GitalyClient::Commit
+ when Gitaly::GitCommit
init_from_gitaly(raw_commit)
else
raise "Invalid raw commit type: #{raw_commit.class}"
end
+ @repository = repository
@head = head
end
@@ -269,19 +261,50 @@ module Gitlab
#
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/324
def to_diff
- diff_from_parent.patch
+ rugged_diff_from_parent.patch
end
# Returns a diff object for the changes from this commit's first parent.
# If there is no parent, then the diff is between this commit and an
- # empty repo. See Repository#diff for keys allowed in the +options+
+ # empty repo. See Repository#diff for keys allowed in the +options+
# hash.
def diff_from_parent(options = {})
- Commit.diff_from_parent(raw_commit, options)
+ Gitlab::GitalyClient.migrate(:commit_raw_diffs) do |is_enabled|
+ if is_enabled
+ @repository.gitaly_commit_client.diff_from_parent(self, options)
+ else
+ rugged_diff_from_parent(options)
+ end
+ end
+ end
+
+ def rugged_diff_from_parent(options = {})
+ options ||= {}
+ break_rewrites = options[:break_rewrites]
+ actual_options = Gitlab::Git::Diff.filter_diff_options(options)
+
+ diff = if rugged_commit.parents.empty?
+ rugged_commit.diff(actual_options.merge(reverse: true))
+ else
+ rugged_commit.parents[0].diff(rugged_commit, actual_options)
+ end
+
+ diff.find_similar!(break_rewrites: break_rewrites)
+ diff
end
def deltas
- @deltas ||= diff_from_parent.each_delta.map { |d| Gitlab::Git::Diff.new(d) }
+ @deltas ||= begin
+ deltas = Gitlab::GitalyClient.migrate(:commit_deltas) do |is_enabled|
+ if is_enabled
+ @repository.gitaly_commit_client.commit_deltas(self)
+ else
+ rugged_diff_from_parent.each_delta
+ end
+ end
+
+ deltas.map { |delta| Gitlab::Git::Diff.new(delta) }
+ end
end
def has_zero_stats?
@@ -296,7 +319,7 @@ module Gitlab
def to_hash
serialize_keys.map.with_object({}) do |key, hash|
- hash[key] = send(key)
+ hash[key] = send(key) # rubocop:disable GitlabSecurity/PublicSend
end
end
@@ -309,14 +332,7 @@ module Gitlab
end
def parents
- case raw_commit
- when Rugged::Commit
- raw_commit.parents.map { |c| Gitlab::Git::Commit.new(c) }
- when Gitlab::GitalyClient::Commit
- parent_ids.map { |oid| self.class.find(raw_commit.repository, oid) }.compact
- else
- raise NotImplementedError, "commit source doesn't support #parents"
- end
+ parent_ids.map { |oid| self.class.find(@repository, oid) }.compact
end
# Get the gpg signature of this commit.
@@ -334,7 +350,7 @@ module Gitlab
def to_patch(options = {})
begin
- raw_commit.to_mbox(options)
+ rugged_commit.to_mbox(options)
rescue Rugged::InvalidError => ex
if ex.message =~ /commit \w+ is a merge commit/i
'Patch format is not currently supported for merge commits.'
@@ -382,13 +398,21 @@ module Gitlab
encode! @committer_email
end
+ def rugged_commit
+ @rugged_commit ||= if raw_commit.is_a?(Rugged::Commit)
+ raw_commit
+ else
+ @repository.rev_parse_target(id)
+ end
+ end
+
private
def init_from_hash(hash)
raw_commit = hash.symbolize_keys
serialize_keys.each do |key|
- send("#{key}=", raw_commit[key])
+ send("#{key}=", raw_commit[key]) # rubocop:disable GitlabSecurity/PublicSend
end
end
@@ -415,10 +439,10 @@ module Gitlab
# subject from the message to make it clearer when there's one
# available but not the other.
@message = (commit.body.presence || commit.subject).dup
- @authored_date = Time.at(commit.author.date.seconds)
+ @authored_date = Time.at(commit.author.date.seconds).utc
@author_name = commit.author.name.dup
@author_email = commit.author.email.dup
- @committed_date = Time.at(commit.committer.date.seconds)
+ @committed_date = Time.at(commit.committer.date.seconds).utc
@committer_name = commit.committer.name.dup
@committer_email = commit.committer.email.dup
@parent_ids = commit.parent_ids
diff --git a/lib/gitlab/git/commit_stats.rb b/lib/gitlab/git/commit_stats.rb
index 57c29ad112c..00acb4763e9 100644
--- a/lib/gitlab/git/commit_stats.rb
+++ b/lib/gitlab/git/commit_stats.rb
@@ -16,7 +16,7 @@ module Gitlab
@deletions = 0
@total = 0
- diff = commit.diff_from_parent
+ diff = commit.rugged_diff_from_parent
diff.each_patch do |p|
# TODO: Use the new Rugged convenience methods when they're released
diff --git a/lib/gitlab/git/diff.rb b/lib/gitlab/git/diff.rb
index 9e00abefd02..ce3d65062e8 100644
--- a/lib/gitlab/git/diff.rb
+++ b/lib/gitlab/git/diff.rb
@@ -143,7 +143,7 @@ module Gitlab
hash = {}
SERIALIZE_KEYS.each do |key|
- hash[key] = send(key)
+ hash[key] = send(key) # rubocop:disable GitlabSecurity/PublicSend
end
hash
@@ -221,7 +221,7 @@ module Gitlab
raw_diff = hash.symbolize_keys
SERIALIZE_KEYS.each do |key|
- send(:"#{key}=", raw_diff[key.to_sym])
+ send(:"#{key}=", raw_diff[key.to_sym]) # rubocop:disable GitlabSecurity/PublicSend
end
end
diff --git a/lib/gitlab/git/diff_collection.rb b/lib/gitlab/git/diff_collection.rb
index 87ed9c3ea26..6a601561c2a 100644
--- a/lib/gitlab/git/diff_collection.rb
+++ b/lib/gitlab/git/diff_collection.rb
@@ -28,7 +28,6 @@ module Gitlab
@limits = self.class.collection_limits(options)
@enforce_limits = !!options.fetch(:limits, true)
@expanded = !!options.fetch(:expanded, true)
- @from_gitaly = options.fetch(:from_gitaly, false)
@line_count = 0
@byte_count = 0
@@ -44,7 +43,7 @@ module Gitlab
return if @iterator.nil?
Gitlab::GitalyClient.migrate(:commit_raw_diffs) do |is_enabled|
- if is_enabled && @from_gitaly
+ if is_enabled && @iterator.is_a?(Gitlab::GitalyClient::DiffStitcher)
each_gitaly_patch(&block)
else
each_rugged_patch(&block)
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 70d793f8e4a..afe4fb58ad0 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -58,17 +58,18 @@ module Gitlab
end
end
- # Alias to old method for compatibility
- def raw
- rugged
- end
-
def rugged
- @rugged ||= Rugged::Repository.new(path, alternates: alternate_object_directories)
+ @rugged ||= circuit_breaker.perform do
+ Rugged::Repository.new(path, alternates: alternate_object_directories)
+ end
rescue Rugged::RepositoryError, Rugged::OSError
raise NoRepository.new('no repository for such path')
end
+ def circuit_breaker
+ @circuit_breaker ||= Gitlab::Git::Storage::CircuitBreaker.for_storage(storage)
+ end
+
# Returns an Array of branch names
# sorted by name ASC
def branch_names
@@ -281,7 +282,14 @@ module Gitlab
# Return repo size in megabytes
def size
- size = popen(%w(du -sk), path).first.strip.to_i
+ size = gitaly_migrate(:repository_size) do |is_enabled|
+ if is_enabled
+ size_by_gitaly
+ else
+ size_by_shelling_out
+ end
+ end
+
(size.to_f / 1024).round(2)
end
@@ -296,21 +304,51 @@ module Gitlab
# after: Time.new(2016, 4, 21, 14, 32, 10)
# )
#
+ # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/446
def log(options)
- raw_log(options).map { |c| Commit.decorate(c) }
+ default_options = {
+ limit: 10,
+ offset: 0,
+ path: nil,
+ follow: false,
+ skip_merges: false,
+ disable_walk: false,
+ after: nil,
+ before: nil
+ }
+
+ options = default_options.merge(options)
+ options[:limit] ||= 0
+ options[:offset] ||= 0
+
+ raw_log(options).map { |c| Commit.decorate(self, c) }
end
- # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/382
- def count_commits(options)
- cmd = %W[#{Gitlab.config.git.bin_path} --git-dir=#{path} rev-list]
- cmd << "--after=#{options[:after].iso8601}" if options[:after]
- cmd << "--before=#{options[:before].iso8601}" if options[:before]
- cmd += %W[--count #{options[:ref]}]
- cmd += %W[-- #{options[:path]}] if options[:path].present?
+ # Used in gitaly-ruby
+ def raw_log(options)
+ actual_ref = options[:ref] || root_ref
+ begin
+ sha = sha_from_ref(actual_ref)
+ rescue Rugged::OdbError, Rugged::InvalidError, Rugged::ReferenceError
+ # Return an empty array if the ref wasn't found
+ return []
+ end
- raw_output = IO.popen(cmd) { |io| io.read }
+ if log_using_shell?(options)
+ log_by_shell(sha, options)
+ else
+ log_by_walk(sha, options)
+ end
+ end
- raw_output.to_i
+ def count_commits(options)
+ gitaly_migrate(:count_commits) do |is_enabled|
+ if is_enabled
+ count_commits_by_gitaly(options)
+ else
+ count_commits_by_shelling_out(options)
+ end
+ end
end
def sha_from_ref(ref)
@@ -327,7 +365,9 @@ module Gitlab
# Return a collection of Rugged::Commits between the two revspec arguments.
# See http://git-scm.com/docs/git-rev-parse.html#_specifying_revisions for
# a detailed list of valid arguments.
- def commits_between(from, to)
+ #
+ # Gitaly note: JV: to be deprecated in favor of Commit.between
+ def rugged_commits_between(from, to)
walker = Rugged::Walker.new(rugged)
walker.sorting(Rugged::SORT_NONE | Rugged::SORT_REVERSE)
@@ -580,29 +620,13 @@ module Gitlab
#
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/327
def ls_files(ref)
- actual_ref = ref || root_ref
-
- begin
- sha_from_ref(actual_ref)
- rescue Rugged::OdbError, Rugged::InvalidError, Rugged::ReferenceError
- # Return an empty array if the ref wasn't found
- return []
- end
-
- cmd = %W(#{Gitlab.config.git.bin_path} --git-dir=#{path} ls-tree)
- cmd += %w(-r)
- cmd += %w(--full-tree)
- cmd += %w(--full-name)
- cmd += %W(-- #{actual_ref})
-
- raw_output = IO.popen(cmd, &:read).split("\n").map do |f|
- stuff, path = f.split("\t")
- _mode, type, _sha = stuff.split(" ")
- path if type == "blob"
- # Contain only blob type
+ gitaly_migrate(:ls_files) do |is_enabled|
+ if is_enabled
+ gitaly_ls_files(ref)
+ else
+ git_ls_files(ref)
+ end
end
-
- raw_output.compact
end
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/328
@@ -686,6 +710,14 @@ module Gitlab
@gitaly_repository_client ||= Gitlab::GitalyClient::RepositoryService.new(self)
end
+ def gitaly_migrate(method, &block)
+ Gitlab::GitalyClient.migrate(method, &block)
+ rescue GRPC::NotFound => e
+ raise NoRepository.new(e)
+ rescue GRPC::BadStatus => e
+ raise CommandError.new(e)
+ end
+
private
# Gitaly note: JV: Trying to get rid of the 'filter' option so we can implement this with 'git'.
@@ -702,36 +734,6 @@ module Gitlab
sort_branches(branches, sort_by)
end
- def raw_log(options)
- default_options = {
- limit: 10,
- offset: 0,
- path: nil,
- follow: false,
- skip_merges: false,
- disable_walk: false,
- after: nil,
- before: nil
- }
-
- options = default_options.merge(options)
- options[:limit] ||= 0
- options[:offset] ||= 0
- actual_ref = options[:ref] || root_ref
- begin
- sha = sha_from_ref(actual_ref)
- rescue Rugged::OdbError, Rugged::InvalidError, Rugged::ReferenceError
- # Return an empty array if the ref wasn't found
- return []
- end
-
- if log_using_shell?(options)
- log_by_shell(sha, options)
- else
- log_by_walk(sha, options)
- end
- end
-
def log_using_shell?(options)
options[:path].present? ||
options[:disable_walk] ||
@@ -852,46 +854,6 @@ module Gitlab
submodule_data.select { |path, data| data['id'] }
end
- # Returns true if +commit+ introduced changes to +path+, using commit
- # trees to make that determination. Uses the history simplification
- # rules that `git log` uses by default, where a commit is omitted if it
- # is TREESAME to any parent.
- #
- # If the +follow+ option is true and the file specified by +path+ was
- # renamed, then the path value is set to the old path.
- def commit_touches_path?(commit, path, follow, walker)
- entry = tree_entry(commit, path)
-
- if commit.parents.empty?
- # This is the root commit, return true if it has +path+ in its tree
- return !entry.nil?
- end
-
- num_treesame = 0
- commit.parents.each do |parent|
- parent_entry = tree_entry(parent, path)
-
- # Only follow the first TREESAME parent for merge commits
- if num_treesame > 0
- walker.hide(parent)
- next
- end
-
- if entry.nil? && parent_entry.nil?
- num_treesame += 1
- elsif entry && parent_entry && entry[:oid] == parent_entry[:oid]
- num_treesame += 1
- end
- end
-
- case num_treesame
- when 0
- detect_rename(commit, commit.parents.first, path) if follow
- true
- else false
- end
- end
-
# Find the entry for +path+ in the tree for +commit+
def tree_entry(commit, path)
pathname = Pathname.new(path)
@@ -919,43 +881,6 @@ module Gitlab
tmp_entry
end
- # Compare +commit+ and +parent+ for +path+. If +path+ is a file and was
- # renamed in +commit+, then set +path+ to the old filename.
- def detect_rename(commit, parent, path)
- diff = parent.diff(commit, paths: [path], disable_pathspec_match: true)
-
- # If +path+ is a filename, not a directory, then we should only have
- # one delta. We don't need to follow renames for directories.
- return nil if diff.each_delta.count > 1
-
- delta = diff.each_delta.first
- if delta.added?
- full_diff = parent.diff(commit)
- full_diff.find_similar!
-
- full_diff.each_delta do |full_delta|
- if full_delta.renamed? && path == full_delta.new_file[:path]
- # Look for the old path in ancestors
- path.replace(full_delta.old_file[:path])
- end
- end
- end
- end
-
- # Returns true if the index entry has the special file mode that denotes
- # a submodule.
- def submodule?(index_entry)
- index_entry[:mode] == 57344
- end
-
- # Return a Rugged::Index that has read from the tree at +ref_name+
- def populated_index(ref_name)
- commit = rev_parse_target(ref_name)
- index = rugged.index
- index.read_tree(commit.tree)
- index
- end
-
# Return the Rugged patches for the diff between +from+ and +to+.
def diff_patches(from, to, options = {}, *paths)
options ||= {}
@@ -1001,16 +926,67 @@ module Gitlab
end.sort_by(&:name)
end
+ def last_commit_for_path_by_rugged(sha, path)
+ sha = last_commit_id_for_path(sha, path)
+ commit(sha)
+ end
+
def tags_from_gitaly
gitaly_ref_client.tags
end
- def gitaly_migrate(method, &block)
- Gitlab::GitalyClient.migrate(method, &block)
- rescue GRPC::NotFound => e
- raise NoRepository.new(e)
- rescue GRPC::BadStatus => e
- raise CommandError.new(e)
+ def size_by_shelling_out
+ popen(%w(du -sk), path).first.strip.to_i
+ end
+
+ def size_by_gitaly
+ gitaly_repository_client.repository_size
+ end
+
+ def count_commits_by_gitaly(options)
+ gitaly_commit_client.commit_count(options[:ref], options)
+ end
+
+ def count_commits_by_shelling_out(options)
+ cmd = %W[#{Gitlab.config.git.bin_path} --git-dir=#{path} rev-list]
+ cmd << "--after=#{options[:after].iso8601}" if options[:after]
+ cmd << "--before=#{options[:before].iso8601}" if options[:before]
+ cmd += %W[--count #{options[:ref]}]
+ cmd += %W[-- #{options[:path]}] if options[:path].present?
+
+ raw_output = IO.popen(cmd) { |io| io.read }
+
+ raw_output.to_i
+ end
+
+ def gitaly_ls_files(ref)
+ gitaly_commit_client.ls_files(ref)
+ end
+
+ def git_ls_files(ref)
+ actual_ref = ref || root_ref
+
+ begin
+ sha_from_ref(actual_ref)
+ rescue Rugged::OdbError, Rugged::InvalidError, Rugged::ReferenceError
+ # Return an empty array if the ref wasn't found
+ return []
+ end
+
+ cmd = %W(#{Gitlab.config.git.bin_path} --git-dir=#{path} ls-tree)
+ cmd += %w(-r)
+ cmd += %w(--full-tree)
+ cmd += %w(--full-name)
+ cmd += %W(-- #{actual_ref})
+
+ raw_output = IO.popen(cmd, &:read).split("\n").map do |f|
+ stuff, path = f.split("\t")
+ _mode, type, _sha = stuff.split(" ")
+ path if type == "blob"
+ # Contain only blob type
+ end
+
+ raw_output.compact
end
end
end
diff --git a/lib/gitlab/git/storage.rb b/lib/gitlab/git/storage.rb
new file mode 100644
index 00000000000..e28be4b8a38
--- /dev/null
+++ b/lib/gitlab/git/storage.rb
@@ -0,0 +1,22 @@
+module Gitlab
+ module Git
+ module Storage
+ class Inaccessible < StandardError
+ attr_reader :retry_after
+
+ def initialize(message = nil, retry_after = nil)
+ super(message)
+ @retry_after = retry_after
+ end
+ end
+
+ CircuitOpen = Class.new(Inaccessible)
+
+ REDIS_KEY_PREFIX = 'storage_accessible:'.freeze
+
+ def self.redis
+ Gitlab::Redis::SharedState
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/git/storage/circuit_breaker.rb b/lib/gitlab/git/storage/circuit_breaker.rb
new file mode 100644
index 00000000000..9ea9367d4b7
--- /dev/null
+++ b/lib/gitlab/git/storage/circuit_breaker.rb
@@ -0,0 +1,144 @@
+module Gitlab
+ module Git
+ module Storage
+ class CircuitBreaker
+ FailureInfo = Struct.new(:last_failure, :failure_count)
+
+ attr_reader :storage,
+ :hostname,
+ :storage_path,
+ :failure_count_threshold,
+ :failure_wait_time,
+ :failure_reset_time,
+ :storage_timeout
+
+ delegate :last_failure, :failure_count, to: :failure_info
+
+ def self.reset_all!
+ pattern = "#{Gitlab::Git::Storage::REDIS_KEY_PREFIX}*"
+
+ Gitlab::Git::Storage.redis.with do |redis|
+ all_storage_keys = redis.keys(pattern)
+ redis.del(*all_storage_keys) unless all_storage_keys.empty?
+ end
+
+ RequestStore.delete(:circuitbreaker_cache)
+ end
+
+ def self.for_storage(storage)
+ cached_circuitbreakers = RequestStore.fetch(:circuitbreaker_cache) do
+ Hash.new do |hash, storage_name|
+ hash[storage_name] = new(storage_name)
+ end
+ end
+
+ cached_circuitbreakers[storage]
+ end
+
+ def initialize(storage, hostname = Gitlab::Environment.hostname)
+ @storage = storage
+ @hostname = hostname
+
+ config = Gitlab.config.repositories.storages[@storage]
+ @storage_path = config['path']
+ @failure_count_threshold = config['failure_count_threshold']
+ @failure_wait_time = config['failure_wait_time']
+ @failure_reset_time = config['failure_reset_time']
+ @storage_timeout = config['storage_timeout']
+ end
+
+ def perform
+ return yield unless Feature.enabled?('git_storage_circuit_breaker')
+
+ check_storage_accessible!
+
+ yield
+ end
+
+ def circuit_broken?
+ return false if no_failures?
+
+ recent_failure = last_failure > failure_wait_time.seconds.ago
+ too_many_failures = failure_count > failure_count_threshold
+
+ recent_failure || too_many_failures
+ end
+
+ # Memoizing the `storage_available` call means we only do it once per
+ # request when the storage is available.
+ #
+ # When the storage appears not available, and the memoized value is `false`
+ # we might want to try again.
+ def storage_available?
+ return @storage_available if @storage_available
+
+ if @storage_available = Gitlab::Git::Storage::ForkedStorageCheck
+ .storage_available?(storage_path, storage_timeout)
+ track_storage_accessible
+ else
+ track_storage_inaccessible
+ end
+
+ @storage_available
+ end
+
+ def check_storage_accessible!
+ if circuit_broken?
+ raise Gitlab::Git::Storage::CircuitOpen.new("Circuit for #{storage} is broken", failure_wait_time)
+ end
+
+ unless storage_available?
+ raise Gitlab::Git::Storage::Inaccessible.new("#{storage} not accessible", failure_wait_time)
+ end
+ end
+
+ def no_failures?
+ last_failure.blank? && failure_count == 0
+ end
+
+ def track_storage_inaccessible
+ @failure_info = FailureInfo.new(Time.now, failure_count + 1)
+
+ Gitlab::Git::Storage.redis.with do |redis|
+ redis.pipelined do
+ redis.hset(cache_key, :last_failure, last_failure.to_i)
+ redis.hincrby(cache_key, :failure_count, 1)
+ redis.expire(cache_key, failure_reset_time)
+ end
+ end
+ end
+
+ def track_storage_accessible
+ return if no_failures?
+
+ @failure_info = FailureInfo.new(nil, 0)
+
+ Gitlab::Git::Storage.redis.with do |redis|
+ redis.pipelined do
+ redis.hset(cache_key, :last_failure, nil)
+ redis.hset(cache_key, :failure_count, 0)
+ end
+ end
+ end
+
+ def failure_info
+ @failure_info ||= get_failure_info
+ end
+
+ def get_failure_info
+ last_failure, failure_count = Gitlab::Git::Storage.redis.with do |redis|
+ redis.hmget(cache_key, :last_failure, :failure_count)
+ end
+
+ last_failure = Time.at(last_failure.to_i) if last_failure.present?
+
+ FailureInfo.new(last_failure, failure_count.to_i)
+ end
+
+ def cache_key
+ @cache_key ||= "#{Gitlab::Git::Storage::REDIS_KEY_PREFIX}#{storage}:#{hostname}"
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/git/storage/forked_storage_check.rb b/lib/gitlab/git/storage/forked_storage_check.rb
new file mode 100644
index 00000000000..91d8241f17b
--- /dev/null
+++ b/lib/gitlab/git/storage/forked_storage_check.rb
@@ -0,0 +1,55 @@
+module Gitlab
+ module Git
+ module Storage
+ module ForkedStorageCheck
+ extend self
+
+ def storage_available?(path, timeout_seconds = 5)
+ status = timeout_check(path, timeout_seconds)
+
+ status.success?
+ end
+
+ def timeout_check(path, timeout_seconds)
+ filesystem_check_pid = check_filesystem_in_process(path)
+
+ deadline = timeout_seconds.seconds.from_now.utc
+ wait_time = 0.01
+ status = nil
+
+ while status.nil?
+ if deadline > Time.now.utc
+ sleep(wait_time)
+ _pid, status = Process.wait2(filesystem_check_pid, Process::WNOHANG)
+ else
+ Process.kill('KILL', filesystem_check_pid)
+ # Blocking wait, so we are sure the process is gone before continuing
+ _pid, status = Process.wait2(filesystem_check_pid)
+ end
+ end
+
+ status
+ end
+
+ # This will spawn a new 2 processes to do the check:
+ # The outer child (waiter) will spawn another child process (stater).
+ #
+ # The stater is the process is performing the actual filesystem check
+ # the check might hang if the filesystem is acting up.
+ # In this case we will send a `KILL` to the waiter, which will still
+ # be responsive while the stater is hanging.
+ def check_filesystem_in_process(path)
+ spawn('ruby', '-e', ruby_check, path, [:out, :err] => '/dev/null')
+ end
+
+ def ruby_check
+ <<~RUBY_FILESYSTEM_CHECK
+ inner_pid = fork { File.stat(ARGV.first) }
+ Process.waitpid(inner_pid)
+ exit $?.exitstatus
+ RUBY_FILESYSTEM_CHECK
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/git/storage/health.rb b/lib/gitlab/git/storage/health.rb
new file mode 100644
index 00000000000..2d723147f4f
--- /dev/null
+++ b/lib/gitlab/git/storage/health.rb
@@ -0,0 +1,91 @@
+module Gitlab
+ module Git
+ module Storage
+ class Health
+ attr_reader :storage_name, :info
+
+ def self.pattern_for_storage(storage_name)
+ "#{Gitlab::Git::Storage::REDIS_KEY_PREFIX}#{storage_name}:*"
+ end
+
+ def self.for_all_storages
+ storage_names = Gitlab.config.repositories.storages.keys
+ results_per_storage = nil
+
+ Gitlab::Git::Storage.redis.with do |redis|
+ keys_per_storage = all_keys_for_storages(storage_names, redis)
+ results_per_storage = load_for_keys(keys_per_storage, redis)
+ end
+
+ results_per_storage.map do |name, info|
+ info.each { |i| i[:failure_count] = i[:failure_count].value.to_i }
+ new(name, info)
+ end
+ end
+
+ def self.all_keys_for_storages(storage_names, redis)
+ keys_per_storage = {}
+
+ redis.pipelined do
+ storage_names.each do |storage_name|
+ pattern = pattern_for_storage(storage_name)
+
+ keys_per_storage[storage_name] = redis.keys(pattern)
+ end
+ end
+
+ keys_per_storage
+ end
+
+ def self.load_for_keys(keys_per_storage, redis)
+ info_for_keys = {}
+
+ redis.pipelined do
+ keys_per_storage.each do |storage_name, keys_future|
+ info_for_storage = keys_future.value.map do |key|
+ { name: key, failure_count: redis.hget(key, :failure_count) }
+ end
+
+ info_for_keys[storage_name] = info_for_storage
+ end
+ end
+
+ info_for_keys
+ end
+
+ def self.for_failing_storages
+ for_all_storages.select(&:failing?)
+ end
+
+ def initialize(storage_name, info)
+ @storage_name = storage_name
+ @info = info
+ end
+
+ def failing_info
+ @failing_info ||= info.select { |info_for_host| info_for_host[:failure_count] > 0 }
+ end
+
+ def failing?
+ failing_info.any?
+ end
+
+ def failing_on_hosts
+ @failing_on_hosts ||= failing_info.map do |info_for_host|
+ info_for_host[:name].split(':').last
+ end
+ end
+
+ def failing_circuit_breakers
+ @failing_circuit_breakers ||= failing_on_hosts.map do |hostname|
+ CircuitBreaker.new(storage_name, hostname)
+ end
+ end
+
+ def total_failures
+ @total_failures ||= failing_info.sum { |info_for_host| info_for_host[:failure_count] }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb
index c90ef282fdd..70177cd0fec 100644
--- a/lib/gitlab/gitaly_client.rb
+++ b/lib/gitlab/gitaly_client.rb
@@ -100,5 +100,9 @@ module Gitlab
path = Rails.root.join(SERVER_VERSION_FILE)
path.read.chomp
end
+
+ def self.encode(s)
+ s.dup.force_encoding(Encoding::ASCII_8BIT)
+ end
end
end
diff --git a/lib/gitlab/gitaly_client/commit.rb b/lib/gitlab/gitaly_client/commit.rb
deleted file mode 100644
index 61fe462d762..00000000000
--- a/lib/gitlab/gitaly_client/commit.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-module Gitlab
- module GitalyClient
- class Commit
- attr_reader :repository, :gitaly_commit
-
- delegate :id, :subject, :body, :author, :committer, :parent_ids, to: :gitaly_commit
-
- def initialize(repository, gitaly_commit)
- @repository = repository
- @gitaly_commit = gitaly_commit
- end
- end
- end
-end
diff --git a/lib/gitlab/gitaly_client/commit_service.rb b/lib/gitlab/gitaly_client/commit_service.rb
index a834781b1f1..93268d9f33c 100644
--- a/lib/gitlab/gitaly_client/commit_service.rb
+++ b/lib/gitlab/gitaly_client/commit_service.rb
@@ -10,6 +10,18 @@ module Gitlab
@repository = repository
end
+ def ls_files(revision)
+ request = Gitaly::ListFilesRequest.new(
+ repository: @gitaly_repo,
+ revision: GitalyClient.encode(revision)
+ )
+
+ response = GitalyClient.call(@repository.storage, :commit_service, :list_files, request)
+ response.flat_map do |msg|
+ msg.paths.map { |d| d.dup.force_encoding(Encoding::UTF_8) }
+ end
+ end
+
def is_ancestor(ancestor_id, child_id)
request = Gitaly::CommitIsAncestorRequest.new(
repository: @gitaly_repo,
@@ -29,22 +41,21 @@ module Gitlab
request = Gitaly::CommitDiffRequest.new(request_params)
response = GitalyClient.call(@repository.storage, :diff_service, :commit_diff, request)
- Gitlab::Git::DiffCollection.new(GitalyClient::DiffStitcher.new(response), options.merge(from_gitaly: true))
+ GitalyClient::DiffStitcher.new(response)
end
def commit_deltas(commit)
request = Gitaly::CommitDeltaRequest.new(commit_diff_request_params(commit))
response = GitalyClient.call(@repository.storage, :diff_service, :commit_delta, request)
- response.flat_map do |msg|
- msg.deltas.map { |d| Gitlab::Git::Diff.new(d) }
- end
+
+ response.flat_map { |msg| msg.deltas }
end
def tree_entry(ref, path, limit = nil)
request = Gitaly::TreeEntryRequest.new(
repository: @gitaly_repo,
revision: ref,
- path: path.dup.force_encoding(Encoding::ASCII_8BIT),
+ path: GitalyClient.encode(path),
limit: limit.to_i
)
@@ -85,15 +96,31 @@ module Gitlab
end
end
- def commit_count(ref)
+ def commit_count(ref, options = {})
request = Gitaly::CountCommitsRequest.new(
repository: @gitaly_repo,
revision: ref
)
+ request.after = Google::Protobuf::Timestamp.new(seconds: options[:after].to_i) if options[:after].present?
+ request.before = Google::Protobuf::Timestamp.new(seconds: options[:before].to_i) if options[:before].present?
+ request.path = options[:path] if options[:path].present?
GitalyClient.call(@repository.storage, :commit_service, :count_commits, request).count
end
+ def last_commit_for_path(revision, path)
+ request = Gitaly::LastCommitForPathRequest.new(
+ repository: @gitaly_repo,
+ revision: GitalyClient.encode(revision),
+ path: GitalyClient.encode(path.to_s)
+ )
+
+ gitaly_commit = GitalyClient.call(@repository.storage, :commit_service, :last_commit_for_path, request).commit
+ return unless gitaly_commit
+
+ Gitlab::Git::Commit.new(@repository, gitaly_commit)
+ end
+
def between(from, to)
request = Gitaly::CommitsBetweenRequest.new(
repository: @gitaly_repo,
@@ -118,6 +145,20 @@ module Gitlab
consume_commits_response(response)
end
+ def commits_by_message(query, revision: '', path: '', limit: 1000, offset: 0)
+ request = Gitaly::CommitsByMessageRequest.new(
+ repository: @gitaly_repo,
+ query: query,
+ revision: revision.to_s.force_encoding(Encoding::ASCII_8BIT),
+ path: path.to_s.force_encoding(Encoding::ASCII_8BIT),
+ limit: limit.to_i,
+ offset: offset.to_i
+ )
+
+ response = GitalyClient.call(@repository.storage, :commit_service, :commits_by_message, request)
+ consume_commits_response(response)
+ end
+
def languages(ref = nil)
request = Gitaly::CommitLanguagesRequest.new(repository: @gitaly_repo, revision: ref || '')
response = GitalyClient.call(@repository.storage, :commit_service, :commit_languages, request)
@@ -125,10 +166,32 @@ module Gitlab
response.languages.map { |l| { value: l.share.round(2), label: l.name, color: l.color, highlight: l.color } }
end
+ def raw_blame(revision, path)
+ request = Gitaly::RawBlameRequest.new(
+ repository: @gitaly_repo,
+ revision: revision,
+ path: path
+ )
+
+ response = GitalyClient.call(@repository.storage, :commit_service, :raw_blame, request)
+ response.reduce("") { |memo, msg| memo << msg.data }
+ end
+
+ def find_commit(revision)
+ request = Gitaly::FindCommitRequest.new(
+ repository: @gitaly_repo,
+ revision: GitalyClient.encode(revision)
+ )
+
+ response = GitalyClient.call(@repository.storage, :commit_service, :find_commit, request)
+
+ response.commit
+ end
+
private
def commit_diff_request_params(commit, options = {})
- parent_id = commit.parents[0]&.id || EMPTY_TREE_ID
+ parent_id = commit.parent_ids.first || EMPTY_TREE_ID
{
repository: @gitaly_repo,
@@ -141,8 +204,7 @@ module Gitlab
def consume_commits_response(response)
response.flat_map do |message|
message.commits.map do |gitaly_commit|
- commit = GitalyClient::Commit.new(@repository, gitaly_commit)
- Gitlab::Git::Commit.new(commit)
+ Gitlab::Git::Commit.new(@repository, gitaly_commit)
end
end
end
diff --git a/lib/gitlab/gitaly_client/diff.rb b/lib/gitlab/gitaly_client/diff.rb
index d459c9a88fb..54df6304865 100644
--- a/lib/gitlab/gitaly_client/diff.rb
+++ b/lib/gitlab/gitaly_client/diff.rb
@@ -7,13 +7,13 @@ module Gitlab
def initialize(params)
params.each do |key, val|
- public_send(:"#{key}=", val)
+ public_send(:"#{key}=", val) # rubocop:disable GitlabSecurity/PublicSend
end
end
def ==(other)
FIELDS.all? do |field|
- public_send(field) == other.public_send(field)
+ public_send(field) == other.public_send(field) # rubocop:disable GitlabSecurity/PublicSend
end
end
end
diff --git a/lib/gitlab/gitaly_client/ref_service.rb b/lib/gitlab/gitaly_client/ref_service.rb
index b0f7548b7dc..919fb68b8c7 100644
--- a/lib/gitlab/gitaly_client/ref_service.rb
+++ b/lib/gitlab/gitaly_client/ref_service.rb
@@ -16,8 +16,7 @@ module Gitlab
response.flat_map do |message|
message.branches.map do |branch|
- gitaly_commit = GitalyClient::Commit.new(@repository, branch.target)
- target_commit = Gitlab::Git::Commit.decorate(gitaly_commit)
+ target_commit = Gitlab::Git::Commit.decorate(@repository, branch.target)
Gitlab::Git::Branch.new(@repository, branch.name, branch.target.id, target_commit)
end
end
@@ -102,8 +101,7 @@ module Gitlab
response.flat_map do |message|
message.tags.map do |gitaly_tag|
if gitaly_tag.target_commit.present?
- commit = GitalyClient::Commit.new(@repository, gitaly_tag.target_commit)
- gitaly_commit = Gitlab::Git::Commit.new(commit)
+ gitaly_commit = Gitlab::Git::Commit.decorate(@repository, gitaly_tag.target_commit)
end
Gitlab::Git::Tag.new(
@@ -141,7 +139,7 @@ module Gitlab
committer_email: response.commit_committer.email.dup
}
- Gitlab::Git::Commit.decorate(hash)
+ Gitlab::Git::Commit.decorate(@repository, hash)
end
end
end
diff --git a/lib/gitlab/gitaly_client/repository_service.rb b/lib/gitlab/gitaly_client/repository_service.rb
index 13e75b256a7..6ad97e62941 100644
--- a/lib/gitlab/gitaly_client/repository_service.rb
+++ b/lib/gitlab/gitaly_client/repository_service.rb
@@ -10,7 +10,7 @@ module Gitlab
def exists?
request = Gitaly::RepositoryExistsRequest.new(repository: @gitaly_repo)
- GitalyClient.call(@storage, :repository_service, :exists, request).exists
+ GitalyClient.call(@storage, :repository_service, :repository_exists, request).exists
end
def garbage_collect(create_bitmap)
@@ -27,6 +27,11 @@ module Gitlab
request = Gitaly::RepackIncrementalRequest.new(repository: @gitaly_repo)
GitalyClient.call(@storage, :repository_service, :repack_incremental, request)
end
+
+ def repository_size
+ request = Gitaly::RepositorySizeRequest.new(repository: @gitaly_repo)
+ GitalyClient.call(@storage, :repository_service, :repository_size, request).size
+ end
end
end
end
diff --git a/lib/gitlab/gitaly_client/util.rb b/lib/gitlab/gitaly_client/util.rb
index f5a4c5493ef..8fc937496af 100644
--- a/lib/gitlab/gitaly_client/util.rb
+++ b/lib/gitlab/gitaly_client/util.rb
@@ -5,7 +5,9 @@ module Gitlab
def repository(repository_storage, relative_path)
Gitaly::Repository.new(
storage_name: repository_storage,
- relative_path: relative_path
+ relative_path: relative_path,
+ git_object_directory: Gitlab::Git::Env['GIT_OBJECT_DIRECTORY'].to_s,
+ git_alternate_object_directories: Array.wrap(Gitlab::Git::Env['GIT_ALTERNATE_OBJECT_DIRECTORIES'])
)
end
end
diff --git a/lib/gitlab/gitlab_import/client.rb b/lib/gitlab/gitlab_import/client.rb
index 86fb6c51765..f1007daab5d 100644
--- a/lib/gitlab/gitlab_import/client.rb
+++ b/lib/gitlab/gitlab_import/client.rb
@@ -71,7 +71,7 @@ module Gitlab
end
def config
- Gitlab.config.omniauth.providers.find{|provider| provider.name == "gitlab"}
+ Gitlab.config.omniauth.providers.find {|provider| provider.name == "gitlab"}
end
def gitlab_options
diff --git a/lib/gitlab/gpg.rb b/lib/gitlab/gpg.rb
index e1d1724295a..45e9f9d65ae 100644
--- a/lib/gitlab/gpg.rb
+++ b/lib/gitlab/gpg.rb
@@ -2,6 +2,8 @@ module Gitlab
module Gpg
extend self
+ MUTEX = Mutex.new
+
module CurrentKeyChain
extend self
@@ -42,21 +44,37 @@ module Gitlab
end
end
- def using_tmp_keychain
- Dir.mktmpdir do |dir|
- @original_dirs ||= [GPGME::Engine.dirinfo('homedir')]
- @original_dirs.push(dir)
-
- GPGME::Engine.home_dir = dir
-
- return_value = yield
+ # Allows thread safe switching of temporary keychain files
+ #
+ # 1. The current thread may use nesting of temporary keychain
+ # 2. Another thread needs to wait for the lock to be released
+ def using_tmp_keychain(&block)
+ if MUTEX.locked? && MUTEX.owned?
+ optimistic_using_tmp_keychain(&block)
+ else
+ MUTEX.synchronize do
+ optimistic_using_tmp_keychain(&block)
+ end
+ end
+ end
- @original_dirs.pop
+ # 1. Returns the custom home directory if one has been set by calling
+ # `GPGME::Engine.home_dir=`
+ # 2. Returns the default home directory otherwise
+ def current_home_dir
+ GPGME::Engine.info.first.home_dir || GPGME::Engine.dirinfo('homedir')
+ end
- GPGME::Engine.home_dir = @original_dirs[-1]
+ private
- return_value
+ def optimistic_using_tmp_keychain
+ previous_dir = current_home_dir
+ Dir.mktmpdir do |dir|
+ GPGME::Engine.home_dir = dir
+ yield
end
+ ensure
+ GPGME::Engine.home_dir = previous_dir
end
end
end
diff --git a/lib/gitlab/health_checks/fs_shards_check.rb b/lib/gitlab/health_checks/fs_shards_check.rb
index 9e91c135956..eef97f54962 100644
--- a/lib/gitlab/health_checks/fs_shards_check.rb
+++ b/lib/gitlab/health_checks/fs_shards_check.rb
@@ -10,7 +10,9 @@ module Gitlab
def readiness
repository_storages.map do |storage_name|
begin
- if !storage_stat_test(storage_name)
+ if !storage_circuitbreaker_test(storage_name)
+ HealthChecks::Result.new(false, 'circuitbreaker tripped', shard: storage_name)
+ elsif !storage_stat_test(storage_name)
HealthChecks::Result.new(false, 'cannot stat storage', shard: storage_name)
else
with_temp_file(storage_name) do |tmp_file_path|
@@ -36,7 +38,8 @@ module Gitlab
[
storage_stat_metrics(storage_name),
storage_write_metrics(storage_name),
- storage_read_metrics(storage_name)
+ storage_read_metrics(storage_name),
+ storage_circuitbreaker_metrics(storage_name)
].flatten
end
end
@@ -121,6 +124,12 @@ module Gitlab
file_contents == RANDOM_STRING
end
+ def storage_circuitbreaker_test(storage_name)
+ Gitlab::Git::Storage::CircuitBreaker.new(storage_name).perform { "OK" }
+ rescue Gitlab::Git::Storage::Inaccessible
+ nil
+ end
+
def storage_stat_metrics(storage_name)
operation_metrics(:filesystem_accessible, :filesystem_access_latency_seconds, shard: storage_name) do
with_timing { storage_stat_test(storage_name) }
@@ -143,6 +152,14 @@ module Gitlab
end
end
end
+
+ def storage_circuitbreaker_metrics(storage_name)
+ operation_metrics(:filesystem_circuitbreaker,
+ :filesystem_circuitbreaker_latency_seconds,
+ shard: storage_name) do
+ with_timing { storage_circuitbreaker_test(storage_name) }
+ end
+ end
end
end
end
diff --git a/lib/gitlab/i18n.rb b/lib/gitlab/i18n.rb
index cc282d1415b..5d106b5c075 100644
--- a/lib/gitlab/i18n.rb
+++ b/lib/gitlab/i18n.rb
@@ -16,7 +16,8 @@ module Gitlab
'eo' => 'Esperanto',
'it' => 'Italiano',
'uk' => 'УкраїнÑька',
- 'ja' => '日本語'
+ 'ja' => '日本語',
+ 'ko' => '한국어'
}.freeze
def available_locales
diff --git a/lib/gitlab/import_export/file_importer.rb b/lib/gitlab/import_export/file_importer.rb
index ffd17118c91..989342389bc 100644
--- a/lib/gitlab/import_export/file_importer.rb
+++ b/lib/gitlab/import_export/file_importer.rb
@@ -47,12 +47,16 @@ module Gitlab
end
def remove_symlinks!
- Dir["#{@shared.export_path}/**/*"].each do |path|
+ extracted_files.each do |path|
FileUtils.rm(path) if File.lstat(path).symlink?
end
true
end
+
+ def extracted_files
+ Dir.glob("#{@shared.export_path}/**/*", File::FNM_DOTMATCH).reject { |f| f =~ /.*\/\.{1,2}$/ }
+ end
end
end
end
diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml
index c8ad3a7a5e0..9d9ebcb389a 100644
--- a/lib/gitlab/import_export/import_export.yml
+++ b/lib/gitlab/import_export/import_export.yml
@@ -3,18 +3,22 @@ project_tree:
- labels:
:priorities
- milestones:
- - :events
+ - events:
+ - :push_event_payload
- issues:
- - :events
+ - events:
+ - :push_event_payload
- :timelogs
- notes:
- :author
- - :events
+ - events:
+ - :push_event_payload
- label_links:
- label:
:priorities
- milestone:
- - :events
+ - events:
+ - :push_event_payload
- snippets:
- :award_emoji
- notes:
@@ -25,21 +29,25 @@ project_tree:
- merge_requests:
- notes:
- :author
- - :events
+ - events:
+ - :push_event_payload
- merge_request_diff:
- :merge_request_diff_commits
- :merge_request_diff_files
- - :events
+ - events:
+ - :push_event_payload
- :timelogs
- label_links:
- label:
:priorities
- milestone:
- - :events
+ - events:
+ - :push_event_payload
- pipelines:
- notes:
- :author
- - :events
+ - events:
+ - :push_event_payload
- :stages
- :statuses
- :triggers
@@ -101,11 +109,14 @@ excluded_attributes:
merge_requests:
- :milestone_id
- :ref_fetched
+ - :merge_jid
award_emoji:
- :awardable_id
statuses:
- :trace
- :token
+ push_event_payload:
+ - :event_id
methods:
labels:
diff --git a/lib/gitlab/import_sources.rb b/lib/gitlab/import_sources.rb
index 52276cbcd9a..5404dc11a87 100644
--- a/lib/gitlab/import_sources.rb
+++ b/lib/gitlab/import_sources.rb
@@ -8,7 +8,7 @@ module Gitlab
ImportSource = Struct.new(:name, :title, :importer)
ImportTable = [
- ImportSource.new('github', 'GitHub', Gitlab::GithubImport::Importer),
+ ImportSource.new('github', 'GitHub', Github::Import),
ImportSource.new('bitbucket', 'Bitbucket', Gitlab::BitbucketImport::Importer),
ImportSource.new('gitlab', 'GitLab.com', Gitlab::GitlabImport::Importer),
ImportSource.new('google_code', 'Google Code', Gitlab::GoogleCodeImport::Importer),
diff --git a/lib/gitlab/key_fingerprint.rb b/lib/gitlab/key_fingerprint.rb
index b75ae512d92..d9a79f7c291 100644
--- a/lib/gitlab/key_fingerprint.rb
+++ b/lib/gitlab/key_fingerprint.rb
@@ -1,55 +1,48 @@
module Gitlab
class KeyFingerprint
- include Gitlab::Popen
+ attr_reader :key, :ssh_key
- attr_accessor :key
+ # Unqualified MD5 fingerprint for compatibility
+ delegate :fingerprint, to: :ssh_key, allow_nil: true
def initialize(key)
@key = key
- end
-
- def fingerprint
- cmd_status = 0
- cmd_output = ''
-
- Tempfile.open('gitlab_key_file') do |file|
- file.puts key
- file.rewind
-
- cmd = []
- cmd.push('ssh-keygen')
- cmd.push('-E', 'md5') if explicit_fingerprint_algorithm?
- cmd.push('-lf', file.path)
-
- cmd_output, cmd_status = popen(cmd, '/tmp')
- end
-
- return nil unless cmd_status.zero?
- # 16 hex bytes separated by ':', optionally starting with "MD5:"
- fingerprint_matches = cmd_output.match(/(MD5:)?(?<fingerprint>(\h{2}:){15}\h{2})/)
- return nil unless fingerprint_matches
-
- fingerprint_matches[:fingerprint]
+ @ssh_key =
+ begin
+ Net::SSH::KeyFactory.load_data_public_key(key)
+ rescue Net::SSH::Exception, NotImplementedError
+ end
end
- private
-
- def explicit_fingerprint_algorithm?
- # OpenSSH 6.8 introduces a new default output format for fingerprints.
- # Check the version and decide which command to use.
-
- version_output, version_status = popen(%w(ssh -V))
- return false unless version_status.zero?
+ def valid?
+ ssh_key.present?
+ end
- version_matches = version_output.match(/OpenSSH_(?<major>\d+)\.(?<minor>\d+)/)
- return false unless version_matches
+ def type
+ return unless valid?
- version_info = Gitlab::VersionInfo.new(version_matches[:major].to_i, version_matches[:minor].to_i)
+ parts = ssh_key.ssh_type.split('-')
+ parts.shift if parts[0] == 'ssh'
- required_version_info = Gitlab::VersionInfo.new(6, 8)
+ parts[0].upcase
+ end
- version_info >= required_version_info
+ def bits
+ return unless valid?
+
+ case type
+ when 'RSA'
+ ssh_key.n.num_bits
+ when 'DSS', 'DSA'
+ ssh_key.p.num_bits
+ when 'ECDSA'
+ ssh_key.group.order.num_bits
+ when 'ED25519'
+ 256
+ else
+ raise "Unsupported key type: #{type}"
+ end
end
end
end
diff --git a/lib/gitlab/metrics/base_sampler.rb b/lib/gitlab/metrics/base_sampler.rb
index 219accfc029..716d20bb91a 100644
--- a/lib/gitlab/metrics/base_sampler.rb
+++ b/lib/gitlab/metrics/base_sampler.rb
@@ -1,20 +1,7 @@
require 'logger'
module Gitlab
module Metrics
- class BaseSampler
- def self.initialize_instance(*args)
- raise "#{name} singleton instance already initialized" if @instance
- @instance = new(*args)
- at_exit(&@instance.method(:stop))
- @instance
- end
-
- def self.instance
- @instance
- end
-
- attr_reader :running
-
+ class BaseSampler < Daemon
# interval - The sampling interval in seconds.
def initialize(interval)
interval_half = interval.to_f / 2
@@ -22,44 +9,7 @@ module Gitlab
@interval = interval
@interval_steps = (-interval_half..interval_half).step(0.1).to_a
- @mutex = Mutex.new
- end
-
- def enabled?
- true
- end
-
- def start
- return unless enabled?
-
- @mutex.synchronize do
- return if running
- @running = true
-
- @thread = Thread.new do
- sleep(sleep_interval)
-
- while running
- safe_sample
-
- sleep(sleep_interval)
- end
- end
- end
- end
-
- def stop
- @mutex.synchronize do
- return unless running
-
- @running = false
-
- if @thread
- @thread.wakeup if @thread.alive?
- @thread.join
- @thread = nil
- end
- end
+ super()
end
def safe_sample
@@ -81,7 +31,7 @@ module Gitlab
# potentially missing anything that happens in between samples).
# 2. Don't sample data at the same interval two times in a row.
def sleep_interval
- while step = @interval_steps.sample
+ while (step = @interval_steps.sample)
if step != @last_step
@last_step = step
@@ -89,6 +39,25 @@ module Gitlab
end
end
end
+
+ private
+
+ attr_reader :running
+
+ def start_working
+ @running = true
+ sleep(sleep_interval)
+
+ while running
+ safe_sample
+
+ sleep(sleep_interval)
+ end
+ end
+
+ def stop_working
+ @running = false
+ end
end
end
end
diff --git a/lib/gitlab/metrics/sidekiq_metrics_exporter.rb b/lib/gitlab/metrics/sidekiq_metrics_exporter.rb
new file mode 100644
index 00000000000..5980a4ded2b
--- /dev/null
+++ b/lib/gitlab/metrics/sidekiq_metrics_exporter.rb
@@ -0,0 +1,39 @@
+require 'webrick'
+require 'prometheus/client/rack/exporter'
+
+module Gitlab
+ module Metrics
+ class SidekiqMetricsExporter < Daemon
+ def enabled?
+ Gitlab::Metrics.metrics_folder_present? && settings.enabled
+ end
+
+ def settings
+ Settings.monitoring.sidekiq_exporter
+ end
+
+ private
+
+ attr_reader :server
+
+ def start_working
+ @server = ::WEBrick::HTTPServer.new(Port: settings.port, BindAddress: settings.address)
+ server.mount "/", Rack::Handler::WEBrick, rack_app
+ server.start
+ end
+
+ def stop_working
+ server.shutdown
+ @server = nil
+ end
+
+ def rack_app
+ Rack::Builder.app do
+ use Rack::Deflater
+ use ::Prometheus::Client::Rack::Exporter
+ run -> (env) { [404, {}, ['']] }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/middleware/webpack_proxy.rb b/lib/gitlab/middleware/webpack_proxy.rb
index 6105d165810..6aecf63231f 100644
--- a/lib/gitlab/middleware/webpack_proxy.rb
+++ b/lib/gitlab/middleware/webpack_proxy.rb
@@ -1,6 +1,7 @@
# This Rack middleware is intended to proxy the webpack assets directory to the
# webpack-dev-server. It is only intended for use in development.
+# :nocov:
module Gitlab
module Middleware
class WebpackProxy < Rack::Proxy
@@ -22,3 +23,4 @@ module Gitlab
end
end
end
+# :nocov:
diff --git a/lib/gitlab/o_auth/session.rb b/lib/gitlab/o_auth/session.rb
index f33bfd0bd0e..30739f2a2c5 100644
--- a/lib/gitlab/o_auth/session.rb
+++ b/lib/gitlab/o_auth/session.rb
@@ -1,3 +1,4 @@
+# :nocov:
module Gitlab
module OAuth
module Session
@@ -15,3 +16,4 @@ module Gitlab
end
end
end
+# :nocov:
diff --git a/lib/gitlab/project_template.rb b/lib/gitlab/project_template.rb
new file mode 100644
index 00000000000..cf461adf697
--- /dev/null
+++ b/lib/gitlab/project_template.rb
@@ -0,0 +1,45 @@
+module Gitlab
+ class ProjectTemplate
+ attr_reader :title, :name
+
+ def initialize(name, title)
+ @name, @title = name, title
+ end
+
+ alias_method :logo, :name
+
+ def file
+ archive_path.open
+ end
+
+ def archive_path
+ Rails.root.join("vendor/project_templates/#{name}.tar.gz")
+ end
+
+ def clone_url
+ "https://gitlab.com/gitlab-org/project-templates/#{name}.git"
+ end
+
+ def ==(other)
+ name == other.name && title == other.title
+ end
+
+ TEMPLATES_TABLE = [
+ ProjectTemplate.new('rails', 'Ruby on Rails')
+ ].freeze
+
+ class << self
+ def all
+ TEMPLATES_TABLE
+ end
+
+ def find(name)
+ all.find { |template| template.name == name.to_s }
+ end
+
+ def archive_directory
+ Rails.root.join("vendor_directory/project_templates")
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/prometheus/queries/additional_metrics_environment_query.rb b/lib/gitlab/prometheus/queries/additional_metrics_environment_query.rb
index db4708b22e4..32fe8201a8d 100644
--- a/lib/gitlab/prometheus/queries/additional_metrics_environment_query.rb
+++ b/lib/gitlab/prometheus/queries/additional_metrics_environment_query.rb
@@ -5,7 +5,7 @@ module Gitlab
include QueryAdditionalMetrics
def query(environment_id)
- Environment.find_by(id: environment_id).try do |environment|
+ ::Environment.find_by(id: environment_id).try do |environment|
query_metrics(
common_query_context(environment, timeframe_start: 8.hours.ago.to_f, timeframe_end: Time.now.to_f)
)
diff --git a/lib/gitlab/prometheus/queries/environment_query.rb b/lib/gitlab/prometheus/queries/environment_query.rb
index 66f29d95177..1d17d3cfd56 100644
--- a/lib/gitlab/prometheus/queries/environment_query.rb
+++ b/lib/gitlab/prometheus/queries/environment_query.rb
@@ -3,7 +3,7 @@ module Gitlab
module Queries
class EnvironmentQuery < BaseQuery
def query(environment_id)
- Environment.find_by(id: environment_id).try do |environment|
+ ::Environment.find_by(id: environment_id).try do |environment|
environment_slug = environment.slug
timeframe_start = 8.hours.ago.to_f
timeframe_end = Time.now.to_f
diff --git a/lib/gitlab/reference_extractor.rb b/lib/gitlab/reference_extractor.rb
index f5b757ace77..bc836dcc08d 100644
--- a/lib/gitlab/reference_extractor.rb
+++ b/lib/gitlab/reference_extractor.rb
@@ -45,7 +45,7 @@ module Gitlab
end
def all
- REFERABLES.each { |referable| send(referable.to_s.pluralize) }
+ REFERABLES.each { |referable| send(referable.to_s.pluralize) } # rubocop:disable GitlabSecurity/PublicSend
@references.values.flatten
end
diff --git a/lib/gitlab/seeder.rb b/lib/gitlab/seeder.rb
index 823f697f51c..f9ab9bd466f 100644
--- a/lib/gitlab/seeder.rb
+++ b/lib/gitlab/seeder.rb
@@ -1,3 +1,4 @@
+# :nocov:
module DeliverNever
def deliver_later
self
@@ -21,3 +22,4 @@ module Gitlab
end
end
end
+# :nocov:
diff --git a/lib/gitlab/shell.rb b/lib/gitlab/shell.rb
index 4366ff336ef..0cb28732402 100644
--- a/lib/gitlab/shell.rb
+++ b/lib/gitlab/shell.rb
@@ -105,12 +105,24 @@ module Gitlab
# fetch_remote("gitlab/gitlab-ci", "upstream")
#
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/387
- def fetch_remote(storage, name, remote, forced: false, no_tags: false)
+ def fetch_remote(storage, name, remote, ssh_auth: nil, forced: false, no_tags: false)
args = [gitlab_shell_projects_path, 'fetch-remote', storage, "#{name}.git", remote, "#{Gitlab.config.gitlab_shell.git_timeout}"]
args << '--force' if forced
args << '--no-tags' if no_tags
- gitlab_shell_fast_execute_raise_error(args)
+ vars = {}
+
+ if ssh_auth&.ssh_import?
+ if ssh_auth.ssh_key_auth? && ssh_auth.ssh_private_key.present?
+ vars['GITLAB_SHELL_SSH_KEY'] = ssh_auth.ssh_private_key
+ end
+
+ if ssh_auth.ssh_known_hosts.present?
+ vars['GITLAB_SHELL_KNOWN_HOSTS'] = ssh_auth.ssh_known_hosts
+ end
+ end
+
+ gitlab_shell_fast_execute_raise_error(args, vars)
end
# Move repository
@@ -293,15 +305,15 @@ module Gitlab
false
end
- def gitlab_shell_fast_execute_raise_error(cmd)
- output, status = gitlab_shell_fast_execute_helper(cmd)
+ def gitlab_shell_fast_execute_raise_error(cmd, vars = {})
+ output, status = gitlab_shell_fast_execute_helper(cmd, vars)
raise Error, output unless status.zero?
true
end
- def gitlab_shell_fast_execute_helper(cmd)
- vars = ENV.to_h.slice(*GITLAB_SHELL_ENV_VARS)
+ def gitlab_shell_fast_execute_helper(cmd, vars = {})
+ vars.merge!(ENV.to_h.slice(*GITLAB_SHELL_ENV_VARS))
# Don't pass along the entire parent environment to prevent gitlab-shell
# from wasting I/O by searching through GEM_PATH
diff --git a/lib/gitlab/url_blocker.rb b/lib/gitlab/url_blocker.rb
index 7e14a566696..fee1a127fd7 100644
--- a/lib/gitlab/url_blocker.rb
+++ b/lib/gitlab/url_blocker.rb
@@ -19,6 +19,8 @@ module Gitlab
return false if internal?(uri)
return true if blocked_port?(uri.port)
+ return true if blocked_user_or_hostname?(uri.user)
+ return true if blocked_user_or_hostname?(uri.hostname)
server_ips = Resolv.getaddresses(uri.hostname)
return true if (blocked_ips & server_ips).any?
@@ -37,6 +39,12 @@ module Gitlab
port < 1024 && !VALID_PORTS.include?(port)
end
+ def blocked_user_or_hostname?(value)
+ return false if value.blank?
+
+ value !~ /\A\p{Alnum}/
+ end
+
def internal?(uri)
internal_web?(uri) || internal_shell?(uri)
end
diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb
index e0ac21305a5..748e0a29184 100644
--- a/lib/gitlab/usage_data.rb
+++ b/lib/gitlab/usage_data.rb
@@ -27,8 +27,8 @@ module Gitlab
ci_pipeline_schedules: ::Ci::PipelineSchedule.count,
deploy_keys: DeployKey.count,
deployments: Deployment.count,
- environments: Environment.count,
- in_review_folder: Environment.in_review_folder.count,
+ environments: ::Environment.count,
+ in_review_folder: ::Environment.in_review_folder.count,
groups: Group.count,
issues: Issue.count,
keys: Key.count,
diff --git a/lib/haml_lint/inline_javascript.rb b/lib/haml_lint/inline_javascript.rb
new file mode 100644
index 00000000000..05668c69006
--- /dev/null
+++ b/lib/haml_lint/inline_javascript.rb
@@ -0,0 +1,16 @@
+unless Rails.env.production?
+ require 'haml_lint/haml_visitor'
+ require 'haml_lint/linter'
+ require 'haml_lint/linter_registry'
+
+ module HamlLint
+ class Linter::InlineJavaScript < Linter
+ include LinterRegistry
+
+ def visit_filter(node)
+ return unless node.filter_type == 'javascript'
+ record_lint(node, 'Inline JavaScript is discouraged (https://docs.gitlab.com/ee/development/gotchas.html#do-not-use-inline-javascript-in-views)')
+ end
+ end
+ end
+end
diff --git a/lib/mattermost/session.rb b/lib/mattermost/session.rb
index 688a79c0441..ef08bd46e17 100644
--- a/lib/mattermost/session.rb
+++ b/lib/mattermost/session.rb
@@ -36,11 +36,12 @@ module Mattermost
def with_session
with_lease do
- raise Mattermost::NoSessionError unless create
+ create
begin
yield self
- rescue Errno::ECONNREFUSED
+ rescue Errno::ECONNREFUSED => e
+ Rails.logger.error(e.message + "\n" + e.backtrace.join("\n"))
raise Mattermost::NoSessionError
ensure
destroy
@@ -85,10 +86,12 @@ module Mattermost
private
def create
- return unless oauth_uri
- return unless token_uri
+ raise Mattermost::NoSessionError unless oauth_uri
+ raise Mattermost::NoSessionError unless token_uri
@token = request_token
+ raise Mattermost::NoSessionError unless @token
+
@headers = {
Authorization: "Bearer #{@token}"
}
@@ -106,11 +109,16 @@ module Mattermost
@oauth_uri = nil
response = get("/api/v3/oauth/gitlab/login", follow_redirects: false)
- return unless 300 <= response.code && response.code < 400
+ return unless (300...400) === response.code
redirect_uri = response.headers['location']
return unless redirect_uri
+ oauth_cookie = parse_cookie(response)
+ @headers = {
+ Cookie: oauth_cookie.to_cookie_string
+ }
+
@oauth_uri = URI.parse(redirect_uri)
end
@@ -124,7 +132,7 @@ module Mattermost
def request_token
response = get(token_uri, follow_redirects: false)
- if 200 <= response.code && response.code < 400
+ if (200...400) === response.code
response.headers['token']
end
end
@@ -156,5 +164,11 @@ module Mattermost
rescue Errno::ECONNREFUSED => e
raise Mattermost::ConnectionError.new(e.message)
end
+
+ def parse_cookie(response)
+ cookie_hash = CookieHash.new
+ response.get_fields('Set-Cookie').each { |c| cookie_hash.add_cookies(c) }
+ cookie_hash
+ end
end
end
diff --git a/lib/rspec_flaky/example.rb b/lib/rspec_flaky/example.rb
new file mode 100644
index 00000000000..b6e790cbbab
--- /dev/null
+++ b/lib/rspec_flaky/example.rb
@@ -0,0 +1,46 @@
+module RspecFlaky
+ # This is a wrapper class for RSpec::Core::Example
+ class Example
+ delegate :status, :exception, to: :execution_result
+
+ def initialize(rspec_example)
+ @rspec_example = rspec_example.try(:example) || rspec_example
+ end
+
+ def uid
+ @uid ||= Digest::MD5.hexdigest("#{description}-#{file}")
+ end
+
+ def example_id
+ rspec_example.id
+ end
+
+ def file
+ metadata[:file_path]
+ end
+
+ def line
+ metadata[:line_number]
+ end
+
+ def description
+ metadata[:full_description]
+ end
+
+ def attempts
+ rspec_example.try(:attempts) || 1
+ end
+
+ private
+
+ attr_reader :rspec_example
+
+ def metadata
+ rspec_example.metadata
+ end
+
+ def execution_result
+ rspec_example.execution_result
+ end
+ end
+end
diff --git a/lib/rspec_flaky/flaky_example.rb b/lib/rspec_flaky/flaky_example.rb
new file mode 100644
index 00000000000..f81fb90e870
--- /dev/null
+++ b/lib/rspec_flaky/flaky_example.rb
@@ -0,0 +1,39 @@
+module RspecFlaky
+ # This represents a flaky RSpec example and is mainly meant to be saved in a JSON file
+ class FlakyExample < OpenStruct
+ def initialize(example)
+ if example.respond_to?(:example_id)
+ super(
+ example_id: example.example_id,
+ file: example.file,
+ line: example.line,
+ description: example.description,
+ last_attempts_count: example.attempts,
+ flaky_reports: 1)
+ else
+ super
+ end
+ end
+
+ def first_flaky_at
+ self[:first_flaky_at] || Time.now
+ end
+
+ def last_flaky_at
+ Time.now
+ end
+
+ def last_flaky_job
+ return unless ENV['CI_PROJECT_URL'] && ENV['CI_JOB_ID']
+
+ "#{ENV['CI_PROJECT_URL']}/-/jobs/#{ENV['CI_JOB_ID']}"
+ end
+
+ def to_h
+ super.merge(
+ first_flaky_at: first_flaky_at,
+ last_flaky_at: last_flaky_at,
+ last_flaky_job: last_flaky_job)
+ end
+ end
+end
diff --git a/lib/rspec_flaky/listener.rb b/lib/rspec_flaky/listener.rb
new file mode 100644
index 00000000000..ec2fbd9e36c
--- /dev/null
+++ b/lib/rspec_flaky/listener.rb
@@ -0,0 +1,75 @@
+require 'json'
+
+module RspecFlaky
+ class Listener
+ attr_reader :all_flaky_examples, :new_flaky_examples
+
+ def initialize
+ @new_flaky_examples = {}
+ @all_flaky_examples = init_all_flaky_examples
+ end
+
+ def example_passed(notification)
+ current_example = RspecFlaky::Example.new(notification.example)
+
+ return unless current_example.attempts > 1
+
+ flaky_example_hash = all_flaky_examples[current_example.uid]
+
+ all_flaky_examples[current_example.uid] =
+ if flaky_example_hash
+ FlakyExample.new(flaky_example_hash).tap do |ex|
+ ex.last_attempts_count = current_example.attempts
+ ex.flaky_reports += 1
+ end
+ else
+ FlakyExample.new(current_example).tap do |ex|
+ new_flaky_examples[current_example.uid] = ex
+ end
+ end
+ end
+
+ def dump_summary(_)
+ write_report_file(all_flaky_examples, all_flaky_examples_report_path)
+
+ if new_flaky_examples.any?
+ Rails.logger.warn "\nNew flaky examples detected:\n"
+ Rails.logger.warn JSON.pretty_generate(to_report(new_flaky_examples))
+
+ write_report_file(new_flaky_examples, new_flaky_examples_report_path)
+ end
+ end
+
+ def to_report(examples)
+ Hash[examples.map { |k, ex| [k, ex.to_h] }]
+ end
+
+ private
+
+ def init_all_flaky_examples
+ return {} unless File.exist?(all_flaky_examples_report_path)
+
+ all_flaky_examples = JSON.parse(File.read(all_flaky_examples_report_path))
+
+ Hash[(all_flaky_examples || {}).map { |k, ex| [k, FlakyExample.new(ex)] }]
+ end
+
+ def write_report_file(examples, file_path)
+ return unless ENV['FLAKY_RSPEC_GENERATE_REPORT'] == 'true'
+
+ report_path_dir = File.dirname(file_path)
+ FileUtils.mkdir_p(report_path_dir) unless Dir.exist?(report_path_dir)
+ File.write(file_path, JSON.pretty_generate(to_report(examples)))
+ end
+
+ def all_flaky_examples_report_path
+ @all_flaky_examples_report_path ||= ENV['ALL_FLAKY_RSPEC_REPORT_PATH'] ||
+ Rails.root.join("rspec_flaky/all-report.json")
+ end
+
+ def new_flaky_examples_report_path
+ @new_flaky_examples_report_path ||= ENV['NEW_FLAKY_RSPEC_REPORT_PATH'] ||
+ Rails.root.join("rspec_flaky/new-report.json")
+ end
+ end
+end
diff --git a/lib/static_model.rb b/lib/static_model.rb
index 185921d8fbe..60e2dd82e4e 100644
--- a/lib/static_model.rb
+++ b/lib/static_model.rb
@@ -18,7 +18,7 @@ module StaticModel
#
# Pass it along if we respond to it.
def [](key)
- send(key) if respond_to?(key)
+ send(key) if respond_to?(key) # rubocop:disable GitlabSecurity/PublicSend
end
def to_param
diff --git a/lib/support/nginx/gitlab b/lib/support/nginx/gitlab
index f25e66d54c8..54f51d9d633 100644
--- a/lib/support/nginx/gitlab
+++ b/lib/support/nginx/gitlab
@@ -25,6 +25,39 @@ map $http_upgrade $connection_upgrade_gitlab {
'' close;
}
+## NGINX 'combined' log format with filtered query strings
+log_format gitlab_access $remote_addr - $remote_user [$time_local] "$request_method $gitlab_filtered_request_uri $server_protocol" $status $body_bytes_sent "$gitlab_filtered_http_referer" "$http_user_agent";
+
+## Remove private_token from the request URI
+# In: /foo?private_token=unfiltered&authenticity_token=unfiltered&rss_token=unfiltered&...
+# Out: /foo?private_token=[FILTERED]&authenticity_token=unfiltered&rss_token=unfiltered&...
+map $request_uri $gitlab_temp_request_uri_1 {
+ default $request_uri;
+ ~(?i)^(?<start>.*)(?<temp>[\?&]private[\-_]token)=[^&]*(?<rest>.*)$ "$start$temp=[FILTERED]$rest";
+}
+
+## Remove authenticity_token from the request URI
+# In: /foo?private_token=[FILTERED]&authenticity_token=unfiltered&rss_token=unfiltered&...
+# Out: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&rss_token=unfiltered&...
+map $gitlab_temp_request_uri_1 $gitlab_temp_request_uri_2 {
+ default $gitlab_temp_request_uri_1;
+ ~(?i)^(?<start>.*)(?<temp>[\?&]authenticity[\-_]token)=[^&]*(?<rest>.*)$ "$start$temp=[FILTERED]$rest";
+}
+
+## Remove rss_token from the request URI
+# In: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&rss_token=unfiltered&...
+# Out: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&rss_token=[FILTERED]&...
+map $gitlab_temp_request_uri_2 $gitlab_filtered_request_uri {
+ default $gitlab_temp_request_uri_2;
+ ~(?i)^(?<start>.*)(?<temp>[\?&]rss[\-_]token)=[^&]*(?<rest>.*)$ "$start$temp=[FILTERED]$rest";
+}
+
+## A version of the referer without the query string
+map $http_referer $gitlab_filtered_http_referer {
+ default $http_referer;
+ ~^(?<temp>.*)\? $temp;
+}
+
## Normal HTTP host
server {
## Either remove "default_server" from the listen line below,
@@ -46,7 +79,7 @@ server {
# set_real_ip_from YOUR_TRUSTED_ADDRESS; ## Replace this with something like 192.168.1.0/24
## Individual nginx logs for this GitLab vhost
- access_log /var/log/nginx/gitlab_access.log;
+ access_log /var/log/nginx/gitlab_access.log gitlab_access;
error_log /var/log/nginx/gitlab_error.log;
location / {
diff --git a/lib/support/nginx/gitlab-pages b/lib/support/nginx/gitlab-pages
index d9746c5c1aa..875c8bcbf3c 100644
--- a/lib/support/nginx/gitlab-pages
+++ b/lib/support/nginx/gitlab-pages
@@ -18,8 +18,11 @@ server {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
+
+ proxy_cache off;
+
# The same address as passed to GitLab Pages: `-listen-proxy`
- proxy_pass http://localhost:8090/;
+ proxy_pass http://localhost:8090/;
}
# Define custom error pages
diff --git a/lib/support/nginx/gitlab-pages-ssl b/lib/support/nginx/gitlab-pages-ssl
index a1ccf266835..62ed482e2bf 100644
--- a/lib/support/nginx/gitlab-pages-ssl
+++ b/lib/support/nginx/gitlab-pages-ssl
@@ -67,8 +67,11 @@ server {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
+
+ proxy_cache off;
+
# The same address as passed to GitLab Pages: `-listen-proxy`
- proxy_pass http://localhost:8090/;
+ proxy_pass http://localhost:8090/;
}
# Define custom error pages
diff --git a/lib/support/nginx/gitlab-ssl b/lib/support/nginx/gitlab-ssl
index 2b40da18bab..ed8131ef24f 100644
--- a/lib/support/nginx/gitlab-ssl
+++ b/lib/support/nginx/gitlab-ssl
@@ -29,6 +29,41 @@ map $http_upgrade $connection_upgrade_gitlab_ssl {
'' close;
}
+
+## NGINX 'combined' log format with filtered query strings
+log_format gitlab_ssl_access $remote_addr - $remote_user [$time_local] "$request_method $gitlab_ssl_filtered_request_uri $server_protocol" $status $body_bytes_sent "$gitlab_ssl_filtered_http_referer" "$http_user_agent";
+
+## Remove private_token from the request URI
+# In: /foo?private_token=unfiltered&authenticity_token=unfiltered&rss_token=unfiltered&...
+# Out: /foo?private_token=[FILTERED]&authenticity_token=unfiltered&rss_token=unfiltered&...
+map $request_uri $gitlab_ssl_temp_request_uri_1 {
+ default $request_uri;
+ ~(?i)^(?<start>.*)(?<temp>[\?&]private[\-_]token)=[^&]*(?<rest>.*)$ "$start$temp=[FILTERED]$rest";
+}
+
+## Remove authenticity_token from the request URI
+# In: /foo?private_token=[FILTERED]&authenticity_token=unfiltered&rss_token=unfiltered&...
+# Out: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&rss_token=unfiltered&...
+map $gitlab_ssl_temp_request_uri_1 $gitlab_ssl_temp_request_uri_2 {
+ default $gitlab_ssl_temp_request_uri_1;
+ ~(?i)^(?<start>.*)(?<temp>[\?&]authenticity[\-_]token)=[^&]*(?<rest>.*)$ "$start$temp=[FILTERED]$rest";
+}
+
+## Remove rss_token from the request URI
+# In: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&rss_token=unfiltered&...
+# Out: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&rss_token=[FILTERED]&...
+map $gitlab_ssl_temp_request_uri_2 $gitlab_ssl_filtered_request_uri {
+ default $gitlab_ssl_temp_request_uri_2;
+ ~(?i)^(?<start>.*)(?<temp>[\?&]rss[\-_]token)=[^&]*(?<rest>.*)$ "$start$temp=[FILTERED]$rest";
+}
+
+## A version of the referer without the query string
+map $http_referer $gitlab_ssl_filtered_http_referer {
+ default $http_referer;
+ ~^(?<temp>.*)\? $temp;
+}
+
+
## Redirects all HTTP traffic to the HTTPS host
server {
## Either remove "default_server" from the listen line below,
@@ -40,7 +75,7 @@ server {
server_name YOUR_SERVER_FQDN; ## Replace this with something like gitlab.example.com
server_tokens off; ## Don't show the nginx version number, a security best practice
return 301 https://$http_host$request_uri;
- access_log /var/log/nginx/gitlab_access.log;
+ access_log /var/log/nginx/gitlab_access.log gitlab_ssl_access;
error_log /var/log/nginx/gitlab_error.log;
}
@@ -93,7 +128,7 @@ server {
# set_real_ip_from YOUR_TRUSTED_ADDRESS; ## Replace this with something like 192.168.1.0/24
## Individual nginx logs for this GitLab vhost
- access_log /var/log/nginx/gitlab_access.log;
+ access_log /var/log/nginx/gitlab_access.log gitlab_ssl_access;
error_log /var/log/nginx/gitlab_error.log;
location / {
diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake
index dbb3b827b9a..1bd36bbe20a 100644
--- a/lib/tasks/gitlab/check.rake
+++ b/lib/tasks/gitlab/check.rake
@@ -41,8 +41,6 @@ namespace :gitlab do
end
namespace :gitlab_shell do
- include SystemCheck::Helpers
-
desc "GitLab | Check the configuration of GitLab Shell"
task check: :environment do
warn_user_is_not_gitlab
@@ -249,8 +247,6 @@ namespace :gitlab do
end
namespace :sidekiq do
- include SystemCheck::Helpers
-
desc "GitLab | Check the configuration of Sidekiq"
task check: :environment do
warn_user_is_not_gitlab
@@ -309,8 +305,6 @@ namespace :gitlab do
end
namespace :incoming_email do
- include SystemCheck::Helpers
-
desc "GitLab | Check the configuration of Reply by email"
task check: :environment do
warn_user_is_not_gitlab
@@ -444,8 +438,6 @@ namespace :gitlab do
end
namespace :ldap do
- include SystemCheck::Helpers
-
task :check, [:limit] => :environment do |_, args|
# Only show up to 100 results because LDAP directories can be very big.
# This setting only affects the `rake gitlab:check` script.
@@ -501,8 +493,6 @@ namespace :gitlab do
end
namespace :repo do
- include SystemCheck::Helpers
-
desc "GitLab | Check the integrity of the repositories managed by GitLab"
task check: :environment do
Gitlab.config.repositories.storages.each do |name, repository_storage|
@@ -517,8 +507,6 @@ namespace :gitlab do
end
namespace :user do
- include SystemCheck::Helpers
-
desc "GitLab | Check the integrity of a specific user's repositories"
task :check_repos, [:username] => :environment do |t, args|
username = args[:username] || prompt("Check repository integrity for fsername? ".color(:blue))
diff --git a/lib/tasks/gitlab/gitaly.rake b/lib/tasks/gitlab/gitaly.rake
index 680e76af471..e337c67a0f5 100644
--- a/lib/tasks/gitlab/gitaly.rake
+++ b/lib/tasks/gitlab/gitaly.rake
@@ -15,13 +15,17 @@ namespace :gitlab do
checkout_or_clone_version(version: version, repo: args.repo, target_dir: args.dir)
_, status = Gitlab::Popen.popen(%w[which gmake])
- command = status.zero? ? 'gmake' : 'make'
+ command = status.zero? ? ['gmake'] : ['make']
+
+ if Rails.env.test?
+ command += %W[BUNDLE_PATH=#{Bundler.bundle_path}]
+ end
Dir.chdir(args.dir) do
create_gitaly_configuration
# In CI we run scripts/gitaly-test-build instead of this command
unless ENV['CI'].present?
- Bundler.with_original_env { run_command!(%w[/usr/bin/env -u BUNDLE_GEMFILE] + [command]) }
+ Bundler.with_original_env { run_command!(%w[/usr/bin/env -u RUBYOPT -u BUNDLE_GEMFILE] + command) }
end
end
end
@@ -66,6 +70,7 @@ namespace :gitlab do
config = { socket_path: address.sub(%r{\Aunix:}, ''), storage: storages }
config[:auth] = { token: 'secret' } if Rails.env.test?
config[:'gitaly-ruby'] = { dir: File.join(Dir.pwd, 'ruby') } if gitaly_ruby
+ config[:'gitlab-shell'] = { dir: Gitlab.config.gitlab_shell.path }
TOML.dump(config)
end
diff --git a/lib/tasks/gitlab/helpers.rake b/lib/tasks/gitlab/helpers.rake
index dd2d5861481..b0a24790c4a 100644
--- a/lib/tasks/gitlab/helpers.rake
+++ b/lib/tasks/gitlab/helpers.rake
@@ -4,5 +4,5 @@ require 'tasks/gitlab/task_helpers'
StateMachines::Machine.ignore_method_conflicts = true if ENV['CRON']
namespace :gitlab do
- include Gitlab::TaskHelpers
+ extend SystemCheck::Helpers
end
diff --git a/lib/tasks/gitlab/task_helpers.rb b/lib/tasks/gitlab/task_helpers.rb
index 28b2d86eed2..d85b810ac66 100644
--- a/lib/tasks/gitlab/task_helpers.rb
+++ b/lib/tasks/gitlab/task_helpers.rb
@@ -5,6 +5,8 @@ module Gitlab
TaskAbortedByUserError = Class.new(StandardError)
module TaskHelpers
+ extend self
+
# Ask if the user wants to continue
#
# Returns "yes" the user chose to continue
diff --git a/lib/tasks/gitlab/update_templates.rake b/lib/tasks/gitlab/update_templates.rake
index 59c32bbe7a4..a7e30423c7a 100644
--- a/lib/tasks/gitlab/update_templates.rake
+++ b/lib/tasks/gitlab/update_templates.rake
@@ -4,6 +4,55 @@ namespace :gitlab do
TEMPLATE_DATA.each { |template| update(template) }
end
+ desc "GitLab | Update project templates"
+ task :update_project_templates do
+ if Rails.env.production?
+ puts "This rake task is not meant fo production instances".red
+ exit(1)
+ end
+ admin = User.find_by(admin: true)
+
+ unless admin
+ puts "No admin user could be found".red
+ exit(1)
+ end
+
+ Gitlab::ProjectTemplate.all.each do |template|
+ params = {
+ import_url: template.clone_url,
+ namespace_id: admin.namespace.id,
+ path: template.title,
+ skip_wiki: true
+ }
+
+ puts "Creating project for #{template.name}"
+ project = Projects::CreateService.new(admin, params).execute
+
+ loop do
+ if project.finished?
+ puts "Import finished for #{template.name}"
+ break
+ end
+
+ if project.failed?
+ puts "Failed to import from #{project_params[:import_url]}".red
+ exit(1)
+ end
+
+ puts "Waiting for the import to finish"
+
+ sleep(5)
+ project.reload
+ end
+
+ Projects::ImportExport::ExportService.new(project, admin).execute
+ FileUtils.cp(project.export_project_path, template.archive_path)
+ Projects::DestroyService.new(admin, project).execute
+ puts "Exported #{template.name}".green
+ end
+ puts "Done".green
+ end
+
def update(template)
sub_dir = template.repo_url.match(/([A-Za-z-]+)\.git\z/)[1]
dir = File.join(vendor_directory, sub_dir)
diff --git a/lib/tasks/haml-lint.rake b/lib/tasks/haml-lint.rake
index 609dfaa48e3..ad2d034b0b4 100644
--- a/lib/tasks/haml-lint.rake
+++ b/lib/tasks/haml-lint.rake
@@ -1,5 +1,6 @@
unless Rails.env.production?
require 'haml_lint/rake_task'
+ require 'haml_lint/inline_javascript'
HamlLint::RakeTask.new
end
diff --git a/lib/tasks/import.rake b/lib/tasks/import.rake
index 50b8e331469..96b8f59242c 100644
--- a/lib/tasks/import.rake
+++ b/lib/tasks/import.rake
@@ -7,7 +7,7 @@ class GithubImport
end
def initialize(token, gitlab_username, project_path, extras)
- @options = { url: 'https://api.github.com', token: token, verbose: true }
+ @options = { token: token, verbose: true }
@project_path = project_path
@current_user = User.find_by_username(gitlab_username)
@github_repo = extras.empty? ? nil : extras.first
@@ -62,6 +62,7 @@ class GithubImport
visibility_level: visibility_level,
import_type: 'github',
import_source: @repo['full_name'],
+ import_url: @repo['clone_url'].sub('://', "://#{@options[:token]}@"),
skip_wiki: @repo['has_wiki']
).execute
end
diff --git a/locale/bg/gitlab.po b/locale/bg/gitlab.po
index 1774c911d71..5c531f0cd7d 100644
--- a/locale/bg/gitlab.po
+++ b/locale/bg/gitlab.po
@@ -4,11 +4,11 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-07-05 08:50-0500\n"
+"POT-Creation-Date: 2017-07-13 12:07-0500\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"PO-Revision-Date: 2017-07-13 08:13-0400\n"
+"PO-Revision-Date: 2017-08-03 04:43-0400\n"
"Last-Translator: Lyubomir Vasilev <lyubomirv@abv.bg>\n"
"Language-Team: Bulgarian (https://translate.zanata.org/project/view/GitLab)\n"
"Language: bg\n"
@@ -1151,6 +1151,9 @@ msgstr "ЧаÑтен"
msgid "VisibilityLevel|Public"
msgstr "Публичен"
+msgid "VisibilityLevel|Unknown"
+msgstr "ÐеизвеÑтно"
+
msgid "Want to see the data? Please ask an administrator for access."
msgstr "ИÑкате ли да видите данните? Помолете админиÑтратор за доÑтъп."
@@ -1160,22 +1163,11 @@ msgstr "ÐÑма доÑтатъчно данни за този етап."
msgid "Withdraw Access Request"
msgstr "ОттеглÑне на заÑвката за доÑтъп"
-msgid ""
-"You are going to remove %{group_name}.\n"
-"Removed groups CANNOT be restored!\n"
-"Are you ABSOLUTELY sure?"
-msgstr ""
-"Ðа път Ñте да премахнете „%{group_name}“.\n"
-"Ðко Ñ Ð¿Ñ€ÐµÐ¼Ð°Ñ…Ð½ÐµÑ‚Ðµ, групата ÐЕ може да бъде възÑтановена!\n"
-"ÐÐИСТИÐРли иÑкате това?"
+msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?"
+msgstr "Ðа път Ñте да премахнете „%{group_name}“. Ðко Ñ Ð¿Ñ€ÐµÐ¼Ð°Ñ…Ð½ÐµÑ‚Ðµ, групата ÐЕ може да бъде възÑтановена! ÐÐИСТИÐРли иÑкате това?"
-msgid ""
-"You are going to remove %{project_name_with_namespace}.\n"
-"Removed project CANNOT be restored!\n"
-"Are you ABSOLUTELY sure?"
-msgstr ""
-"Ðа път Ñте да премахнете „%{project_name_with_namespace}“.\n"
-"Ðко го премахнете, той ÐЕ може да бъде възÑтановен!\n"
+msgid "You are going to remove %{project_name_with_namespace}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?"
+msgstr "Ðа път Ñте да премахнете „%{project_name_with_namespace}“. Ðко го премахнете, той ÐЕ може да бъде възÑтановен!"
"ÐÐИСТИÐРли иÑкате това?"
msgid ""
diff --git a/locale/en/gitlab.po b/locale/en/gitlab.po
index 46bf4e33997..0ac591d4927 100644
--- a/locale/en/gitlab.po
+++ b/locale/en/gitlab.po
@@ -17,17 +17,34 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"\n"
+msgid "%d commit"
+msgid_plural "%d commits"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "%s additional commit has been omitted to prevent performance issues."
msgid_plural "%s additional commits have been omitted to prevent performance issues."
msgstr[0] ""
msgstr[1] ""
-msgid "%d commit"
-msgid_plural "%d commits"
+msgid "%{commit_author_link} committed %{commit_timeago}"
+msgstr ""
+
+msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will allow access on the next attempt."
+msgstr ""
+
+msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will block access for %{number_of_seconds} seconds."
+msgstr ""
+
+msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved."
+msgstr ""
+
+msgid "%{storage_name}: failed storage access attempt on host:"
+msgid_plural "%{storage_name}: %{failed_attempts} failed storage access attempts:"
msgstr[0] ""
msgstr[1] ""
-msgid "%{commit_author_link} committed %{commit_timeago}"
+msgid "(checkout the %{link} for information on how to install it)."
msgstr ""
msgid "1 pipeline"
@@ -41,6 +58,9 @@ msgstr ""
msgid "About auto deploy"
msgstr ""
+msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again."
+msgstr ""
+
msgid "Active"
msgstr ""
@@ -68,6 +88,18 @@ msgstr ""
msgid "Are you sure you want to delete this pipeline schedule?"
msgstr ""
+msgid "Are you sure you want to discard your changes?"
+msgstr ""
+
+msgid "Are you sure you want to reset registration token?"
+msgstr ""
+
+msgid "Are you sure you want to reset the health check token?"
+msgstr ""
+
+msgid "Are you sure?"
+msgstr ""
+
msgid "Attach a file by drag &amp; drop or %{upload_link}"
msgstr ""
@@ -109,6 +141,9 @@ msgstr ""
msgid "Cancel"
msgstr ""
+msgid "Cancel edit"
+msgstr ""
+
msgid "ChangeTypeActionLabel|Pick into branch"
msgstr ""
@@ -234,6 +269,9 @@ msgstr ""
msgid "Create New Directory"
msgstr ""
+msgid "Create a new branch"
+msgstr ""
+
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
@@ -311,9 +349,15 @@ msgstr[1] ""
msgid "Description"
msgstr ""
+msgid "Details"
+msgstr ""
+
msgid "Directory name"
msgstr ""
+msgid "Discard changes"
+msgstr ""
+
msgid "Don't show again"
msgstr ""
@@ -397,12 +441,36 @@ msgstr ""
msgid "From merge request merge until deploy to production"
msgstr ""
+msgid "Git storage health information has been reset"
+msgstr ""
+
+msgid "GitLab Runner section"
+msgstr ""
+
msgid "Go to your fork"
msgstr ""
msgid "GoToYourFork|Fork"
msgstr ""
+msgid "Health Check"
+msgstr ""
+
+msgid "Health information can be retrieved from the following endpoints. More information is available"
+msgstr ""
+
+msgid "HealthCheck|Access token is"
+msgstr ""
+
+msgid "HealthCheck|Healthy"
+msgstr ""
+
+msgid "HealthCheck|No Health Problems Detected"
+msgstr ""
+
+msgid "HealthCheck|Unhealthy"
+msgstr ""
+
msgid "Home"
msgstr ""
@@ -412,6 +480,9 @@ msgstr ""
msgid "Import repository"
msgstr ""
+msgid "Install a Runner compatible with GitLab CI"
+msgstr ""
+
msgid "Interval Pattern"
msgstr ""
@@ -470,6 +541,9 @@ msgstr ""
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr ""
+msgid "More information is available|here"
+msgstr ""
+
msgid "New Issue"
msgid_plural "New Issues"
msgstr[0] ""
@@ -682,6 +756,9 @@ msgstr ""
msgid "Project access must be granted explicitly to each user."
msgstr ""
+msgid "Project details"
+msgstr ""
+
msgid "Project export could not be deleted."
msgstr ""
@@ -754,9 +831,21 @@ msgstr ""
msgid "Remove project"
msgstr ""
+msgid "Repository"
+msgstr ""
+
msgid "Request Access"
msgstr ""
+msgid "Reset git storage health information"
+msgstr ""
+
+msgid "Reset health check access token"
+msgstr ""
+
+msgid "Reset runners registration token"
+msgstr ""
+
msgid "Revert this commit"
msgstr ""
@@ -781,6 +870,9 @@ msgstr ""
msgid "Select a timezone"
msgstr ""
+msgid "Select existing branch"
+msgstr ""
+
msgid "Select target branch"
msgstr ""
@@ -807,12 +899,18 @@ msgstr[1] ""
msgid "Source code"
msgstr ""
+msgid "Specify the following URL during the Runner setup:"
+msgstr ""
+
msgid "StarProject|Star"
msgstr ""
msgid "Start a %{new_merge_request} with these changes"
msgstr ""
+msgid "Start the Runner!"
+msgstr ""
+
msgid "Switch branch/tag"
msgstr ""
@@ -875,6 +973,9 @@ msgstr ""
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr ""
+msgid "There are problems accessing Git storage: "
+msgstr ""
+
msgid "This means you can not push code until you create an empty repository or import existing one."
msgstr ""
@@ -1044,6 +1145,9 @@ msgstr ""
msgid "UploadLink|click to upload"
msgstr ""
+msgid "Use the following registration token during setup:"
+msgstr ""
+
msgid "Use your global notification setting"
msgstr ""
@@ -1059,6 +1163,9 @@ msgstr ""
msgid "VisibilityLevel|Public"
msgstr ""
+msgid "VisibilityLevel|Unknown"
+msgstr ""
+
msgid "Want to see the data? Please ask an administrator for access."
msgstr ""
@@ -1068,16 +1175,10 @@ msgstr ""
msgid "Withdraw Access Request"
msgstr ""
-msgid ""
-"You are going to remove %{group_name}.\n"
-"Removed groups CANNOT be restored!\n"
-"Are you ABSOLUTELY sure?"
+msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?"
msgstr ""
-msgid ""
-"You are going to remove %{project_name_with_namespace}.\n"
-"Removed project CANNOT be restored!\n"
-"Are you ABSOLUTELY sure?"
+msgid "You are going to remove %{project_name_with_namespace}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?"
msgstr ""
msgid "You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?"
diff --git a/locale/eo/gitlab.po b/locale/eo/gitlab.po
index 62dbc2621f4..94ae131186b 100644
--- a/locale/eo/gitlab.po
+++ b/locale/eo/gitlab.po
@@ -4,11 +4,11 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-07-05 08:50-0500\n"
+"POT-Creation-Date: 2017-07-13 12:07-0500\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"PO-Revision-Date: 2017-07-13 08:46-0400\n"
+"PO-Revision-Date: 2017-08-03 04:44-0400\n"
"Last-Translator: Lyubomir Vasilev <lyubomirv@abv.bg>\n"
"Language-Team: Esperanto (https://translate.zanata.org/project/view/GitLab)\n"
"Language: eo\n"
@@ -1152,6 +1152,9 @@ msgstr "Privata"
msgid "VisibilityLevel|Public"
msgstr "Publika"
+msgid "VisibilityLevel|Unknown"
+msgstr "Nekonata"
+
msgid "Want to see the data? Please ask an administrator for access."
msgstr ""
"Ĉu vi volas vidi la datenojn? Bonvolu peti atingeblon de administranto."
@@ -1162,30 +1165,14 @@ msgstr "Ne estas sufiĉe da datenoj por montri ĉi tiun etapon."
msgid "Withdraw Access Request"
msgstr "Nuligi la peton pri atingeblo"
-msgid ""
-"You are going to remove %{group_name}.\n"
-"Removed groups CANNOT be restored!\n"
-"Are you ABSOLUTELY sure?"
-msgstr ""
-"Vi forigos „%{group_name}“.\n"
-"Oni NE POVAS malfari la forigon de grupo!\n"
-"Ĉu vi estas ABSOLUTE certa?"
+msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?"
+msgstr "Vi forigos „%{group_name}“. Oni NE POVAS malfari la forigon de grupo! Ĉu vi estas ABSOLUTE certa?"
-msgid ""
-"You are going to remove %{project_name_with_namespace}.\n"
-"Removed project CANNOT be restored!\n"
-"Are you ABSOLUTELY sure?"
-msgstr ""
-"Vi forigos „%{project_name_with_namespace}“.\n"
-"Oni NE POVAS malfari la forigon de projekto!\n"
-"Ĉu vi estas ABSOLUTE certa?"
+msgid "You are going to remove %{project_name_with_namespace}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?"
+msgstr "Vi forigos „%{project_name_with_namespace}“. Oni NE POVAS malfari la forigon de projekto! Ĉu vi estas ABSOLUTE certa?"
-msgid ""
-"You are going to remove the fork relationship to source project "
-"%{forked_from_project}. Are you ABSOLUTELY sure?"
-msgstr ""
-"Vi forigos la rilaton de la disbranĉigo al la originala projekto, "
-"„%{forked_from_project}“. Ĉu vi estas ABSOLUTE certa?"
+msgid "You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?"
+msgstr "Vi forigos la rilaton de la disbranĉigo al la originala projekto, „%{forked_from_project}“. Ĉu vi estas ABSOLUTE certa?"
msgid ""
"You are going to transfer %{project_name_with_namespace} to another owner. "
diff --git a/locale/es/gitlab.po b/locale/es/gitlab.po
index 5c669d51a68..e43fd5fea15 100644
--- a/locale/es/gitlab.po
+++ b/locale/es/gitlab.po
@@ -1071,23 +1071,11 @@ msgstr "No hay suficientes datos para mostrar en esta etapa."
msgid "Withdraw Access Request"
msgstr "Retirar Solicitud de Acceso"
-msgid ""
-"You are going to remove %{group_name}.\n"
-"Removed groups CANNOT be restored!\n"
-"Are you ABSOLUTELY sure?"
-msgstr ""
-"Va a eliminar %{group_name}.\n"
-"¡El grupo eliminado NO puede ser restaurado!\n"
-"¿Estás TOTALMENTE seguro?"
+msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?"
+msgstr "Va a eliminar %{group_name}. ¡El grupo eliminado NO puede ser restaurado! ¿Estás TOTALMENTE seguro?"
-msgid ""
-"You are going to remove %{project_name_with_namespace}.\n"
-"Removed project CANNOT be restored!\n"
-"Are you ABSOLUTELY sure?"
-msgstr ""
-"Va a eliminar %{project_name_with_namespace}.\n"
-"¡El proyecto eliminado NO puede ser restaurado!\n"
-"¿Estás TOTALMENTE seguro?"
+msgid "You are going to remove %{project_name_with_namespace}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?"
+msgstr "Va a eliminar %{project_name_with_namespace}. ¡El proyecto eliminado NO puede ser restaurado! ¿Estás TOTALMENTE seguro?"
msgid "You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?"
msgstr "Vas a eliminar el enlace de la bifurcación con el proyecto original %{forked_from_project}. ¿Estás TOTALMENTE seguro?"
diff --git a/locale/fr/gitlab.po b/locale/fr/gitlab.po
index 959654c7849..83f31f7a3b2 100644
--- a/locale/fr/gitlab.po
+++ b/locale/fr/gitlab.po
@@ -5,13 +5,13 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-07-05 08:50-0500\n"
+"POT-Creation-Date: 2017-07-13 12:07-0500\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"PO-Revision-Date: 2017-07-19 09:45-0400\n"
-"Last-Translator: Dremor <egeorget@opmbx.org>\n"
"Language-Team: French (https://translate.zanata.org/project/view/GitLab)\n"
+"PO-Revision-Date: 2017-08-03 03:35-0400\n"
+"Last-Translator: Rémy Coutable <remy@rymai.me>\n"
"Language: fr\n"
"X-Generator: Zanata 3.9.6\n"
"Plural-Forms: nplurals=2; plural=(n > 1)\n"
@@ -417,7 +417,7 @@ msgstr[0] "Fourche"
msgstr[1] "Fourches"
msgid "ForkedFromProjectPath|Forked from"
-msgstr "Fouché depuis"
+msgstr "Fourché depuis"
msgid "From issue creation until deploy to production"
msgstr "Depuis la création de l'incident jusqu'au déploiement en production"
@@ -1161,6 +1161,9 @@ msgstr "Privé"
msgid "VisibilityLevel|Public"
msgstr "Public"
+msgid "VisibilityLevel|Unknown"
+msgstr "Inconnu"
+
msgid "Want to see the data? Please ask an administrator for access."
msgstr ""
"Vous voulez voir les données ? Merci de contacter un administrateur pour en "
@@ -1172,22 +1175,11 @@ msgstr "Nous n'avons pas suffisamment de données pour afficher cette étape."
msgid "Withdraw Access Request"
msgstr "Retirer la demande d'accès"
-msgid ""
-"You are going to remove %{group_name}.\n"
-"Removed groups CANNOT be restored!\n"
-"Are you ABSOLUTELY sure?"
-msgstr ""
-"Vous êtes sur le point de supprimer %{group_name}. Les groupes supprimés NE "
-"PEUVENT PAS être restaurés ! Êtes vous ABSOLUMENT sûr ?"
+msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?"
+msgstr "Vous êtes sur le point de supprimer %{group_name}. Les groupes supprimés NE PEUVENT PAS être restaurés ! Êtes vous ABSOLUMENT sûr ?"
-msgid ""
-"You are going to remove %{project_name_with_namespace}.\n"
-"Removed project CANNOT be restored!\n"
-"Are you ABSOLUTELY sure?"
-msgstr ""
-"Vous êtes sur le point de supprimer %{project_name_with_namespace}.\n"
-"Les projets supprimés NE PEUVENT PAS être restaurés !\n"
-"Êtes vous ABSOLUMENT sûr ? "
+msgid "You are going to remove %{project_name_with_namespace}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?"
+msgstr "Vous êtes sur le point de supprimer %{project_name_with_namespace}. Les projets supprimés NE PEUVENT PAS être restaurés ! Êtes vous ABSOLUMENT sûr ?"
msgid ""
"You are going to remove the fork relationship to source project "
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index babef3ed0af..e60504e1395 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -1072,16 +1072,10 @@ msgstr ""
msgid "Withdraw Access Request"
msgstr ""
-msgid ""
-"You are going to remove %{group_name}.\n"
-"Removed groups CANNOT be restored!\n"
-"Are you ABSOLUTELY sure?"
+msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?"
msgstr ""
-msgid ""
-"You are going to remove %{project_name_with_namespace}.\n"
-"Removed project CANNOT be restored!\n"
-"Are you ABSOLUTELY sure?"
+msgid "You are going to remove %{project_name_with_namespace}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?"
msgstr ""
msgid "You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?"
diff --git a/locale/it/gitlab.po b/locale/it/gitlab.po
index d4fac6ab34e..e719a3988e3 100644
--- a/locale/it/gitlab.po
+++ b/locale/it/gitlab.po
@@ -4,13 +4,13 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-06-28 13:32+0200\n"
+"POT-Creation-Date: 2017-07-13 12:07-0500\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"PO-Revision-Date: 2017-07-12 05:45-0400\n"
-"Last-Translator: Paolo Falomo <info@paolofalomo.it>\n"
"Language-Team: Italian (https://translate.zanata.org/project/view/GitLab)\n"
+"PO-Revision-Date: 2017-08-07 10:15-0400\n"
+"Last-Translator: Paolo Falomo <info@paolofalomo.it>\n"
"Language: it\n"
"X-Generator: Zanata 3.9.6\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
@@ -647,6 +647,12 @@ msgstr "Tutto"
msgid "PipelineSchedules|Inactive"
msgstr "Inattiva"
+msgid "PipelineSchedules|Input variable key"
+msgstr "Chiave della variabile"
+
+msgid "PipelineSchedules|Input variable value"
+msgstr "Valore della variabile"
+
msgid "PipelineSchedules|Next Run"
msgstr "Prossima esecuzione"
@@ -656,12 +662,18 @@ msgstr "Nessuna"
msgid "PipelineSchedules|Provide a short description for this pipeline"
msgstr "Fornisci una breve descrizione per questa pipeline"
+msgid "PipelineSchedules|Remove variable row"
+msgstr "Rimuovi riga della variabile"
+
msgid "PipelineSchedules|Take ownership"
msgstr "Prendi possesso"
msgid "PipelineSchedules|Target"
msgstr "Target"
+msgid "PipelineSchedules|Variables"
+msgstr "Variabili"
+
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr "Personalizzato"
@@ -1144,6 +1156,9 @@ msgstr "Privato"
msgid "VisibilityLevel|Public"
msgstr "Pubblico"
+msgid "VisibilityLevel|Unknown"
+msgstr "Sconosciuto"
+
msgid "Want to see the data? Please ask an administrator for access."
msgstr ""
"Vuoi visualizzare i dati? Richiedi l'accesso ad un amministratore, grazie."
@@ -1154,14 +1169,11 @@ msgstr "Non ci sono sufficienti dati da mostrare su questo stadio"
msgid "Withdraw Access Request"
msgstr "Ritira richiesta d'accesso"
-msgid ""
-"You are going to remove %{project_name_with_namespace}.\n"
-"Removed project CANNOT be restored!\n"
-"Are you ABSOLUTELY sure?"
-msgstr ""
-"Stai per rimuovere %{project_name_with_namespace}.\n"
-"I progetti rimossi NON POSSONO essere ripristinati\n"
-"Sei assolutamente sicuro?"
+msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?"
+msgstr "Stai per rimuovere il gruppo %{group_name}. I gruppi rimossi NON POSSONO esser ripristinati! Sei ASSOLUTAMENTE sicuro?"
+
+msgid "You are going to remove %{project_name_with_namespace}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?"
+msgstr "Stai per rimuovere %{project_name_with_namespace}. I progetti rimossi NON POSSONO essere ripristinati! Sei assolutamente sicuro?"
msgid ""
"You are going to remove the fork relationship to source project "
diff --git a/locale/ja/gitlab.po b/locale/ja/gitlab.po
index 801674ce964..bfa97aa21d7 100644
--- a/locale/ja/gitlab.po
+++ b/locale/ja/gitlab.po
@@ -8,13 +8,13 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-07-05 08:50-0500\n"
+"POT-Creation-Date: 2017-07-13 12:07-0500\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"Last-Translator: YANO TETTER <tetuyano+zana@gmail.com>\n"
-"PO-Revision-Date: 2017-07-19 09:45-0400\n"
-"Last-Translator: YANO Tethurou <tetuyano+zana@gmail.com>\n"
+"PO-Revision-Date: 2017-08-06 11:23-0400\n"
+"Last-Translator: Taisuke Inoue <taisuke.inoue.jp@gmail.com>\n"
+"Language-Team: Japanese "Language-Team: Russian (https://translate.zanata.org/project/view/GitLab)\n"
"Language: ja\n"
"X-Generator: Zanata 3.9.6\n"
"Plural-Forms: nplurals=1; plural=0\n"
@@ -1107,6 +1107,9 @@ msgstr "プライベート"
msgid "VisibilityLevel|Public"
msgstr "パブリック"
+msgid "VisibilityLevel|Unknown"
+msgstr "ä¸æ˜Ž"
+
msgid "Want to see the data? Please ask an administrator for access."
msgstr "ã“ã®ãƒ‡ãƒ¼ã‚¿ã‚’å‚ç…§ã—ãŸã„ã§ã™ã‹ï¼Ÿã‚¢ã‚¯ã‚»ã‚¹ã™ã‚‹ã«ã¯ç®¡ç†è€…ã«å•ã„åˆã‚ã›ã¦ãã ã•ã„。"
@@ -1116,22 +1119,11 @@ msgstr "データä¸è¶³ã®ãŸã‚ã€ã“ã®ã‚¹ãƒ†ãƒ¼ã‚¸ã®è¡¨ç¤ºã¯ã§ãã¾ã›ã‚“
msgid "Withdraw Access Request"
msgstr "アクセスリクエストをå–り消ã™"
-msgid ""
-"You are going to remove %{group_name}.\n"
-"Removed groups CANNOT be restored!\n"
-"Are you ABSOLUTELY sure?"
-msgstr "%{group_name} グループを削除ã—よã†ã¨ã—ã¦ã„ã¾ã™ã€‚\n"
-"削除ã•ã‚ŒãŸã‚°ãƒ«ãƒ¼ãƒ—ã¯çµ¶å¯¾ã«å…ƒã«æˆ»ã›ã¾ã›ã‚“ï¼\n"
-"本当ã«ã‚ˆã‚ã—ã„ã§ã™ã‹ï¼Ÿ"
+msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?"
+msgstr "%{group_name} グループを削除ã—よã†ã¨ã—ã¦ã„ã¾ã™ã€‚ 削除ã•ã‚ŒãŸã‚°ãƒ«ãƒ¼ãƒ—ã¯çµ¶å¯¾ã«å…ƒã«æˆ»ã›ã¾ã›ã‚“ï¼æœ¬å½“ã«ã‚ˆã‚ã—ã„ã§ã™ã‹ï¼Ÿ"
-msgid ""
-"You are going to remove %{project_name_with_namespace}.\n"
-"Removed project CANNOT be restored!\n"
-"Are you ABSOLUTELY sure?"
-msgstr ""
-"%{project_name_with_namespace} プロジェクトを削除ã—よã†ã¨ã—ã¦ã„ã¾ã™ã€‚\n"
-"削除ã•ã‚ŒãŸãƒ—ロジェクトã¯çµ¶å¯¾ã«å…ƒã«ã¯æˆ»ã›ã¾ã›ã‚“ï¼\n"
-"本当ã«ã‚ˆã‚ã—ã„ã§ã™ã‹ï¼Ÿ"
+msgid "You are going to remove %{project_name_with_namespace}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?"
+msgstr "%{project_name_with_namespace} プロジェクトを削除ã—よã†ã¨ã—ã¦ã„ã¾ã™ã€‚削除ã•ã‚ŒãŸãƒ—ロジェクトã¯çµ¶å¯¾ã«å…ƒã«ã¯æˆ»ã›ã¾ã›ã‚“ï¼æœ¬å½“ã«ã‚ˆã‚ã—ã„ã§ã™ã‹ï¼Ÿ"
msgid ""
"You are going to remove the fork relationship to source project "
diff --git a/locale/ko/gitlab.po b/locale/ko/gitlab.po
new file mode 100644
index 00000000000..340c8955d20
--- /dev/null
+++ b/locale/ko/gitlab.po
@@ -0,0 +1,1200 @@
+# Korean translations for gitlab package.
+# Copyright (C) 2017 THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the gitlab package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, 2017.
+# Huang Tao <htve@outlook.com>, 2017. #zanata
+msgid ""
+msgstr ""
+"Project-Id-Version: gitlab 1.0.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2017-07-13 12:07-0500\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"PO-Revision-Date: 2017-08-08 08:32-0400\n"
+"Last-Translator: chang-ho,cha <changho.cha@gmail.com>\n"
+"Language-Team: Korean (https://translate.zanata.org/project/view/GitLab)\n"
+"Language: ko\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: Zanata 3.9.6\n"
+
+msgid "%d commit"
+msgid_plural "%d commits"
+msgstr[0] "%d 커밋"
+
+msgid "%s additional commit has been omitted to prevent performance issues."
+msgid_plural ""
+"%s additional commits have been omitted to prevent performance issues."
+msgstr[0] "%s 추가 ì»¤ë°‹ì€ ì„±ëŠ¥ ì´ìŠˆë¥¼ 방지하기 위해 ìƒëžµë˜ì—ˆìŠµë‹ˆë‹¤."
+
+msgid "%{commit_author_link} committed %{commit_timeago}"
+msgstr "%{commit_timeago} ì— %{commit_author_link} ë‹˜ì´ ì»¤ë°‹í•˜ì˜€ìŠµë‹ˆë‹¤. "
+
+msgid "1 pipeline"
+msgid_plural "%d pipelines"
+msgstr[0] "1 파ì´í”„ë¼ì¸"
+msgstr[1] "%d 파ì´í”„ë¼ì¸"
+
+msgid "A collection of graphs regarding Continuous Integration"
+msgstr "지ì†ì ì¸ í†µí•©ì— ê´€í•œ 그래프 모ìŒ"
+
+msgid "About auto deploy"
+msgstr "ìžë™ ë°°í¬ ì •ë³´"
+
+msgid "Active"
+msgstr "활성"
+
+msgid "Activity"
+msgstr "활ë™"
+
+msgid "Add Changelog"
+msgstr "변경 로그 추가"
+
+msgid "Add Contribution guide"
+msgstr "기여 ê°€ì´ë“œ 추가"
+
+msgid "Add License"
+msgstr "ë¼ì´ì„ ìŠ¤ 추가"
+
+msgid "Add an SSH key to your profile to pull or push via SSH."
+msgstr "í”„ë¡œí•„ì— SSH 키를 추가하여 SSH를 통해 Pull 하거나 Push합니다."
+
+msgid "Add new directory"
+msgstr "새 디렉토리 추가"
+
+msgid "Archived project! Repository is read-only"
+msgstr "프로ì íŠ¸ê°€ ë³´ê´€ë˜ì—ˆìŠµë‹ˆë‹¤! 저장소는 ì½ê¸°ë§Œ 가능합니다."
+
+msgid "Are you sure you want to delete this pipeline schedule?"
+msgstr "ì´ íŒŒì´í”„ë¼ì¸ ìŠ¤ì¼€ì¥´ì„ ì‚­ì œ 하시겠습니까?"
+
+msgid "Attach a file by drag &amp; drop or %{upload_link}"
+msgstr "드래그 &amp; 드롭 ë˜ëŠ” %{upload_link}"
+
+msgid "Branch"
+msgid_plural "Branches"
+msgstr[0] "브랜치"
+
+msgid ""
+"Branch <strong>%{branch_name}</strong> was created. To set up auto deploy, "
+"choose a GitLab CI Yaml template and commit your changes. "
+"%{link_to_autodeploy_doc}"
+msgstr ""
+"<strong>%{branch_name}</strong> 브랜치가 ìƒì„±ë˜ì—ˆìŠµë‹ˆë‹¤. ìžë™ ë°°í¬ë¥¼ 설정하려면 GitLab CI Yaml "
+"í…œí”Œë¦¿ì„ ì„ íƒí•˜ê³  변경 ì‚¬í•­ì„ ì ìš©í•˜ì‹­ì‹œì˜¤. %{link_to_autodeploy_doc}"
+
+msgid "BranchSwitcherPlaceholder|Search branches"
+msgstr "브랜치 검색"
+
+msgid "BranchSwitcherTitle|Switch branch"
+msgstr "브랜치 변경"
+
+msgid "Branches"
+msgstr "브랜치"
+
+msgid "Browse Directory"
+msgstr "디렉토리 찾아보기"
+
+msgid "Browse File"
+msgstr "íŒŒì¼ ì°¾ì•„ë³´ê¸°"
+
+msgid "Browse Files"
+msgstr "íŒŒì¼ ì°¾ì•„ë³´ê¸°"
+
+msgid "Browse files"
+msgstr "íŒŒì¼ ì°¾ì•„ë³´ê¸°"
+
+msgid "ByAuthor|by"
+msgstr "작성ìž"
+
+msgid "CI configuration"
+msgstr "CI 설정"
+
+msgid "Cancel"
+msgstr "취소"
+
+msgid "ChangeTypeActionLabel|Pick into branch"
+msgstr "브랜치ì—ì„œ Pick"
+
+msgid "ChangeTypeActionLabel|Revert in branch"
+msgstr "브랜치ì—ì„œ Revert"
+
+msgid "ChangeTypeAction|Cherry-pick"
+msgstr "Cherry-pick"
+
+msgid "ChangeTypeAction|Revert"
+msgstr "Revert"
+
+msgid "Changelog"
+msgstr "변경사항"
+
+msgid "Charts"
+msgstr "차트"
+
+msgid "Cherry-pick this commit"
+msgstr "ì´ ì»¤ë°‹ì„ Cherry-pick"
+
+msgid "Cherry-pick this merge request"
+msgstr "ì´ ë¨¸ì§€ 리퀘스트를 Cherry-pick"
+
+msgid "CiStatusLabel|canceled"
+msgstr "취소ë¨"
+
+msgid "CiStatusLabel|created"
+msgstr "ìƒì„±ë˜ì—ˆìŠµë‹ˆë‹¤."
+
+msgid "CiStatusLabel|failed"
+msgstr "실패"
+
+msgid "CiStatusLabel|manual action"
+msgstr "ìˆ˜ë™ ì‹¤í–‰"
+
+msgid "CiStatusLabel|passed"
+msgstr "성공"
+
+msgid "CiStatusLabel|passed with warnings"
+msgstr "경고와 함께 성공"
+
+msgid "CiStatusLabel|pending"
+msgstr "대기"
+
+msgid "CiStatusLabel|skipped"
+msgstr "건너 뜀"
+
+msgid "CiStatusLabel|waiting for manual action"
+msgstr "ìˆ˜ë™ ì‹¤í–‰ 대기 중"
+
+msgid "CiStatusText|blocked"
+msgstr "차단ë¨"
+
+msgid "CiStatusText|canceled"
+msgstr "취소ë¨"
+
+msgid "CiStatusText|created"
+msgstr "ìƒì„±ë¨"
+
+msgid "CiStatusText|failed"
+msgstr "실패"
+
+msgid "CiStatusText|manual"
+msgstr "매뉴얼"
+
+msgid "CiStatusText|passed"
+msgstr "성공"
+
+msgid "CiStatusText|pending"
+msgstr "보류 중"
+
+msgid "CiStatusText|skipped"
+msgstr "건너 뜀"
+
+msgid "CiStatus|running"
+msgstr "실행 중"
+
+msgid "Commit"
+msgid_plural "Commits"
+msgstr[0] "커밋"
+
+msgid "Commit duration in minutes for last 30 commits"
+msgstr "최근 30 ê±´ì˜ ì»¤ë°‹ 소요시간 (분)"
+
+msgid "Commit message"
+msgstr "커밋 메시지"
+
+msgid "CommitBoxTitle|Commit"
+msgstr "커밋"
+
+msgid "CommitMessage|Add %{file_name}"
+msgstr "%{file_name} 추가"
+
+msgid "Commits"
+msgstr "커밋"
+
+msgid "Commits feed"
+msgstr "커밋 피드"
+
+msgid "Commits|History"
+msgstr "ì´ë ¥"
+
+msgid "Committed by"
+msgstr "커밋한 사용ìž"
+
+msgid "Compare"
+msgstr "비êµ"
+
+msgid "Contribution guide"
+msgstr "ê¸°ì—¬ì— ëŒ€í•œ 안내"
+
+msgid "Contributors"
+msgstr "기여해 주신 분들"
+
+msgid "Copy URL to clipboard"
+msgstr "URLì„ í´ë¦½ë³´ë“œì— 복사"
+
+msgid "Copy commit SHA to clipboard"
+msgstr "ì»¤ë°‹ì˜ SHA를 í´ë¦½ë³´ë“œë¡œ 복사합니다"
+
+msgid "Create New Directory"
+msgstr "새 디렉토리 만들기"
+
+msgid ""
+"Create a personal access token on your account to pull or push via "
+"%{protocol}."
+msgstr "%{protocol}ì„ (를) 통해 Pull 하거나 Push í•  ê°œì¸ ì•¡ì„¸ìŠ¤ 토í°ì„ 만드십시오."
+
+msgid "Create directory"
+msgstr "디렉토리 만들기"
+
+msgid "Create empty bare repository"
+msgstr "빈 bare 저장소 만들기"
+
+msgid "Create merge request"
+msgstr "머지 리퀘스트 만들기"
+
+msgid "Create new..."
+msgstr "새로 만들기 ..."
+
+msgid "CreateNewFork|Fork"
+msgstr "í¬í¬"
+
+msgid "CreateTag|Tag"
+msgstr "태그"
+
+msgid "CreateTokenToCloneLink|create a personal access token"
+msgstr "ê°œì¸ ì•¡ì„¸ìŠ¤ í† í° ë§Œë“¤ê¸°"
+
+msgid "Cron Timezone"
+msgstr "Cron 시간대"
+
+msgid "Cron syntax"
+msgstr "í¬ë¡  구문"
+
+msgid "Custom notification events"
+msgstr "ì‚¬ìš©ìž ì •ì˜ ì•Œë¦¼ ì´ë²¤íŠ¸"
+
+msgid ""
+"Custom notification levels are the same as participating levels. With custom "
+"notification levels you will also receive notifications for select events. "
+"To find out more, check out %{notification_link}."
+msgstr ""
+"ì‚¬ìš©ìž ì •ì˜ ì•Œë¦¼ ìˆ˜ì¤€ì€ ì°¸ì—¬ 수준과 ë™ì¼í•©ë‹ˆë‹¤. 맞춤 알림 ìˆ˜ì¤€ì„ ì‚¬ìš©í•˜ë©´ ì¼ë¶€ ì´ë²¤íŠ¸ì— 대한 ì•Œë¦¼ë„ ë°›ê²Œë©ë‹ˆë‹¤. ìžì„¸í•œ ë‚´ìš©ì€ "
+"%{notification_link}ì„ í™•ì¸í•˜ì‹­ì‹œì˜¤."
+
+msgid "Cycle Analytics"
+msgstr "Cycle Analytics"
+
+msgid ""
+"Cycle Analytics gives an overview of how much time it takes to go from idea "
+"to production in your project."
+msgstr "Cycle Analytics는 프로ì íŠ¸ì—ì„œ ì•„ì´ë””어를 프로ë•ì…˜ìœ¼ë¡œ 옮기는 ë° ê±¸ë¦¬ëŠ” ì‹œê°„ì„ ëŒ€ëžµì ìœ¼ë¡œ ë³´ì—¬ì¤ë‹ˆë‹¤."
+
+msgid "CycleAnalyticsStage|Code"
+msgstr "코드"
+
+msgid "CycleAnalyticsStage|Issue"
+msgstr "ì´ìŠˆ"
+
+msgid "CycleAnalyticsStage|Plan"
+msgstr "계íš"
+
+msgid "CycleAnalyticsStage|Production"
+msgstr "프로ë•ì…˜"
+
+msgid "CycleAnalyticsStage|Review"
+msgstr "리뷰"
+
+msgid "CycleAnalyticsStage|Staging"
+msgstr "스테ì´ì§•"
+
+msgid "CycleAnalyticsStage|Test"
+msgstr "테스트"
+
+msgid "Define a custom pattern with cron syntax"
+msgstr "cron êµ¬ë¬¸ì„ ì‚¬ìš©í•˜ì—¬ ì‚¬ìš©ìž ì •ì˜ íŒ¨í„´ ì •ì˜"
+
+msgid "Delete"
+msgstr "삭제 "
+
+msgid "Deploy"
+msgid_plural "Deploys"
+msgstr[0] "ë°°í¬"
+
+msgid "Description"
+msgstr "설명"
+
+msgid "Directory name"
+msgstr "디렉토리 ì´ë¦„"
+
+msgid "Don't show again"
+msgstr "다시 표시하지 ì•ŠìŒ"
+
+msgid "Download"
+msgstr "다운로드"
+
+msgid "Download tar"
+msgstr "tar 다운로드"
+
+msgid "Download tar.bz2"
+msgstr "tar.bz2 다운로드"
+
+msgid "Download tar.gz"
+msgstr "tar.gz 다운로드"
+
+msgid "Download zip"
+msgstr "zip 다운로드"
+
+msgid "DownloadArtifacts|Download"
+msgstr "다운로드"
+
+msgid "DownloadCommit|Email Patches"
+msgstr "ì´ë©”ì¼ íŒ¨ì¹˜"
+
+msgid "DownloadCommit|Plain Diff"
+msgstr "Plain Diff"
+
+msgid "DownloadSource|Download"
+msgstr "다운로드"
+
+msgid "Edit"
+msgstr "편집"
+
+msgid "Edit Pipeline Schedule %{id}"
+msgstr "파ì´í”„ë¼ì¸ 스케줄 편집 %{id}"
+
+msgid "Every day (at 4:00am)"
+msgstr "ë§¤ì¼ (오전 4ì‹œì—)"
+
+msgid "Every month (on the 1st at 4:00am)"
+msgstr "매월 (1ì¼ ì˜¤ì „ 4ì‹œ)"
+
+msgid "Every week (Sundays at 4:00am)"
+msgstr "매주 (ì¼ìš”ì¼ ì˜¤ì „ 4ì‹œì—)"
+
+msgid "Failed to change the owner"
+msgstr "소유ìžë¥¼ 변경하지 못했습니다"
+
+msgid "Failed to remove the pipeline schedule"
+msgstr "파ì´í”„ë¼ì¸ ìŠ¤ì¼€ì¤„ì„ ì œê±°í•˜ì§€ 못했습니다."
+
+msgid "Files"
+msgstr "파ì¼"
+
+msgid "Filter by commit message"
+msgstr "커밋 메시지로 필터"
+
+msgid "Find by path"
+msgstr "경로로 찾기"
+
+msgid "Find file"
+msgstr "íŒŒì¼ ì°¾ê¸°"
+
+msgid "FirstPushedBy|First"
+msgstr "처ìŒ"
+
+msgid "FirstPushedBy|pushed by"
+msgstr "푸시한 사용ìž"
+
+msgid "Fork"
+msgid_plural "Forks"
+msgstr[0] "í¬í¬"
+
+msgid "ForkedFromProjectPath|Forked from"
+msgstr "í¬í¬í•œ 사용ìž"
+
+msgid "From issue creation until deploy to production"
+msgstr "ì´ìŠˆ ìƒì„±ì—ì„œ 프로ë•ì…˜ ë°°í¬ê¹Œì§€"
+
+msgid "From merge request merge until deploy to production"
+msgstr "머지 리퀘스트 머지ì—ì„œ 프로ë•ì…˜ í™˜ê²½ì— ë°°í¬ê¹Œì§€"
+
+msgid "Go to your fork"
+msgstr "ë‹¹ì‹ ì˜ í¬í¬ë¡œ ì´ë™í•˜ì„¸ìš”"
+
+msgid "GoToYourFork|Fork"
+msgstr "í¬í¬"
+
+msgid "Home"
+msgstr "홈"
+
+msgid "Housekeeping successfully started"
+msgstr "Housekeepingì´ ì„±ê³µì ìœ¼ë¡œ 시작ë˜ì—ˆìŠµë‹ˆë‹¤"
+
+msgid "Import repository"
+msgstr "저장소 가져 오기"
+
+msgid "Interval Pattern"
+msgstr "주기 패턴"
+
+msgid "Introducing Cycle Analytics"
+msgstr "Cycle Analytics 소개"
+
+msgid "Jobs for last month"
+msgstr "지난달 Jobs"
+
+msgid "Jobs for last week"
+msgstr "지난주 Jobs"
+
+msgid "Jobs for last year"
+msgstr "지난해 Jobs"
+
+msgid "LFSStatus|Disabled"
+msgstr "Disabled"
+
+msgid "LFSStatus|Enabled"
+msgstr "Enabled"
+
+msgid "Last %d day"
+msgid_plural "Last %d days"
+msgstr[0] "최근 %d ì¼"
+
+msgid "Last Pipeline"
+msgstr "최근 파ì´í”„ë¼ì¸"
+
+msgid "Last Update"
+msgstr "최근 ì—…ë°ì´íŠ¸:"
+
+msgid "Last commit"
+msgstr "최근 커밋"
+
+msgid "Learn more in the"
+msgstr "ë” ìžì„¸ížˆ 알아보기"
+
+msgid "Learn more in the|pipeline schedules documentation"
+msgstr "파ì´í”„ë¼ì¸ 스케쥴 문서로부터 ë” ì•Œì•„ë³´ê¸°"
+
+msgid "Leave group"
+msgstr "그룹 떠나기"
+
+msgid "Leave project"
+msgstr "프로ì íŠ¸ì—ì„œ 나가기"
+
+msgid "Limited to showing %d event at most"
+msgid_plural "Limited to showing %d events at most"
+msgstr[0] "최대 %d ì´ë²¤íŠ¸ 만 표시하는 것으로 제한ë©ë‹ˆë‹¤."
+
+msgid "Median"
+msgstr "중앙값"
+
+msgid "MissingSSHKeyWarningLink|add an SSH key"
+msgstr "SSH 키 추가"
+
+msgid "New Issue"
+msgid_plural "New Issues"
+msgstr[0] "새 ì´ìŠˆ"
+
+msgid "New Pipeline Schedule"
+msgstr "새로운 파ì´í”„ë¼ì¸ ì¼ì •"
+
+msgid "New branch"
+msgstr "새 브랜치"
+
+msgid "New directory"
+msgstr "새 디렉토리"
+
+msgid "New file"
+msgstr "새 파ì¼"
+
+msgid "New issue"
+msgstr "새 ì´ìŠˆ"
+
+msgid "New merge request"
+msgstr "새 머지 리퀘스트"
+
+msgid "New schedule"
+msgstr "새 ì¼ì •"
+
+msgid "New snippet"
+msgstr "새 스니펫"
+
+msgid "New tag"
+msgstr "새 태그 "
+
+msgid "No repository"
+msgstr "저장소 ì—†ìŒ"
+
+msgid "No schedules"
+msgstr "ì¼ì • ì—†ìŒ"
+
+msgid "Not available"
+msgstr "사용할 수 ì—†ìŒ"
+
+msgid "Not enough data"
+msgstr "ë°ì´í„°ê°€ 충분하지 않습니다."
+
+msgid "Notification events"
+msgstr "알림 ì´ë²¤íŠ¸"
+
+msgid "NotificationEvent|Close issue"
+msgstr "ì´ìŠˆ 닫기"
+
+msgid "NotificationEvent|Close merge request"
+msgstr "머지 리퀘스트 닫기"
+
+msgid "NotificationEvent|Failed pipeline"
+msgstr "실패한 파ì´í”„ë¼ì¸"
+
+msgid "NotificationEvent|Merge merge request"
+msgstr "머지 리퀘스트 머지하기"
+
+msgid "NotificationEvent|New issue"
+msgstr "새 ì´ìŠˆ"
+
+msgid "NotificationEvent|New merge request"
+msgstr "새 머지 리퀘스트"
+
+msgid "NotificationEvent|New note"
+msgstr "새 노트"
+
+msgid "NotificationEvent|Reassign issue"
+msgstr "ì´ìŠˆ 재지정"
+
+msgid "NotificationEvent|Reassign merge request"
+msgstr "머지 리퀘스트 재 할당"
+
+msgid "NotificationEvent|Reopen issue"
+msgstr "ì´ìŠˆ 다시 열기"
+
+msgid "NotificationEvent|Successful pipeline"
+msgstr "성공ì ì¸ 파ì´í”„ë¼ì¸"
+
+msgid "NotificationLevel|Custom"
+msgstr "커스텀"
+
+msgid "NotificationLevel|Disabled"
+msgstr "사용 안 함"
+
+msgid "NotificationLevel|Global"
+msgstr "글로벌"
+
+msgid "NotificationLevel|On mention"
+msgstr "언급"
+
+msgid "NotificationLevel|Participate"
+msgstr "참여"
+
+msgid "NotificationLevel|Watch"
+msgstr "Watch"
+
+msgid "OfSearchInADropdown|Filter"
+msgstr "í•„í„°"
+
+msgid "OpenedNDaysAgo|Opened"
+msgstr "열린"
+
+msgid "Options"
+msgstr "옵션 "
+
+msgid "Owner"
+msgstr "소유ìž"
+
+msgid "Pipeline"
+msgstr "파ì´í”„ë¼ì¸"
+
+msgid "Pipeline Health"
+msgstr "파ì´í”„ë¼ì¸ ìƒíƒœ"
+
+msgid "Pipeline Schedule"
+msgstr "파ì´í”„ë¼ì¸ 스케쥴"
+
+msgid "Pipeline Schedules"
+msgstr "파ì´í”„ë¼ì¸ 스케쥴"
+
+msgid "PipelineCharts|Failed:"
+msgstr "실패 :"
+
+msgid "PipelineCharts|Overall statistics"
+msgstr "전체 통계"
+
+msgid "PipelineCharts|Success ratio:"
+msgstr "성공 비율 :"
+
+msgid "PipelineCharts|Successful:"
+msgstr "성공 :"
+
+msgid "PipelineCharts|Total:"
+msgstr "합계 :"
+
+msgid "PipelineSchedules|Activated"
+msgstr "활성화 ë¨"
+
+msgid "PipelineSchedules|Active"
+msgstr "활성"
+
+msgid "PipelineSchedules|All"
+msgstr "모ë‘"
+
+msgid "PipelineSchedules|Inactive"
+msgstr "비활성"
+
+msgid "PipelineSchedules|Input variable key"
+msgstr "입력 변수 키"
+
+msgid "PipelineSchedules|Input variable value"
+msgstr "입력 변수 값"
+
+msgid "PipelineSchedules|Next Run"
+msgstr "ë‹¤ìŒ ì‹¤í–‰"
+
+msgid "PipelineSchedules|None"
+msgstr "ì—†ìŒ"
+
+msgid "PipelineSchedules|Provide a short description for this pipeline"
+msgstr "ì´ íŒŒì´í”„ë¼ì¸ì— 대한 간단한 설명 제공"
+
+msgid "PipelineSchedules|Remove variable row"
+msgstr "변수 행 제거"
+
+msgid "PipelineSchedules|Take ownership"
+msgstr "소유권 가져 오기"
+
+msgid "PipelineSchedules|Target"
+msgstr "대ìƒ"
+
+msgid "PipelineSchedules|Variables"
+msgstr "변수"
+
+msgid "PipelineSheduleIntervalPattern|Custom"
+msgstr "ì‚¬ìš©ìž ì •ì˜"
+
+msgid "Pipelines"
+msgstr "파ì´í”„ë¼ì¸"
+
+msgid "Pipelines charts"
+msgstr "파ì´í”„ë¼ì¸ 차트"
+
+msgid "Pipeline|all"
+msgstr "모ë‘"
+
+msgid "Pipeline|success"
+msgstr "성공"
+
+msgid "Pipeline|with stage"
+msgstr "스테ì´ì§•"
+
+msgid "Pipeline|with stages"
+msgstr "스테ì´ì§•"
+
+msgid "Project '%{project_name}' queued for deletion."
+msgstr "'%{project_name}'프로ì íŠ¸ê°€ ì‚­ì œ 처리 중입니다."
+
+msgid "Project '%{project_name}' was successfully created."
+msgstr "'%{project_name}'프로ì íŠ¸ê°€ 성공ì ìœ¼ë¡œ ìƒì„±ë˜ì—ˆìŠµë‹ˆë‹¤."
+
+msgid "Project '%{project_name}' was successfully updated."
+msgstr "'%{project_name}'프로ì íŠ¸ê°€ 성공ì ìœ¼ë¡œ ì—…ë°ì´íŠ¸ë˜ì—ˆìŠµë‹ˆë‹¤."
+
+msgid "Project '%{project_name}' will be deleted."
+msgstr "'%{project_name}'프로ì íŠ¸ê°€ ì‚­ì œë©ë‹ˆë‹¤."
+
+msgid "Project access must be granted explicitly to each user."
+msgstr "프로ì íŠ¸ 액세스는 ê° ì‚¬ìš©ìžì—게 명시ì ìœ¼ë¡œ 부여ë˜ì–´ì•¼í•©ë‹ˆë‹¤."
+
+msgid "Project export could not be deleted."
+msgstr "프로ì íŠ¸ 내보내기를 삭제할 수 없습니다."
+
+msgid "Project export has been deleted."
+msgstr "프로ì íŠ¸ 내보내기가 ì‚­ì œë˜ì—ˆìŠµë‹ˆë‹¤."
+
+msgid ""
+"Project export link has expired. Please generate a new export from your "
+"project settings."
+msgstr "프로ì íŠ¸ 내보내기 ë§í¬ê°€ 만료ë˜ì—ˆìŠµë‹ˆë‹¤. 프로ì íŠ¸ 설정ì—ì„œ 새 내보내기를 ìƒì„±í•˜ì‹­ì‹œì˜¤."
+
+msgid "Project export started. A download link will be sent by email."
+msgstr "프로ì íŠ¸ 내보내기가 시작ë˜ì—ˆìŠµë‹ˆë‹¤. 다운로드 ë§í¬ëŠ” ì´ë©”ì¼ë¡œ 전송ë©ë‹ˆë‹¤."
+
+msgid "Project home"
+msgstr "프로ì íŠ¸ 홈"
+
+msgid "ProjectFeature|Disabled"
+msgstr "사용 안 함"
+
+msgid "ProjectFeature|Everyone with access"
+msgstr "ì ‘ê·¼ ê¶Œí•œì„ ê°€ì§„ 모든 ì´ë“¤"
+
+msgid "ProjectFeature|Only team members"
+msgstr "íŒ€ì› ë§Œ"
+
+msgid "ProjectFileTree|Name"
+msgstr "ì´ë¦„"
+
+msgid "ProjectLastActivity|Never"
+msgstr "Never"
+
+msgid "ProjectLifecycle|Stage"
+msgstr "스테ì´ì§•"
+
+msgid "ProjectNetworkGraph|Graph"
+msgstr "그래프"
+
+msgid "Read more"
+msgstr "ë” ì½ê¸°"
+
+msgid "Readme"
+msgstr "Readme"
+
+msgid "RefSwitcher|Branches"
+msgstr "브랜치"
+
+msgid "RefSwitcher|Tags"
+msgstr "태그"
+
+msgid "Related Commits"
+msgstr "관련 커밋"
+
+msgid "Related Deployed Jobs"
+msgstr "관련 ë°°í¬ ëœ ìž‘ì—…"
+
+msgid "Related Issues"
+msgstr "관련 ì´ìŠˆ"
+
+msgid "Related Jobs"
+msgstr "관련 Jobs"
+
+msgid "Related Merge Requests"
+msgstr "관련 머지 리퀘스트"
+
+msgid "Related Merged Requests"
+msgstr "관련 머지 리퀘스트"
+
+msgid "Remind later"
+msgstr "ë‚˜ì¤‘ì— ë‹¤ì‹œ 알림"
+
+msgid "Remove project"
+msgstr "프로ì íŠ¸ ì‚­ì œ"
+
+msgid "Request Access"
+msgstr "액세스 요청"
+
+msgid "Revert this commit"
+msgstr "ì´ ì»¤ë°‹ ë˜ëŒë¦¬ê¸°"
+
+msgid "Revert this merge request"
+msgstr "ì´ ë¨¸ì§€ 리퀘스트 ë˜ëŒë¦¬ê¸°"
+
+msgid "Save pipeline schedule"
+msgstr "파ì´í”„ë¼ì¸ 스케줄 저장"
+
+msgid "Schedule a new pipeline"
+msgstr "새로운 파ì´í”„ë¼ì¸ 스케줄 잡기"
+
+msgid "Scheduling Pipelines"
+msgstr "파ì´í”„ë¼ì¸ 스케줄ë§"
+
+msgid "Search branches and tags"
+msgstr "브랜치 ë° íƒœê·¸ 검색"
+
+msgid "Select Archive Format"
+msgstr "ì•„ì¹´ì´ë¸Œ í¬ë§· ì„ íƒ"
+
+msgid "Select a timezone"
+msgstr "시간대 ì„ íƒ"
+
+msgid "Select target branch"
+msgstr "ëŒ€ìƒ ë¸Œëžœì¹˜ ì„ íƒ"
+
+msgid "Set a password on your account to pull or push via %{protocol}."
+msgstr "%{protocol} í”„ë¡œí† ì½œì„ í†µí•´ Pull 하거나 Push하려면 ê³„ì •ì— íŒ¨ìŠ¤ì›Œë“œë¥¼ 설정하십시오."
+
+msgid "Set up CI"
+msgstr "CI 설정"
+
+msgid "Set up Koding"
+msgstr "Koding 설정"
+
+msgid "Set up auto deploy"
+msgstr "ìžë™ ë°°í¬ ì„¤ì •"
+
+msgid "SetPasswordToCloneLink|set a password"
+msgstr "패스워드 설정"
+
+msgid "Showing %d event"
+msgid_plural "Showing %d events"
+msgstr[0] "%d ê°œì˜ ì´ë²¤íŠ¸ 표시 중"
+
+msgid "Source code"
+msgstr "소스 코드"
+
+msgid "StarProject|Star"
+msgstr "별표"
+
+msgid "Start a %{new_merge_request} with these changes"
+msgstr "ì´ ë³€ê²½ 사항으로 %{new_merge_request} ì„ ì‹œìž‘í•˜ì‹­ì‹œì˜¤."
+
+msgid "Switch branch/tag"
+msgstr "스위치 브랜치/태그"
+
+msgid "Tag"
+msgid_plural "Tags"
+msgstr[0] "태그"
+
+msgid "Tags"
+msgstr "태그 "
+
+msgid "Target Branch"
+msgstr "ëŒ€ìƒ ë¸Œëžœì¹˜"
+
+msgid ""
+"The coding stage shows the time from the first commit to creating the merge "
+"request. The data will automatically be added here once you create your "
+"first merge request."
+msgstr ""
+"Coding Stage는 첫 번째 커밋ì—서부터 머지 리퀘스트 ìƒì„±ê¹Œì§€ì˜ ì‹œê°„ì„ ë³´ì—¬ì¤ë‹ˆë‹¤. 첫 번째 머지 ë¦¬í€˜ìŠ¤íŠ¸ì„ ìƒì„±í•˜ë©´ ë°ì´í„°ê°€ "
+"ìžë™ìœ¼ë¡œ ì—¬ê¸°ì— ì¶”ê°€ë©ë‹ˆë‹¤."
+
+msgid "The collection of events added to the data gathered for that stage."
+msgstr "해당 단계ì—ì„œ 수집 ëœ ë°ì´í„°ê°€ ì´ë²¤íŠ¸ 모ìŒì— 추가ë˜ì—ˆìŠµë‹ˆë‹¤."
+
+msgid "The fork relationship has been removed."
+msgstr "í¬í¬ 관계가 제거ë˜ì—ˆìŠµë‹ˆë‹¤."
+
+msgid ""
+"The issue stage shows the time it takes from creating an issue to assigning "
+"the issue to a milestone, or add the issue to a list on your Issue Board. "
+"Begin creating issues to see data for this stage."
+msgstr ""
+"ì´ìŠˆ 단계ì—는 ì´ìŠˆë¥¼ 작성하여 마ì¼ìŠ¤í†¤ìœ¼ë¡œ 지정하는 ë° ê±¸ë¦¬ëŠ” 시간 ë˜ëŠ” ì´ìŠˆ ë³´ë“œì˜ ëª©ë¡ì— ì´ìŠˆë¥¼ 추가하는 ì‹œê°„ì´ í‘œì‹œë©ë‹ˆë‹¤. ì´ "
+"ë‹¨ê³„ì˜ ë°ì´í„°ë¥¼ 보기 위해서는 ì´ìŠˆë¥¼ 먼저 작성해야 합니다."
+
+msgid "The phase of the development lifecycle."
+msgstr "개발 ìˆ˜ëª…ì£¼ê¸°ì˜ ë‹¨ê³„."
+
+msgid ""
+"The pipelines schedule runs pipelines in the future, repeatedly, for "
+"specific branches or tags. Those scheduled pipelines will inherit limited "
+"project access based on their associated user."
+msgstr ""
+"파ì´í”„ë¼ì¸ ì¼ì •ì€ ë¯¸ëž˜ì— íŠ¹ì • 브랜치 ë˜ëŠ” íƒœê·¸ì— ëŒ€í•´ 반복ì ìœ¼ë¡œ 파ì´í”„ë¼ì¸ì„ 실행합니다. ì˜ˆì •ëœ íŒŒì´í”„ë¼ì¸ì€ 관련 사용ìžë¥¼ 기반으로 "
+"ì œí•œëœ í”„ë¡œì íŠ¸ 액세스 ê¶Œí•œì„ ìƒì†ë°›ìŠµë‹ˆë‹¤."
+
+msgid ""
+"The planning stage shows the time from the previous step to pushing your "
+"first commit. This time will be added automatically once you push your first "
+"commit."
+msgstr "ê³„íš ë‹¨ê³„ì—서는 ì´ì „ 단계ì—ì„œ 첫 번째 커밋 ì‹œê°„ì´ í‘œì‹œë©ë‹ˆë‹¤. ì´ ì‹œê°„ì€ ì²« 번째 ì»¤ë°‹ì„ ëˆ„ë¥´ë©´ ìžë™ìœ¼ë¡œ 추가ë©ë‹ˆë‹¤."
+
+msgid ""
+"The production stage shows the total time it takes between creating an issue "
+"and deploying the code to production. The data will be automatically added "
+"once you have completed the full idea to production cycle."
+msgstr ""
+"프로ë•ì…˜ 단계ì—서는 문제를 만들고 코드를 프로ë•ì…˜ 환경으로 ë°°í¬í•˜ëŠ” ë° ê±¸ë¦¬ëŠ” ì´ ì‹œê°„ì„ ë³´ì—¬ì¤ë‹ˆë‹¤. ìƒì‚°ì£¼ê¸°ì— 대한 완전한 ì•„ì´ë””어를 "
+"ì–»ì€ í›„ì—는 ë°ì´í„°ê°€ ìžë™ìœ¼ë¡œ 추가ë©ë‹ˆë‹¤."
+
+msgid "The project can be accessed by any logged in user."
+msgstr "ì´ í”„ë¡œì íŠ¸ëŠ” ë¡œê·¸ì¸ í•œ 사용ìžê°€ë§Œ 액세스 í•  수 있습니다."
+
+msgid "The project can be accessed without any authentication."
+msgstr "ì´ í”„ë¡œì íŠ¸ëŠ” ì¸ì¦ì—†ì´ 액세스 í•  수 있습니다."
+
+msgid "The repository for this project does not exist."
+msgstr "ì´ í”„ë¡œì íŠ¸ì˜ 저장소가 존재하지 않습니다."
+
+msgid ""
+"The review stage shows the time from creating the merge request to merging "
+"it. The data will automatically be added after you merge your first merge "
+"request."
+msgstr ""
+"Review 단계ì—서는 머지 리퀘스트를 작성한 후 ë¨¸ì§€í•˜ê¸°ê¹Œì§€ì˜ ì‹œê°„ì„ ë³´ì—¬ì¤ë‹ˆë‹¤. ë°ì´í„°ëŠ” 첫 번째 머지 ë¦¬í€˜ìŠ¤íŠ¸ì„ ë¨¸ì§€ í•œ í›„ì— "
+"ìžë™ìœ¼ë¡œ 추가ë©ë‹ˆë‹¤."
+
+msgid ""
+"The staging stage shows the time between merging the MR and deploying code "
+"to the production environment. The data will be automatically added once you "
+"deploy to production for the first time."
+msgstr ""
+"Staging 단계ì—서는 MR 머지과 프로ë•ì…˜ í™˜ê²½ì— ì½”ë“œ ë°°í¬ ì‚¬ì´ì˜ ì‹œê°„ì„ ë³´ì—¬ì¤ë‹ˆë‹¤. ë°ì´í„°ë¥¼ Production í™˜ê²½ì— ì²˜ìŒ "
+"ë°°í¬í•˜ë©´ ë°ì´í„°ê°€ ìžë™ìœ¼ë¡œ 추가ë©ë‹ˆë‹¤."
+
+msgid ""
+"The testing stage shows the time GitLab CI takes to run every pipeline for "
+"the related merge request. The data will automatically be added after your "
+"first pipeline finishes running."
+msgstr ""
+"테스트 단계ì—서는 GitLab CIê°€ 관련 머지 ë¦¬í€˜ìŠ¤íŠ¸ì„ ìœ„í•´ 모든 파ì´í”„ë¼ì¸ì„ 실행하는 ë° ê±¸ë¦¬ëŠ” ì‹œê°„ì„ ë³´ì—¬ì¤ë‹ˆë‹¤. 첫 번째 "
+"파ì´í”„ë¼ì¸ ì‹¤í–‰ì´ ì™„ë£Œë˜ë©´ ë°ì´í„°ê°€ ìžë™ìœ¼ë¡œ 추가ë©ë‹ˆë‹¤."
+
+msgid "The time taken by each data entry gathered by that stage."
+msgstr "해당 단계ì—ì„œ 수집 í•œ ê° ë°ì´í„° ìž…ë ¥ì— ì†Œìš” ëœ ì‹œê°„"
+
+msgid ""
+"The value lying at the midpoint of a series of observed values. E.g., "
+"between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 ="
+" 6."
+msgstr ""
+"ê°’ì€ ì¼ë ¨ì˜ 관측 ê°’ 중ì ì— 있습니다. 예를 들어, 3, 5, 9 사ì´ì˜ 중간 ê°’ì€ 5입니다. 3, 5, 7, 8 사ì´ì˜ 중간 ê°’ì€ (5 "
+"+ 7) / 2 = 6입니다."
+
+msgid ""
+"This means you can not push code until you create an empty repository or "
+"import existing one."
+msgstr "즉, 빈 저장소를 만들거나 기존 저장소를 가져올 때까지 코드를 Push 할 수 없습니다."
+
+msgid "Time before an issue gets scheduled"
+msgstr "ì´ìŠˆê°€ 스케줄ë˜ê¸° ì „ì˜ ì‹œê°„"
+
+msgid "Time before an issue starts implementation"
+msgstr "ì´ìŠˆê°€ 구현ë˜ê¸° ì „ì˜ ì‹œê°„"
+
+msgid "Time between merge request creation and merge/close"
+msgstr "머지 리퀘스트 ìƒì„±ê³¼ 머지 / 닫기 사ì´ì˜ 시간"
+
+msgid "Time until first merge request"
+msgstr "첫 번째 머지 ë¦¬í€˜ìŠ¤íŠ¸ê¹Œì§€ì˜ ì‹œê°„"
+
+msgid "Timeago|%s days ago"
+msgstr "%s ì¼ ì „"
+
+msgid "Timeago|%s days remaining"
+msgstr "%s ì¼ ë‚¨ìŒ"
+
+msgid "Timeago|%s hours remaining"
+msgstr "%s 시간 남ìŒ"
+
+msgid "Timeago|%s minutes ago"
+msgstr "%s 분 전"
+
+msgid "Timeago|%s minutes remaining"
+msgstr "%s 분 남ìŒ"
+
+msgid "Timeago|%s months ago"
+msgstr "%s 개월 전"
+
+msgid "Timeago|%s months remaining"
+msgstr "%s 개월 남ìŒ"
+
+msgid "Timeago|%s seconds remaining"
+msgstr "%s ì´ˆ 남ìŒ"
+
+msgid "Timeago|%s weeks ago"
+msgstr "%s 주 전"
+
+msgid "Timeago|%s weeks remaining"
+msgstr "%s 주 남ìŒ"
+
+msgid "Timeago|%s years ago"
+msgstr "%s ë…„ ì „"
+
+msgid "Timeago|%s years remaining"
+msgstr "%s ë…„ 남ìŒ"
+
+msgid "Timeago|1 day remaining"
+msgstr "1 ì¼ ë‚¨ìŒ"
+
+msgid "Timeago|1 hour remaining"
+msgstr "1 시간 남ìŒ"
+
+msgid "Timeago|1 minute remaining"
+msgstr "1 분 남ìŒ"
+
+msgid "Timeago|1 month remaining"
+msgstr "1 개월 남ìŒ"
+
+msgid "Timeago|1 week remaining"
+msgstr "1 주 남ìŒ"
+
+msgid "Timeago|1 year remaining"
+msgstr "1 ë…„ 남ìŒ"
+
+msgid "Timeago|Past due"
+msgstr "기한 초과"
+
+msgid "Timeago|a day ago"
+msgstr "1 ì¼ ì „"
+
+msgid "Timeago|a month ago"
+msgstr "1 달 전"
+
+msgid "Timeago|a week ago"
+msgstr "1 ì£¼ì¼ ì „"
+
+msgid "Timeago|a while"
+msgstr "잠시 전"
+
+msgid "Timeago|a year ago"
+msgstr "1 ë…„ ì „"
+
+msgid "Timeago|about %s hours ago"
+msgstr "약 %s 시간 전"
+
+msgid "Timeago|about a minute ago"
+msgstr "약 1 분 전"
+
+msgid "Timeago|about an hour ago"
+msgstr "약 1 시간 전"
+
+msgid "Timeago|in %s days"
+msgstr "%s ì¼ ì´ë‚´"
+
+msgid "Timeago|in %s hours"
+msgstr "%s 시간 ì´ë‚´"
+
+msgid "Timeago|in %s minutes"
+msgstr "%s 분 ì´ë‚´"
+
+msgid "Timeago|in %s months"
+msgstr "%s 개월 ì´ë‚´"
+
+msgid "Timeago|in %s seconds"
+msgstr "%s ì´ˆ ì´ë‚´"
+
+msgid "Timeago|in %s weeks"
+msgstr "%s 주 ì´ë‚´"
+
+msgid "Timeago|in %s years"
+msgstr "%s ë…„ ì´ë‚´"
+
+msgid "Timeago|in 1 day"
+msgstr "1 ì¼ ì´ë‚´"
+
+msgid "Timeago|in 1 hour"
+msgstr "1 시간 ì´ë‚´"
+
+msgid "Timeago|in 1 minute"
+msgstr "1 분 ì´ë‚´"
+
+msgid "Timeago|in 1 month"
+msgstr "1 개월 ì´ë‚´"
+
+msgid "Timeago|in 1 week"
+msgstr "1 ì£¼ì¼ ì´ë‚´"
+
+msgid "Timeago|in 1 year"
+msgstr "1 ë…„ ì´ë‚´"
+
+msgid "Timeago|less than a minute ago"
+msgstr "1 분미만"
+
+msgid "Time|hr"
+msgid_plural "Time|hrs"
+msgstr[0] "시간"
+
+msgid "Time|min"
+msgid_plural "Time|mins"
+msgstr[0] "분"
+
+msgid "Time|s"
+msgstr "ì´ˆ"
+
+msgid "Total Time"
+msgstr "시간 합계:"
+
+msgid "Total test time for all commits/merges"
+msgstr "모든 커밋 / ë¨¸ì§€ì˜ ì´ í…ŒìŠ¤íŠ¸ 시간"
+
+msgid "Unstar"
+msgstr "별표 제거"
+
+msgid "Upload New File"
+msgstr "새 íŒŒì¼ ì—…ë¡œë“œ"
+
+msgid "Upload file"
+msgstr "íŒŒì¼ ì—…ë¡œë“œ"
+
+msgid "UploadLink|click to upload"
+msgstr "업로드하려면 í´ë¦­í•˜ì‹­ì‹œì˜¤."
+
+msgid "Use your global notification setting"
+msgstr "전체 알림 설정 사용"
+
+msgid "View open merge request"
+msgstr "열린 머지 리퀘스트보기"
+
+msgid "VisibilityLevel|Internal"
+msgstr "내부"
+
+msgid "VisibilityLevel|Private"
+msgstr "Private"
+
+msgid "VisibilityLevel|Public"
+msgstr "Public"
+
+msgid "VisibilityLevel|Unknown"
+msgstr "ì•Œ 수 ì—†ìŒ"
+
+msgid "Want to see the data? Please ask an administrator for access."
+msgstr "ì´ ë°ì´í„°ë¥¼ ë³´ê³  싶ì€ê°€ìš”? 관리ìžì—게 액세스 ê¶Œí•œì„ ìš”ì²­í•˜ì„¸ìš”."
+
+msgid "We don't have enough data to show this stage."
+msgstr "ì´ ë‹¨ê³„ë¥¼ ë³´ì—¬ì£¼ê¸°ì— ì¶©ë¶„í•œ ë°ì´í„°ê°€ 없습니다."
+
+msgid "Withdraw Access Request"
+msgstr "액세스 요청 철회"
+
+msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?"
+msgstr "%{group_name} ê·¸ë£¹ì„ ì œê±°í•˜ë ¤ê³ í•©ë‹ˆë‹¤. \"ì •ë§ë¡œ\" 확실합니까?"
+
+msgid "You are going to remove %{project_name_with_namespace}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?"
+msgstr "%{project_name_with_namespace} 프로ì íŠ¸ë¥¼ 삭제하려고합니다. "ì‚­ì œëœ í”„ë¡œì íŠ¸ë¥¼ ë³µì› í•  수 없습니다! \"ì •ë§ë¡œ\" 확실합니까?"
+
+msgid ""
+"You are going to remove the fork relationship to source project "
+"%{forked_from_project}. Are you ABSOLUTELY sure?"
+msgstr "í¬í¬ 관계를 소스 프로ì íŠ¸ %{forked_from_project}ì— ëŒ€í•´ 제거하려고합니다. \"ì •ë§ë¡œ\" 확실합니까?"
+
+msgid ""
+"You are going to transfer %{project_name_with_namespace} to another owner. "
+"Are you ABSOLUTELY sure?"
+msgstr "%{project_name_with_namespace}ì„ ë‹¤ë¥¸ 소유ìžì—게 ì´ì „하려고합니다. \"ì •ë§ë¡œ\" 확실합니까?"
+
+msgid "You can only add files when you are on a branch"
+msgstr "ë¸Œëžœì¹˜ì— ìžˆì„ ë•Œì—만 파ì¼ì„ 추가 í•  수 있습니다."
+
+msgid "You have reached your project limit"
+msgstr "프로ì íŠ¸ ìˆ«ìž í•œë„ì— ë„달했습니다."
+
+msgid "You must sign in to star a project"
+msgstr "프로ì íŠ¸ì— 별표를 표시하려면 ë¡œê·¸ì¸ í•´ì•¼ 합니다."
+
+msgid "You need permission."
+msgstr "ê¶Œí•œì´ í•„ìš”í•©ë‹ˆë‹¤."
+
+msgid "You will not get any notifications via email"
+msgstr "ì´ë©”ì¼ë¡œ ì•Œë¦¼ì„ ë°›ì§€ 않습니다."
+
+msgid "You will only receive notifications for the events you choose"
+msgstr "ì„ íƒí•œ ì´ë²¤íŠ¸ì— 대한 알림만 받습니다."
+
+msgid ""
+"You will only receive notifications for threads you have participated in"
+msgstr "참여한 ìŠ¤ë ˆë“œì— ëŒ€í•œ 알림만 받습니다."
+
+msgid "You will receive notifications for any activity"
+msgstr "모든 활ë™ì— 대한 ì•Œë¦¼ì„ ë°›ê²Œë©ë‹ˆë‹¤."
+
+msgid ""
+"You will receive notifications only for comments in which you were "
+"@mentioned"
+msgstr "ë‹¹ì‹ ì€ ë‹¹ì‹ ì´ @mentioned í•œ ì½”ë©˜íŠ¸ì— ëŒ€í•´ì„œë§Œ 통지를 받게ë©ë‹ˆë‹¤."
+
+msgid ""
+"You won't be able to pull or push project code via %{protocol} until you "
+"%{set_password_link} on your account"
+msgstr ""
+"ë‹¹ì‹ ì˜ ê³„ì •ì— %{set_password_link} ì„ í•˜ê¸° ì „ì—는 %{protocol} í”„ë¡œí† ì½œì„ í†µí•´ 프로ì íŠ¸ 코드를 Pull 하거나 "
+"Push 할 수 없습니다"
+
+msgid ""
+"You won't be able to pull or push project code via SSH until you "
+"%{add_ssh_key_link} to your profile"
+msgstr ""
+"ë‹¹ì‹ ì˜ í”„ë¡œí•„ì— %{add_ssh_key_link} 를 하기 ì „ì—는 SSH를 통해 프로ì íŠ¸ 코드를 Pull 하거나 Push í•  수 "
+"없습니다"
+
+msgid "Your name"
+msgstr "ê·€í•˜ì˜ ì´ë¦„"
+
+msgid "day"
+msgid_plural "days"
+msgstr[0] "ì¼"
+
+msgid "new merge request"
+msgstr "새 머지 리퀘스트"
+
+msgid "notification emails"
+msgstr "알림 ì´ë©”ì¼"
+
+msgid "parent"
+msgid_plural "parents"
+msgstr[0] "부모"
+
diff --git a/locale/ko/gitlab.po.time_stamp b/locale/ko/gitlab.po.time_stamp
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/locale/ko/gitlab.po.time_stamp
diff --git a/locale/pt_BR/gitlab.po b/locale/pt_BR/gitlab.po
index 9e3c78b6148..a2df8ea549c 100644
--- a/locale/pt_BR/gitlab.po
+++ b/locale/pt_BR/gitlab.po
@@ -6,13 +6,13 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-07-05 08:50-0500\n"
+"POT-Creation-Date: 2017-07-13 12:07-0500\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language-Team: Portuguese (Brazil) (https://translate.zanata.org/project/view/GitLab)\n"
-"PO-Revision-Date: 2017-08-01 09:47-0400\n"
-"Last-Translator: Huang Tao <htve@outlook.com>\n"
+"PO-Revision-Date: 2017-08-03 11:29-0400\n"
+"Last-Translator: Alexandre Alencar <alexandre.alencar@gmail.com>\n"
"Language: pt-BR\n"
"X-Generator: Zanata 3.9.6\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
@@ -92,7 +92,7 @@ msgid "BranchSwitcherPlaceholder|Search branches"
msgstr "Procurar por branches"
msgid "BranchSwitcherTitle|Switch branch"
-msgstr "BranchSwitcherTitle|Mudar de branch"
+msgstr "Mudar de branch"
msgid "Branches"
msgstr "Branches"
@@ -1152,6 +1152,9 @@ msgstr "Privado"
msgid "VisibilityLevel|Public"
msgstr "Público"
+msgid "VisibilityLevel|Unknown"
+msgstr "Desconhecido"
+
msgid "Want to see the data? Please ask an administrator for access."
msgstr "Precisa visualizar os dados? Solicite acesso ao administrador."
@@ -1161,30 +1164,12 @@ msgstr "Esta etapa não possui dados suficientes para exibição."
msgid "Withdraw Access Request"
msgstr "Remover Requisição de Acesso"
-msgid ""
-"You are going to remove %{group_name}.\n"
-"Removed groups CANNOT be restored!\n"
-"Are you ABSOLUTELY sure?"
-msgstr ""
-"Você vai remover %{group_name}.\n"
-"Grupos removidos NÃO PODEM ser restaurados!\n"
-"Você está ABSOLUTAMENTE certo?"
-msgid ""
-"You are going to remove %{project_name_with_namespace}.\n"
-"Removed project CANNOT be restored!\n"
-"Are you ABSOLUTELY sure?"
-msgstr ""
-"Você irá remover %{project_name_with_namespace}.\n"
-"O projeto removido NÃO PODE ser restaurado!\n"
-"Tem certeza ABSOLUTA?"
+msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?"
+msgstr "Você vai remover %{group_name}. Grupos removidos NÃO PODEM ser restaurados! Você está ABSOLUTAMENTE certo?"
-msgid ""
-"You are going to remove the fork relationship to source project "
-"%{forked_from_project}. Are you ABSOLUTELY sure?"
-msgstr ""
-"Você ira remover o relacionamento de fork com o projeto original "
-"%{forked_from_project}. Tem certeza ABSOLUTA?"
+msgid "You are going to remove %{project_name_with_namespace}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?"
+msgstr "Você irá remover %{project_name_with_namespace}. O projeto removido NÃO PODE ser restaurado! Tem certeza ABSOLUTA?"
msgid ""
"You are going to transfer %{project_name_with_namespace} to another owner. "
diff --git a/locale/ru/gitlab.po b/locale/ru/gitlab.po
index f9e8dcd05e7..6661232850a 100644
--- a/locale/ru/gitlab.po
+++ b/locale/ru/gitlab.po
@@ -4,12 +4,12 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-07-05 08:50-0500\n"
+"POT-Creation-Date: 2017-07-13 12:07-0500\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language-Team: Russian (https://translate.zanata.org/project/view/GitLab)\n"
-"PO-Revision-Date: 2017-08-01 09:15-0400\n"
+"PO-Revision-Date: 2017-08-06 11:23-0400\n"
"Last-Translator: Ðндрей П. <fenixnow33@gmail.com>\n"
"Language: ru\n"
"X-Generator: Zanata 3.9.6\n"
@@ -221,7 +221,7 @@ msgid "CommitBoxTitle|Commit"
msgstr "Коммит"
msgid "CommitMessage|Add %{file_name}"
-msgstr "CommitMessage|Добавить %{file_name}"
+msgstr "Добавлен %{file_name}"
msgid "Commits"
msgstr "Коммиты"
@@ -301,14 +301,14 @@ msgstr ""
"поÑмотрите %{notification_link}."
msgid "Cycle Analytics"
-msgstr "ÐналитичеÑкий цикл"
+msgstr "Цикл Ðналитик"
msgid ""
"Cycle Analytics gives an overview of how much time it takes to go from idea "
"to production in your project."
msgstr ""
-"ÐналитичеÑкий цикл дает предÑтавление о том, Ñколько времени требуетÑÑ, "
-"чтобы перейти от идеи к производÑтву в проекте."
+"Цикл Ðналитик дает предÑтавление о том, Ñколько времени требуетÑÑ, чтобы "
+"перейти от идеи к производÑтву в проекте."
msgid "CycleAnalyticsStage|Code"
msgstr "ÐапиÑание кода"
@@ -452,7 +452,7 @@ msgid "Interval Pattern"
msgstr "Шаблон интервала"
msgid "Introducing Cycle Analytics"
-msgstr "Внедрение Цикла Ðналитики"
+msgstr "Внедрение Цикла Ðналитик"
msgid "Jobs for last month"
msgstr "Работы за прошлый меÑÑц"
@@ -599,10 +599,10 @@ msgid "NotificationLevel|Global"
msgstr "Глобальный"
msgid "NotificationLevel|On mention"
-msgstr "С упоминанием"
+msgstr "Упоминание"
msgid "NotificationLevel|Participate"
-msgstr "По учаÑтию"
+msgstr "УчаÑтие"
msgid "NotificationLevel|Watch"
msgstr "ОтÑлеживать"
@@ -740,7 +740,7 @@ msgstr ""
"почте."
msgid "Project home"
-msgstr "ДомашнÑÑ Ñтраница проекта"
+msgstr "ДомашнÑÑ Ñтраница"
msgid "ProjectFeature|Disabled"
msgstr "Отключено"
@@ -874,7 +874,7 @@ msgid "Tags"
msgstr "Теги"
msgid "Target Branch"
-msgstr "Ð¦ÐµÐ»ÐµÐ²Ð°Ñ Ð²ÐµÑ‚ÐºÐ°"
+msgstr "Ветка"
msgid ""
"The coding stage shows the time from the first commit to creating the merge "
@@ -1167,6 +1167,9 @@ msgstr "Приватный"
msgid "VisibilityLevel|Public"
msgstr "Публичный"
+msgid "VisibilityLevel|Unknown"
+msgstr "Ðе определен"
+
msgid "Want to see the data? Please ask an administrator for access."
msgstr "Хотите увидеть данные? ОбратитеÑÑŒ к админиÑтратору за доÑтупом."
@@ -1176,23 +1179,11 @@ msgstr "Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¿Ð¾ Ñтапу отÑутÑтвует."
msgid "Withdraw Access Request"
msgstr "Отменить Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð´Ð¾Ñтупа"
-msgid ""
-"You are going to remove %{group_name}.\n"
-"Removed groups CANNOT be restored!\n"
-"Are you ABSOLUTELY sure?"
-msgstr ""
-"Ð’Ñ‹ ÑобираетеÑÑŒ удалить %{group_name}.\n"
-"Удаленные группы ÐЕ МОГУТ быть воÑÑтановлены!\n"
-"Ð’Ñ‹ ÐБСОЛЮТÐО уверены?"
+msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?"
+msgstr "Ð’Ñ‹ ÑобираетеÑÑŒ удалить %{group_name}. Удаленные группы ÐЕ МОГУТ быть воÑÑтановлены! Ð’Ñ‹ ÐБСОЛЮТÐО уверены?"
-msgid ""
-"You are going to remove %{project_name_with_namespace}.\n"
-"Removed project CANNOT be restored!\n"
-"Are you ABSOLUTELY sure?"
-msgstr ""
-"Вы хотите удалить %{project_name_with_namespace}.\n"
-"Удаленный проект ÐЕ МОЖЕТ быть воÑÑтановлен!\n"
-"Ð’Ñ‹ ÐБСОЛЮТÐО уверены?"
+msgid "You are going to remove %{project_name_with_namespace}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?"
+msgstr "Ð’Ñ‹ хотите удалить %{project_name_with_namespace}. Удаленный проект ÐЕ МОЖЕТ быть воÑÑтановлен! Ð’Ñ‹ ÐБСОЛЮТÐО уверены?"
msgid ""
"You are going to remove the fork relationship to source project "
diff --git a/locale/uk/gitlab.po b/locale/uk/gitlab.po
index c1b99be3433..0ac0499e315 100644
--- a/locale/uk/gitlab.po
+++ b/locale/uk/gitlab.po
@@ -4,12 +4,12 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-07-05 08:50-0500\n"
+"POT-Creation-Date: 2017-07-13 12:07-0500\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language-Team: Ukrainian (https://translate.zanata.org/project/view/GitLab)\n"
-"PO-Revision-Date: 2017-08-01 09:15-0400\n"
+"PO-Revision-Date: 2017-08-06 11:23-0400\n"
"Last-Translator: Ðндрей Витюк <andruwa13@gmail.com>\n"
"Language: uk\n"
"X-Generator: Zanata 3.9.6\n"
@@ -209,9 +209,7 @@ msgstr[1] "Комміта"
msgstr[2] "Коммітів"
msgid "Commit duration in minutes for last 30 commits"
-msgstr ""
-"ТриваліÑÑ‚ÑŒ коммітів протÑгом декількох хвилин на протÑзі 30 оÑтанніх "
-"коммітів"
+msgstr "ТриваліÑÑ‚ÑŒ оÑтанніх 30 коммітів у хвилинах"
msgid "Commit message"
msgstr "Комміт повідомленнÑ"
@@ -399,7 +397,7 @@ msgid "Failed to remove the pipeline schedule"
msgstr "Ðе вдалоÑÑ Ð²Ð¸Ð´Ð°Ð»Ð¸Ñ‚Ð¸ розклад Конвеєра"
msgid "Files"
-msgstr "Файлів"
+msgstr "Файли"
msgid "Filter by commit message"
msgstr "Фільтрувати Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ ÐºÐ¾Ð¼Ð¼Ñ–Ñ‚Ñ–Ð²"
@@ -496,9 +494,9 @@ msgstr "Залишити проект"
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
+msgstr[0] "ÐžÐ±Ð¼ÐµÐ¶ÐµÐ½Ð½Ñ %d події"
+msgstr[1] "ÐžÐ±Ð¼ÐµÐ¶ÐµÐ½Ð½Ñ %d подій"
+msgstr[2] "ÐžÐ±Ð¼ÐµÐ¶ÐµÐ½Ð½Ñ %d подій"
msgid "Median"
msgstr "Медіана"
@@ -846,9 +844,9 @@ msgstr "вÑтановити пароль"
msgid "Showing %d event"
msgid_plural "Showing %d events"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
+msgstr[0] "Показано %d подію"
+msgstr[1] "Показано %d події"
+msgstr[2] "Показано %d подій"
msgid "Source code"
msgstr "Код"
@@ -879,9 +877,12 @@ msgid ""
"request. The data will automatically be added here once you create your "
"first merge request."
msgstr ""
+"Ðа Ñтадії напиÑÐ°Ð½Ð½Ñ ÐºÐ¾Ð´Ñƒ, показує Ñ‡Ð°Ñ Ð¿ÐµÑ€ÑˆÐ¾Ð³Ð¾ комміту до ÑÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð·Ð°Ð¿Ð¸Ñ‚Ñƒ на "
+"об'єднаннÑ. Дані будуть автоматично додані піÑÐ»Ñ ÑÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð²Ð°ÑˆÐ¾Ð³Ð¾ першого "
+"запиту на об'єднаннÑ."
msgid "The collection of events added to the data gathered for that stage."
-msgstr ""
+msgstr "ÐšÐ¾Ð»ÐµÐºÑ†Ñ–Ñ Ð¿Ð¾Ð´Ñ–Ð¹ додана до даних, зібраних Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ етапу."
msgid "The fork relationship has been removed."
msgstr "Зв'Ñзок форка видалена."
@@ -912,12 +913,17 @@ msgid ""
"first commit. This time will be added automatically once you push your first "
"commit."
msgstr ""
+"Ðа етапі Ð¿Ð»Ð°Ð½ÑƒÐ²Ð°Ð½Ð½Ñ Ð²Ñ–Ð´Ð¾Ð±Ñ€Ð°Ð¶Ð°Ñ”Ñ‚ÑŒÑÑ Ñ‡Ð°Ñ Ð²Ñ–Ð´ попереднього кроку до першого "
+"комміту. ДодаєтьÑÑ Ð°Ð²Ñ‚Ð¾Ð¼Ð°Ñ‚Ð¸Ñ‡Ð½Ð¾, Ñк тільки відправитÑÑ Ð¿ÐµÑ€ÑˆÐ¸Ð¹ комміт."
msgid ""
"The production stage shows the total time it takes between creating an issue "
"and deploying the code to production. The data will be automatically added "
"once you have completed the full idea to production cycle."
msgstr ""
+"Ð¡Ñ‚Ð°Ð´Ñ–Ñ ÐŸÐ ÐžÐ”Ð°ÐºÑˆÐ¸Ð½ показує загальний Ñ‡Ð°Ñ Ð¼Ñ–Ð¶ ÑтвореннÑм проблеми та "
+"розгортаннÑм коду у ПРОДакшині. Дані будуть автоматично додані піÑÐ»Ñ "
+"Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð½Ñ Ð¿Ð¾Ð²Ð½Ð¾Ñ— ідеї до ПРОДакшин циклу."
msgid "The project can be accessed by any logged in user."
msgstr "ДоÑтуп до проекту можливий будь-Ñким зареєÑтрованим кориÑтувачем."
@@ -933,27 +939,37 @@ msgid ""
"it. The data will automatically be added after you merge your first merge "
"request."
msgstr ""
+"Ð¡Ñ‚Ð°Ð´Ñ–Ñ Ð¾Ð³Ð»Ñду показує Ñ‡Ð°Ñ Ð²Ñ–Ð´ ÑÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð·Ð°Ð¿Ð¸Ñ‚Ñƒ про об'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð´Ð¾ його "
+"виконаннÑ. Дані будуть автоматично додані піÑÐ»Ñ Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð½Ñ Ð¿ÐµÑ€ÑˆÐ¾Ð³Ð¾ запиту на "
+"злиттÑ."
msgid ""
"The staging stage shows the time between merging the MR and deploying code "
"to the production environment. The data will be automatically added once you "
"deploy to production for the first time."
msgstr ""
+"Ð¡Ñ‚Ð°Ð´Ñ–Ñ Ð”Ð•Ð’ показує Ñ‡Ð°Ñ Ð¼Ñ–Ð¶ злиттÑм \"MR\" та розгортаннÑм коду у ПРОДакшин. "
+"Дані автоматично додаютьÑÑ Ð¿Ñ–ÑÐ»Ñ Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚Ð°Ð½Ð½Ñ Ñƒ ПРОДакшин вперше."
msgid ""
"The testing stage shows the time GitLab CI takes to run every pipeline for "
"the related merge request. The data will automatically be added after your "
"first pipeline finishes running."
msgstr ""
+"Ð¡Ñ‚Ð°Ð´Ñ–Ñ Ñ‚ÐµÑÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ð¾ÐºÐ°Ð·ÑƒÑ” чаÑ, Ñкий GitLab CI виконує Ð´Ð»Ñ Ð·Ð°Ð¿ÑƒÑку кожного "
+"конвеєра Ð´Ð»Ñ Ð²Ñ–Ð´Ð¿Ð¾Ð²Ñ–Ð´Ð½Ð¾Ð³Ð¾ запиту злиттÑ. Дані будуть автоматично додані "
+"піÑÐ»Ñ Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð½Ñ Ð¿ÐµÑ€ÑˆÐ¾Ð³Ð¾ конвеєра."
msgid "The time taken by each data entry gathered by that stage."
-msgstr ""
+msgstr "ЧаÑ, витрачений на кожен елемент, зібраний на цьому етапі."
msgid ""
"The value lying at the midpoint of a series of observed values. E.g., "
"between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 ="
" 6."
msgstr ""
+"Середнє Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð² Ñ€Ñдку. Приклад: між 3, 5, 9, Ñередніми 5, між 3, 5, 7, 8, "
+"Ñередніми (5 + 7) / 2 = 6."
msgid ""
"This means you can not push code until you create an empty repository or "
@@ -963,10 +979,10 @@ msgstr ""
"репозиторій або ÐЕ імпортуєте Ñ–Ñнуючий."
msgid "Time before an issue gets scheduled"
-msgstr ""
+msgstr "Ð§Ð°Ñ Ð´Ð¾ початку потраплÑÐ½Ð½Ñ Ð¿Ñ€Ð¾Ð±Ð»ÐµÐ¼Ð¸ в планувальник"
msgid "Time before an issue starts implementation"
-msgstr ""
+msgstr "Ð§Ð°Ñ Ð´Ð¾ початку роботи над проблемою"
msgid "Time between merge request creation and merge/close"
msgstr "Ð§Ð°Ñ Ð¼Ñ–Ð¶ ÑтвореннÑм запиту Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ñ– злиттÑм або закриттÑм"
@@ -1047,7 +1063,7 @@ msgid "Timeago|a year ago"
msgstr "рік тому"
msgid "Timeago|about %s hours ago"
-msgstr "Близько %s годин тому"
+msgstr "Близько %s годин(и) тому"
msgid "Timeago|about a minute ago"
msgstr "Близько хвилини тому"
@@ -1145,6 +1161,9 @@ msgstr "Приватний"
msgid "VisibilityLevel|Public"
msgstr "Публічний"
+msgid "VisibilityLevel|Unknown"
+msgstr "Ðевідомий"
+
msgid "Want to see the data? Please ask an administrator for access."
msgstr "Хочете побачити дані? Будь лаÑка, попроÑить у адмініÑтратора доÑтуп."
@@ -1154,23 +1173,11 @@ msgstr "Ми не маємо доÑтатньо даних Ð´Ð»Ñ Ð¿Ð¾ÐºÐ°Ð·Ñƒ Ñ
msgid "Withdraw Access Request"
msgstr "СкаÑувати запит доÑтупу"
-msgid ""
-"You are going to remove %{group_name}.\n"
-"Removed groups CANNOT be restored!\n"
-"Are you ABSOLUTELY sure?"
-msgstr ""
-"Ви хочете видалити %{group_name}.\n"
-"Видалені групи ÐЕ МОЖÐРбуду відновити!\n"
-"Ви ÐБСОЛЮТÐО впевнені?"
+msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?"
+msgstr "Ви хочете видалити %{group_name}. Видалені групи ÐЕ МОЖÐРбуду відновити! Ви ÐБСОЛЮТÐО впевнені?"
-msgid ""
-"You are going to remove %{project_name_with_namespace}.\n"
-"Removed project CANNOT be restored!\n"
-"Are you ABSOLUTELY sure?"
-msgstr ""
-"Ви хочете видалити %{project_name_with_namespace}.\n"
-"Видалений проект ÐЕ МОЖЕ бути відновлений!\n"
-"Ви ÐБСОЛЮТÐО впевнені?"
+msgid "You are going to remove %{project_name_with_namespace}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?"
+msgstr "Ви хочете видалити %{project_name_with_namespace}. Видалений проект ÐЕ МОЖЕ бути відновлений! Ви ÐБСОЛЮТÐО впевнені?"
msgid ""
"You are going to remove the fork relationship to source project "
diff --git a/locale/zh_CN/gitlab.po b/locale/zh_CN/gitlab.po
index f471e7def25..a3d0027212c 100644
--- a/locale/zh_CN/gitlab.po
+++ b/locale/zh_CN/gitlab.po
@@ -1089,6 +1089,9 @@ msgstr "ç§æœ‰"
msgid "VisibilityLevel|Public"
msgstr "公开"
+msgid "VisibilityLevel|Unknown"
+msgstr "未知"
+
msgid "Want to see the data? Please ask an administrator for access."
msgstr "æƒé™ä¸è¶³ã€‚如需查看相关数æ®ï¼Œè¯·å‘管ç†å‘˜ç”³è¯·æƒé™ã€‚"
@@ -1098,18 +1101,10 @@ msgstr "该阶段的数æ®ä¸è¶³ï¼Œæ— æ³•æ˜¾ç¤ºã€‚"
msgid "Withdraw Access Request"
msgstr "å–消æƒé™ç”³è¯·"
-msgid ""
-"You are going to remove %{group_name}.\n"
-"Removed groups CANNOT be restored!\n"
-"Are you ABSOLUTELY sure?"
-msgstr "å³å°†åˆ é™¤ %{group_name}。\n"
-"已删除的群组无法æ¢å¤ï¼\n"
-"确定继续å—?"
+msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?"
+msgstr "å³å°†åˆ é™¤ %{group_name}。已删除的群组无法æ¢å¤ï¼ç¡®å®šç»§ç»­å—?"
-msgid ""
-"You are going to remove %{project_name_with_namespace}.\n"
-"Removed project CANNOT be restored!\n"
-"Are you ABSOLUTELY sure?"
+msgid "You are going to remove %{project_name_with_namespace}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?"
msgstr "å³å°†è¦åˆ é™¤ %{project_name_with_namespace}。已删除的项目无法æ¢å¤ï¼ç¡®å®šç»§ç»­å—?"
msgid ""
diff --git a/locale/zh_HK/gitlab.po b/locale/zh_HK/gitlab.po
index 1b7c39f8f62..f4d33862a36 100644
--- a/locale/zh_HK/gitlab.po
+++ b/locale/zh_HK/gitlab.po
@@ -1088,6 +1088,9 @@ msgstr "ç§æœ‰"
msgid "VisibilityLevel|Public"
msgstr "公開"
+msgid "VisibilityLevel|Unknown"
+msgstr "未知"
+
msgid "Want to see the data? Please ask an administrator for access."
msgstr "權é™ä¸è¶³ã€‚如需查看相關數據,請å‘管ç†å“¡ç”³è«‹æ¬Šé™ã€‚"
@@ -1097,18 +1100,10 @@ msgstr "該階段的數據ä¸è¶³ï¼Œç„¡æ³•é¡¯ç¤ºã€‚"
msgid "Withdraw Access Request"
msgstr "å–消權é™ç”³è¯·"
-msgid ""
-"You are going to remove %{group_name}.\n"
-"Removed groups CANNOT be restored!\n"
-"Are you ABSOLUTELY sure?"
-msgstr "å³å°‡åˆªé™¤ %{group_name}。\n"
-"已刪除的群組無法æ¢å¾©ï¼\n"
-"確定繼續嗎?"
+msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?"
+msgstr "å³å°‡åˆªé™¤ %{group_name}。已刪除的群組無法æ¢å¾©ï¼ç¢ºå®šç¹¼çºŒå—Žï¼Ÿ"
-msgid ""
-"You are going to remove %{project_name_with_namespace}.\n"
-"Removed project CANNOT be restored!\n"
-"Are you ABSOLUTELY sure?"
+msgid "You are going to remove %{project_name_with_namespace}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?"
msgstr "å³å°‡è¦åˆªé™¤ %{project_name_with_namespace}。已刪除的項目無法æ¢è¤‡ï¼ç¢ºå®šç¹¼çºŒå—Žï¼Ÿ"
msgid ""
diff --git a/locale/zh_TW/gitlab.po b/locale/zh_TW/gitlab.po
index af663275602..205d4712316 100644
--- a/locale/zh_TW/gitlab.po
+++ b/locale/zh_TW/gitlab.po
@@ -12,8 +12,8 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language-Team: Chinese (Taiwan) (https://translate.zanata.org/project/view/GitLab)\n"
-"PO-Revision-Date: 2017-07-20 09:50-0400\n"
-"Last-Translator: Lin Jen-Shin <godfat@godfat.org>\n"
+"PO-Revision-Date: 2017-08-07 03:30-0400\n"
+"Last-Translator: Huang Tao <htve@outlook.com>\n"
"Language: zh-TW\n"
"X-Generator: Zanata 3.9.6\n"
"Plural-Forms: nplurals=1; plural=0\n"
@@ -1099,6 +1099,9 @@ msgstr "ç§æœ‰"
msgid "VisibilityLevel|Public"
msgstr "公開"
+msgid "VisibilityLevel|Unknown"
+msgstr "ä¸æ˜Ž"
+
msgid "Want to see the data? Please ask an administrator for access."
msgstr "權é™ä¸è¶³ã€‚如需查看相關資料,請å‘管ç†å“¡ç”³è«‹æ¬Šé™ã€‚"
@@ -1108,18 +1111,10 @@ msgstr "因該階段的資料ä¸è¶³è€Œç„¡æ³•é¡¯ç¤ºç›¸é—œè³‡è¨Š"
msgid "Withdraw Access Request"
msgstr "å–消權é™ç”³è«‹"
-msgid ""
-"You are going to remove %{group_name}.\n"
-"Removed groups CANNOT be restored!\n"
-"Are you ABSOLUTELY sure?"
-msgstr "å³å°‡è¦åˆªé™¤ %{group_name}。\n"
-"被刪除的群組完全無法救回來喔ï¼\n"
-"真的「100%確定ã€è¦é€™éº¼åšå—Žï¼Ÿ"
+msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?"
+msgstr "å³å°‡è¦åˆªé™¤ %{group_name}。被刪除的群組完全無法救回來喔ï¼çœŸçš„「100%確定ã€è¦é€™éº¼åšå—Žï¼Ÿ"
-msgid ""
-"You are going to remove %{project_name_with_namespace}.\n"
-"Removed project CANNOT be restored!\n"
-"Are you ABSOLUTELY sure?"
+msgid "You are going to remove %{project_name_with_namespace}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?"
msgstr "å³å°‡è¦åˆªé™¤ %{project_name_with_namespace}。被刪除的專案完全無法救回來喔ï¼çœŸçš„「100%確定ã€è¦é€™éº¼åšå—Žï¼Ÿ"
msgid ""
diff --git a/package.json b/package.json
index 6b6577ec06e..c5247a63e67 100644
--- a/package.json
+++ b/package.json
@@ -12,14 +12,16 @@
"webpack-prod": "NODE_ENV=production webpack --config config/webpack.config.js"
},
"dependencies": {
+ "axios": "^0.16.2",
"babel-core": "^6.22.1",
"babel-eslint": "^7.2.1",
- "babel-loader": "^6.2.10",
+ "babel-loader": "^7.1.1",
"babel-plugin-transform-define": "^1.2.0",
"babel-preset-latest": "^6.24.0",
"babel-preset-stage-2": "^6.22.0",
"bootstrap-sass": "^3.3.6",
"compression-webpack-plugin": "^0.3.2",
+ "copy-webpack-plugin": "^4.0.1",
"core-js": "^2.4.1",
"cropper": "^2.3.0",
"css-loader": "^0.28.0",
@@ -31,6 +33,7 @@
"eslint-plugin-html": "^2.0.1",
"exports-loader": "^0.6.4",
"file-loader": "^0.11.1",
+ "imports-loader": "^0.7.1",
"jed": "^1.1.1",
"jquery": "^2.2.1",
"jquery-ujs": "^1.2.1",
@@ -38,9 +41,9 @@
"jszip": "^3.1.3",
"jszip-utils": "^0.0.2",
"marked": "^0.3.6",
+ "monaco-editor": "0.8.3",
"mousetrap": "^1.4.6",
"name-all-modules-plugin": "^1.0.1",
- "pdfjs-dist": "^1.8.252",
"pikaday": "^1.5.1",
"prismjs": "^1.6.0",
"raphael": "^2.2.7",
@@ -49,7 +52,6 @@
"react-dev-utils": "^0.5.2",
"select2": "3.5.2-browserify",
"sql.js": "^0.4.0",
- "stats-webpack-plugin": "^0.4.3",
"three": "^0.84.0",
"three-orbit-controls": "^82.1.0",
"three-stl-loader": "^1.0.4",
@@ -61,14 +63,15 @@
"vue-loader": "^11.3.4",
"vue-resource": "^1.3.4",
"vue-template-compiler": "^2.2.6",
- "webpack": "^2.6.1",
- "webpack-bundle-analyzer": "^2.8.2"
+ "webpack": "^3.4.0",
+ "webpack-bundle-analyzer": "^2.8.2",
+ "webpack-stats-plugin": "^0.1.5"
},
"devDependencies": {
"babel-plugin-istanbul": "^4.0.0",
"eslint": "^3.10.1",
"eslint-config-airbnb-base": "^10.0.1",
- "eslint-import-resolver-webpack": "^0.8.1",
+ "eslint-import-resolver-webpack": "^0.8.3",
"eslint-plugin-filenames": "^1.1.0",
"eslint-plugin-import": "^2.2.0",
"eslint-plugin-jasmine": "^2.1.0",
@@ -82,8 +85,8 @@
"karma-jasmine": "^1.1.0",
"karma-mocha-reporter": "^2.2.2",
"karma-sourcemap-loader": "^0.3.7",
- "karma-webpack": "^2.0.2",
+ "karma-webpack": "^2.0.4",
"nodemon": "^1.11.0",
- "webpack-dev-server": "^2.4.2"
+ "webpack-dev-server": "^2.6.1"
}
}
diff --git a/rubocop/cop/migration/safer_boolean_column.rb b/rubocop/cop/migration/safer_boolean_column.rb
new file mode 100644
index 00000000000..0335c25d85d
--- /dev/null
+++ b/rubocop/cop/migration/safer_boolean_column.rb
@@ -0,0 +1,94 @@
+require_relative '../../migration_helpers'
+
+module RuboCop
+ module Cop
+ module Migration
+ # This cop requires a default value and disallows nulls for boolean
+ # columns on small tables.
+ #
+ # In general, this prevents 3-state-booleans.
+ # https://robots.thoughtbot.com/avoid-the-threestate-boolean-problem
+ #
+ # In particular, for the `application_settings` table, this ensures that
+ # upgraded installations get a proper default for the new boolean setting.
+ # A developer might otherwise mistakenly assume that a value in
+ # `ApplicationSetting.defaults` is sufficient.
+ #
+ # See https://gitlab.com/gitlab-org/gitlab-ee/issues/2750 for more
+ # information.
+ class SaferBooleanColumn < RuboCop::Cop::Cop
+ include MigrationHelpers
+
+ DEFAULT_OFFENSE = 'Boolean columns on the `%s` table should have a default. You may wish to use `add_column_with_default`.'.freeze
+ NULL_OFFENSE = 'Boolean columns on the `%s` table should disallow nulls.'.freeze
+ DEFAULT_AND_NULL_OFFENSE = 'Boolean columns on the `%s` table should have a default and should disallow nulls. You may wish to use `add_column_with_default`.'.freeze
+
+ SMALL_TABLES = %i[
+ application_settings
+ ].freeze
+
+ def_node_matcher :add_column?, <<~PATTERN
+ (send nil :add_column $...)
+ PATTERN
+
+ def on_send(node)
+ return unless in_migration?(node)
+
+ matched = add_column?(node)
+
+ return unless matched
+
+ table, _, type = matched.to_a.take(3).map(&:children).map(&:first)
+ opts = matched[3]
+
+ return unless SMALL_TABLES.include?(table) && type == :boolean
+
+ no_default = no_default?(opts)
+ nulls_allowed = nulls_allowed?(opts)
+
+ offense = if no_default && nulls_allowed
+ DEFAULT_AND_NULL_OFFENSE
+ elsif no_default
+ DEFAULT_OFFENSE
+ elsif nulls_allowed
+ NULL_OFFENSE
+ end
+
+ add_offense(node, :expression, format(offense, table)) if offense
+ end
+
+ def no_default?(opts)
+ return true unless opts
+
+ each_hash_node_pair(opts) do |key, value|
+ return value == 'nil' if key == :default
+ end
+ end
+
+ def nulls_allowed?(opts)
+ return true unless opts
+
+ each_hash_node_pair(opts) do |key, value|
+ return value != 'false' if key == :null
+ end
+ end
+
+ def each_hash_node_pair(hash_node, &block)
+ hash_node.each_node(:pair) do |pair|
+ key = hash_pair_key(pair)
+ value = hash_pair_value(pair)
+ yield(key, value)
+ end
+ end
+
+ def hash_pair_key(pair)
+ pair.children[0].children[0]
+ end
+
+ def hash_pair_value(pair)
+ pair.children[1].source
+ end
+ end
+ end
+ end
+end
diff --git a/rubocop/rubocop.rb b/rubocop/rubocop.rb
index 3fbd5b0163c..1b6e8991a17 100644
--- a/rubocop/rubocop.rb
+++ b/rubocop/rubocop.rb
@@ -13,6 +13,7 @@ require_relative 'cop/migration/add_concurrent_index'
require_relative 'cop/migration/add_index'
require_relative 'cop/migration/add_timestamps'
require_relative 'cop/migration/datetime'
+require_relative 'cop/migration/safer_boolean_column'
require_relative 'cop/migration/hash_index'
require_relative 'cop/migration/remove_concurrent_index'
require_relative 'cop/migration/remove_index'
diff --git a/scripts/detect-new-flaky-examples b/scripts/detect-new-flaky-examples
new file mode 100755
index 00000000000..3bee4f9a34b
--- /dev/null
+++ b/scripts/detect-new-flaky-examples
@@ -0,0 +1,21 @@
+#!/usr/bin/env ruby
+
+require 'json'
+
+report_file = ARGV.shift
+unless report_file
+ puts 'usage: detect-new-flaky-examples <report-file>'
+ exit 1
+end
+
+puts "Loading #{report_file}..."
+report = JSON.parse(File.read(report_file))
+
+if report.any?
+ puts "New flaky examples were detected!\n"
+ puts JSON.pretty_generate(report)
+ exit 1
+else
+ puts "No new flaky examples detected.\n"
+ exit 0
+end
diff --git a/scripts/gitaly-test-build b/scripts/gitaly-test-build
index 44d314009e2..95d9fe0f176 100755
--- a/scripts/gitaly-test-build
+++ b/scripts/gitaly-test-build
@@ -1,5 +1,7 @@
#!/usr/bin/env ruby
+require 'fileutils'
+
# This script assumes tmp/tests/gitaly already contains the correct
# Gitaly version. We just have to compile it and run its 'bundle
# install'. We have this separate script for that because weird things
@@ -7,4 +9,11 @@
# called 'bundle install' using a different Gemfile, as happens with
# gitlab-ce and gitaly.
-abort 'gitaly build failed' unless system('make', chdir: 'tmp/tests/gitaly')
+dir = 'tmp/tests/gitaly'
+
+abort 'gitaly build failed' unless system('make', chdir: dir)
+
+# Make the 'gitaly' executable look newer than 'GITALY_SERVER_VERSION'.
+# Without this a gitaly executable created in the setup-test-env job
+# will look stale compared to GITALY_SERVER_VERSION.
+FileUtils.touch(File.join(dir, 'gitaly'), mtime: Time.now + (1 << 24))
diff --git a/scripts/merge-reports b/scripts/merge-reports
index aad76bcc327..3a421f1f1fc 100755
--- a/scripts/merge-reports
+++ b/scripts/merge-reports
@@ -4,7 +4,7 @@ require 'json'
main_report_file = ARGV.shift
unless main_report_file
- puts 'usage: merge_reports <main-report> [extra reports...]'
+ puts 'usage: merge-reports <main-report> [extra reports...]'
exit 1
end
diff --git a/spec/bin/changelog_spec.rb b/spec/bin/changelog_spec.rb
index 91aff0db7cc..6d8b9865dcb 100644
--- a/spec/bin/changelog_spec.rb
+++ b/spec/bin/changelog_spec.rb
@@ -4,56 +4,90 @@ load File.expand_path('../../bin/changelog', __dir__)
describe 'bin/changelog' do
describe ChangelogOptionParser do
- it 'parses --ammend' do
- options = described_class.parse(%w[foo bar --amend])
+ describe '.parse' do
+ it 'parses --amend' do
+ options = described_class.parse(%w[foo bar --amend])
- expect(options.amend).to eq true
- end
+ expect(options.amend).to eq true
+ end
- it 'parses --force and -f' do
- %w[--force -f].each do |flag|
- options = described_class.parse(%W[foo #{flag} bar])
+ it 'parses --force and -f' do
+ %w[--force -f].each do |flag|
+ options = described_class.parse(%W[foo #{flag} bar])
- expect(options.force).to eq true
+ expect(options.force).to eq true
+ end
end
- end
- it 'parses --merge-request and -m' do
- %w[--merge-request -m].each do |flag|
- options = described_class.parse(%W[foo #{flag} 1234 bar])
+ it 'parses --merge-request and -m' do
+ %w[--merge-request -m].each do |flag|
+ options = described_class.parse(%W[foo #{flag} 1234 bar])
- expect(options.merge_request).to eq 1234
+ expect(options.merge_request).to eq 1234
+ end
end
- end
- it 'parses --dry-run and -n' do
- %w[--dry-run -n].each do |flag|
- options = described_class.parse(%W[foo #{flag} bar])
+ it 'parses --dry-run and -n' do
+ %w[--dry-run -n].each do |flag|
+ options = described_class.parse(%W[foo #{flag} bar])
- expect(options.dry_run).to eq true
+ expect(options.dry_run).to eq true
+ end
end
- end
- it 'parses --git-username and -u' do
- allow(described_class).to receive(:git_user_name).and_return('Jane Doe')
+ it 'parses --git-username and -u' do
+ allow(described_class).to receive(:git_user_name).and_return('Jane Doe')
- %w[--git-username -u].each do |flag|
- options = described_class.parse(%W[foo #{flag} bar])
+ %w[--git-username -u].each do |flag|
+ options = described_class.parse(%W[foo #{flag} bar])
- expect(options.author).to eq 'Jane Doe'
+ expect(options.author).to eq 'Jane Doe'
+ end
+ end
+
+ it 'parses --type and -t' do
+ %w[--type -t].each do |flag|
+ options = described_class.parse(%W[foo #{flag} security])
+
+ expect(options.type).to eq 'security'
+ end
end
- end
- it 'parses -h' do
- expect do
- expect { described_class.parse(%w[foo -h bar]) }.to output.to_stdout
- end.to raise_error(SystemExit)
+ it 'parses -h' do
+ expect do
+ expect { described_class.parse(%w[foo -h bar]) }.to output.to_stdout
+ end.to raise_error(SystemExit)
+ end
+
+ it 'assigns title' do
+ options = described_class.parse(%W[foo -m 1 bar\n -u baz\r\n --amend])
+
+ expect(options.title).to eq 'foo bar baz'
+ end
end
- it 'assigns title' do
- options = described_class.parse(%W[foo -m 1 bar\n -u baz\r\n --amend])
+ describe '.read_type' do
+ let(:type) { '1' }
- expect(options.title).to eq 'foo bar baz'
+ it 'reads type from $stdin' do
+ expect($stdin).to receive(:getc).and_return(type)
+ expect do
+ expect(described_class.read_type).to eq('added')
+ end.to output.to_stdout
+ end
+
+ context 'invalid type given' do
+ let(:type) { '99' }
+
+ it 'shows error message and exits the program' do
+ allow($stdin).to receive(:getc).and_return(type)
+ expect do
+ expect do
+ expect { described_class.read_type }.to raise_error(SystemExit)
+ end.to output("Invalid category index, please select an index between 1 and 7\n").to_stderr
+ end.to output.to_stdout
+ end
+ end
end
end
end
diff --git a/spec/config/mail_room_spec.rb b/spec/config/mail_room_spec.rb
index a31e44fa928..74634dac713 100644
--- a/spec/config/mail_room_spec.rb
+++ b/spec/config/mail_room_spec.rb
@@ -20,12 +20,12 @@ describe 'mail_room.yml' do
YAML.load(output)
end
- before(:each) do
+ before do
stub_env('GITLAB_REDIS_QUEUES_CONFIG_FILE', absolute_path(queues_config_path))
clear_queues_raw_config
end
- after(:each) do
+ after do
clear_queues_raw_config
end
diff --git a/spec/controllers/admin/health_check_controller_spec.rb b/spec/controllers/admin/health_check_controller_spec.rb
new file mode 100644
index 00000000000..0b8e0c8a065
--- /dev/null
+++ b/spec/controllers/admin/health_check_controller_spec.rb
@@ -0,0 +1,25 @@
+require 'spec_helper'
+
+describe Admin::HealthCheckController, broken_storage: true do
+ let(:admin) { create(:admin) }
+
+ before do
+ sign_in(admin)
+ end
+
+ describe 'GET show' do
+ it 'loads the git storage health information' do
+ get :show
+
+ expect(assigns[:failing_storage_statuses]).not_to be_nil
+ end
+ end
+
+ describe 'POST reset_storage_health' do
+ it 'resets all storage health information' do
+ expect(Gitlab::Git::Storage::CircuitBreaker).to receive(:reset_all!)
+
+ post :reset_storage_health
+ end
+ end
+end
diff --git a/spec/controllers/admin/projects_controller_spec.rb b/spec/controllers/admin/projects_controller_spec.rb
index 65587064eb1..373260b3978 100644
--- a/spec/controllers/admin/projects_controller_spec.rb
+++ b/spec/controllers/admin/projects_controller_spec.rb
@@ -12,12 +12,24 @@ describe Admin::ProjectsController do
it 'retrieves the project for the given visibility level' do
get :index, visibility_level: [Gitlab::VisibilityLevel::PUBLIC]
+
expect(response.body).to match(project.name)
end
it 'does not retrieve the project' do
get :index, visibility_level: [Gitlab::VisibilityLevel::INTERNAL]
+
expect(response.body).not_to match(project.name)
end
+
+ it 'does not respond with projects pending deletion' do
+ pending_delete_project = create(:project, pending_delete: true)
+
+ get :index
+
+ expect(response).to have_http_status(200)
+ expect(response.body).not_to match(pending_delete_project.name)
+ expect(response.body).to match(project.name)
+ end
end
end
diff --git a/spec/controllers/admin/users_controller_spec.rb b/spec/controllers/admin/users_controller_spec.rb
index 29c449d6aa9..3d21b695af4 100644
--- a/spec/controllers/admin/users_controller_spec.rb
+++ b/spec/controllers/admin/users_controller_spec.rb
@@ -127,7 +127,7 @@ describe Admin::UsersController do
describe 'POST create' do
it 'creates the user' do
- expect{ post :create, user: attributes_for(:user) }.to change{ User.count }.by(1)
+ expect { post :create, user: attributes_for(:user) }.to change { User.count }.by(1)
end
it 'shows only one error message for an invalid email' do
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb
index 1641bddea11..331903a5543 100644
--- a/spec/controllers/application_controller_spec.rb
+++ b/spec/controllers/application_controller_spec.rb
@@ -108,6 +108,30 @@ describe ApplicationController do
end
end
+ describe 'rescue from Gitlab::Git::Storage::Inaccessible' do
+ controller(described_class) do
+ def index
+ raise Gitlab::Git::Storage::Inaccessible.new('broken', 100)
+ end
+ end
+
+ it 'renders a 503 when storage is not available' do
+ sign_in(create(:user))
+
+ get :index
+
+ expect(response.status).to eq(503)
+ end
+
+ it 'renders includes a Retry-After header' do
+ sign_in(create(:user))
+
+ get :index
+
+ expect(response.headers['Retry-After']).to eq(100)
+ end
+ end
+
describe 'response format' do
controller(described_class) do
def index
diff --git a/spec/controllers/autocomplete_controller_spec.rb b/spec/controllers/autocomplete_controller_spec.rb
index 3c396e36b24..2fbab1e4040 100644
--- a/spec/controllers/autocomplete_controller_spec.rb
+++ b/spec/controllers/autocomplete_controller_spec.rb
@@ -1,8 +1,8 @@
require 'spec_helper'
describe AutocompleteController do
- let!(:project) { create(:project) }
- let!(:user) { create(:user) }
+ let(:project) { create(:project) }
+ let(:user) { project.owner }
context 'GET users' do
let!(:user2) { create(:user) }
@@ -11,7 +11,6 @@ describe AutocompleteController do
context 'project members' do
before do
sign_in(user)
- project.add_master(user)
end
describe 'GET #users with project ID' do
@@ -19,11 +18,11 @@ describe AutocompleteController do
get(:users, project_id: project.id)
end
- let(:body) { JSON.parse(response.body) }
-
- it { expect(body).to be_kind_of(Array) }
- it { expect(body.size).to eq 2 }
- it { expect(body.map { |u| u["username"] }).to include(user.username) }
+ it 'returns the project members' do
+ expect(json_response).to be_kind_of(Array)
+ expect(json_response.size).to eq(1)
+ expect(json_response.map { |u| u["username"] }).to include(user.username)
+ end
end
describe 'GET #users with unknown project' do
@@ -39,20 +38,20 @@ describe AutocompleteController do
let(:group) { create(:group) }
before do
- sign_in(user)
group.add_owner(user)
+ sign_in(user)
end
- let(:body) { JSON.parse(response.body) }
-
describe 'GET #users with group ID' do
before do
get(:users, group_id: group.id)
end
- it { expect(body).to be_kind_of(Array) }
- it { expect(body.size).to eq 1 }
- it { expect(body.first["username"]).to eq user.username }
+ it 'returns the group members' do
+ expect(json_response).to be_kind_of(Array)
+ expect(json_response.size).to eq(1)
+ expect(json_response.first["username"]).to eq user.username
+ end
end
describe 'GET #users with unknown group ID' do
@@ -65,23 +64,22 @@ describe AutocompleteController do
end
context 'non-member login for public project' do
- let!(:project) { create(:project, :public) }
+ let(:project) { create(:project, :public) }
before do
sign_in(non_member)
- project.add_master(user)
end
- let(:body) { JSON.parse(response.body) }
-
describe 'GET #users with project ID' do
before do
get(:users, project_id: project.id, current_user: true)
end
- it { expect(body).to be_kind_of(Array) }
- it { expect(body.size).to eq 3 }
- it { expect(body.map { |u| u['username'] }).to include(user.username, non_member.username) }
+ it 'returns the project members and non-members' do
+ expect(json_response).to be_kind_of(Array)
+ expect(json_response.size).to eq(2)
+ expect(json_response.map { |u| u['username'] }).to include(user.username, non_member.username)
+ end
end
end
@@ -91,10 +89,8 @@ describe AutocompleteController do
get(:users)
end
- let(:body) { JSON.parse(response.body) }
-
- it { expect(body).to be_kind_of(Array) }
- it { expect(body.size).to eq User.count }
+ it { expect(json_response).to be_kind_of(Array) }
+ it { expect(json_response.size).to eq User.count }
end
context 'user order' do
@@ -106,7 +102,7 @@ describe AutocompleteController do
sign_in(user)
get(:users, search: 'user')
- response_usernames = JSON.parse(response.body).map { |user| user['username'] }
+ response_usernames = json_response.map { |user| user['username'] }
expect(response_usernames.take(3)).to match_array([user.username, reported_user.username, user1.username])
end
@@ -120,15 +116,12 @@ describe AutocompleteController do
get(:users, per_page: per_page)
end
- let(:body) { JSON.parse(response.body) }
-
- it { expect(body).to be_kind_of(Array) }
- it { expect(body.size).to eq per_page }
+ it { expect(json_response).to be_kind_of(Array) }
+ it { expect(json_response.size).to eq(per_page) }
end
context 'unauthenticated user' do
let(:public_project) { create(:project, :public) }
- let(:body) { JSON.parse(response.body) }
describe 'GET #users with public project' do
before do
@@ -136,8 +129,8 @@ describe AutocompleteController do
get(:users, project_id: public_project.id)
end
- it { expect(body).to be_kind_of(Array) }
- it { expect(body.size).to eq 2 }
+ it { expect(json_response).to be_kind_of(Array) }
+ it { expect(json_response.size).to eq 2 }
end
describe 'GET #users with project' do
@@ -170,8 +163,8 @@ describe AutocompleteController do
get(:users)
end
- it { expect(body).to be_kind_of(Array) }
- it { expect(body.size).to eq 0 }
+ it { expect(json_response).to be_kind_of(Array) }
+ it { expect(json_response).to be_empty }
end
describe 'GET #users with todo filter' do
@@ -179,14 +172,12 @@ describe AutocompleteController do
get :users, todo_filter: true
expect(response.status).to eq 200
- expect(body).to be_kind_of(Array)
+ expect(json_response).to be_kind_of(Array)
end
end
end
context 'author of issuable included' do
- let(:body) { JSON.parse(response.body) }
-
context 'authenticated' do
before do
sign_in(user)
@@ -195,13 +186,13 @@ describe AutocompleteController do
it 'includes the author' do
get(:users, author_id: non_member.id)
- expect(body.first["username"]).to eq non_member.username
+ expect(json_response.first["username"]).to eq non_member.username
end
it 'rejects non existent user ids' do
get(:users, author_id: 99999)
- expect(body.collect { |u| u['id'] }).not_to include(99999)
+ expect(json_response.collect { |u| u['id'] }).not_to include(99999)
end
end
@@ -209,7 +200,7 @@ describe AutocompleteController do
it 'returns empty result' do
get(:users, author_id: non_member.id)
- expect(body).to be_empty
+ expect(json_response).to be_empty
end
end
end
@@ -222,10 +213,9 @@ describe AutocompleteController do
it 'skips the user IDs passed' do
get(:users, skip_users: [user, user2].map(&:id))
- other_user_ids = [non_member, project.owner, project.creator].map(&:id)
- response_user_ids = JSON.parse(response.body).map { |user| user['id'] }
+ response_user_ids = json_response.map { |user| user['id'] }
- expect(response_user_ids).to contain_exactly(*other_user_ids)
+ expect(response_user_ids).to contain_exactly(non_member.id)
end
end
end
@@ -249,17 +239,15 @@ describe AutocompleteController do
get(:projects, project_id: project.id)
end
- let(:body) { JSON.parse(response.body) }
-
- it do
- expect(body).to be_kind_of(Array)
- expect(body.size).to eq 2
+ it 'returns projects' do
+ expect(json_response).to be_kind_of(Array)
+ expect(json_response.size).to eq(2)
- expect(body.first['id']).to eq 0
- expect(body.first['name_with_namespace']).to eq 'No project'
+ expect(json_response.first['id']).to eq(0)
+ expect(json_response.first['name_with_namespace']).to eq 'No project'
- expect(body.last['id']).to eq authorized_project.id
- expect(body.last['name_with_namespace']).to eq authorized_project.name_with_namespace
+ expect(json_response.last['id']).to eq authorized_project.id
+ expect(json_response.last['name_with_namespace']).to eq authorized_project.name_with_namespace
end
end
end
@@ -275,14 +263,12 @@ describe AutocompleteController do
get(:projects, project_id: project.id, search: 'rugged')
end
- let(:body) { JSON.parse(response.body) }
+ it 'returns projects' do
+ expect(json_response).to be_kind_of(Array)
+ expect(json_response.size).to eq(2)
- it do
- expect(body).to be_kind_of(Array)
- expect(body.size).to eq 2
-
- expect(body.last['id']).to eq authorized_search_project.id
- expect(body.last['name_with_namespace']).to eq authorized_search_project.name_with_namespace
+ expect(json_response.last['id']).to eq authorized_search_project.id
+ expect(json_response.last['name_with_namespace']).to eq authorized_search_project.name_with_namespace
end
end
end
@@ -304,11 +290,9 @@ describe AutocompleteController do
get(:projects, project_id: project.id)
end
- let(:body) { JSON.parse(response.body) }
-
- it do
- expect(body).to be_kind_of(Array)
- expect(body.size).to eq 3 # Of a total of 4
+ it 'returns projects' do
+ expect(json_response).to be_kind_of(Array)
+ expect(json_response.size).to eq 3 # Of a total of 4
end
end
end
@@ -328,17 +312,15 @@ describe AutocompleteController do
get(:projects, project_id: project.id, offset_id: authorized_project.id)
end
- let(:body) { JSON.parse(response.body) }
-
- it do
- expect(body.detect { |item| item['id'] == 0 }).to be_nil # 'No project' is not there
- expect(body.detect { |item| item['id'] == authorized_project.id }).to be_nil # Offset project is not there either
+ it 'returns "No project"' do
+ expect(json_response.detect { |item| item['id'] == 0 }).to be_nil # 'No project' is not there
+ expect(json_response.detect { |item| item['id'] == authorized_project.id }).to be_nil # Offset project is not there either
end
end
end
context 'authorized projects without admin_issue ability' do
- before(:each) do
+ before do
authorized_project.add_guest(user)
expect(user.can?(:admin_issue, authorized_project)).to eq(false)
@@ -349,13 +331,10 @@ describe AutocompleteController do
get(:projects, project_id: project.id)
end
- let(:body) { JSON.parse(response.body) }
-
- it do
- expect(body).to be_kind_of(Array)
- expect(body.size).to eq 1 # 'No project'
-
- expect(body.first['id']).to eq 0
+ it 'returns a single "No project"' do
+ expect(json_response).to be_kind_of(Array)
+ expect(json_response.size).to eq(1) # 'No project'
+ expect(json_response.first['id']).to eq 0
end
end
end
diff --git a/spec/controllers/invites_controller_spec.rb b/spec/controllers/invites_controller_spec.rb
index e478a253b3f..e00403118a0 100644
--- a/spec/controllers/invites_controller_spec.rb
+++ b/spec/controllers/invites_controller_spec.rb
@@ -24,7 +24,7 @@ describe InvitesController do
describe 'GET #decline' do
it 'declines user' do
get :decline, id: token
- expect{member.reload}.to raise_error ActiveRecord::RecordNotFound
+ expect {member.reload}.to raise_error ActiveRecord::RecordNotFound
expect(response).to have_http_status(302)
expect(flash[:notice]).to include 'You have declined the invitation to join'
diff --git a/spec/controllers/projects/blob_controller_spec.rb b/spec/controllers/projects/blob_controller_spec.rb
index 59f33197e8f..64b9af7b845 100644
--- a/spec/controllers/projects/blob_controller_spec.rb
+++ b/spec/controllers/projects/blob_controller_spec.rb
@@ -35,6 +35,26 @@ describe Projects::BlobController do
end
end
+ context 'with file path and JSON format' do
+ context "valid branch, valid file" do
+ let(:id) { 'master/README.md' }
+
+ before do
+ get(:show,
+ namespace_id: project.namespace,
+ project_id: project,
+ id: id,
+ format: :json)
+ end
+
+ it do
+ expect(response).to be_ok
+ expect(json_response).to have_key 'html'
+ expect(json_response).to have_key 'raw_path'
+ end
+ end
+ end
+
context 'with tree path' do
before do
get(:show,
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index bdee3894a13..b571b11dcac 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -1,7 +1,7 @@
require('spec_helper')
describe Projects::IssuesController do
- let(:project) { create(:project_empty_repo) }
+ let(:project) { create(:project) }
let(:user) { create(:user) }
let(:issue) { create(:issue, project: project) }
@@ -292,13 +292,13 @@ describe Projects::IssuesController do
it 'rejects an issue recognized as a spam' do
expect(Gitlab::Recaptcha).to receive(:load_configurations!).and_return(true)
- expect { update_spam_issue }.not_to change{ issue.reload.title }
+ expect { update_spam_issue }.not_to change { issue.reload.title }
end
it 'rejects an issue recognized as a spam when recaptcha disabled' do
stub_application_setting(recaptcha_enabled: false)
- expect { update_spam_issue }.not_to change{ issue.reload.title }
+ expect { update_spam_issue }.not_to change { issue.reload.title }
end
it 'creates a spam log' do
@@ -358,7 +358,7 @@ describe Projects::IssuesController do
end
it 'accepts an issue after recaptcha is verified' do
- expect{ update_verified_issue }.to change{ issue.reload.title }.to(spammy_title)
+ expect { update_verified_issue }.to change { issue.reload.title }.to(spammy_title)
end
it 'marks spam log as recaptcha_verified' do
@@ -841,7 +841,7 @@ describe Projects::IssuesController do
describe 'POST #toggle_award_emoji' do
before do
sign_in(user)
- project.team << [user, :developer]
+ project.add_developer(user)
end
it "toggles the award emoji" do
@@ -855,6 +855,8 @@ describe Projects::IssuesController do
end
describe 'POST create_merge_request' do
+ let(:project) { create(:project, :repository) }
+
before do
project.add_developer(user)
sign_in(user)
diff --git a/spec/controllers/projects/repositories_controller_spec.rb b/spec/controllers/projects/repositories_controller_spec.rb
index 9c55d159fa0..f712d1e0d63 100644
--- a/spec/controllers/projects/repositories_controller_spec.rb
+++ b/spec/controllers/projects/repositories_controller_spec.rb
@@ -6,7 +6,7 @@ describe Projects::RepositoriesController do
describe "GET archive" do
context 'as a guest' do
it 'responds with redirect in correct format' do
- get :archive, namespace_id: project.namespace, project_id: project, format: "zip"
+ get :archive, namespace_id: project.namespace, project_id: project, format: "zip", ref: 'master'
expect(response.header["Content-Type"]).to start_with('text/html')
expect(response).to be_redirect
diff --git a/spec/controllers/projects/todos_controller_spec.rb b/spec/controllers/projects/todos_controller_spec.rb
index 974330e2bbd..41d211ed1bb 100644
--- a/spec/controllers/projects/todos_controller_spec.rb
+++ b/spec/controllers/projects/todos_controller_spec.rb
@@ -67,7 +67,7 @@ describe Projects::TodosController do
end
it "doesn't create todo" do
- expect{ go }.not_to change { user.todos.count }
+ expect { go }.not_to change { user.todos.count }
expect(response).to have_http_status(404)
end
end
@@ -135,7 +135,7 @@ describe Projects::TodosController do
end
it "doesn't create todo" do
- expect{ go }.not_to change { user.todos.count }
+ expect { go }.not_to change { user.todos.count }
expect(response).to have_http_status(404)
end
end
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index 34095ef6250..c0e48046937 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -107,6 +107,20 @@ describe ProjectsController do
end
end
+ context 'when the storage is not available', broken_storage: true do
+ let(:project) { create(:project, :broken_storage) }
+ before do
+ project.add_developer(user)
+ sign_in(user)
+ end
+
+ it 'renders a 503' do
+ get :show, namespace_id: project.namespace, id: project
+
+ expect(response).to have_http_status(503)
+ end
+ end
+
context "project with empty repo" do
let(:empty_project) { create(:project_empty_repo, :public) }
@@ -564,6 +578,118 @@ describe ProjectsController do
end
end
+ describe '#export' do
+ before do
+ sign_in(user)
+
+ project.add_master(user)
+ end
+
+ context 'when project export is enabled' do
+ it 'returns 302' do
+ get :export, namespace_id: project.namespace, id: project
+
+ expect(response).to have_http_status(302)
+ end
+ end
+
+ context 'when project export is disabled' do
+ before do
+ stub_application_setting(project_export_enabled?: false)
+ end
+
+ it 'returns 404' do
+ get :export, namespace_id: project.namespace, id: project
+
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
+
+ describe '#download_export' do
+ before do
+ sign_in(user)
+
+ project.add_master(user)
+ end
+
+ context 'when project export is enabled' do
+ it 'returns 302' do
+ get :download_export, namespace_id: project.namespace, id: project
+
+ expect(response).to have_http_status(302)
+ end
+ end
+
+ context 'when project export is disabled' do
+ before do
+ stub_application_setting(project_export_enabled?: false)
+ end
+
+ it 'returns 404' do
+ get :download_export, namespace_id: project.namespace, id: project
+
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
+
+ describe '#remove_export' do
+ before do
+ sign_in(user)
+
+ project.add_master(user)
+ end
+
+ context 'when project export is enabled' do
+ it 'returns 302' do
+ post :remove_export, namespace_id: project.namespace, id: project
+
+ expect(response).to have_http_status(302)
+ end
+ end
+
+ context 'when project export is disabled' do
+ before do
+ stub_application_setting(project_export_enabled?: false)
+ end
+
+ it 'returns 404' do
+ post :remove_export, namespace_id: project.namespace, id: project
+
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
+
+ describe '#generate_new_export' do
+ before do
+ sign_in(user)
+
+ project.add_master(user)
+ end
+
+ context 'when project export is enabled' do
+ it 'returns 302' do
+ post :generate_new_export, namespace_id: project.namespace, id: project
+
+ expect(response).to have_http_status(302)
+ end
+ end
+
+ context 'when project export is disabled' do
+ before do
+ stub_application_setting(project_export_enabled?: false)
+ end
+
+ it 'returns 404' do
+ post :generate_new_export, namespace_id: project.namespace, id: project
+
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
+
def project_moved_message(redirect_route, project)
"Project '#{redirect_route.path}' was moved to '#{project.full_path}'. Please update any links and bookmarks that may still have the old path."
end
diff --git a/spec/controllers/registrations_controller_spec.rb b/spec/controllers/registrations_controller_spec.rb
index 634563fc290..5a4ab39ab86 100644
--- a/spec/controllers/registrations_controller_spec.rb
+++ b/spec/controllers/registrations_controller_spec.rb
@@ -5,7 +5,7 @@ describe RegistrationsController do
let(:user_params) { { user: { name: 'new_user', username: 'new_username', email: 'new@user.com', password: 'Any_password' } } }
context 'email confirmation' do
- around(:each) do |example|
+ around do |example|
perform_enqueued_jobs do
example.run
end
@@ -15,7 +15,7 @@ describe RegistrationsController do
it 'signs the user in' do
allow_any_instance_of(ApplicationSetting).to receive(:send_user_confirmation_email).and_return(false)
- expect { post(:create, user_params) }.not_to change{ ActionMailer::Base.deliveries.size }
+ expect { post(:create, user_params) }.not_to change { ActionMailer::Base.deliveries.size }
expect(subject.current_user).not_to be_nil
end
end
diff --git a/spec/controllers/snippets/notes_controller_spec.rb b/spec/controllers/snippets/notes_controller_spec.rb
index 1c494b8c7ab..225753333ee 100644
--- a/spec/controllers/snippets/notes_controller_spec.rb
+++ b/spec/controllers/snippets/notes_controller_spec.rb
@@ -138,7 +138,7 @@ describe Snippets::NotesController do
end
it "deletes the note" do
- expect{ delete :destroy, request_params }.to change{ Note.count }.from(1).to(0)
+ expect { delete :destroy, request_params }.to change { Note.count }.from(1).to(0)
end
context 'system note' do
@@ -147,7 +147,7 @@ describe Snippets::NotesController do
end
it "does not delete the note" do
- expect{ delete :destroy, request_params }.not_to change{ Note.count }
+ expect { delete :destroy, request_params }.not_to change { Note.count }
end
end
end
@@ -166,7 +166,7 @@ describe Snippets::NotesController do
end
it "does not update the note" do
- expect{ delete :destroy, request_params }.not_to change{ Note.count }
+ expect { delete :destroy, request_params }.not_to change { Note.count }
end
end
end
diff --git a/spec/controllers/snippets_controller_spec.rb b/spec/controllers/snippets_controller_spec.rb
index 475ceda11fe..7c5d059760f 100644
--- a/spec/controllers/snippets_controller_spec.rb
+++ b/spec/controllers/snippets_controller_spec.rb
@@ -186,8 +186,8 @@ describe SnippetsController do
end
context 'when the snippet description contains a file' do
- let(:picture_file) { '/system/temp/secret56/picture.jpg' }
- let(:text_file) { '/system/temp/secret78/text.txt' }
+ let(:picture_file) { '/-/system/temp/secret56/picture.jpg' }
+ let(:text_file) { '/-/system/temp/secret78/text.txt' }
let(:description) do
"Description with picture: ![picture](/uploads#{picture_file}) and "\
"text: [text.txt](/uploads#{text_file})"
@@ -208,8 +208,8 @@ describe SnippetsController do
snippet = subject
expected_description = "Description with picture: "\
- "![picture](/uploads/system/personal_snippet/#{snippet.id}/secret56/picture.jpg) and "\
- "text: [text.txt](/uploads/system/personal_snippet/#{snippet.id}/secret78/text.txt)"
+ "![picture](/uploads/-/system/personal_snippet/#{snippet.id}/secret56/picture.jpg) and "\
+ "text: [text.txt](/uploads/-/system/personal_snippet/#{snippet.id}/secret78/text.txt)"
expect(snippet.description).to eq(expected_description)
end
diff --git a/spec/controllers/uploads_controller_spec.rb b/spec/controllers/uploads_controller_spec.rb
index b3a40f5d15c..b29f3d861be 100644
--- a/spec/controllers/uploads_controller_spec.rb
+++ b/spec/controllers/uploads_controller_spec.rb
@@ -102,7 +102,7 @@ describe UploadsController do
subject
expect(response.body).to match '\"alt\":\"rails_sample\"'
- expect(response.body).to match "\"url\":\"/uploads/system/temp"
+ expect(response.body).to match "\"url\":\"/uploads/-/system/temp"
end
it 'does not create an Upload record' do
@@ -119,7 +119,7 @@ describe UploadsController do
subject
expect(response.body).to match '\"alt\":\"doc_sample.txt\"'
- expect(response.body).to match "\"url\":\"/uploads/system/temp"
+ expect(response.body).to match "\"url\":\"/uploads/-/system/temp"
end
it 'does not create an Upload record' do
diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb
index a64ad73cba8..2cecd2646fc 100644
--- a/spec/controllers/users_controller_spec.rb
+++ b/spec/controllers/users_controller_spec.rb
@@ -92,8 +92,14 @@ describe UsersController do
before do
sign_in(user)
project.team << [user, :developer]
- EventCreateService.new.push(project, user, [])
- EventCreateService.new.push(forked_project, user, [])
+
+ push_data = Gitlab::DataBuilder::Push.build_sample(project, user)
+
+ fork_push_data = Gitlab::DataBuilder::Push
+ .build_sample(forked_project, user)
+
+ EventCreateService.new.push(project, user, push_data)
+ EventCreateService.new.push(forked_project, user, fork_push_data)
end
it 'includes forked projects' do
diff --git a/spec/factories/conversational_development_index_metrics.rb b/spec/factories/conversational_development_index_metrics.rb
index a5412629195..3806c43ba15 100644
--- a/spec/factories/conversational_development_index_metrics.rb
+++ b/spec/factories/conversational_development_index_metrics.rb
@@ -2,32 +2,42 @@ FactoryGirl.define do
factory :conversational_development_index_metric, class: ConversationalDevelopmentIndex::Metric do
leader_issues 9.256
instance_issues 1.234
+ percentage_issues 13.331
leader_notes 30.33333
instance_notes 28.123
+ percentage_notes 92.713
leader_milestones 16.2456
instance_milestones 1.234
+ percentage_milestones 7.595
leader_boards 5.2123
instance_boards 3.254
+ percentage_boards 62.429
leader_merge_requests 1.2
instance_merge_requests 0.6
+ percentage_merge_requests 50.0
leader_ci_pipelines 12.1234
instance_ci_pipelines 2.344
+ percentage_ci_pipelines 19.334
leader_environments 3.3333
instance_environments 2.2222
+ percentage_environments 66.672
leader_deployments 1.200
instance_deployments 0.771
+ percentage_deployments 64.25
leader_projects_prometheus_active 0.111
instance_projects_prometheus_active 0.109
+ percentage_projects_prometheus_active 98.198
leader_service_desk_issues 15.891
instance_service_desk_issues 13.345
+ percentage_service_desk_issues 83.978
end
end
diff --git a/spec/factories/deployments.rb b/spec/factories/deployments.rb
index 29ad1af9fd9..e5abfd67d60 100644
--- a/spec/factories/deployments.rb
+++ b/spec/factories/deployments.rb
@@ -10,6 +10,10 @@ FactoryGirl.define do
after(:build) do |deployment, evaluator|
deployment.project ||= deployment.environment.project
+
+ unless deployment.project.repository_exists?
+ allow(deployment.project.repository).to receive(:fetch_ref)
+ end
end
end
end
diff --git a/spec/factories/events.rb b/spec/factories/events.rb
index 11d2016955c..ad9f7e2caef 100644
--- a/spec/factories/events.rb
+++ b/spec/factories/events.rb
@@ -2,6 +2,7 @@ FactoryGirl.define do
factory :event do
project
author factory: :user
+ action Event::JOINED
trait(:created) { action Event::CREATED }
trait(:updated) { action Event::UPDATED }
@@ -20,4 +21,19 @@ FactoryGirl.define do
target factory: :closed_issue
end
end
+
+ factory :push_event, class: PushEvent do
+ project factory: :project_empty_repo
+ author factory: :user
+ action Event::PUSHED
+ end
+
+ factory :push_event_payload do
+ event
+ commit_count 1
+ action :pushed
+ ref_type :branch
+ ref 'master'
+ commit_to '3cdce97ed87c91368561584e7358f4d46e3e173c'
+ end
end
diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb
index 1bc530d06db..cbec716d6ea 100644
--- a/spec/factories/merge_requests.rb
+++ b/spec/factories/merge_requests.rb
@@ -68,6 +68,17 @@ FactoryGirl.define do
merge_user author
end
+ after(:build) do |merge_request|
+ target_project = merge_request.target_project
+ source_project = merge_request.source_project
+
+ # Fake `write_ref` if we don't have repository
+ # We have too many existing tests replying on this behaviour
+ unless [target_project, source_project].all?(&:repository_exists?)
+ allow(merge_request).to receive(:write_ref)
+ end
+ end
+
factory :merged_merge_request, traits: [:merged]
factory :closed_merge_request, traits: [:closed]
factory :reopened_merge_request, traits: [:opened]
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index be3f219e8bf..4a2034b31b3 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -8,12 +8,47 @@ FactoryGirl.define do
factory :project, class: 'Project' do
sequence(:name) { |n| "project#{n}" }
path { name.downcase.gsub(/\s/, '_') }
- namespace
- creator
-
# Behaves differently to nil due to cache_has_external_issue_tracker
has_external_issue_tracker false
+ # Associations
+ namespace
+ creator { group ? create(:user) : namespace&.owner }
+
+ # Nest Project Feature attributes
+ transient do
+ wiki_access_level ProjectFeature::ENABLED
+ builds_access_level ProjectFeature::ENABLED
+ snippets_access_level ProjectFeature::ENABLED
+ issues_access_level ProjectFeature::ENABLED
+ merge_requests_access_level ProjectFeature::ENABLED
+ repository_access_level ProjectFeature::ENABLED
+ end
+
+ after(:create) do |project, evaluator|
+ # Builds and MRs can't have higher visibility level than repository access level.
+ builds_access_level = [evaluator.builds_access_level, evaluator.repository_access_level].min
+ merge_requests_access_level = [evaluator.merge_requests_access_level, evaluator.repository_access_level].min
+
+ project.project_feature.update_columns(
+ wiki_access_level: evaluator.wiki_access_level,
+ builds_access_level: builds_access_level,
+ snippets_access_level: evaluator.snippets_access_level,
+ issues_access_level: evaluator.issues_access_level,
+ merge_requests_access_level: merge_requests_access_level,
+ repository_access_level: evaluator.repository_access_level)
+
+ # Normally the class Projects::CreateService is used for creating
+ # projects, and this class takes care of making sure the owner and current
+ # user have access to the project. Our specs don't use said service class,
+ # thus we must manually refresh things here.
+ unless project.group || project.pending_delete
+ project.add_master(project.owner)
+ end
+
+ project.group&.refresh_members_authorized_projects
+ end
+
trait :public do
visibility_level Gitlab::VisibilityLevel::PUBLIC
end
@@ -54,6 +89,12 @@ FactoryGirl.define do
avatar { File.open(Rails.root.join('spec/fixtures/dk.png')) }
end
+ trait :broken_storage do
+ after(:create) do |project|
+ project.update_column(:repository_storage, 'broken')
+ end
+ end
+
# Test repository - https://gitlab.com/gitlab-org/gitlab-test
trait :repository do
path { 'gitlabhq' }
@@ -61,30 +102,28 @@ FactoryGirl.define do
test_repo
transient do
- create_template nil
+ create_templates nil
end
after :create do |project, evaluator|
- if evaluator.create_template
- args = evaluator.create_template
-
- project.add_user(args[:user], args[:access])
+ if evaluator.create_templates
+ templates_path = "#{evaluator.create_templates}_templates"
project.repository.create_file(
- args[:user],
- ".gitlab/#{args[:path]}/bug.md",
+ project.creator,
+ ".gitlab/#{templates_path}/bug.md",
'something valid',
message: 'test 3',
branch_name: 'master')
project.repository.create_file(
- args[:user],
- ".gitlab/#{args[:path]}/template_test.md",
+ project.creator,
+ ".gitlab/#{templates_path}/template_test.md",
'template_test',
message: 'test 1',
branch_name: 'master')
project.repository.create_file(
- args[:user],
- ".gitlab/#{args[:path]}/feature_proposal.md",
+ project.creator,
+ ".gitlab/#{templates_path}/feature_proposal.md",
'feature_proposal',
message: 'test 2',
branch_name: 'master')
@@ -136,44 +175,6 @@ FactoryGirl.define do
trait(:repository_enabled) { repository_access_level ProjectFeature::ENABLED }
trait(:repository_disabled) { repository_access_level ProjectFeature::DISABLED }
trait(:repository_private) { repository_access_level ProjectFeature::PRIVATE }
-
- # Nest Project Feature attributes
- transient do
- wiki_access_level ProjectFeature::ENABLED
- builds_access_level ProjectFeature::ENABLED
- snippets_access_level ProjectFeature::ENABLED
- issues_access_level ProjectFeature::ENABLED
- merge_requests_access_level ProjectFeature::ENABLED
- repository_access_level ProjectFeature::ENABLED
- end
-
- after(:create) do |project, evaluator|
- # Builds and MRs can't have higher visibility level than repository access level.
- builds_access_level = [evaluator.builds_access_level, evaluator.repository_access_level].min
- merge_requests_access_level = [evaluator.merge_requests_access_level, evaluator.repository_access_level].min
-
- project.project_feature
- .update_attributes!(
- wiki_access_level: evaluator.wiki_access_level,
- builds_access_level: builds_access_level,
- snippets_access_level: evaluator.snippets_access_level,
- issues_access_level: evaluator.issues_access_level,
- merge_requests_access_level: merge_requests_access_level,
- repository_access_level: evaluator.repository_access_level
- )
-
- # Normally the class Projects::CreateService is used for creating
- # projects, and this class takes care of making sure the owner and current
- # user have access to the project. Our specs don't use said service class,
- # thus we must manually refresh things here.
- owner = project.owner
-
- if owner && owner.is_a?(User) && !project.pending_delete
- project.members.create!(user: owner, access_level: Gitlab::Access::MASTER)
- end
-
- project.group&.refresh_members_authorized_projects
- end
end
# Project with empty repository
diff --git a/spec/factories/users.rb b/spec/factories/users.rb
index e60fe713bc3..4000cd085b7 100644
--- a/spec/factories/users.rb
+++ b/spec/factories/users.rb
@@ -1,5 +1,5 @@
FactoryGirl.define do
- factory :user, aliases: [:author, :assignee, :recipient, :owner, :creator, :resource_owner] do
+ factory :user, aliases: [:author, :assignee, :recipient, :owner, :resource_owner] do
email { generate(:email) }
name { generate(:name) }
username { generate(:username) }
@@ -8,6 +8,10 @@ FactoryGirl.define do
confirmation_token { nil }
can_create_group true
+ after(:stub) do |user|
+ user.notification_email = user.email
+ end
+
before(:create) do |user|
user.ensure_rss_token
end
diff --git a/spec/features/admin/admin_health_check_spec.rb b/spec/features/admin/admin_health_check_spec.rb
index 106e7370a98..37fd3e171eb 100644
--- a/spec/features/admin/admin_health_check_spec.rb
+++ b/spec/features/admin/admin_health_check_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature "Admin Health Check" do
+feature "Admin Health Check", feature: true, broken_storage: true do
include StubENV
before do
@@ -55,4 +55,26 @@ feature "Admin Health Check" do
expect(page).to have_content('The server is on fire')
end
end
+
+ context 'with repository storage failures' do
+ before do
+ # Track a failure
+ Gitlab::Git::Storage::CircuitBreaker.for_storage('broken').perform { nil } rescue nil
+ visit admin_health_check_path
+ end
+
+ it 'shows storage failure information' do
+ hostname = Gitlab::Environment.hostname
+
+ expect(page).to have_content('broken: failed storage access attempt on host:')
+ expect(page).to have_content("#{hostname}: 1 of 10 failures.")
+ end
+
+ it 'allows resetting storage failures' do
+ click_button 'Reset git storage health information'
+
+ expect(page).to have_content('Git storage health information has been reset')
+ expect(page).not_to have_content('failed storage access attempt')
+ end
+ end
end
diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb
index c1eced417cf..5db42175c15 100644
--- a/spec/features/admin/admin_settings_spec.rb
+++ b/spec/features/admin/admin_settings_spec.rb
@@ -35,6 +35,7 @@ feature 'Admin updates settings' do
fill_in 'Help page text', with: 'Example text'
check 'Hide marketing-related entries from help'
fill_in 'Support page URL', with: 'http://example.com/help'
+ uncheck 'Project export enabled'
click_button 'Save'
expect(current_application_settings.gravatar_enabled).to be_falsey
@@ -42,6 +43,7 @@ feature 'Admin updates settings' do
expect(current_application_settings.help_page_text).to eq "Example text"
expect(current_application_settings.help_page_hide_commercial_content).to be_truthy
expect(current_application_settings.help_page_support_url).to eq "http://example.com/help"
+ expect(current_application_settings.project_export_enabled).to be_falsey
expect(page).to have_content "Application settings saved successfully"
end
@@ -69,6 +71,14 @@ feature 'Admin updates settings' do
expect(find('#service_push_channel').value).to eq '#test_channel'
end
+ context 'sign-in restrictions', :js do
+ it 'de-activates oauth sign-in source' do
+ find('.btn', text: 'GitLab.com').click
+
+ expect(find('.btn', text: 'GitLab.com')).not_to have_css('.active')
+ end
+ end
+
def check_all_events
page.check('Active')
page.check('Push')
diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb
index c51b81c1cff..ce458431c55 100644
--- a/spec/features/boards/boards_spec.rb
+++ b/spec/features/boards/boards_spec.rb
@@ -233,7 +233,7 @@ describe 'Issue Boards', js: true do
wait_for_board_cards(4, 1)
expect(find('.board:nth-child(3)')).to have_content(issue6.title)
- expect(find('.board:nth-child(3)').all('.card').last).not_to have_content(development.title)
+ expect(find('.board:nth-child(3)').all('.card').last).to have_content(development.title)
end
it 'issue moves between lists' do
@@ -244,7 +244,7 @@ describe 'Issue Boards', js: true do
wait_for_board_cards(4, 1)
expect(find('.board:nth-child(2)')).to have_content(issue7.title)
- expect(find('.board:nth-child(2)').all('.card').first).not_to have_content(planning.title)
+ expect(find('.board:nth-child(2)').all('.card').first).to have_content(planning.title)
end
it 'issue moves from closed' do
diff --git a/spec/features/boards/sidebar_spec.rb b/spec/features/boards/sidebar_spec.rb
index 373cd92793e..c3bf50ef9d1 100644
--- a/spec/features/boards/sidebar_spec.rb
+++ b/spec/features/boards/sidebar_spec.rb
@@ -15,10 +15,12 @@ describe 'Issue Boards', js: true do
let!(:list) { create(:list, board: board, label: development, position: 0) }
let(:card) { find('.board:nth-child(2)').first('.card') }
- before do
- Timecop.freeze
+ around do |example|
+ Timecop.freeze { example.run }
+ end
- project.team << [user, :master]
+ before do
+ project.add_master(user)
sign_in(user)
@@ -26,10 +28,6 @@ describe 'Issue Boards', js: true do
wait_for_requests
end
- after do
- Timecop.return
- end
-
it 'shows sidebar when clicking issue' do
click_card(card)
@@ -257,7 +255,7 @@ describe 'Issue Boards', js: true do
end
end
- expect(card).to have_selector('.label', count: 2)
+ expect(card).to have_selector('.label', count: 3)
expect(card).to have_content(bug.title)
end
@@ -283,7 +281,7 @@ describe 'Issue Boards', js: true do
end
end
- expect(card).to have_selector('.label', count: 3)
+ expect(card).to have_selector('.label', count: 4)
expect(card).to have_content(bug.title)
expect(card).to have_content(regression.title)
end
@@ -308,7 +306,7 @@ describe 'Issue Boards', js: true do
end
end
- expect(card).not_to have_selector('.label')
+ expect(card).to have_selector('.label', count: 1)
expect(card).not_to have_content(stretch.title)
end
end
diff --git a/spec/features/calendar_spec.rb b/spec/features/calendar_spec.rb
index 64fbc80cb81..9a597a2d690 100644
--- a/spec/features/calendar_spec.rb
+++ b/spec/features/calendar_spec.rb
@@ -42,14 +42,14 @@ feature 'Contributions Calendar', :js do
end
def push_code_contribution
- push_params = {
- project: contributed_project,
- action: Event::PUSHED,
- author_id: user.id,
- data: { commit_count: 3 }
- }
-
- Event.create(push_params)
+ event = create(:push_event, project: contributed_project, author: user)
+
+ create(:push_event_payload,
+ event: event,
+ commit_from: '11f9ac0a48b62cef25eedede4c1819964f08d5ce',
+ commit_to: '1cf19a015df3523caf0a1f9d40c98a267d6a2fc2',
+ commit_count: 3,
+ ref: 'master')
end
def note_comment_contribution
diff --git a/spec/features/cycle_analytics_spec.rb b/spec/features/cycle_analytics_spec.rb
index 5c60cca10b9..bfe9dac3bd4 100644
--- a/spec/features/cycle_analytics_spec.rb
+++ b/spec/features/cycle_analytics_spec.rb
@@ -24,6 +24,12 @@ feature 'Cycle Analytics', js: true do
expect(page).to have_content('Introducing Cycle Analytics')
end
+ it 'shows pipeline summary' do
+ expect(new_issues_counter).to have_content('-')
+ expect(commits_counter).to have_content('-')
+ expect(deploys_counter).to have_content('-')
+ end
+
it 'shows active stage with empty message' do
expect(page).to have_selector('.stage-nav-item.active', text: 'Issue')
expect(page).to have_content("We don't have enough data to show this stage.")
@@ -42,6 +48,12 @@ feature 'Cycle Analytics', js: true do
visit project_cycle_analytics_path(project)
end
+ it 'shows pipeline summary' do
+ expect(new_issues_counter).to have_content('1')
+ expect(commits_counter).to have_content('2')
+ expect(deploys_counter).to have_content('1')
+ end
+
it 'shows data on each stage' do
expect_issue_to_be_present
@@ -63,6 +75,20 @@ feature 'Cycle Analytics', js: true do
click_stage('Production')
expect_issue_to_be_present
end
+
+ context "when I change the time period observed" do
+ before do
+ _two_weeks_old_issue = create(:issue, project: project, created_at: 2.weeks.ago)
+
+ click_button('Last 30 days')
+ click_link('Last 7 days')
+ wait_for_requests
+ end
+
+ it 'shows only relevant data' do
+ expect(new_issues_counter).to have_content('1')
+ end
+ end
end
context "when my preferred language is Spanish" do
@@ -109,6 +135,18 @@ feature 'Cycle Analytics', js: true do
end
end
+ def new_issues_counter
+ find(:xpath, "//p[contains(text(),'New Issue')]/preceding-sibling::h3")
+ end
+
+ def commits_counter
+ find(:xpath, "//p[contains(text(),'Commits')]/preceding-sibling::h3")
+ end
+
+ def deploys_counter
+ find(:xpath, "//p[contains(text(),'Deploy')]/preceding-sibling::h3")
+ end
+
def expect_issue_to_be_present
expect(find('.stage-events')).to have_content(issue.title)
expect(find('.stage-events')).to have_content(issue.author.name)
diff --git a/spec/features/dashboard/activity_spec.rb b/spec/features/dashboard/activity_spec.rb
index 4917dfcf1d1..582868bac1e 100644
--- a/spec/features/dashboard/activity_spec.rb
+++ b/spec/features/dashboard/activity_spec.rb
@@ -23,27 +23,19 @@ feature 'Dashboard > Activity' do
create(:merge_request, author: user, source_project: project, target_project: project)
end
- let(:push_event_data) do
- {
- before: Gitlab::Git::BLANK_SHA,
- after: '0220c11b9a3e6c69dc8fd35321254ca9a7b98f7e',
- ref: 'refs/heads/new_design',
- user_id: user.id,
- user_name: user.name,
- repository: {
- name: project.name,
- url: 'localhost/rubinius',
- description: '',
- homepage: 'localhost/rubinius',
- private: true
- }
- }
- end
-
let(:note) { create(:note, project: project, noteable: merge_request) }
let!(:push_event) do
- create(:event, :pushed, data: push_event_data, project: project, author: user)
+ event = create(:push_event, project: project, author: user)
+
+ create(:push_event_payload,
+ event: event,
+ action: :created,
+ commit_to: '0220c11b9a3e6c69dc8fd35321254ca9a7b98f7e',
+ ref: 'new_design',
+ commit_count: 1)
+
+ event
end
let!(:merged_event) do
diff --git a/spec/features/dashboard/issues_spec.rb b/spec/features/dashboard/issues_spec.rb
index be6f78ee607..795335aa106 100644
--- a/spec/features/dashboard/issues_spec.rb
+++ b/spec/features/dashboard/issues_spec.rb
@@ -79,12 +79,21 @@ RSpec.describe 'Dashboard Issues' do
end
end
- it 'shows the new issue page', :js do
+ it 'shows the new issue page', js: true do
find('.new-project-item-select-button').trigger('click')
+
wait_for_requests
- find('.select2-results li').click
- expect(page).to have_current_path("/#{project.path_with_namespace}/issues/new")
+ project_path = "/#{project.path_with_namespace}"
+ project_json = { name: project.name_with_namespace, url: project_path }.to_json
+
+ # similate selection, and prevent overlap by dropdown menu
+ execute_script("$('.project-item-select').val('#{project_json}').trigger('change');")
+ execute_script("$('#select2-drop-mask').remove();")
+
+ find('.new-project-item-link').trigger('click')
+
+ expect(page).to have_current_path("#{project_path}/issues/new")
page.within('#content-body') do
expect(page).to have_selector('.issue-form')
diff --git a/spec/features/groups/empty_states_spec.rb b/spec/features/groups/empty_states_spec.rb
index 7f28553c44e..243e8536168 100644
--- a/spec/features/groups/empty_states_spec.rb
+++ b/spec/features/groups/empty_states_spec.rb
@@ -38,7 +38,7 @@ feature 'Groups Merge Requests Empty States' do
it 'should show a new merge request button' do
within '.empty-state' do
- expect(page).to have_content('New merge request')
+ expect(page).to have_content('create merge request')
end
end
@@ -63,7 +63,7 @@ feature 'Groups Merge Requests Empty States' do
it 'should not show a new merge request button' do
within '.empty-state' do
- expect(page).not_to have_link('New merge request')
+ expect(page).not_to have_link('create merge request')
end
end
end
diff --git a/spec/features/groups/group_settings_spec.rb b/spec/features/groups/group_settings_spec.rb
index acb21eab03f..d0316cfb18d 100644
--- a/spec/features/groups/group_settings_spec.rb
+++ b/spec/features/groups/group_settings_spec.rb
@@ -57,7 +57,7 @@ feature 'Edit group settings' do
TestEnv.clean_test_path
end
- after(:example) do
+ after do
TestEnv.clean_test_path
end
diff --git a/spec/features/groups/milestone_spec.rb b/spec/features/groups/milestone_spec.rb
index 574bbe0e0e1..32b3e13c624 100644
--- a/spec/features/groups/milestone_spec.rb
+++ b/spec/features/groups/milestone_spec.rb
@@ -5,14 +5,12 @@ feature 'Group milestones', :js do
let!(:project) { create(:project_empty_repo, group: group) }
let(:user) { create(:group_member, :master, user: create(:user), group: group ).user }
- before do
- Timecop.freeze
-
- sign_in(user)
+ around do |example|
+ Timecop.freeze { example.run }
end
- after do
- Timecop.return
+ before do
+ sign_in(user)
end
context 'create a milestone' do
diff --git a/spec/features/issues/create_branch_merge_request_spec.rb b/spec/features/issues/create_branch_merge_request_spec.rb
index f59f687cf51..546dc7e8a49 100644
--- a/spec/features/issues/create_branch_merge_request_spec.rb
+++ b/spec/features/issues/create_branch_merge_request_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-feature 'Create Branch/Merge Request Dropdown on issue page', js: true do
+feature 'Create Branch/Merge Request Dropdown on issue page', :feature, :js do
let(:user) { create(:user) }
let!(:project) { create(:project, :repository) }
let(:issue) { create(:issue, project: project, title: 'Cherry-Coloured Funk') }
@@ -14,10 +14,14 @@ feature 'Create Branch/Merge Request Dropdown on issue page', js: true do
it 'allows creating a merge request from the issue page' do
visit project_issue_path(project, issue)
- select_dropdown_option('create-mr')
-
- expect(page).to have_content('WIP: Resolve "Cherry-Coloured Funk"')
- expect(current_path).to eq(project_merge_request_path(project, MergeRequest.first))
+ perform_enqueued_jobs do
+ select_dropdown_option('create-mr')
+
+ expect(page).to have_content('WIP: Resolve "Cherry-Coloured Funk"')
+ expect(current_path).to eq(project_merge_request_path(project, MergeRequest.first))
+
+ wait_for_requests
+ end
visit project_issue_path(project, issue)
diff --git a/spec/features/issues/issue_sidebar_spec.rb b/spec/features/issues/issue_sidebar_spec.rb
index 8e22441e0e8..af11b474842 100644
--- a/spec/features/issues/issue_sidebar_spec.rb
+++ b/spec/features/issues/issue_sidebar_spec.rb
@@ -130,8 +130,8 @@ feature 'Issue Sidebar' do
it 'adds new label' do
page.within('.block.labels') do
fill_in 'new_label_name', with: 'wontfix'
- page.find(".suggest-colors a", match: :first).click
- click_button 'Create'
+ page.find('.suggest-colors a', match: :first).trigger('click')
+ page.find('button', text: 'Create').trigger('click')
page.within('.dropdown-page-one') do
expect(page).to have_content 'wontfix'
@@ -142,8 +142,8 @@ feature 'Issue Sidebar' do
it 'shows error message if label title is taken' do
page.within('.block.labels') do
fill_in 'new_label_name', with: label.title
- page.find('.suggest-colors a', match: :first).click
- click_button 'Create'
+ page.find('.suggest-colors a', match: :first).trigger('click')
+ page.find('button', text: 'Create').trigger('click')
page.within('.dropdown-page-two') do
expect(page).to have_content 'Title has already been taken'
diff --git a/spec/features/issues/note_polling_spec.rb b/spec/features/issues/note_polling_spec.rb
index 9f08ecc214b..62dbc3efb01 100644
--- a/spec/features/issues/note_polling_spec.rb
+++ b/spec/features/issues/note_polling_spec.rb
@@ -133,8 +133,6 @@ feature 'Issue notes polling', :js do
def click_edit_action(note)
note_element = find("#note_#{note.id}")
- open_more_actions_dropdown(note)
-
note_element.find('.js-note-edit').click
end
end
diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb
index 489baa4291f..3c8e37ff920 100644
--- a/spec/features/issues_spec.rb
+++ b/spec/features/issues_spec.rb
@@ -706,4 +706,29 @@ describe 'Issues' do
expect(page).to have_text("updated title")
end
end
+
+ describe 'confidential issue#show', js: true do
+ it 'shows confidential sibebar information as confidential and can be turned off' do
+ issue = create(:issue, :confidential, project: project)
+
+ visit project_issue_path(project, issue)
+
+ expect(page).to have_css('.confidential-issue-warning')
+ expect(page).to have_css('.is-confidential')
+ expect(page).not_to have_css('.is-not-confidential')
+
+ find('.confidential-edit').click
+ expect(page).to have_css('.confidential-warning-message')
+
+ within('.confidential-warning-message') do
+ find('.btn-close').click
+ end
+
+ wait_for_requests
+
+ visit project_issue_path(project, issue)
+
+ expect(page).not_to have_css('.is-confidential')
+ end
+ end
end
diff --git a/spec/features/merge_requests/closes_issues_spec.rb b/spec/features/merge_requests/closes_issues_spec.rb
index 0e97254eada..299b4f5708a 100644
--- a/spec/features/merge_requests/closes_issues_spec.rb
+++ b/spec/features/merge_requests/closes_issues_spec.rb
@@ -26,17 +26,11 @@ feature 'Merge Request closing issues message', js: true do
wait_for_requests
end
- context 'not closing or mentioning any issue' do
- it 'does not display closing issue message' do
- expect(page).not_to have_css('.mr-widget-footer')
- end
- end
-
context 'closing issues but not mentioning any other issue' do
let(:merge_request_description) { "Description\n\nclosing #{issue_1.to_reference}, #{issue_2.to_reference}" }
it 'does not display closing issue message' do
- expect(page).to have_content("Closes issues #{issue_1.to_reference} and #{issue_2.to_reference}")
+ expect(page).to have_content("Closes #{issue_1.to_reference} and #{issue_2.to_reference}")
end
end
@@ -44,7 +38,7 @@ feature 'Merge Request closing issues message', js: true do
let(:merge_request_description) { "Description\n\nRefers to #{issue_1.to_reference} and #{issue_2.to_reference}" }
it 'does not display closing issue message' do
- expect(page).to have_content("Issues #{issue_1.to_reference} and #{issue_2.to_reference} are mentioned but will not be closed.")
+ expect(page).to have_content("Mentions #{issue_1.to_reference} and #{issue_2.to_reference}")
end
end
@@ -52,8 +46,8 @@ feature 'Merge Request closing issues message', js: true do
let(:merge_request_title) { "closes #{issue_1.to_reference}\n\n refers to #{issue_2.to_reference}" }
it 'does not display closing issue message' do
- expect(page).to have_content("Closes issue #{issue_1.to_reference}.")
- expect(page).to have_content("Issue #{issue_2.to_reference} is mentioned but will not be closed.")
+ expect(page).to have_content("Closes #{issue_1.to_reference}")
+ expect(page).to have_content("Mentions #{issue_2.to_reference}")
end
end
@@ -61,7 +55,7 @@ feature 'Merge Request closing issues message', js: true do
let(:merge_request_title) { "closing #{issue_1.to_reference}, #{issue_2.to_reference}" }
it 'does not display closing issue message' do
- expect(page).to have_content("Closes issues #{issue_1.to_reference} and #{issue_2.to_reference}")
+ expect(page).to have_content("Closes #{issue_1.to_reference} and #{issue_2.to_reference}")
end
end
@@ -69,7 +63,7 @@ feature 'Merge Request closing issues message', js: true do
let(:merge_request_title) { "Refers to #{issue_1.to_reference} and #{issue_2.to_reference}" }
it 'does not display closing issue message' do
- expect(page).to have_content("Issues #{issue_1.to_reference} and #{issue_2.to_reference} are mentioned but will not be closed.")
+ expect(page).to have_content("Mentions #{issue_1.to_reference} and #{issue_2.to_reference}")
end
end
@@ -77,8 +71,8 @@ feature 'Merge Request closing issues message', js: true do
let(:merge_request_title) { "closes #{issue_1.to_reference}\n\n refers to #{issue_2.to_reference}" }
it 'does not display closing issue message' do
- expect(page).to have_content("Closes issue #{issue_1.to_reference}. Issue #{issue_2.to_reference} is mentioned but will not be closed.")
- expect(page).to have_content("Issue #{issue_2.to_reference} is mentioned but will not be closed.")
+ expect(page).to have_content("Closes #{issue_1.to_reference}")
+ expect(page).to have_content("Mentions #{issue_2.to_reference}")
end
end
end
diff --git a/spec/features/merge_requests/diff_notes_avatars_spec.rb b/spec/features/merge_requests/diff_notes_avatars_spec.rb
index 2d9419d6124..c4f02311f13 100644
--- a/spec/features/merge_requests/diff_notes_avatars_spec.rb
+++ b/spec/features/merge_requests/diff_notes_avatars_spec.rb
@@ -157,7 +157,7 @@ feature 'Diff note avatars', js: true do
end
page.within find("[id='#{position.line_code(project.repository)}']") do
- find('.diff-notes-collapse').click
+ find('.diff-notes-collapse').trigger('click')
expect(page).to have_selector('img.js-diff-comment-avatar', count: 3)
expect(find('.diff-comments-more-count')).to have_content '+1'
diff --git a/spec/features/merge_requests/diffs_spec.rb b/spec/features/merge_requests/diffs_spec.rb
index 201be4b9e40..a8f5dc275e4 100644
--- a/spec/features/merge_requests/diffs_spec.rb
+++ b/spec/features/merge_requests/diffs_spec.rb
@@ -5,7 +5,7 @@ feature 'Diffs URL', js: true do
let(:merge_request) { create(:merge_request, source_project: project) }
context 'when visit with */* as accept header' do
- before(:each) do
+ before do
page.driver.add_header('Accept', '*/*')
end
diff --git a/spec/features/merge_requests/discussion_spec.rb b/spec/features/merge_requests/discussion_spec.rb
index d1cc43e0690..05789bbd31d 100644
--- a/spec/features/merge_requests/discussion_spec.rb
+++ b/spec/features/merge_requests/discussion_spec.rb
@@ -26,7 +26,7 @@ feature 'Merge Request Discussions' do
let(:outdated_diff_refs) { project.commit("874797c3a73b60d2187ed6e2fcabd289ff75171e").diff_refs }
- before(:each) do
+ before do
visit project_merge_request_path(project, merge_request)
end
@@ -71,7 +71,7 @@ feature 'Merge Request Discussions' do
end
end
- before(:each) do
+ before do
visit project_merge_request_path(project, merge_request)
end
diff --git a/spec/features/merge_requests/filter_by_labels_spec.rb b/spec/features/merge_requests/filter_by_labels_spec.rb
index 1d52a4659ad..9912e8165e6 100644
--- a/spec/features/merge_requests/filter_by_labels_spec.rb
+++ b/spec/features/merge_requests/filter_by_labels_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-feature 'Merge Request filtering by Labels', js: true do
+feature 'Merge Request filtering by Labels', :js do
include FilteredSearchHelpers
include MergeRequestHelpers
@@ -12,9 +12,9 @@ feature 'Merge Request filtering by Labels', js: true do
let!(:feature) { create(:label, project: project, title: 'feature') }
let!(:enhancement) { create(:label, project: project, title: 'enhancement') }
- let!(:mr1) { create(:merge_request, title: "Bugfix1", source_project: project, target_project: project, source_branch: "bugfix1") }
- let!(:mr2) { create(:merge_request, title: "Bugfix2", source_project: project, target_project: project, source_branch: "bugfix2") }
- let!(:mr3) { create(:merge_request, title: "Feature1", source_project: project, target_project: project, source_branch: "feature1") }
+ let!(:mr1) { create(:merge_request, title: "Bugfix1", source_project: project, target_project: project, source_branch: "fix") }
+ let!(:mr2) { create(:merge_request, title: "Bugfix2", source_project: project, target_project: project, source_branch: "wip") }
+ let!(:mr3) { create(:merge_request, title: "Feature1", source_project: project, target_project: project, source_branch: "improve/awesome") }
before do
mr1.labels << bug
@@ -25,7 +25,7 @@ feature 'Merge Request filtering by Labels', js: true do
mr3.title = "Feature1"
mr3.labels << feature
- project.team << [user, :master]
+ project.add_master(user)
sign_in(user)
visit project_merge_requests_path(project)
diff --git a/spec/features/merge_requests/filter_merge_requests_spec.rb b/spec/features/merge_requests/filter_merge_requests_spec.rb
index f0019be86ad..3686131fee4 100644
--- a/spec/features/merge_requests/filter_merge_requests_spec.rb
+++ b/spec/features/merge_requests/filter_merge_requests_spec.rb
@@ -12,7 +12,7 @@ describe 'Filter merge requests' do
let!(:wontfix) { create(:label, project: project, title: "Won't fix") }
before do
- project.team << [user, :master]
+ project.add_master(user)
group.add_developer(user)
sign_in(user)
create(:merge_request, source_project: project, target_project: project)
@@ -170,7 +170,7 @@ describe 'Filter merge requests' do
describe 'filter merge requests by text' do
before do
- create(:merge_request, title: "Bug", source_project: project, target_project: project, source_branch: "bug")
+ create(:merge_request, title: "Bug", source_project: project, target_project: project, source_branch: "wip")
bug_label = create(:label, project: project, title: 'bug')
milestone = create(:milestone, title: "8", project: project)
@@ -179,7 +179,7 @@ describe 'Filter merge requests' do
title: "Bug 2",
source_project: project,
target_project: project,
- source_branch: "bug2",
+ source_branch: "fix",
milestone: milestone,
author: user,
assignee: user)
@@ -259,12 +259,12 @@ describe 'Filter merge requests' do
end
end
- describe 'filter merge requests and sort', js: true do
+ describe 'filter merge requests and sort', :js do
before do
bug_label = create(:label, project: project, title: 'bug')
- mr1 = create(:merge_request, title: "Frontend", source_project: project, target_project: project, source_branch: "Frontend")
- mr2 = create(:merge_request, title: "Bug 2", source_project: project, target_project: project, source_branch: "bug2")
+ mr1 = create(:merge_request, title: "Frontend", source_project: project, target_project: project, source_branch: "wip")
+ mr2 = create(:merge_request, title: "Bug 2", source_project: project, target_project: project, source_branch: "fix")
mr1.labels << bug_label
mr2.labels << bug_label
diff --git a/spec/features/merge_requests/form_spec.rb b/spec/features/merge_requests/form_spec.rb
index 6ffb05c5030..89410b0e90f 100644
--- a/spec/features/merge_requests/form_spec.rb
+++ b/spec/features/merge_requests/form_spec.rb
@@ -41,7 +41,7 @@ describe 'New/edit merge request', :js do
expect(page).to have_content user2.name
end
- click_link 'Assign to me'
+ find('a', text: 'Assign to me').trigger('click')
expect(find('input[name="merge_request[assignee_id]"]', visible: false).value).to match(user.id.to_s)
page.within '.js-assignee-search' do
expect(page).to have_content user.name
diff --git a/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb b/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb
index 574f5fe353e..ac46cc1f0e4 100644
--- a/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb
+++ b/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb
@@ -41,8 +41,8 @@ feature 'Merge When Pipeline Succeeds', :js do
it 'activates the Merge when pipeline succeeds feature' do
click_button "Merge when pipeline succeeds"
- expect(page).to have_content "Set by #{user.name} to be merged automatically when the pipeline succeeds."
- expect(page).to have_content "The source branch will not be removed."
+ expect(page).to have_content "Set by #{user.name} to be merged automatically when the pipeline succeeds"
+ expect(page).to have_content "The source branch will not be removed"
expect(page).to have_selector ".js-cancel-auto-merge"
visit_merge_request(merge_request) # Needed to refresh the page
expect(page).to have_content /enabled an automatic merge when the pipeline for \h{8} succeeds/i
@@ -97,11 +97,11 @@ feature 'Merge When Pipeline Succeeds', :js do
describe 'enabling Merge when pipeline succeeds via dropdown' do
it 'activates the Merge when pipeline succeeds feature' do
- click_button 'Select merge moment'
+ find('.js-merge-moment').click
click_link 'Merge when pipeline succeeds'
- expect(page).to have_content "Set by #{user.name} to be merged automatically when the pipeline succeeds."
- expect(page).to have_content "The source branch will not be removed."
+ expect(page).to have_content "Set by #{user.name} to be merged automatically when the pipeline succeeds"
+ expect(page).to have_content "The source branch will not be removed"
expect(page).to have_link "Cancel automatic merge"
end
end
diff --git a/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb b/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb
index 5c6eec44ff7..59e67420333 100644
--- a/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb
+++ b/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb
@@ -43,7 +43,7 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', js: t
wait_for_requests
expect(page).to have_button 'Merge when pipeline succeeds'
- expect(page).not_to have_button 'Select merge moment'
+ expect(page).not_to have_button '.js-merge-moment'
end
end
@@ -56,7 +56,7 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', js: t
wait_for_requests
expect(page).to have_css('button[disabled="disabled"]', text: 'Merge')
- expect(page).to have_content('Please retry the job or push a new commit to fix the failure.')
+ expect(page).to have_content('Please retry the job or push a new commit to fix the failure')
end
end
@@ -69,7 +69,7 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', js: t
wait_for_requests
expect(page).not_to have_button 'Merge'
- expect(page).to have_content('Please retry the job or push a new commit to fix the failure.')
+ expect(page).to have_content('Please retry the job or push a new commit to fix the failure')
end
end
@@ -113,7 +113,7 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', js: t
expect(page).to have_button 'Merge when pipeline succeeds'
- click_button 'Select merge moment'
+ page.find('.js-merge-moment').click
expect(page).to have_content 'Merge immediately'
end
end
diff --git a/spec/features/merge_requests/pipelines_spec.rb b/spec/features/merge_requests/pipelines_spec.rb
index b3d6cf8deb4..347ce788b36 100644
--- a/spec/features/merge_requests/pipelines_spec.rb
+++ b/spec/features/merge_requests/pipelines_spec.rb
@@ -1,45 +1,88 @@
require 'spec_helper'
feature 'Pipelines for Merge Requests', js: true do
- given(:user) { create(:user) }
- given(:merge_request) { create(:merge_request) }
- given(:project) { merge_request.target_project }
+ describe 'pipeline tab' do
+ given(:user) { create(:user) }
+ given(:merge_request) { create(:merge_request) }
+ given(:project) { merge_request.target_project }
- before do
- project.team << [user, :master]
- sign_in user
- end
-
- context 'with pipelines' do
- let!(:pipeline) do
- create(:ci_empty_pipeline,
- project: merge_request.source_project,
- ref: merge_request.source_branch,
- sha: merge_request.diff_head_sha)
+ before do
+ project.team << [user, :master]
+ sign_in user
end
- before do
- visit project_merge_request_path(project, merge_request)
+ context 'with pipelines' do
+ let!(:pipeline) do
+ create(:ci_empty_pipeline,
+ project: merge_request.source_project,
+ ref: merge_request.source_branch,
+ sha: merge_request.diff_head_sha)
+ end
+
+ before do
+ visit project_merge_request_path(project, merge_request)
+ end
+
+ scenario 'user visits merge request pipelines tab' do
+ page.within('.merge-request-tabs') do
+ click_link('Pipelines')
+ end
+ wait_for_requests
+
+ expect(page).to have_selector('.stage-cell')
+ end
end
- scenario 'user visits merge request pipelines tab' do
- page.within('.merge-request-tabs') do
- click_link('Pipelines')
+ context 'without pipelines' do
+ before do
+ visit project_merge_request_path(project, merge_request)
end
- wait_for_requests
- expect(page).to have_selector('.stage-cell')
+ scenario 'user visits merge request page' do
+ page.within('.merge-request-tabs') do
+ expect(page).to have_no_link('Pipelines')
+ end
+ end
end
end
- context 'without pipelines' do
- before do
- visit project_merge_request_path(project, merge_request)
+ describe 'race condition' do
+ given(:project) { create(:project, :repository) }
+ given(:user) { create(:user) }
+ given(:build_push_data) { { ref: 'feature', checkout_sha: TestEnv::BRANCH_SHA['feature'] } }
+
+ given(:merge_request_params) do
+ { "source_branch" => "feature", "source_project_id" => project.id,
+ "target_branch" => "master", "target_project_id" => project.id, "title" => "A" }
+ end
+
+ background do
+ project.add_master(user)
+ sign_in user
end
- scenario 'user visits merge request page' do
- page.within('.merge-request-tabs') do
- expect(page).to have_no_link('Pipelines')
+ context 'when pipeline and merge request were created simultaneously' do
+ background do
+ stub_ci_pipeline_to_return_yaml_file
+
+ threads = []
+
+ threads << Thread.new do
+ @merge_request = MergeRequests::CreateService.new(project, user, merge_request_params).execute
+ end
+
+ threads << Thread.new do
+ @pipeline = Ci::CreatePipelineService.new(project, user, build_push_data).execute(:push)
+ end
+
+ threads.each { |thr| thr.join }
+ end
+
+ scenario 'user sees pipeline in merge request widget' do
+ visit project_merge_request_path(project, @merge_request)
+
+ expect(page.find(".ci-widget")).to have_content(TestEnv::BRANCH_SHA['feature'])
+ expect(page.find(".ci-widget")).to have_content("##{@pipeline.id}")
end
end
end
diff --git a/spec/features/merge_requests/reset_filters_spec.rb b/spec/features/merge_requests/reset_filters_spec.rb
index c1b90e5f875..eed95816bdf 100644
--- a/spec/features/merge_requests/reset_filters_spec.rb
+++ b/spec/features/merge_requests/reset_filters_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-feature 'Merge requests filter clear button', js: true do
+feature 'Merge requests filter clear button', :js do
include FilteredSearchHelpers
include MergeRequestHelpers
include IssueHelpers
@@ -9,8 +9,8 @@ feature 'Merge requests filter clear button', js: true do
let!(:user) { create(:user) }
let!(:milestone) { create(:milestone, project: project) }
let!(:bug) { create(:label, project: project, name: 'bug')}
- let!(:mr1) { create(:merge_request, title: "Feature", source_project: project, target_project: project, source_branch: "Feature", milestone: milestone, author: user, assignee: user) }
- let!(:mr2) { create(:merge_request, title: "Bugfix1", source_project: project, target_project: project, source_branch: "Bugfix1") }
+ let!(:mr1) { create(:merge_request, title: "Feature", source_project: project, target_project: project, source_branch: "improve/awesome", milestone: milestone, author: user, assignee: user) }
+ let!(:mr2) { create(:merge_request, title: "Bugfix1", source_project: project, target_project: project, source_branch: "fix") }
let(:merge_request_css) { '.merge-request' }
let(:clear_search_css) { '.filtered-search-box .clear-search' }
diff --git a/spec/features/merge_requests/user_lists_merge_requests_spec.rb b/spec/features/merge_requests/user_lists_merge_requests_spec.rb
index d62b035b40b..20008b4e7f9 100644
--- a/spec/features/merge_requests/user_lists_merge_requests_spec.rb
+++ b/spec/features/merge_requests/user_lists_merge_requests_spec.rb
@@ -24,12 +24,10 @@ describe 'Projects > Merge requests > User lists merge requests' do
milestone: create(:milestone, due_date: '2013-12-12'),
created_at: 2.minutes.ago,
updated_at: 2.minutes.ago)
- # lfs in itself is not a great choice for the title if one wants to match the whole body content later on
- # just think about the scenario when faker generates 'Chester Runolfsson' as the user's name
create(:merge_request,
- title: 'merge_lfs',
+ title: 'merge-test',
source_project: project,
- source_branch: 'merge_lfs',
+ source_branch: 'merge-test',
created_at: 3.minutes.ago,
updated_at: 10.seconds.ago)
end
@@ -38,7 +36,7 @@ describe 'Projects > Merge requests > User lists merge requests' do
visit_merge_requests(project, assignee_id: IssuableFinder::NONE)
expect(current_path).to eq(project_merge_requests_path(project))
- expect(page).to have_content 'merge_lfs'
+ expect(page).to have_content 'merge-test'
expect(page).not_to have_content 'fix'
expect(page).not_to have_content 'markdown'
expect(count_merge_requests).to eq(1)
@@ -47,7 +45,7 @@ describe 'Projects > Merge requests > User lists merge requests' do
it 'filters on a specific assignee' do
visit_merge_requests(project, assignee_id: user.id)
- expect(page).not_to have_content 'merge_lfs'
+ expect(page).not_to have_content 'merge-test'
expect(page).to have_content 'fix'
expect(page).to have_content 'markdown'
expect(count_merge_requests).to eq(2)
@@ -57,14 +55,14 @@ describe 'Projects > Merge requests > User lists merge requests' do
visit_merge_requests(project, sort: sort_value_recently_created)
expect(first_merge_request).to include('fix')
- expect(last_merge_request).to include('merge_lfs')
+ expect(last_merge_request).to include('merge-test')
expect(count_merge_requests).to eq(3)
end
it 'sorts by oldest' do
visit_merge_requests(project, sort: sort_value_oldest_created)
- expect(first_merge_request).to include('merge_lfs')
+ expect(first_merge_request).to include('merge-test')
expect(last_merge_request).to include('fix')
expect(count_merge_requests).to eq(3)
end
@@ -72,7 +70,7 @@ describe 'Projects > Merge requests > User lists merge requests' do
it 'sorts by last updated' do
visit_merge_requests(project, sort: sort_value_recently_updated)
- expect(first_merge_request).to include('merge_lfs')
+ expect(first_merge_request).to include('merge-test')
expect(count_merge_requests).to eq(3)
end
diff --git a/spec/features/merge_requests/user_posts_diff_notes_spec.rb b/spec/features/merge_requests/user_posts_diff_notes_spec.rb
index 1cfd78663e5..f89dd38e5cd 100644
--- a/spec/features/merge_requests/user_posts_diff_notes_spec.rb
+++ b/spec/features/merge_requests/user_posts_diff_notes_spec.rb
@@ -71,7 +71,7 @@ feature 'Merge requests > User posts diff notes', :js do
end
context 'with an unfolded line' do
- before(:each) do
+ before do
find('.js-unfold', match: :first).click
wait_for_requests
end
@@ -120,7 +120,7 @@ feature 'Merge requests > User posts diff notes', :js do
end
context 'with an unfolded line' do
- before(:each) do
+ before do
find('.js-unfold', match: :first).click
wait_for_requests
end
diff --git a/spec/features/merge_requests/user_posts_notes_spec.rb b/spec/features/merge_requests/user_posts_notes_spec.rb
index 74d21822a59..d7cda73ab40 100644
--- a/spec/features/merge_requests/user_posts_notes_spec.rb
+++ b/spec/features/merge_requests/user_posts_notes_spec.rb
@@ -75,7 +75,6 @@ describe 'Merge requests > User posts notes', :js do
describe 'editing the note' do
before do
find('.note').hover
- open_more_actions_dropdown(note)
find('.js-note-edit').click
end
@@ -104,7 +103,6 @@ describe 'Merge requests > User posts notes', :js do
wait_for_requests
find('.note').hover
- open_more_actions_dropdown(note)
find('.js-note-edit').click
@@ -132,7 +130,6 @@ describe 'Merge requests > User posts notes', :js do
describe 'deleting an attachment' do
before do
find('.note').hover
- open_more_actions_dropdown(note)
find('.js-note-edit').click
end
diff --git a/spec/features/merge_requests/user_uses_slash_commands_spec.rb b/spec/features/merge_requests/user_uses_slash_commands_spec.rb
index 43cab65d287..95c50df1896 100644
--- a/spec/features/merge_requests/user_uses_slash_commands_spec.rb
+++ b/spec/features/merge_requests/user_uses_slash_commands_spec.rb
@@ -3,17 +3,17 @@ require 'rails_helper'
feature 'Merge Requests > User uses quick actions', js: true do
include QuickActionsHelpers
- let(:user) { create(:user) }
- let(:project) { create(:project, :public, :repository) }
- let(:merge_request) { create(:merge_request, source_project: project) }
- let!(:milestone) { create(:milestone, project: project, title: 'ASAP') }
-
it_behaves_like 'issuable record that supports quick actions in its description and notes', :merge_request do
let(:issuable) { create(:merge_request, source_project: project) }
let(:new_url_opts) { { merge_request: { source_branch: 'feature', target_branch: 'master' } } }
end
describe 'merge-request-only commands' do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :public, :repository) }
+ let(:merge_request) { create(:merge_request, source_project: project) }
+ let!(:milestone) { create(:milestone, project: project, title: 'ASAP') }
+
before do
project.team << [user, :master]
sign_in(user)
diff --git a/spec/features/merge_requests/widget_spec.rb b/spec/features/merge_requests/widget_spec.rb
index 69e31c7481f..fd991293ee9 100644
--- a/spec/features/merge_requests/widget_spec.rb
+++ b/spec/features/merge_requests/widget_spec.rb
@@ -219,4 +219,17 @@ describe 'Merge request', :js do
expect(page).to have_field('remove-source-branch-input', disabled: true)
end
end
+
+ context 'ongoing merge process' do
+ it 'shows Merging state' do
+ allow_any_instance_of(MergeRequest).to receive(:merge_ongoing?).and_return(true)
+
+ visit project_merge_request_path(project, merge_request)
+
+ wait_for_requests
+
+ expect(page).not_to have_button('Merge')
+ expect(page).to have_content('This merge request is in the process of being merged')
+ end
+ end
end
diff --git a/spec/features/password_reset_spec.rb b/spec/features/password_reset_spec.rb
index 5e1e7dc078f..b45972b7f6b 100644
--- a/spec/features/password_reset_spec.rb
+++ b/spec/features/password_reset_spec.rb
@@ -16,7 +16,7 @@ feature 'Password reset' do
user.send_reset_password_instructions
user.update_attribute(:reset_password_sent_at, 5.minutes.ago)
- expect{ forgot_password(user) }.to change{ user.reset_password_sent_at }
+ expect { forgot_password(user) }.to change { user.reset_password_sent_at }
expect(page).to have_content(I18n.t('devise.passwords.send_paranoid_instructions'))
expect(current_path).to eq new_user_session_path
end
@@ -27,7 +27,7 @@ feature 'Password reset' do
# Reload because PG handles datetime less precisely than Ruby/Rails
user.reload
- expect{ forgot_password(user) }.not_to change{ user.reset_password_sent_at }
+ expect { forgot_password(user) }.not_to change { user.reset_password_sent_at }
expect(page).to have_content(I18n.t('devise.passwords.send_paranoid_instructions'))
expect(current_path).to eq new_user_session_path
end
diff --git a/spec/features/profiles/account_spec.rb b/spec/features/profiles/account_spec.rb
index 9944a6e1ff1..dcd0449dbcb 100644
--- a/spec/features/profiles/account_spec.rb
+++ b/spec/features/profiles/account_spec.rb
@@ -35,7 +35,7 @@ feature 'Profile > Account' do
TestEnv.clean_test_path
end
- after(:example) do
+ after do
TestEnv.clean_test_path
end
diff --git a/spec/features/profiles/preferences_spec.rb b/spec/features/profiles/preferences_spec.rb
index 9123aa9d155..c935cdfd5c4 100644
--- a/spec/features/profiles/preferences_spec.rb
+++ b/spec/features/profiles/preferences_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'Profile > Preferences' do
+describe 'Profile > Preferences', :js do
let(:user) { create(:user) }
before do
@@ -8,28 +8,32 @@ describe 'Profile > Preferences' do
visit profile_preferences_path
end
- describe 'User changes their syntax highlighting theme', js: true do
+ describe 'User changes their syntax highlighting theme' do
it 'creates a flash message' do
choose 'user_color_scheme_id_5'
+ wait_for_requests
+
expect_preferences_saved_message
end
it 'updates their preference' do
choose 'user_color_scheme_id_5'
- allowing_for_delay do
- visit page.current_path
- expect(page).to have_checked_field('user_color_scheme_id_5')
- end
+ wait_for_requests
+ refresh
+
+ expect(page).to have_checked_field('user_color_scheme_id_5')
end
end
- describe 'User changes their default dashboard', js: true do
+ describe 'User changes their default dashboard' do
it 'creates a flash message' do
select 'Starred Projects', from: 'user_dashboard'
click_button 'Save'
+ wait_for_requests
+
expect_preferences_saved_message
end
@@ -37,12 +41,12 @@ describe 'Profile > Preferences' do
select 'Starred Projects', from: 'user_dashboard'
click_button 'Save'
- allowing_for_delay do
- find('#logo').click
+ wait_for_requests
+
+ find('#logo').click
- expect(page).to have_content("You don't have starred projects yet")
- expect(page.current_path).to eq starred_dashboard_projects_path
- end
+ expect(page).to have_content("You don't have starred projects yet")
+ expect(page.current_path).to eq starred_dashboard_projects_path
find('.shortcuts-activity').trigger('click')
diff --git a/spec/features/projects/features_visibility_spec.rb b/spec/features/projects/features_visibility_spec.rb
index 4044202eb6b..24691629063 100644
--- a/spec/features/projects/features_visibility_spec.rb
+++ b/spec/features/projects/features_visibility_spec.rb
@@ -20,21 +20,25 @@ describe 'Edit Project Settings' do
visit edit_project_path(project)
select 'Disabled', from: "project_project_feature_attributes_#{tool_name}_access_level"
- click_button 'Save changes'
+ page.within('.sharing-permissions') do
+ click_button 'Save changes'
+ end
wait_for_requests
expect(page).not_to have_selector(".shortcuts-#{shortcut_name}")
select 'Everyone with access', from: "project_project_feature_attributes_#{tool_name}_access_level"
- click_button 'Save changes'
+ page.within('.sharing-permissions') do
+ click_button 'Save changes'
+ end
wait_for_requests
expect(page).to have_selector(".shortcuts-#{shortcut_name}")
select 'Only team members', from: "project_project_feature_attributes_#{tool_name}_access_level"
- click_button 'Save changes'
+ page.within('.sharing-permissions') do
+ click_button 'Save changes'
+ end
wait_for_requests
expect(page).to have_selector(".shortcuts-#{shortcut_name}")
-
- sleep 0.1
end
end
end
@@ -174,7 +178,11 @@ describe 'Edit Project Settings' do
it "disables repository related features" do
select "Disabled", from: "project_project_feature_attributes_repository_access_level"
- expect(find(".edit-project")).to have_selector("select.disabled", count: 2)
+ page.within('.sharing-permissions') do
+ click_button "Save changes"
+ end
+
+ expect(find(".sharing-permissions")).to have_selector("select.disabled", count: 2)
end
it "shows empty features project homepage" do
@@ -182,7 +190,9 @@ describe 'Edit Project Settings' do
select "Disabled", from: "project_project_feature_attributes_issues_access_level"
select "Disabled", from: "project_project_feature_attributes_wiki_access_level"
- click_button "Save changes"
+ page.within('.sharing-permissions') do
+ click_button "Save changes"
+ end
wait_for_requests
visit project_path(project)
@@ -195,7 +205,9 @@ describe 'Edit Project Settings' do
select "Disabled", from: "project_project_feature_attributes_issues_access_level"
select "Disabled", from: "project_project_feature_attributes_wiki_access_level"
- click_button "Save changes"
+ page.within('.sharing-permissions') do
+ click_button "Save changes"
+ end
wait_for_requests
visit activity_project_path(project)
@@ -236,7 +248,9 @@ describe 'Edit Project Settings' do
end
def save_changes_and_check_activity_tab
- click_button "Save changes"
+ page.within('.sharing-permissions') do
+ click_button "Save changes"
+ end
wait_for_requests
visit activity_project_path(project)
diff --git a/spec/features/projects/import_export/import_file_spec.rb b/spec/features/projects/import_export/import_file_spec.rb
index c0cfb9eafe2..6a324d32ca7 100644
--- a/spec/features/projects/import_export/import_file_spec.rb
+++ b/spec/features/projects/import_export/import_file_spec.rb
@@ -10,7 +10,7 @@ feature 'Import/Export - project import integration test', js: true do
allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
end
- after(:each) do
+ after do
FileUtils.rm_rf(export_path, secure: true)
end
@@ -29,8 +29,9 @@ feature 'Import/Export - project import integration test', js: true do
fill_in :project_path, with: 'test-project-path', visible: true
click_link 'GitLab export'
- expect(page).to have_content('GitLab project export')
+ expect(page).to have_content('Import an exported GitLab project')
expect(URI.parse(current_url).query).to eq("namespace_id=#{namespace.id}&path=test-project-path")
+ expect(Gitlab::ImportExport).to receive(:import_upload_path).with(filename: /\A\h{32}_test-project-path\z/).and_call_original
attach_file('file', file)
@@ -60,17 +61,6 @@ feature 'Import/Export - project import integration test', js: true do
expect(page).to have_content('Project could not be imported')
end
end
-
- scenario 'project with no name' do
- create(:project, namespace: namespace)
-
- visit new_project_path
-
- select2(namespace.id, from: '#project_namespace_id')
-
- # Check for tooltip disabled import button
- expect(find('.import_gitlab_project')['title']).to eq('Please enter a valid project name.')
- end
end
context 'when limited to the default user namespace' do
diff --git a/spec/features/projects/pipeline_schedules_spec.rb b/spec/features/projects/pipeline_schedules_spec.rb
index 605415d2af4..24b335a7068 100644
--- a/spec/features/projects/pipeline_schedules_spec.rb
+++ b/spec/features/projects/pipeline_schedules_spec.rb
@@ -219,6 +219,25 @@ feature 'Pipeline Schedules', :js do
end
end
end
+
+ context 'when active is true and next_run_at is NULL' do
+ background do
+ create(:ci_pipeline_schedule, project: project, owner: user).tap do |pipeline_schedule|
+ pipeline_schedule.update_attribute(:cron, nil) # Consequently next_run_at will be nil
+ end
+ end
+
+ scenario 'user edit and recover the problematic pipeline schedule' do
+ visit_pipelines_schedules
+ find(".content-list .pipeline-schedule-table-row:nth-child(1) .btn-group a[title='Edit']").click
+ fill_in 'schedule_cron', with: '* 1 2 3 4'
+ click_button 'Save pipeline schedule'
+
+ page.within('.pipeline-schedule-table-row:nth-child(1)') do
+ expect(page).to have_css(".next-run-cell time")
+ end
+ end
+ end
end
context 'logged in as non-member' do
diff --git a/spec/features/projects/project_settings_spec.rb b/spec/features/projects/project_settings_spec.rb
index 59603310f51..80d91e5915f 100644
--- a/spec/features/projects/project_settings_spec.rb
+++ b/spec/features/projects/project_settings_spec.rb
@@ -14,7 +14,9 @@ describe 'Edit Project Settings' do
it 'shows errors for invalid project name' do
visit edit_project_path(project)
fill_in 'project_name_edit', with: 'foo&bar'
- click_button 'Save changes'
+ page.within('.general-settings') do
+ click_button 'Save changes'
+ end
expect(page).to have_field 'project_name_edit', with: 'foo&bar'
expect(page).to have_content "Name can contain only letters, digits, emojis, '_', '.', dash, space. It must start with letter, digit, emoji or '_'."
expect(page).to have_button 'Save changes'
@@ -23,7 +25,9 @@ describe 'Edit Project Settings' do
it 'shows a successful notice when the project is updated' do
visit edit_project_path(project)
fill_in 'project_name_edit', with: 'hello world'
- click_button 'Save changes'
+ page.within('.general-settings') do
+ click_button 'Save changes'
+ end
expect(page).to have_content "Project 'hello world' was successfully updated."
end
end
@@ -61,7 +65,7 @@ describe 'Edit Project Settings' do
TestEnv.clean_test_path
end
- after(:example) do
+ after do
TestEnv.clean_test_path
end
@@ -103,11 +107,11 @@ describe 'Edit Project Settings' do
TestEnv.clean_test_path
end
- before(:example) do
+ before do
group.add_owner(user)
end
- after(:example) do
+ after do
TestEnv.clean_test_path
end
diff --git a/spec/features/projects/settings/merge_requests_settings_spec.rb b/spec/features/projects/settings/merge_requests_settings_spec.rb
index a011fab2333..104ce08d9f3 100644
--- a/spec/features/projects/settings/merge_requests_settings_spec.rb
+++ b/spec/features/projects/settings/merge_requests_settings_spec.rb
@@ -20,6 +20,9 @@ feature 'Project settings > Merge Requests', :js do
expect(page).to have_content('Only allow merge requests to be merged if all discussions are resolved')
select 'Disabled', from: "project_project_feature_attributes_merge_requests_access_level"
+ within('.sharing-permissions-form') do
+ click_on('Save changes')
+ end
expect(page).not_to have_content('Only allow merge requests to be merged if the pipeline succeeds')
expect(page).not_to have_content('Only allow merge requests to be merged if all discussions are resolved')
@@ -37,6 +40,9 @@ feature 'Project settings > Merge Requests', :js do
expect(page).to have_content('Only allow merge requests to be merged if all discussions are resolved')
select 'Everyone with access', from: "project_project_feature_attributes_builds_access_level"
+ within('.sharing-permissions-form') do
+ click_on('Save changes')
+ end
expect(page).to have_content('Only allow merge requests to be merged if the pipeline succeeds')
expect(page).to have_content('Only allow merge requests to be merged if all discussions are resolved')
@@ -55,6 +61,9 @@ feature 'Project settings > Merge Requests', :js do
expect(page).not_to have_content('Only allow merge requests to be merged if all discussions are resolved')
select 'Everyone with access', from: "project_project_feature_attributes_merge_requests_access_level"
+ within('.sharing-permissions-form') do
+ click_on('Save changes')
+ end
expect(page).to have_content('Only allow merge requests to be merged if the pipeline succeeds')
expect(page).to have_content('Only allow merge requests to be merged if all discussions are resolved')
@@ -73,7 +82,9 @@ feature 'Project settings > Merge Requests', :js do
scenario 'when unchecked sets :printing_merge_request_link_enabled to false' do
uncheck('project_printing_merge_request_link_enabled')
- click_on('Save')
+ within('.merge-request-settings-form') do
+ click_on('Save changes')
+ end
# Wait for save to complete and page to reload
checkbox = find_field('project_printing_merge_request_link_enabled')
diff --git a/spec/features/projects/user_edits_files_spec.rb b/spec/features/projects/user_edits_files_spec.rb
index 8ae89c980b9..3129aad8473 100644
--- a/spec/features/projects/user_edits_files_spec.rb
+++ b/spec/features/projects/user_edits_files_spec.rb
@@ -1,10 +1,6 @@
require 'spec_helper'
describe 'User edits files' do
- let(:fork_message) do
- "You're not allowed to make changes to this project directly. "\
- "A fork of this project has been created that you can make changes in, so you can submit a merge request."
- end
let(:project) { create(:project, :repository, name: 'Shop') }
let(:project2) { create(:project, :repository, name: 'Another Project', path: 'another-project') }
let(:project_tree_path_root_ref) { project_tree_path(project, project.repository.root_ref) }
@@ -24,6 +20,9 @@ describe 'User edits files' do
it 'inserts a content of a file', js: true do
click_link('.gitignore')
find('.js-edit-blob').click
+
+ wait_for_requests
+
execute_script("ace.edit('editor').setValue('*.rbca')")
expect(evaluate_script('ace.edit("editor").getValue()')).to eq('*.rbca')
@@ -39,6 +38,9 @@ describe 'User edits files' do
it 'commits an edited file', js: true do
click_link('.gitignore')
find('.js-edit-blob').click
+
+ wait_for_requests
+
execute_script("ace.edit('editor').setValue('*.rbca')")
fill_in(:commit_message, with: 'New commit message', visible: true)
click_button('Commit changes')
@@ -53,6 +55,9 @@ describe 'User edits files' do
it 'commits an edited file to a new branch', js: true do
click_link('.gitignore')
find('.js-edit-blob').click
+
+ wait_for_requests
+
execute_script("ace.edit('editor').setValue('*.rbca')")
fill_in(:commit_message, with: 'New commit message', visible: true)
fill_in(:branch_name, with: 'new_branch_name', visible: true)
@@ -69,6 +74,9 @@ describe 'User edits files' do
it 'shows the diff of an edited file', js: true do
click_link('.gitignore')
find('.js-edit-blob').click
+
+ wait_for_requests
+
execute_script("ace.edit('editor').setValue('*.rbca')")
click_link('Preview changes')
@@ -91,7 +99,12 @@ describe 'User edits files' do
click_link('Fork')
- expect(page).to have_content(fork_message)
+ expect(page).to have_content(
+ "You're not allowed to make changes to this project directly. "\
+ "A fork of this project has been created that you can make changes in, so you can submit a merge request."
+ )
+
+ wait_for_requests
execute_script("ace.edit('editor').setValue('*.rbca')")
@@ -106,6 +119,9 @@ describe 'User edits files' do
expect(page).to have_button('Cancel')
click_link('Fork')
+
+ wait_for_requests
+
execute_script("ace.edit('editor').setValue('*.rbca')")
fill_in(:commit_message, with: 'New commit message', visible: true)
click_button('Commit changes')
diff --git a/spec/features/projects/wiki/user_updates_wiki_page_spec.rb b/spec/features/projects/wiki/user_updates_wiki_page_spec.rb
index e3739a705bf..64a80aec205 100644
--- a/spec/features/projects/wiki/user_updates_wiki_page_spec.rb
+++ b/spec/features/projects/wiki/user_updates_wiki_page_spec.rb
@@ -55,7 +55,7 @@ feature 'Projects > Wiki > User updates wiki page' do
scenario 'page has been updated since the user opened the edit page' do
click_link 'Edit'
- wiki_page.update('Update')
+ wiki_page.update(content: 'Update')
click_button 'Save changes'
diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb
index dbcdac902d5..d3d7915bebf 100644
--- a/spec/features/projects_spec.rb
+++ b/spec/features/projects_spec.rb
@@ -1,6 +1,27 @@
require 'spec_helper'
feature 'Project' do
+ describe 'creating from template' do
+ let(:user) { create(:user) }
+ let(:template) { Gitlab::ProjectTemplate.find(:rails) }
+
+ before do
+ sign_in user
+ visit new_project_path
+ end
+
+ it "allows creation from templates" do
+ page.choose(template.name)
+ fill_in("project_path", with: template.name)
+
+ page.within '#content-body' do
+ click_button "Create project"
+ end
+
+ expect(page).to have_content 'This project Loading..'
+ end
+ end
+
describe 'description' do
let(:project) { create(:project, :repository) }
let(:path) { project_path(project) }
@@ -146,6 +167,21 @@ feature 'Project' do
end
end
+ describe 'activity view' do
+ let(:user) { create(:user, project_view: 'activity') }
+ let(:project) { create(:project, :repository) }
+
+ before do
+ project.team << [user, :master]
+ sign_in user
+ visit project_path(project)
+ end
+
+ it 'loads activity', :js do
+ expect(page).to have_selector('.event-item')
+ end
+ end
+
def remove_with_confirm(button_text, confirm_with)
click_button button_text
fill_in 'confirm_name_input', with: confirm_with
diff --git a/spec/features/snippets/notes_on_personal_snippets_spec.rb b/spec/features/snippets/notes_on_personal_snippets_spec.rb
index f1d0905738b..c0c293dee78 100644
--- a/spec/features/snippets/notes_on_personal_snippets_spec.rb
+++ b/spec/features/snippets/notes_on_personal_snippets_spec.rb
@@ -91,11 +91,7 @@ describe 'Comments on personal snippets', :js do
context 'when editing a note' do
it 'changes the text' do
- open_more_actions_dropdown(snippet_notes[0])
-
- page.within("#notes-list li#note_#{snippet_notes[0].id}") do
- click_on 'Edit comment'
- end
+ find('.js-note-edit').click
page.within('.current-note-edit-form') do
fill_in 'note[note]', with: 'new content'
diff --git a/spec/features/snippets/user_creates_snippet_spec.rb b/spec/features/snippets/user_creates_snippet_spec.rb
index a919f5fa20b..d732383a1e1 100644
--- a/spec/features/snippets/user_creates_snippet_spec.rb
+++ b/spec/features/snippets/user_creates_snippet_spec.rb
@@ -41,7 +41,7 @@ feature 'User creates snippet', :js do
expect(page).to have_content('My Snippet')
link = find('a.no-attachment-icon img[alt="banana_sample"]')['src']
- expect(link).to match(%r{/uploads/system/temp/\h{32}/banana_sample\.gif\z})
+ expect(link).to match(%r{/uploads/-/system/temp/\h{32}/banana_sample\.gif\z})
visit(link)
expect(page.status_code).to eq(200)
@@ -59,7 +59,7 @@ feature 'User creates snippet', :js do
wait_for_requests
link = find('a.no-attachment-icon img[alt="banana_sample"]')['src']
- expect(link).to match(%r{/uploads/system/personal_snippet/#{Snippet.last.id}/\h{32}/banana_sample\.gif\z})
+ expect(link).to match(%r{/uploads/-/system/personal_snippet/#{Snippet.last.id}/\h{32}/banana_sample\.gif\z})
visit(link)
expect(page.status_code).to eq(200)
@@ -84,7 +84,7 @@ feature 'User creates snippet', :js do
end
expect(page).to have_content('Hello World!')
link = find('a.no-attachment-icon img[alt="banana_sample"]')['src']
- expect(link).to match(%r{/uploads/system/personal_snippet/#{Snippet.last.id}/\h{32}/banana_sample\.gif\z})
+ expect(link).to match(%r{/uploads/-/system/personal_snippet/#{Snippet.last.id}/\h{32}/banana_sample\.gif\z})
visit(link)
expect(page.status_code).to eq(200)
diff --git a/spec/features/snippets/user_edits_snippet_spec.rb b/spec/features/snippets/user_edits_snippet_spec.rb
index 26070e508e2..71de6b6bd1c 100644
--- a/spec/features/snippets/user_edits_snippet_spec.rb
+++ b/spec/features/snippets/user_edits_snippet_spec.rb
@@ -33,7 +33,7 @@ feature 'User edits snippet', :js do
wait_for_requests
link = find('a.no-attachment-icon img[alt="banana_sample"]')['src']
- expect(link).to match(%r{/uploads/system/personal_snippet/#{snippet.id}/\h{32}/banana_sample\.gif\z})
+ expect(link).to match(%r{/uploads/-/system/personal_snippet/#{snippet.id}/\h{32}/banana_sample\.gif\z})
end
it 'updates the snippet to make it internal' do
diff --git a/spec/features/task_lists_spec.rb b/spec/features/task_lists_spec.rb
index c14826df55a..580258f77eb 100644
--- a/spec/features/task_lists_spec.rb
+++ b/spec/features/task_lists_spec.rb
@@ -52,8 +52,8 @@ feature 'Task Lists' do
before do
Warden.test_mode!
- project.team << [user, :master]
- project.team << [user2, :guest]
+ project.add_master(user)
+ project.add_guest(user2)
login_as(user)
end
diff --git a/spec/features/triggers_spec.rb b/spec/features/triggers_spec.rb
index 8d12a492feb..47664de469a 100644
--- a/spec/features/triggers_spec.rb
+++ b/spec/features/triggers_spec.rb
@@ -82,7 +82,7 @@ feature 'Triggers', js: true do
end
describe 'trigger "Take ownership" workflow' do
- before(:each) do
+ before do
create(:ci_trigger, owner: user2, project: @project, description: trigger_title)
visit project_settings_ci_cd_path(@project)
end
@@ -104,7 +104,7 @@ feature 'Triggers', js: true do
end
describe 'trigger "Revoke" workflow' do
- before(:each) do
+ before do
create(:ci_trigger, owner: user2, project: @project, description: trigger_title)
visit project_settings_ci_cd_path(@project)
end
diff --git a/spec/features/users_spec.rb b/spec/features/users_spec.rb
index ff004d85272..15b89dac572 100644
--- a/spec/features/users_spec.rb
+++ b/spec/features/users_spec.rb
@@ -73,7 +73,7 @@ feature 'Users', js: true do
let(:loading_icon) { '.fa.fa-spinner' }
let(:username_input) { 'new_user_username' }
- before(:each) do
+ before do
visit new_user_session_path
click_link 'Register'
end
@@ -104,7 +104,7 @@ feature 'Users', js: true do
end
def errors_on_page(page)
- page.find('#error_explanation').find('ul').all('li').map{ |item| item.text }.join("\n")
+ page.find('#error_explanation').find('ul').all('li').map { |item| item.text }.join("\n")
end
def number_of_errors_on_page(page)
diff --git a/spec/finders/admin/projects_finder_spec.rb b/spec/finders/admin/projects_finder_spec.rb
index 4e367d39cf3..28e36330029 100644
--- a/spec/finders/admin/projects_finder_spec.rb
+++ b/spec/finders/admin/projects_finder_spec.rb
@@ -38,6 +38,12 @@ describe Admin::ProjectsFinder do
it { is_expected.to match_array([shared_project, public_project, internal_project, private_project]) }
end
+ context 'with pending delete project' do
+ let!(:pending_delete_project) { create(:project, pending_delete: true) }
+
+ it { is_expected.not_to include(pending_delete_project) }
+ end
+
context 'filter by namespace_id' do
let(:namespace) { create(:namespace) }
let!(:project_in_namespace) { create(:project, namespace: namespace) }
diff --git a/spec/finders/contributed_projects_finder_spec.rb b/spec/finders/contributed_projects_finder_spec.rb
index 2d079ea83b4..60ea98e61c7 100644
--- a/spec/finders/contributed_projects_finder_spec.rb
+++ b/spec/finders/contributed_projects_finder_spec.rb
@@ -14,8 +14,8 @@ describe ContributedProjectsFinder do
private_project.add_developer(current_user)
public_project.add_master(source_user)
- create(:event, :pushed, project: public_project, target: public_project, author: source_user)
- create(:event, :pushed, project: private_project, target: private_project, author: source_user)
+ create(:push_event, project: public_project, author: source_user)
+ create(:push_event, project: private_project, author: source_user)
end
describe 'without a current user' do
diff --git a/spec/finders/environments_finder_spec.rb b/spec/finders/environments_finder_spec.rb
index 0c063f6d5ee..3a8a1e7de74 100644
--- a/spec/finders/environments_finder_spec.rb
+++ b/spec/finders/environments_finder_spec.rb
@@ -12,7 +12,7 @@ describe EnvironmentsFinder do
context 'tagged deployment' do
before do
- create(:deployment, environment: environment, ref: '1.0', tag: true, sha: project.commit.id)
+ create(:deployment, environment: environment, ref: 'v1.1.0', tag: true, sha: project.commit.id)
end
it 'returns environment when with_tags is set' do
diff --git a/spec/fixtures/api/schemas/entities/merge_request.json b/spec/fixtures/api/schemas/entities/merge_request.json
index 7ffa82fc4bd..2f12b671dec 100644
--- a/spec/fixtures/api/schemas/entities/merge_request.json
+++ b/spec/fixtures/api/schemas/entities/merge_request.json
@@ -19,7 +19,6 @@
"human_time_estimate": { "type": ["integer", "null"] },
"human_total_time_spent": { "type": ["integer", "null"] },
"in_progress_merge_commit_sha": { "type": ["string", "null"] },
- "locked_at": { "type": ["string", "null"] },
"merge_error": { "type": ["string", "null"] },
"merge_commit_sha": { "type": ["string", "null"] },
"merge_params": { "type": ["object", "null"] },
@@ -94,7 +93,8 @@
"commit_change_content_path": { "type": "string" },
"remove_wip_path": { "type": "string" },
"commits_count": { "type": "integer" },
- "remove_source_branch": { "type": ["boolean", "null"] }
+ "remove_source_branch": { "type": ["boolean", "null"] },
+ "merge_ongoing": { "type": "boolean" }
},
"additionalProperties": false
}
diff --git a/spec/fixtures/api/schemas/public_api/v4/comment.json b/spec/fixtures/api/schemas/public_api/v4/comment.json
new file mode 100644
index 00000000000..52cfe86aeeb
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/comment.json
@@ -0,0 +1,21 @@
+{
+ "type": "object",
+ "required" : [
+ "name",
+ "message",
+ "commit",
+ "release"
+ ],
+ "properties" : {
+ "name": { "type": "string" },
+ "message": { "type": ["string", "null"] },
+ "commit": { "$ref": "commit/basic.json" },
+ "release": {
+ "oneOf": [
+ { "type": "null" },
+ { "$ref": "release.json" }
+ ]
+ }
+ },
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/commit/detail.json b/spec/fixtures/api/schemas/public_api/v4/commit/detail.json
new file mode 100644
index 00000000000..b7b2535c204
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/commit/detail.json
@@ -0,0 +1,16 @@
+{
+ "type": "object",
+ "allOf": [
+ { "$ref": "basic.json" },
+ {
+ "required" : [
+ "stats",
+ "status"
+ ],
+ "properties": {
+ "stats": { "$ref": "../commit_stats.json" },
+ "status": { "type": ["string", "null"] }
+ }
+ }
+ ]
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/commit_note.json b/spec/fixtures/api/schemas/public_api/v4/commit_note.json
new file mode 100644
index 00000000000..02081989271
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/commit_note.json
@@ -0,0 +1,19 @@
+{
+ "type": "object",
+ "required" : [
+ "note",
+ "path",
+ "line",
+ "line_type",
+ "author",
+ "created_at"
+ ],
+ "properties" : {
+ "note": { "type": ["string", "null"] },
+ "path": { "type": ["string", "null"] },
+ "line": { "type": ["integer", "null"] },
+ "line_type": { "type": ["string", "null"] },
+ "author": { "$ref": "user/basic.json" },
+ "created_at": { "type": "date" }
+ }
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/commit_notes.json b/spec/fixtures/api/schemas/public_api/v4/commit_notes.json
new file mode 100644
index 00000000000..d65a7d677ea
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/commit_notes.json
@@ -0,0 +1,4 @@
+{
+ "type": "array",
+ "items": { "$ref": "commit_note.json" }
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/commit_stats.json b/spec/fixtures/api/schemas/public_api/v4/commit_stats.json
new file mode 100644
index 00000000000..779384c62e6
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/commit_stats.json
@@ -0,0 +1,14 @@
+{
+ "type": "object",
+ "required" : [
+ "additions",
+ "deletions",
+ "total"
+ ],
+ "properties" : {
+ "additions": { "type": "integer" },
+ "deletions": { "type": "integer" },
+ "total": { "type": "integer" }
+ },
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/commits.json b/spec/fixtures/api/schemas/public_api/v4/commits.json
new file mode 100644
index 00000000000..98b17a96071
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/commits.json
@@ -0,0 +1,4 @@
+{
+ "type": "array",
+ "items": { "$ref": "commit/basic.json" }
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/release.json b/spec/fixtures/api/schemas/public_api/v4/release.json
new file mode 100644
index 00000000000..6612c2a9911
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/release.json
@@ -0,0 +1,12 @@
+{
+ "type": "object",
+ "required" : [
+ "tag_name",
+ "description"
+ ],
+ "properties" : {
+ "tag_name": { "type": ["string", "null"] },
+ "description": { "type": "string" }
+ },
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/tag.json b/spec/fixtures/api/schemas/public_api/v4/tag.json
new file mode 100644
index 00000000000..52cfe86aeeb
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/tag.json
@@ -0,0 +1,21 @@
+{
+ "type": "object",
+ "required" : [
+ "name",
+ "message",
+ "commit",
+ "release"
+ ],
+ "properties" : {
+ "name": { "type": "string" },
+ "message": { "type": ["string", "null"] },
+ "commit": { "$ref": "commit/basic.json" },
+ "release": {
+ "oneOf": [
+ { "type": "null" },
+ { "$ref": "release.json" }
+ ]
+ }
+ },
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/tags.json b/spec/fixtures/api/schemas/public_api/v4/tags.json
new file mode 100644
index 00000000000..eae352e7f87
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/tags.json
@@ -0,0 +1,4 @@
+{
+ "type": "array",
+ "items": { "$ref": "tag.json" }
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/user/basic.json b/spec/fixtures/api/schemas/public_api/v4/user/basic.json
new file mode 100644
index 00000000000..9f69d31971c
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/user/basic.json
@@ -0,0 +1,15 @@
+{
+ "type": "object",
+ "required": [
+ "id",
+ "state",
+ "avatar_url",
+ "web_url"
+ ],
+ "properties": {
+ "id": { "type": "integer" },
+ "state": { "type": "string" },
+ "avatar_url": { "type": "string" },
+ "web_url": { "type": "string" }
+ }
+}
diff --git a/spec/fixtures/encoding/Japanese.md b/spec/fixtures/encoding/Japanese.md
new file mode 100644
index 00000000000..dd469c9f232
--- /dev/null
+++ b/spec/fixtures/encoding/Japanese.md
@@ -0,0 +1,42 @@
++++
+date = "2017-05-21T13:05:07+09:00"
+title = "レイヤ"
+weight = 10
+
++++
+
+## ã“ã®ãƒãƒ¥ãƒ¼ãƒˆãƒªã‚¢ãƒ«ã§æ‰±ã†å†…容
+1. Redactedã«ãŠã‘ã‚‹2D開発ã§ã®ãƒ¬ã‚¤ãƒ¤ã®åŸºæœ¬çš„ãªæ¦‚è¦
+2. スクリーン上ã®ã‚¹ãƒ—ライトã®é †åºä»˜ã‘方法
+
+### Redactedã«ãŠã‘ã‚‹2D開発ã§ã®ãƒ¬ã‚¤ãƒ¤ã®åŸºæœ¬çš„ãªæ¦‚è¦
+2Dã«ãŠã„ã¦ã¯z軸ãŒå­˜åœ¨ã—ãªã„ãŸã‚ã€ã‚·ãƒ¼ãƒ³å†…è¦ç´ ã®æ画順を制御ã™ã‚‹ãŸã‚ã«ã¯ä»£æ›¿ã¨ãªã‚‹ä»•çµ„ã¿ãŒå¿…è¦ã§ã™ã€‚
+Redactedã§ã¯**レイヤ**ã«ãŠã‘ã‚‹**zIndex**属性を制御å¯èƒ½ã«ã™ã‚‹ã“ã¨ã§ã€ã“ã®èª²é¡Œã‚’解決ã—ã¦ã„ã¾ã™ã€‚
+**デフォルトã§ã¯ã€zIndexã¯0ã¨ãªã‚Šã‚ªãƒ–ジェクトã¯ãƒ¬ã‚¤ãƒ¤ã«è¿½åŠ ã•ã‚ŒãŸé †ç•ªã«æç”»ã•ã‚Œã¾ã™ã€‚**
+
+レイヤã«ã¯ã„ãã¤ã‹ã®é‡è¦ãªç‰¹æ€§ãŒã‚ã‚Šã¾ã™ã€‚
+
+* レイヤã«ã¯ãƒ¬ã‚¤ãƒ¤åŒ–ã•ã‚ŒãŸã‚ªãƒ–ジェクトã®ã¿ã‚’å«ã‚ã‚‹ã“ã¨ãŒã§ãã¾ã™ã€‚(**3Dモデルã¯çµ¶å¯¾ã«è¿½åŠ ã—ãªã„ã§ãã ã•ã„**)
+* レイヤã¯ãƒ¬ã‚¤ãƒ¤åŒ–ã•ã‚ŒãŸã‚ªãƒ–ジェクトã§ã™ã€‚(ã—ãŸãŒã£ã¦ã€ãƒ¬ã‚¤ãƒ¤ã«ã¯ä»–ã®ãƒ¬ã‚¤ãƒ¤ã‚’å«ã‚ã‚‹ã“ã¨ãŒã§ãã¾ã™)
+* レイヤ化ã•ã‚ŒãŸã‚ªãƒ–ジェクトã¯ã€æœ€å¤§ã§1ã¤ã®ãƒ¬ã‚¤ãƒ¤ã«å±žã™ã“ã¨ãŒã§ãã¾ã™ã€‚
+
+レイヤを直接åˆæœŸåŒ–ã™ã‚‹ã“ã¨ã¯ã§ãã¾ã›ã‚“ãŒã€ãã®æ´¾ç”Ÿã‚¯ãƒ©ã‚¹ã¯åˆæœŸåŒ–ã™ã‚‹ã“ã¨ãŒå¯èƒ½ã§ã™ã€‚**Scene2D**ã¨**コンテナ**ã¯ã€**レイヤ**ã‹ã‚‰æ´¾ç”Ÿã™ã‚‹2ã¤ã®ä¸»ãªã‚ªãƒ–ジェクトã§ã™ã€‚ã™ã¹ã¦ã®åˆæœŸåŒ–(createContainerã€instantiateã€...)ã¯ãƒ¬ã‚¤ãƒ¤ä¸Šã§è¡Œã‚ã‚Œã¾ã™ã€‚ã¤ã¾ã‚Šã€2Dã§åˆæœŸåŒ–ã•ã‚Œã‚‹ã™ã¹ã¦ã®ã‚ªãƒ–ジェクトã¯ã€zIndexプロパティをæŒã¤ãƒ¬ã‚¤ãƒ¤åŒ–ã•ã‚ŒãŸã‚ªãƒ–ジェクトã§ã™ã€‚
+
+**zIndexã¯ã‚°ãƒ­ãƒ¼ãƒãƒ«ã§ã¯ã‚ã‚Šã¾ã›ã‚“!**
+
+CSSã¨ã¯ç•°ãªã‚Šã€zIndexã¯ã™ã¹ã¦ã®ã‚ªãƒ–ジェクトã«å¯¾ã—ã¦ã‚°ãƒ­ãƒ¼ãƒãƒ«ã§ã¯ã‚ã‚Šã¾ã›ã‚“。zIndexプロパティã¯è¦ªãƒ¬ã‚¤ãƒ¤ã«å¯¾ã—ã¦ãƒ­ãƒ¼ã‚«ãƒ«ã§ã™ã€‚詳細ã«ã¤ãã¾ã—ã¦ã¯ã€ã‚³ãƒ³ãƒ†ãƒŠãƒãƒ¥ãƒ¼ãƒˆãƒªã‚¢ãƒ«ã§èª¬æ˜Žã—ã¦ã„ã¾ã™ã€‚ [TODO: Link]。
+
+### スクリーン上ã®ã‚¹ãƒ—ライトã®é †åºä»˜ã‘方法
+ã“ã‚Œã¾ã§å­¦ã‚“ã ã“ã¨ã‚’生ã‹ã—ã¦ã€ç”»é¢ã«ã‚¹ãƒ—ライトを表示ã—ã¦ã€zIndexã®è¨­å®šã‚’ã—ã¦ã¿ã¾ã—ょã†ï¼
+
+* ã¾ãšã€æœ€åˆã« (A,B,C) スプライトを生æˆã—ã¾ã™ã€‚
+* スプライトAをシーンã«è¿½åŠ ã—ã¾ã™ï¼ˆzIndex = 0ã€æ¨™æº–色)
+* スプライトBをシーン2ã«è¿½åŠ ã™ã‚‹ã¨ã€**スプライトAã®ä¸Šã«**表示ã•ã‚Œã¾ã™ï¼ˆzIndex = 0ã€èµ¤è‰²ï¼‰
+* 最後ã«ã‚¹ãƒ—ライトCをシーンã«è¿½åŠ ã—ã¾ã™ï¼ˆé’色)ãŒã€ã‚¹ãƒ—ライトã®zIndexã‚’-1ã«è¨­å®šã™ã‚‹ã¨ã€ã‚¹ãƒ—ライトã¯Aã¨Bã®å¾Œå´ã«è¡¨ç¤ºã•ã‚Œã¾ã™ã€‚
+
+{{< code "static/tutorials/layers.html" >}}
+
+### ソースコード全体
+```js
+{{< snippet "static/tutorials/layers.html" >}}
+```
diff --git a/spec/fixtures/markdown.md.erb b/spec/fixtures/markdown.md.erb
index 58b43805705..4f46e40ce7a 100644
--- a/spec/fixtures/markdown.md.erb
+++ b/spec/fixtures/markdown.md.erb
@@ -227,8 +227,11 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
- Milestone in another project: <%= xmilestone.to_reference(project) %>
- Ignored in code: `<%= simple_milestone.to_reference %>`
- Ignored in links: [Link to <%= simple_milestone.to_reference %>](#milestone-link)
-- Milestone by URL: <%= urls.project_milestone_url(milestone.project, milestone) %>
+- Milestone by URL: <%= urls.milestone_url(milestone) %>
- Link to milestone by URL: [Milestone](<%= milestone.to_reference %>)
+- Group milestone by name: <%= Milestone.reference_prefix %><%= group_milestone.name %>
+- Group milestone by name in quotes: <%= group_milestone.to_reference(format: :name) %>
+- Group milestone by URL is ignore: <%= urls.milestone_url(group_milestone) %>
### Task Lists
diff --git a/spec/helpers/defer_script_tag_helper_spec.rb b/spec/helpers/defer_script_tag_helper_spec.rb
new file mode 100644
index 00000000000..d10b6f134e4
--- /dev/null
+++ b/spec/helpers/defer_script_tag_helper_spec.rb
@@ -0,0 +1,13 @@
+# coding: utf-8
+require 'spec_helper'
+
+describe DeferScriptTagHelper do
+ describe 'script tag' do
+ script_url = 'test.js'
+
+ it 'returns an script tag with defer=true' do
+ expect(javascript_include_tag(script_url).to_s)
+ .to eq "<script src=\"/javascripts/#{script_url}\" defer=\"defer\"></script>"
+ end
+ end
+end
diff --git a/spec/helpers/gitlab_routing_helper_spec.rb b/spec/helpers/gitlab_routing_helper_spec.rb
index 537e457513f..a44b200c5da 100644
--- a/spec/helpers/gitlab_routing_helper_spec.rb
+++ b/spec/helpers/gitlab_routing_helper_spec.rb
@@ -63,44 +63,4 @@ describe GitlabRoutingHelper do
it { expect(resend_invite_group_member_path(group_member)).to eq resend_invite_group_group_member_path(group_member.source, group_member) }
end
end
-
- describe '#milestone_path' do
- context 'for a group milestone' do
- let(:milestone) { build_stubbed(:milestone, group: group, iid: 1) }
-
- it 'links to the group milestone page' do
- expect(milestone_path(milestone))
- .to eq(group_milestone_path(group, milestone))
- end
- end
-
- context 'for a project milestone' do
- let(:milestone) { build_stubbed(:milestone, project: project, iid: 1) }
-
- it 'links to the project milestone page' do
- expect(milestone_path(milestone))
- .to eq(project_milestone_path(project, milestone))
- end
- end
- end
-
- describe '#milestone_url' do
- context 'for a group milestone' do
- let(:milestone) { build_stubbed(:milestone, group: group, iid: 1) }
-
- it 'links to the group milestone page' do
- expect(milestone_url(milestone))
- .to eq(group_milestone_url(group, milestone))
- end
- end
-
- context 'for a project milestone' do
- let(:milestone) { build_stubbed(:milestone, project: project, iid: 1) }
-
- it 'links to the project milestone page' do
- expect(milestone_url(milestone))
- .to eq(project_milestone_url(project, milestone))
- end
- end
- end
end
diff --git a/spec/helpers/milestones_routing_helper_spec.rb b/spec/helpers/milestones_routing_helper_spec.rb
new file mode 100644
index 00000000000..dc13a43c2ab
--- /dev/null
+++ b/spec/helpers/milestones_routing_helper_spec.rb
@@ -0,0 +1,46 @@
+require 'spec_helper'
+
+describe MilestonesRoutingHelper do
+ let(:project) { build_stubbed(:project) }
+ let(:group) { build_stubbed(:group) }
+
+ describe '#milestone_path' do
+ context 'for a group milestone' do
+ let(:milestone) { build_stubbed(:milestone, group: group, iid: 1) }
+
+ it 'links to the group milestone page' do
+ expect(milestone_path(milestone))
+ .to eq(group_milestone_path(group, milestone))
+ end
+ end
+
+ context 'for a project milestone' do
+ let(:milestone) { build_stubbed(:milestone, project: project, iid: 1) }
+
+ it 'links to the project milestone page' do
+ expect(milestone_path(milestone))
+ .to eq(project_milestone_path(project, milestone))
+ end
+ end
+ end
+
+ describe '#milestone_url' do
+ context 'for a group milestone' do
+ let(:milestone) { build_stubbed(:milestone, group: group, iid: 1) }
+
+ it 'links to the group milestone page' do
+ expect(milestone_url(milestone))
+ .to eq(group_milestone_url(group, milestone))
+ end
+ end
+
+ context 'for a project milestone' do
+ let(:milestone) { build_stubbed(:milestone, project: project, iid: 1) }
+
+ it 'links to the project milestone page' do
+ expect(milestone_url(milestone))
+ .to eq(project_milestone_url(project, milestone))
+ end
+ end
+ end
+end
diff --git a/spec/helpers/pagination_helper_spec.rb b/spec/helpers/pagination_helper_spec.rb
new file mode 100644
index 00000000000..e235475fb47
--- /dev/null
+++ b/spec/helpers/pagination_helper_spec.rb
@@ -0,0 +1,23 @@
+require 'spec_helper'
+
+describe PaginationHelper do
+ describe '#paginate_collection' do
+ let(:collection) { User.all.page(1) }
+
+ it 'paginates a collection without using a COUNT' do
+ without_count = collection.without_count
+
+ expect(helper).to receive(:paginate_without_count)
+ .with(without_count)
+ .and_call_original
+
+ helper.paginate_collection(without_count)
+ end
+
+ it 'paginates a collection using a COUNT' do
+ expect(helper).to receive(:paginate_with_count).and_call_original
+
+ helper.paginate_collection(collection)
+ end
+ end
+end
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index 236a7c29634..37a5e6b474e 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -411,4 +411,48 @@ describe ProjectsHelper do
end
end
end
+
+ describe '#has_projects_or_name?' do
+ let(:projects) do
+ create(:project)
+ Project.all
+ end
+
+ it 'returns true when there are projects' do
+ expect(helper.has_projects_or_name?(projects, {})).to eq(true)
+ end
+
+ it 'returns true when there are no projects but a name is given' do
+ expect(helper.has_projects_or_name?(Project.none, name: 'foo')).to eq(true)
+ end
+
+ it 'returns false when there are no projects and there is no name' do
+ expect(helper.has_projects_or_name?(Project.none, {})).to eq(false)
+ end
+ end
+
+ describe '#any_projects?' do
+ before do
+ create(:project)
+ end
+
+ it 'returns true when projects will be returned' do
+ expect(helper.any_projects?(Project.all)).to eq(true)
+ end
+
+ it 'returns false when no projects will be returned' do
+ expect(helper.any_projects?(Project.none)).to eq(false)
+ end
+
+ it 'only executes a single query when a LIMIT is applied' do
+ relation = Project.limit(1)
+ recorder = ActiveRecord::QueryRecorder.new do
+ 2.times do
+ helper.any_projects?(relation)
+ end
+ end
+
+ expect(recorder.count).to eq(1)
+ end
+ end
end
diff --git a/spec/helpers/storage_health_helper_spec.rb b/spec/helpers/storage_health_helper_spec.rb
new file mode 100644
index 00000000000..874498e6338
--- /dev/null
+++ b/spec/helpers/storage_health_helper_spec.rb
@@ -0,0 +1,20 @@
+require 'spec_helper'
+
+describe StorageHealthHelper do
+ describe '#failing_storage_health_message' do
+ let(:health) do
+ Gitlab::Git::Storage::Health.new(
+ "<script>alert('storage name');)</script>",
+ []
+ )
+ end
+
+ it 'escapes storage names' do
+ escaped_storage_name = '&lt;script&gt;alert(&#39;storage name&#39;);)&lt;/script&gt;'
+
+ result = helper.failing_storage_health_message(health)
+
+ expect(result).to include(escaped_storage_name)
+ end
+ end
+end
diff --git a/spec/initializers/6_validations_spec.rb b/spec/initializers/6_validations_spec.rb
index 0877770c167..83283f03940 100644
--- a/spec/initializers/6_validations_spec.rb
+++ b/spec/initializers/6_validations_spec.rb
@@ -23,6 +23,16 @@ describe '6_validations' do
end
end
+ context 'when one of the settings is incorrect' do
+ before do
+ mock_storages('foo' => { 'path' => 'tmp/tests/paths/a/b/c', 'failure_count_threshold' => 'not a number' })
+ end
+
+ it 'throws an error' do
+ expect { validate_storages_config }.to raise_error(/failure_count_threshold/)
+ end
+ end
+
context 'with invalid storage names' do
before do
mock_storages('name with spaces' => { 'path' => 'tmp/tests/paths/a/b/c' })
@@ -84,6 +94,17 @@ describe '6_validations' do
expect { validate_storages_paths }.not_to raise_error
end
end
+
+ describe 'inaccessible storage' do
+ before do
+ mock_storages('foo' => { 'path' => 'tmp/tests/a/path/that/does/not/exist' })
+ end
+
+ it 'passes through with a warning' do
+ expect(Rails.logger).to receive(:error)
+ expect { validate_storages_paths }.not_to raise_error
+ end
+ end
end
def mock_storages(storages)
diff --git a/spec/initializers/settings_spec.rb b/spec/initializers/settings_spec.rb
index ebdabcf93f1..e5ec90cb8f9 100644
--- a/spec/initializers/settings_spec.rb
+++ b/spec/initializers/settings_spec.rb
@@ -2,6 +2,17 @@ require 'spec_helper'
require_relative '../../config/initializers/1_settings'
describe Settings do
+ describe '#repositories' do
+ it 'assigns the default failure attributes' do
+ repository_settings = Gitlab.config.repositories.storages['broken']
+
+ expect(repository_settings['failure_count_threshold']).to eq(10)
+ expect(repository_settings['failure_wait_time']).to eq(30)
+ expect(repository_settings['failure_reset_time']).to eq(1800)
+ expect(repository_settings['storage_timeout']).to eq(5)
+ end
+ end
+
describe '#host_without_www' do
context 'URL with protocol' do
it 'returns the host' do
diff --git a/spec/javascripts/abuse_reports_spec.js b/spec/javascripts/abuse_reports_spec.js
index 069d857eab6..13cab81dd60 100644
--- a/spec/javascripts/abuse_reports_spec.js
+++ b/spec/javascripts/abuse_reports_spec.js
@@ -6,10 +6,10 @@ import '~/abuse_reports';
const FIXTURE = 'abuse_reports/abuse_reports_list.html.raw';
const MAX_MESSAGE_LENGTH = 500;
- let messages;
+ let $messages;
const assertMaxLength = $message => expect($message.text().length).toEqual(MAX_MESSAGE_LENGTH);
- const findMessage = searchText => messages.filter(
+ const findMessage = searchText => $messages.filter(
(index, element) => element.innerText.indexOf(searchText) > -1,
).first();
@@ -18,7 +18,7 @@ import '~/abuse_reports';
beforeEach(function () {
loadFixtures(FIXTURE);
this.abuseReports = new global.AbuseReports();
- messages = $('.abuse-reports .message');
+ $messages = $('.abuse-reports .message');
});
it('should truncate long messages', () => {
diff --git a/spec/javascripts/ajax_loading_spinner_spec.js b/spec/javascripts/ajax_loading_spinner_spec.js
index 1518ae68b0d..46e072a8ebb 100644
--- a/spec/javascripts/ajax_loading_spinner_spec.js
+++ b/spec/javascripts/ajax_loading_spinner_spec.js
@@ -1,4 +1,3 @@
-import '~/extensions/array';
import 'jquery';
import 'jquery-ujs';
import '~/ajax_loading_spinner';
diff --git a/spec/javascripts/blob/blob_file_dropzone_spec.js b/spec/javascripts/blob/blob_file_dropzone_spec.js
new file mode 100644
index 00000000000..2c8183ff77b
--- /dev/null
+++ b/spec/javascripts/blob/blob_file_dropzone_spec.js
@@ -0,0 +1,42 @@
+import 'dropzone';
+import BlobFileDropzone from '~/blob/blob_file_dropzone';
+
+describe('BlobFileDropzone', () => {
+ preloadFixtures('blob/show.html.raw');
+
+ beforeEach(() => {
+ loadFixtures('blob/show.html.raw');
+ const form = $('.js-upload-blob-form');
+ this.blobFileDropzone = new BlobFileDropzone(form, 'POST');
+ this.dropzone = $('.js-upload-blob-form .dropzone').get(0).dropzone;
+ this.replaceFileButton = $('#submit-all');
+ });
+
+ describe('submit button', () => {
+ it('requires file', () => {
+ spyOn(window, 'alert');
+
+ this.replaceFileButton.click();
+
+ expect(window.alert).toHaveBeenCalled();
+ });
+
+ it('is disabled while uploading', () => {
+ spyOn(window, 'alert');
+
+ const file = {
+ name: 'some-file.jpg',
+ type: 'jpg',
+ };
+ const fakeEvent = jQuery.Event('drop', {
+ dataTransfer: { files: [file] },
+ });
+
+ this.dropzone.listeners[0].events.drop(fakeEvent);
+ this.replaceFileButton.click();
+
+ expect(window.alert).not.toHaveBeenCalled();
+ expect(this.replaceFileButton.is(':disabled')).toEqual(true);
+ });
+ });
+});
diff --git a/spec/javascripts/blob/viewer/index_spec.js b/spec/javascripts/blob/viewer/index_spec.js
index af04e7c1e72..cfa6650d85f 100644
--- a/spec/javascripts/blob/viewer/index_spec.js
+++ b/spec/javascripts/blob/viewer/index_spec.js
@@ -3,10 +3,10 @@ import BlobViewer from '~/blob/viewer/index';
describe('Blob viewer', () => {
let blob;
- preloadFixtures('blob/show.html.raw');
+ preloadFixtures('snippets/show.html.raw');
beforeEach(() => {
- loadFixtures('blob/show.html.raw');
+ loadFixtures('snippets/show.html.raw');
$('#modal-upload-blob').remove();
blob = new BlobViewer();
diff --git a/spec/javascripts/boards/issue_card_spec.js b/spec/javascripts/boards/issue_card_spec.js
index bd9b4fbfdd3..69cfcbbce5a 100644
--- a/spec/javascripts/boards/issue_card_spec.js
+++ b/spec/javascripts/boards/issue_card_spec.js
@@ -238,12 +238,6 @@ describe('Issue card component', () => {
});
describe('labels', () => {
- it('does not render any', () => {
- expect(
- component.$el.querySelector('.label'),
- ).toBeNull();
- });
-
describe('exists', () => {
beforeEach((done) => {
component.issue.addLabel(label1);
@@ -251,16 +245,21 @@ describe('Issue card component', () => {
Vue.nextTick(() => done());
});
- it('does not render list label', () => {
+ it('renders list label', () => {
expect(
component.$el.querySelectorAll('.label').length,
- ).toBe(1);
+ ).toBe(2);
});
it('renders label', () => {
+ const nodes = [];
+ component.$el.querySelectorAll('.label').forEach((label) => {
+ nodes.push(label.title);
+ });
+
expect(
- component.$el.querySelector('.label').textContent,
- ).toContain(label1.title);
+ nodes.includes(label1.description),
+ ).toBe(true);
});
it('sets label description as title', () => {
@@ -270,9 +269,14 @@ describe('Issue card component', () => {
});
it('sets background color of button', () => {
+ const nodes = [];
+ component.$el.querySelectorAll('.label').forEach((label) => {
+ nodes.push(label.style.backgroundColor);
+ });
+
expect(
- component.$el.querySelector('.label').style.backgroundColor,
- ).toContain(label1.color);
+ nodes.includes(label1.color),
+ ).toBe(true);
});
});
});
diff --git a/spec/javascripts/breakpoints_spec.js b/spec/javascripts/breakpoints_spec.js
new file mode 100644
index 00000000000..b1b5d36c1fb
--- /dev/null
+++ b/spec/javascripts/breakpoints_spec.js
@@ -0,0 +1,15 @@
+import bp, {
+ breakpoints,
+} from '~/breakpoints';
+
+describe('breakpoints', () => {
+ Object.keys(breakpoints).forEach((key) => {
+ const size = breakpoints[key];
+
+ it(`returns ${key} when larger than ${size}`, () => {
+ spyOn(bp, 'windowWidth').and.returnValue(size + 10);
+
+ expect(bp.getBreakpointSize()).toBe(key);
+ });
+ });
+});
diff --git a/spec/javascripts/build_spec.js b/spec/javascripts/build_spec.js
index be90dbdd88a..35149611095 100644
--- a/spec/javascripts/build_spec.js
+++ b/spec/javascripts/build_spec.js
@@ -5,7 +5,6 @@ import '~/lib/utils/datetime_utility';
import '~/lib/utils/url_utility';
import '~/build';
import '~/breakpoints';
-import 'vendor/jquery.nicescroll';
describe('Build', () => {
const BUILD_URL = `${gl.TEST_HOST}/frontend-fixtures/builds-project/-/jobs/1`;
diff --git a/spec/javascripts/extensions/array_spec.js b/spec/javascripts/extensions/array_spec.js
deleted file mode 100644
index b1b81b4efc2..00000000000
--- a/spec/javascripts/extensions/array_spec.js
+++ /dev/null
@@ -1,22 +0,0 @@
-/* eslint-disable space-before-function-paren, no-var */
-
-import '~/extensions/array';
-
-(function() {
- describe('Array extensions', function() {
- describe('first', function() {
- return it('returns the first item', function() {
- var arr;
- arr = [0, 1, 2, 3, 4, 5];
- return expect(arr.first()).toBe(0);
- });
- });
- describe('last', function() {
- return it('returns the last item', function() {
- var arr;
- arr = [0, 1, 2, 3, 4, 5];
- return expect(arr.last()).toBe(5);
- });
- });
- });
-}).call(window);
diff --git a/spec/javascripts/filtered_search/dropdown_utils_spec.js b/spec/javascripts/filtered_search/dropdown_utils_spec.js
index 244f170ab7a..b1b3d43f241 100644
--- a/spec/javascripts/filtered_search/dropdown_utils_spec.js
+++ b/spec/javascripts/filtered_search/dropdown_utils_spec.js
@@ -1,4 +1,3 @@
-import '~/extensions/array';
import '~/filtered_search/dropdown_utils';
import '~/filtered_search/filtered_search_tokenizer';
import '~/filtered_search/filtered_search_dropdown_manager';
diff --git a/spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js b/spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js
index 9e2076dc383..5c7e9115aac 100644
--- a/spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js
+++ b/spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js
@@ -1,4 +1,3 @@
-import '~/extensions/array';
import '~/filtered_search/filtered_search_visual_tokens';
import '~/filtered_search/filtered_search_tokenizer';
import '~/filtered_search/filtered_search_dropdown_manager';
diff --git a/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js b/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js
index 1a7631994b4..69b424c3af5 100644
--- a/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js
+++ b/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js
@@ -1,4 +1,3 @@
-import '~/extensions/array';
import '~/filtered_search/filtered_search_token_keys';
describe('Filtered Search Token Keys', () => {
diff --git a/spec/javascripts/filtered_search/filtered_search_tokenizer_spec.js b/spec/javascripts/filtered_search/filtered_search_tokenizer_spec.js
index e4a15c83c23..585bea9b499 100644
--- a/spec/javascripts/filtered_search/filtered_search_tokenizer_spec.js
+++ b/spec/javascripts/filtered_search/filtered_search_tokenizer_spec.js
@@ -1,4 +1,3 @@
-import '~/extensions/array';
import '~/filtered_search/filtered_search_token_keys';
import '~/filtered_search/filtered_search_tokenizer';
diff --git a/spec/javascripts/fixtures/abuse_reports.rb b/spec/javascripts/fixtures/abuse_reports.rb
index de673f94d72..387858cba77 100644
--- a/spec/javascripts/fixtures/abuse_reports.rb
+++ b/spec/javascripts/fixtures/abuse_reports.rb
@@ -14,7 +14,7 @@ describe Admin::AbuseReportsController, '(JavaScript fixtures)', type: :controll
clean_frontend_fixtures('abuse_reports/')
end
- before(:each) do
+ before do
sign_in(admin)
end
diff --git a/spec/javascripts/fixtures/blob.rb b/spec/javascripts/fixtures/blob.rb
index 16490ad5039..2dffc42b0ef 100644
--- a/spec/javascripts/fixtures/blob.rb
+++ b/spec/javascripts/fixtures/blob.rb
@@ -13,7 +13,7 @@ describe Projects::BlobController, '(JavaScript fixtures)', type: :controller do
clean_frontend_fixtures('blob/')
end
- before(:each) do
+ before do
sign_in(admin)
end
diff --git a/spec/javascripts/fixtures/boards.rb b/spec/javascripts/fixtures/boards.rb
index d7c3dc0a235..494c9cabdcc 100644
--- a/spec/javascripts/fixtures/boards.rb
+++ b/spec/javascripts/fixtures/boards.rb
@@ -13,7 +13,7 @@ describe Projects::BoardsController, '(JavaScript fixtures)', type: :controller
clean_frontend_fixtures('boards/')
end
- before(:each) do
+ before do
sign_in(admin)
end
diff --git a/spec/javascripts/fixtures/branches.rb b/spec/javascripts/fixtures/branches.rb
index a059818145b..bb3bdf7c215 100644
--- a/spec/javascripts/fixtures/branches.rb
+++ b/spec/javascripts/fixtures/branches.rb
@@ -13,7 +13,7 @@ describe Projects::BranchesController, '(JavaScript fixtures)', type: :controlle
clean_frontend_fixtures('branches/')
end
- before(:each) do
+ before do
sign_in(admin)
end
diff --git a/spec/javascripts/fixtures/dashboard.rb b/spec/javascripts/fixtures/dashboard.rb
index e83db8daaf2..793ffa7c220 100644
--- a/spec/javascripts/fixtures/dashboard.rb
+++ b/spec/javascripts/fixtures/dashboard.rb
@@ -13,7 +13,7 @@ describe Dashboard::ProjectsController, '(JavaScript fixtures)', type: :controll
clean_frontend_fixtures('dashboard/')
end
- before(:each) do
+ before do
sign_in(admin)
end
diff --git a/spec/javascripts/fixtures/deploy_keys.rb b/spec/javascripts/fixtures/deploy_keys.rb
index fca3f5b1bfe..bea161c514f 100644
--- a/spec/javascripts/fixtures/deploy_keys.rb
+++ b/spec/javascripts/fixtures/deploy_keys.rb
@@ -12,7 +12,7 @@ describe Projects::DeployKeysController, '(JavaScript fixtures)', type: :control
clean_frontend_fixtures('deploy_keys/')
end
- before(:each) do
+ before do
sign_in(admin)
end
diff --git a/spec/javascripts/fixtures/environments.rb b/spec/javascripts/fixtures/environments.rb
index 3474f4696ef..d2457d75419 100644
--- a/spec/javascripts/fixtures/environments.rb
+++ b/spec/javascripts/fixtures/environments.rb
@@ -14,7 +14,7 @@ describe Projects::EnvironmentsController, '(JavaScript fixtures)', type: :contr
clean_frontend_fixtures('environments/metrics')
end
- before(:each) do
+ before do
sign_in(admin)
end
diff --git a/spec/javascripts/fixtures/issues.rb b/spec/javascripts/fixtures/issues.rb
index 1a30909977e..d3ad50af1b9 100644
--- a/spec/javascripts/fixtures/issues.rb
+++ b/spec/javascripts/fixtures/issues.rb
@@ -13,7 +13,7 @@ describe Projects::IssuesController, '(JavaScript fixtures)', type: :controller
clean_frontend_fixtures('issues/')
end
- before(:each) do
+ before do
sign_in(admin)
end
diff --git a/spec/javascripts/fixtures/jobs.rb b/spec/javascripts/fixtures/jobs.rb
index dc7dde1138c..83a96797506 100644
--- a/spec/javascripts/fixtures/jobs.rb
+++ b/spec/javascripts/fixtures/jobs.rb
@@ -17,7 +17,7 @@ describe Projects::JobsController, '(JavaScript fixtures)', type: :controller do
clean_frontend_fixtures('builds/')
end
- before(:each) do
+ before do
sign_in(admin)
end
diff --git a/spec/javascripts/fixtures/labels.rb b/spec/javascripts/fixtures/labels.rb
index 2e4811b64a4..814f065f3a4 100644
--- a/spec/javascripts/fixtures/labels.rb
+++ b/spec/javascripts/fixtures/labels.rb
@@ -22,7 +22,7 @@ describe 'Labels (JavaScript fixtures)' do
describe Groups::LabelsController, '(JavaScript fixtures)', type: :controller do
render_views
- before(:each) do
+ before do
sign_in(admin)
end
@@ -39,7 +39,7 @@ describe 'Labels (JavaScript fixtures)' do
describe Projects::LabelsController, '(JavaScript fixtures)', type: :controller do
render_views
- before(:each) do
+ before do
sign_in(admin)
end
diff --git a/spec/javascripts/fixtures/merge_requests.rb b/spec/javascripts/fixtures/merge_requests.rb
index f9d8b5c569c..f97a5d2b5de 100644
--- a/spec/javascripts/fixtures/merge_requests.rb
+++ b/spec/javascripts/fixtures/merge_requests.rb
@@ -33,7 +33,7 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont
clean_frontend_fixtures('merge_requests/')
end
- before(:each) do
+ before do
sign_in(admin)
end
diff --git a/spec/javascripts/fixtures/merge_requests_diffs.rb b/spec/javascripts/fixtures/merge_requests_diffs.rb
index 4481a187f63..6e0a97d2e3f 100644
--- a/spec/javascripts/fixtures/merge_requests_diffs.rb
+++ b/spec/javascripts/fixtures/merge_requests_diffs.rb
@@ -25,7 +25,7 @@ describe Projects::MergeRequests::DiffsController, '(JavaScript fixtures)', type
clean_frontend_fixtures('merge_request_diffs/')
end
- before(:each) do
+ before do
sign_in(admin)
end
diff --git a/spec/javascripts/fixtures/pipelines.rb b/spec/javascripts/fixtures/pipelines.rb
index daafbac86db..bb85da50f0f 100644
--- a/spec/javascripts/fixtures/pipelines.rb
+++ b/spec/javascripts/fixtures/pipelines.rb
@@ -19,7 +19,7 @@ describe Projects::PipelinesController, '(JavaScript fixtures)', type: :controll
clean_frontend_fixtures('pipelines/')
end
- before(:each) do
+ before do
sign_in(admin)
end
diff --git a/spec/javascripts/fixtures/project_select_combo_button.html.haml b/spec/javascripts/fixtures/project_select_combo_button.html.haml
new file mode 100644
index 00000000000..54bc1a59279
--- /dev/null
+++ b/spec/javascripts/fixtures/project_select_combo_button.html.haml
@@ -0,0 +1,6 @@
+.project-item-select-holder
+ %input.project-item-select{ data: { group_id: '12345' , relative_path: 'issues/new' } }
+ %a.new-project-item-link{ data: { label: 'New issue' }, href: ''}
+ %i.fa.fa-spinner.spin
+ %a.new-project-item-select-button
+ %i.fa.fa-caret-down
diff --git a/spec/javascripts/fixtures/projects.rb b/spec/javascripts/fixtures/projects.rb
index 6c33b240e5c..f09d44a49d1 100644
--- a/spec/javascripts/fixtures/projects.rb
+++ b/spec/javascripts/fixtures/projects.rb
@@ -13,7 +13,7 @@ describe ProjectsController, '(JavaScript fixtures)', type: :controller do
clean_frontend_fixtures('projects/')
end
- before(:each) do
+ before do
sign_in(admin)
end
diff --git a/spec/javascripts/fixtures/prometheus_service.rb b/spec/javascripts/fixtures/prometheus_service.rb
index 3200577b326..7a46e47bb15 100644
--- a/spec/javascripts/fixtures/prometheus_service.rb
+++ b/spec/javascripts/fixtures/prometheus_service.rb
@@ -14,7 +14,7 @@ describe Projects::ServicesController, '(JavaScript fixtures)', type: :controlle
clean_frontend_fixtures('services/prometheus')
end
- before(:each) do
+ before do
sign_in(admin)
end
diff --git a/spec/javascripts/fixtures/services.rb b/spec/javascripts/fixtures/services.rb
index 554451d1bbf..0a3c64d5d31 100644
--- a/spec/javascripts/fixtures/services.rb
+++ b/spec/javascripts/fixtures/services.rb
@@ -15,7 +15,7 @@ describe Projects::ServicesController, '(JavaScript fixtures)', type: :controlle
clean_frontend_fixtures('services/')
end
- before(:each) do
+ before do
sign_in(admin)
end
diff --git a/spec/javascripts/fixtures/snippet.rb b/spec/javascripts/fixtures/snippet.rb
new file mode 100644
index 00000000000..01bfb87b0c1
--- /dev/null
+++ b/spec/javascripts/fixtures/snippet.rb
@@ -0,0 +1,27 @@
+require 'spec_helper'
+
+describe SnippetsController, '(JavaScript fixtures)', type: :controller do
+ include JavaScriptFixturesHelpers
+
+ let(:admin) { create(:admin) }
+ let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
+ let(:project) { create(:project, :repository, namespace: namespace, path: 'branches-project') }
+ let(:snippet) { create(:personal_snippet, title: 'snippet.md', content: '# snippet', file_name: 'snippet.md', author: admin) }
+
+ render_views
+
+ before(:all) do
+ clean_frontend_fixtures('snippets/')
+ end
+
+ before do
+ sign_in(admin)
+ end
+
+ it 'snippets/show.html.raw' do |example|
+ get(:show, id: snippet.to_param)
+
+ expect(response).to be_success
+ store_frontend_fixture(response, example.description)
+ end
+end
diff --git a/spec/javascripts/fixtures/todos.rb b/spec/javascripts/fixtures/todos.rb
index a81ef8c5492..ba630365c18 100644
--- a/spec/javascripts/fixtures/todos.rb
+++ b/spec/javascripts/fixtures/todos.rb
@@ -18,7 +18,7 @@ describe 'Todos (JavaScript fixtures)' do
describe Dashboard::TodosController, '(JavaScript fixtures)', type: :controller do
render_views
- before(:each) do
+ before do
sign_in(admin)
end
@@ -33,7 +33,7 @@ describe 'Todos (JavaScript fixtures)' do
describe Projects::TodosController, '(JavaScript fixtures)', type: :controller do
render_views
- before(:each) do
+ before do
sign_in(admin)
end
diff --git a/spec/javascripts/fly_out_nav_spec.js b/spec/javascripts/fly_out_nav_spec.js
new file mode 100644
index 00000000000..65a7459c5ed
--- /dev/null
+++ b/spec/javascripts/fly_out_nav_spec.js
@@ -0,0 +1,329 @@
+import Cookies from 'js-cookie';
+import {
+ calculateTop,
+ showSubLevelItems,
+ canShowSubItems,
+ canShowActiveSubItems,
+ mouseEnterTopItems,
+ mouseLeaveTopItem,
+ setOpenMenu,
+ mousePos,
+ getHideSubItemsInterval,
+ documentMouseMove,
+} from '~/fly_out_nav';
+import bp from '~/breakpoints';
+
+describe('Fly out sidebar navigation', () => {
+ let el;
+ let breakpointSize = 'lg';
+
+ beforeEach(() => {
+ el = document.createElement('div');
+ el.style.position = 'relative';
+ document.body.appendChild(el);
+
+ spyOn(bp, 'getBreakpointSize').and.callFake(() => breakpointSize);
+
+ setOpenMenu(null);
+ });
+
+ afterEach(() => {
+ document.body.innerHTML = '';
+ breakpointSize = 'lg';
+ mousePos.length = 0;
+ });
+
+ describe('calculateTop', () => {
+ it('returns boundingRect top', () => {
+ const boundingRect = {
+ top: 100,
+ height: 100,
+ };
+
+ expect(
+ calculateTop(boundingRect, 100),
+ ).toBe(100);
+ });
+
+ it('returns boundingRect - bottomOverflow', () => {
+ const boundingRect = {
+ top: window.innerHeight - 50,
+ height: 100,
+ };
+
+ expect(
+ calculateTop(boundingRect, 100),
+ ).toBe(window.innerHeight - 50);
+ });
+ });
+
+ describe('getHideSubItemsInterval', () => {
+ beforeEach(() => {
+ el.innerHTML = '<div class="sidebar-sub-level-items" style="position: fixed; top: 0; left: 100px; height: 50px;"></div>';
+ });
+
+ it('returns 0 if currentOpenMenu is nil', () => {
+ expect(
+ getHideSubItemsInterval(),
+ ).toBe(0);
+ });
+
+ it('returns 0 when mouse above sub-items', () => {
+ showSubLevelItems(el);
+ documentMouseMove({
+ clientX: el.getBoundingClientRect().left,
+ clientY: el.getBoundingClientRect().top,
+ });
+ documentMouseMove({
+ clientX: el.getBoundingClientRect().left,
+ clientY: el.getBoundingClientRect().top - 50,
+ });
+
+ expect(
+ getHideSubItemsInterval(),
+ ).toBe(0);
+ });
+
+ it('returns 0 when mouse is below sub-items', () => {
+ const subItems = el.querySelector('.sidebar-sub-level-items');
+
+ showSubLevelItems(el);
+ documentMouseMove({
+ clientX: el.getBoundingClientRect().left,
+ clientY: el.getBoundingClientRect().top,
+ });
+ documentMouseMove({
+ clientX: el.getBoundingClientRect().left,
+ clientY: (el.getBoundingClientRect().top - subItems.getBoundingClientRect().height) + 50,
+ });
+
+ expect(
+ getHideSubItemsInterval(),
+ ).toBe(0);
+ });
+
+ it('returns 300 when mouse is moved towards sub-items', () => {
+ documentMouseMove({
+ clientX: el.getBoundingClientRect().left,
+ clientY: el.getBoundingClientRect().top,
+ });
+ showSubLevelItems(el);
+ documentMouseMove({
+ clientX: el.getBoundingClientRect().left + 20,
+ clientY: el.getBoundingClientRect().top + 10,
+ });
+
+ expect(
+ getHideSubItemsInterval(),
+ ).toBe(300);
+ });
+ });
+
+ describe('mouseLeaveTopItem', () => {
+ beforeEach(() => {
+ spyOn(el.classList, 'remove');
+ });
+
+ it('removes is-over class if currentOpenMenu is null', () => {
+ mouseLeaveTopItem(el);
+
+ expect(
+ el.classList.remove,
+ ).toHaveBeenCalledWith('is-over');
+ });
+
+ it('removes is-over class if currentOpenMenu is null & there are sub-items', () => {
+ el.innerHTML = '<div class="sidebar-sub-level-items" style="position: absolute;"></div>';
+
+ mouseLeaveTopItem(el);
+
+ expect(
+ el.classList.remove,
+ ).toHaveBeenCalledWith('is-over');
+ });
+
+ it('does not remove is-over class if currentOpenMenu is the passed in sub-items', () => {
+ el.innerHTML = '<div class="sidebar-sub-level-items" style="position: absolute;"></div>';
+
+ setOpenMenu(el.querySelector('.sidebar-sub-level-items'));
+ mouseLeaveTopItem(el);
+
+ expect(
+ el.classList.remove,
+ ).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('mouseEnterTopItems', () => {
+ beforeEach(() => {
+ jasmine.clock().install();
+
+ el.innerHTML = '<div class="sidebar-sub-level-items" style="position: absolute; top: 0; left: 100px; height: 200px;"></div>';
+ });
+
+ afterEach(() => {
+ jasmine.clock().uninstall();
+ });
+
+ it('shows sub-items after 0ms if no menu is open', () => {
+ mouseEnterTopItems(el);
+
+ expect(
+ getHideSubItemsInterval(),
+ ).toBe(0);
+
+ jasmine.clock().tick(0);
+
+ expect(
+ el.querySelector('.sidebar-sub-level-items').style.display,
+ ).toBe('block');
+ });
+
+ it('shows sub-items after 300ms if a menu is currently open', () => {
+ documentMouseMove({
+ clientX: el.getBoundingClientRect().left,
+ clientY: el.getBoundingClientRect().top,
+ });
+
+ setOpenMenu(el.querySelector('.sidebar-sub-level-items'));
+
+ documentMouseMove({
+ clientX: el.getBoundingClientRect().left + 20,
+ clientY: el.getBoundingClientRect().top + 10,
+ });
+
+ mouseEnterTopItems(el);
+
+ expect(
+ getHideSubItemsInterval(),
+ ).toBe(300);
+
+ jasmine.clock().tick(300);
+
+ expect(
+ el.querySelector('.sidebar-sub-level-items').style.display,
+ ).toBe('block');
+ });
+ });
+
+ describe('showSubLevelItems', () => {
+ beforeEach(() => {
+ el.innerHTML = '<div class="sidebar-sub-level-items" style="position: absolute;"></div>';
+ });
+
+ it('adds is-over class to el', () => {
+ spyOn(el.classList, 'add');
+
+ showSubLevelItems(el);
+
+ expect(
+ el.classList.add,
+ ).toHaveBeenCalledWith('is-over');
+ });
+
+ it('does not show sub-items on mobile', () => {
+ breakpointSize = 'xs';
+
+ showSubLevelItems(el);
+
+ expect(
+ el.querySelector('.sidebar-sub-level-items').style.display,
+ ).not.toBe('block');
+ });
+
+ it('shows sub-items', () => {
+ showSubLevelItems(el);
+
+ expect(
+ el.querySelector('.sidebar-sub-level-items').style.display,
+ ).toBe('block');
+ });
+
+ it('sets transform of sub-items', () => {
+ const subItems = el.querySelector('.sidebar-sub-level-items');
+ showSubLevelItems(el);
+
+ expect(
+ subItems.style.transform,
+ ).toBe(`translate3d(0px, ${Math.floor(el.getBoundingClientRect().top)}px, 0px)`);
+ });
+
+ it('sets is-above when element is above', () => {
+ const subItems = el.querySelector('.sidebar-sub-level-items');
+ subItems.style.height = `${window.innerHeight + el.offsetHeight}px`;
+ el.style.top = `${window.innerHeight - el.offsetHeight}px`;
+
+ spyOn(subItems.classList, 'add');
+
+ showSubLevelItems(el);
+
+ expect(
+ subItems.classList.add,
+ ).toHaveBeenCalledWith('is-above');
+ });
+ });
+
+ describe('canShowSubItems', () => {
+ it('returns true if on desktop size', () => {
+ expect(
+ canShowSubItems(),
+ ).toBeTruthy();
+ });
+
+ it('returns false if on mobile size', () => {
+ breakpointSize = 'xs';
+
+ expect(
+ canShowSubItems(),
+ ).toBeFalsy();
+ });
+ });
+
+ describe('canShowActiveSubItems', () => {
+ afterEach(() => {
+ Cookies.remove('sidebar_collapsed');
+ });
+
+ it('returns true by default', () => {
+ expect(
+ canShowActiveSubItems(el),
+ ).toBeTruthy();
+ });
+
+ it('returns false when cookie is false & element is active', () => {
+ Cookies.set('sidebar_collapsed', 'false');
+ el.classList.add('active');
+
+ expect(
+ canShowActiveSubItems(el),
+ ).toBeFalsy();
+ });
+
+ it('returns true when cookie is false & element is active', () => {
+ Cookies.set('sidebar_collapsed', 'true');
+ el.classList.add('active');
+
+ expect(
+ canShowActiveSubItems(el),
+ ).toBeTruthy();
+ });
+
+ it('returns true when element is active & breakpoint is sm', () => {
+ breakpointSize = 'sm';
+ el.classList.add('active');
+
+ expect(
+ canShowActiveSubItems(el),
+ ).toBeTruthy();
+ });
+
+ it('returns true when element is active & breakpoint is md', () => {
+ breakpointSize = 'md';
+ el.classList.add('active');
+
+ expect(
+ canShowActiveSubItems(el),
+ ).toBeTruthy();
+ });
+ });
+});
diff --git a/spec/javascripts/labels_issue_sidebar_spec.js b/spec/javascripts/labels_issue_sidebar_spec.js
index c99f379b871..e47adc49224 100644
--- a/spec/javascripts/labels_issue_sidebar_spec.js
+++ b/spec/javascripts/labels_issue_sidebar_spec.js
@@ -4,7 +4,6 @@
import '~/gl_dropdown';
import 'select2';
-import 'vendor/jquery.nicescroll';
import '~/api';
import '~/create_label';
import '~/issuable_context';
diff --git a/spec/javascripts/lib/utils/sticky_spec.js b/spec/javascripts/lib/utils/sticky_spec.js
new file mode 100644
index 00000000000..c3ee3ef9825
--- /dev/null
+++ b/spec/javascripts/lib/utils/sticky_spec.js
@@ -0,0 +1,52 @@
+import { isSticky } from '~/lib/utils/sticky';
+
+describe('sticky', () => {
+ const el = {
+ offsetTop: 0,
+ classList: {},
+ };
+
+ beforeEach(() => {
+ el.offsetTop = 0;
+ el.classList.add = jasmine.createSpy('spy');
+ el.classList.remove = jasmine.createSpy('spy');
+ });
+
+ describe('classList.remove', () => {
+ it('does not call classList.remove when stuck', () => {
+ isSticky(el, 0, 0);
+
+ expect(
+ el.classList.remove,
+ ).not.toHaveBeenCalled();
+ });
+
+ it('calls classList.remove when not stuck', () => {
+ el.offsetTop = 10;
+ isSticky(el, 0, 0);
+
+ expect(
+ el.classList.remove,
+ ).toHaveBeenCalledWith('is-stuck');
+ });
+ });
+
+ describe('classList.add', () => {
+ it('calls classList.add when stuck', () => {
+ isSticky(el, 0, 0);
+
+ expect(
+ el.classList.add,
+ ).toHaveBeenCalledWith('is-stuck');
+ });
+
+ it('does not call classList.add when not stuck', () => {
+ el.offsetTop = 10;
+ isSticky(el, 0, 0);
+
+ expect(
+ el.classList.add,
+ ).not.toHaveBeenCalled();
+ });
+ });
+});
diff --git a/spec/javascripts/pdf/index_spec.js b/spec/javascripts/pdf/index_spec.js
index f661fae5fe2..bebed432f91 100644
--- a/spec/javascripts/pdf/index_spec.js
+++ b/spec/javascripts/pdf/index_spec.js
@@ -1,8 +1,8 @@
/* eslint-disable import/no-unresolved */
import Vue from 'vue';
-import { PDFJS } from 'pdfjs-dist';
-import workerSrc from 'vendor/pdf.worker';
+import { PDFJS } from 'vendor/pdf';
+import workerSrc from 'vendor/pdf.worker.min';
import PDFLab from '~/pdf/index.vue';
import pdf from '../fixtures/blob/pdf/test.pdf';
diff --git a/spec/javascripts/pdf/page_spec.js b/spec/javascripts/pdf/page_spec.js
index ac76ebbfbe6..ac5b21e8f6c 100644
--- a/spec/javascripts/pdf/page_spec.js
+++ b/spec/javascripts/pdf/page_spec.js
@@ -1,8 +1,8 @@
/* eslint-disable import/no-unresolved */
import Vue from 'vue';
-import pdfjsLib from 'pdfjs-dist';
-import workerSrc from 'vendor/pdf.worker';
+import pdfjsLib from 'vendor/pdf';
+import workerSrc from 'vendor/pdf.worker.min';
import PageComponent from '~/pdf/page/index.vue';
import testPDF from '../fixtures/blob/pdf/test.pdf';
diff --git a/spec/javascripts/project_select_combo_button_spec.js b/spec/javascripts/project_select_combo_button_spec.js
new file mode 100644
index 00000000000..e10a5a3bef6
--- /dev/null
+++ b/spec/javascripts/project_select_combo_button_spec.js
@@ -0,0 +1,105 @@
+import ProjectSelectComboButton from '~/project_select_combo_button';
+
+const fixturePath = 'static/project_select_combo_button.html.raw';
+
+describe('Project Select Combo Button', function () {
+ preloadFixtures(fixturePath);
+
+ beforeEach(function () {
+ this.defaults = {
+ label: 'Select project to create issue',
+ groupId: 12345,
+ projectMeta: {
+ name: 'My Cool Project',
+ url: 'http://mycoolproject.com',
+ },
+ newProjectMeta: {
+ name: 'My Other Cool Project',
+ url: 'http://myothercoolproject.com',
+ },
+ localStorageKey: 'group-12345-new-issue-recent-project',
+ relativePath: 'issues/new',
+ };
+
+ loadFixtures(fixturePath);
+
+ this.newItemBtn = document.querySelector('.new-project-item-link');
+ this.projectSelectInput = document.querySelector('.project-item-select');
+ });
+
+ describe('on page load when localStorage is empty', function () {
+ beforeEach(function () {
+ this.comboButton = new ProjectSelectComboButton(this.projectSelectInput);
+ });
+
+ it('newItemBtn is disabled', function () {
+ expect(this.newItemBtn.hasAttribute('disabled')).toBe(true);
+ expect(this.newItemBtn.classList.contains('disabled')).toBe(true);
+ });
+
+ it('newItemBtn href is null', function () {
+ expect(this.newItemBtn.getAttribute('href')).toBe('');
+ });
+
+ it('newItemBtn text is the plain default label', function () {
+ expect(this.newItemBtn.textContent).toBe(this.defaults.label);
+ });
+ });
+
+ describe('on page load when localStorage is filled', function () {
+ beforeEach(function () {
+ window.localStorage
+ .setItem(this.defaults.localStorageKey, JSON.stringify(this.defaults.projectMeta));
+ this.comboButton = new ProjectSelectComboButton(this.projectSelectInput);
+ });
+
+ it('newItemBtn is not disabled', function () {
+ expect(this.newItemBtn.hasAttribute('disabled')).toBe(false);
+ expect(this.newItemBtn.classList.contains('disabled')).toBe(false);
+ });
+
+ it('newItemBtn href is correctly set', function () {
+ expect(this.newItemBtn.getAttribute('href')).toBe(this.defaults.projectMeta.url);
+ });
+
+ it('newItemBtn text is the cached label', function () {
+ expect(this.newItemBtn.textContent)
+ .toBe(`New issue in ${this.defaults.projectMeta.name}`);
+ });
+
+ afterEach(function () {
+ window.localStorage.clear();
+ });
+ });
+
+ describe('after selecting a new project', function () {
+ beforeEach(function () {
+ this.comboButton = new ProjectSelectComboButton(this.projectSelectInput);
+
+ // mock the effect of selecting an item from the projects dropdown (select2)
+ $('.project-item-select')
+ .val(JSON.stringify(this.defaults.newProjectMeta))
+ .trigger('change');
+ });
+
+ it('newItemBtn is not disabled', function () {
+ expect(this.newItemBtn.hasAttribute('disabled')).toBe(false);
+ expect(this.newItemBtn.classList.contains('disabled')).toBe(false);
+ });
+
+ it('newItemBtn href is correctly set', function () {
+ expect(this.newItemBtn.getAttribute('href'))
+ .toBe('http://myothercoolproject.com/issues/new');
+ });
+
+ it('newItemBtn text is the selected project label', function () {
+ expect(this.newItemBtn.textContent)
+ .toBe(`New issue in ${this.defaults.newProjectMeta.name}`);
+ });
+
+ afterEach(function () {
+ window.localStorage.clear();
+ });
+ });
+});
+
diff --git a/spec/javascripts/projects/project_import_gitlab_project_spec.js b/spec/javascripts/projects/project_import_gitlab_project_spec.js
new file mode 100644
index 00000000000..2f1aae109e3
--- /dev/null
+++ b/spec/javascripts/projects/project_import_gitlab_project_spec.js
@@ -0,0 +1,25 @@
+import projectImportGitlab from '~/projects/project_import_gitlab_project';
+
+describe('Import Gitlab project', () => {
+ let projectName;
+ beforeEach(() => {
+ projectName = 'project';
+ window.history.pushState({}, null, `?path=${projectName}`);
+
+ setFixtures(`
+ <input class="js-path-name" />
+ `);
+
+ projectImportGitlab.bindEvents();
+ });
+
+ afterEach(() => {
+ window.history.pushState({}, null, '');
+ });
+
+ describe('path name', () => {
+ it('should fill in the project name derived from the previously filled project name', () => {
+ expect(document.querySelector('.js-path-name').value).toEqual(projectName);
+ });
+ });
+});
diff --git a/spec/javascripts/repo/components/repo_commit_section_spec.js b/spec/javascripts/repo/components/repo_commit_section_spec.js
new file mode 100644
index 00000000000..db2b7d51626
--- /dev/null
+++ b/spec/javascripts/repo/components/repo_commit_section_spec.js
@@ -0,0 +1,158 @@
+import Vue from 'vue';
+import repoCommitSection from '~/repo/components/repo_commit_section.vue';
+import RepoStore from '~/repo/stores/repo_store';
+import RepoHelper from '~/repo/helpers/repo_helper';
+import Api from '~/api';
+
+describe('RepoCommitSection', () => {
+ const branch = 'master';
+ const projectUrl = 'projectUrl';
+ const openedFiles = [{
+ id: 0,
+ changed: true,
+ url: `/namespace/${projectUrl}/blob/${branch}/dir/file0.ext`,
+ newContent: 'a',
+ }, {
+ id: 1,
+ changed: true,
+ url: `/namespace/${projectUrl}/blob/${branch}/dir/file1.ext`,
+ newContent: 'b',
+ }, {
+ id: 2,
+ url: `/namespace/${projectUrl}/blob/${branch}/dir/file2.ext`,
+ changed: false,
+ }];
+
+ RepoStore.projectUrl = projectUrl;
+
+ function createComponent() {
+ const RepoCommitSection = Vue.extend(repoCommitSection);
+
+ return new RepoCommitSection().$mount();
+ }
+
+ it('renders a commit section', () => {
+ RepoStore.isCommitable = true;
+ RepoStore.targetBranch = branch;
+ RepoStore.openedFiles = openedFiles;
+
+ spyOn(RepoHelper, 'getBranch').and.returnValue(branch);
+
+ const vm = createComponent();
+ const changedFiles = [...vm.$el.querySelectorAll('.changed-files > li')];
+ const commitMessage = vm.$el.querySelector('#commit-message');
+ const submitCommit = vm.$el.querySelector('.submit-commit');
+ const targetBranch = vm.$el.querySelector('.target-branch');
+
+ expect(vm.$el.querySelector(':scope > form')).toBeTruthy();
+ expect(vm.$el.querySelector('.staged-files').textContent).toEqual('Staged files (2)');
+ expect(changedFiles.length).toEqual(2);
+
+ changedFiles.forEach((changedFile, i) => {
+ const filePath = RepoHelper.getFilePathFromFullPath(openedFiles[i].url, branch);
+
+ expect(changedFile.textContent).toEqual(filePath);
+ });
+
+ expect(commitMessage.tagName).toEqual('TEXTAREA');
+ expect(commitMessage.name).toEqual('commit-message');
+ expect(submitCommit.type).toEqual('submit');
+ expect(submitCommit.disabled).toBeTruthy();
+ expect(submitCommit.querySelector('.fa-spinner.fa-spin')).toBeFalsy();
+ expect(vm.$el.querySelector('.commit-summary').textContent).toEqual('Commit 2 files');
+ expect(targetBranch.querySelector(':scope > label').textContent).toEqual('Target branch');
+ expect(targetBranch.querySelector('.help-block').textContent).toEqual(branch);
+ });
+
+ it('does not render if not isCommitable', () => {
+ RepoStore.isCommitable = false;
+ RepoStore.openedFiles = [{
+ id: 0,
+ changed: true,
+ }];
+
+ const vm = createComponent();
+
+ expect(vm.$el.innerHTML).toBeFalsy();
+ });
+
+ it('does not render if no changedFiles', () => {
+ RepoStore.isCommitable = true;
+ RepoStore.openedFiles = [];
+
+ const vm = createComponent();
+
+ expect(vm.$el.innerHTML).toBeFalsy();
+ });
+
+ it('shows commit submit and summary if commitMessage and spinner if submitCommitsLoading', (done) => {
+ const projectId = 'projectId';
+ const commitMessage = 'commitMessage';
+ RepoStore.isCommitable = true;
+ RepoStore.openedFiles = openedFiles;
+ RepoStore.projectId = projectId;
+
+ spyOn(RepoHelper, 'getBranch').and.returnValue(branch);
+
+ const vm = createComponent();
+ const commitMessageEl = vm.$el.querySelector('#commit-message');
+ const submitCommit = vm.$el.querySelector('.submit-commit');
+
+ vm.commitMessage = commitMessage;
+
+ Vue.nextTick(() => {
+ expect(commitMessageEl.value).toBe(commitMessage);
+ expect(submitCommit.disabled).toBeFalsy();
+
+ spyOn(vm, 'makeCommit').and.callThrough();
+ spyOn(Api, 'commitMultiple');
+
+ submitCommit.click();
+
+ Vue.nextTick(() => {
+ expect(vm.makeCommit).toHaveBeenCalled();
+ expect(submitCommit.querySelector('.fa-spinner.fa-spin')).toBeTruthy();
+
+ const args = Api.commitMultiple.calls.allArgs()[0];
+ const { commit_message, actions, branch: payloadBranch } = args[1];
+
+ expect(args[0]).toBe(projectId);
+ expect(commit_message).toBe(commitMessage);
+ expect(actions.length).toEqual(2);
+ expect(payloadBranch).toEqual(branch);
+ expect(actions[0].action).toEqual('update');
+ expect(actions[1].action).toEqual('update');
+ expect(actions[0].content).toEqual(openedFiles[0].newContent);
+ expect(actions[1].content).toEqual(openedFiles[1].newContent);
+ expect(actions[0].file_path)
+ .toEqual(RepoHelper.getFilePathFromFullPath(openedFiles[0].url, branch));
+ expect(actions[1].file_path)
+ .toEqual(RepoHelper.getFilePathFromFullPath(openedFiles[1].url, branch));
+
+ done();
+ });
+ });
+ });
+
+ describe('methods', () => {
+ describe('resetCommitState', () => {
+ it('should reset store vars and scroll to top', () => {
+ const vm = {
+ submitCommitsLoading: true,
+ changedFiles: new Array(10),
+ openedFiles: new Array(10),
+ commitMessage: 'commitMessage',
+ editMode: true,
+ };
+
+ repoCommitSection.methods.resetCommitState.call(vm);
+
+ expect(vm.submitCommitsLoading).toEqual(false);
+ expect(vm.changedFiles).toEqual([]);
+ expect(vm.openedFiles).toEqual([]);
+ expect(vm.commitMessage).toEqual('');
+ expect(vm.editMode).toEqual(false);
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/repo/components/repo_edit_button_spec.js b/spec/javascripts/repo/components/repo_edit_button_spec.js
new file mode 100644
index 00000000000..df2f9697acc
--- /dev/null
+++ b/spec/javascripts/repo/components/repo_edit_button_spec.js
@@ -0,0 +1,51 @@
+import Vue from 'vue';
+import repoEditButton from '~/repo/components/repo_edit_button.vue';
+import RepoStore from '~/repo/stores/repo_store';
+
+describe('RepoEditButton', () => {
+ function createComponent() {
+ const RepoEditButton = Vue.extend(repoEditButton);
+
+ return new RepoEditButton().$mount();
+ }
+
+ it('renders an edit button that toggles the view state', (done) => {
+ RepoStore.isCommitable = true;
+ RepoStore.changedFiles = [];
+
+ const vm = createComponent();
+
+ expect(vm.$el.tagName).toEqual('BUTTON');
+ expect(vm.$el.textContent).toMatch('Edit');
+
+ spyOn(vm, 'editClicked').and.callThrough();
+
+ vm.$el.click();
+
+ Vue.nextTick(() => {
+ expect(vm.editClicked).toHaveBeenCalled();
+ expect(vm.$el.textContent).toMatch('Cancel edit');
+ done();
+ });
+ });
+
+ it('does not render if not isCommitable', () => {
+ RepoStore.isCommitable = false;
+
+ const vm = createComponent();
+
+ expect(vm.$el.innerHTML).toBeUndefined();
+ });
+
+ describe('methods', () => {
+ describe('editClicked', () => {
+ it('sets dialog to open when there are changedFiles', () => {
+
+ });
+
+ it('toggles editMode and calls toggleBlobView', () => {
+
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/repo/components/repo_editor_spec.js b/spec/javascripts/repo/components/repo_editor_spec.js
new file mode 100644
index 00000000000..35e0c995163
--- /dev/null
+++ b/spec/javascripts/repo/components/repo_editor_spec.js
@@ -0,0 +1,26 @@
+import Vue from 'vue';
+import repoEditor from '~/repo/components/repo_editor.vue';
+import RepoStore from '~/repo/stores/repo_store';
+
+describe('RepoEditor', () => {
+ function createComponent() {
+ const RepoEditor = Vue.extend(repoEditor);
+
+ return new RepoEditor().$mount();
+ }
+
+ it('renders an ide container', () => {
+ const monacoInstance = jasmine.createSpyObj('monacoInstance', ['onMouseUp', 'onKeyUp', 'setModel', 'updateOptions']);
+ const monaco = {
+ editor: jasmine.createSpyObj('editor', ['create']),
+ };
+ RepoStore.monaco = monaco;
+
+ monaco.editor.create.and.returnValue(monacoInstance);
+ spyOn(repoEditor.watch, 'blobRaw');
+
+ const vm = createComponent();
+
+ expect(vm.$el.id).toEqual('ide');
+ });
+});
diff --git a/spec/javascripts/repo/components/repo_file_buttons_spec.js b/spec/javascripts/repo/components/repo_file_buttons_spec.js
new file mode 100644
index 00000000000..e1f25e4485f
--- /dev/null
+++ b/spec/javascripts/repo/components/repo_file_buttons_spec.js
@@ -0,0 +1,82 @@
+import Vue from 'vue';
+import repoFileButtons from '~/repo/components/repo_file_buttons.vue';
+import RepoStore from '~/repo/stores/repo_store';
+
+describe('RepoFileButtons', () => {
+ function createComponent() {
+ const RepoFileButtons = Vue.extend(repoFileButtons);
+
+ return new RepoFileButtons().$mount();
+ }
+
+ it('renders Raw, Blame, History, Permalink and Preview toggle', () => {
+ const activeFile = {
+ extension: 'md',
+ url: 'url',
+ raw_path: 'raw_path',
+ blame_path: 'blame_path',
+ commits_path: 'commits_path',
+ permalink: 'permalink',
+ };
+ const activeFileLabel = 'activeFileLabel';
+ RepoStore.openedFiles = new Array(1);
+ RepoStore.activeFile = activeFile;
+ RepoStore.activeFileLabel = activeFileLabel;
+ RepoStore.editMode = true;
+
+ const vm = createComponent();
+ const raw = vm.$el.querySelector('.raw');
+ const blame = vm.$el.querySelector('.blame');
+ const history = vm.$el.querySelector('.history');
+
+ expect(vm.$el.id).toEqual('repo-file-buttons');
+ expect(raw.href).toMatch(`/${activeFile.raw_path}`);
+ expect(raw.textContent).toEqual('Raw');
+ expect(blame.href).toMatch(`/${activeFile.blame_path}`);
+ expect(blame.textContent).toEqual('Blame');
+ expect(history.href).toMatch(`/${activeFile.commits_path}`);
+ expect(history.textContent).toEqual('History');
+ expect(vm.$el.querySelector('.permalink').textContent).toEqual('Permalink');
+ expect(vm.$el.querySelector('.preview').textContent).toEqual(activeFileLabel);
+ });
+
+ it('triggers rawPreviewToggle on preview click', () => {
+ const activeFile = {
+ extension: 'md',
+ url: 'url',
+ };
+ RepoStore.openedFiles = new Array(1);
+ RepoStore.activeFile = activeFile;
+ RepoStore.editMode = true;
+
+ const vm = createComponent();
+ const preview = vm.$el.querySelector('.preview');
+
+ spyOn(vm, 'rawPreviewToggle');
+
+ preview.click();
+
+ expect(vm.rawPreviewToggle).toHaveBeenCalled();
+ });
+
+ it('does not render preview toggle if not canPreview', () => {
+ const activeFile = {
+ extension: 'abcd',
+ url: 'url',
+ };
+ RepoStore.openedFiles = new Array(1);
+ RepoStore.activeFile = activeFile;
+
+ const vm = createComponent();
+
+ expect(vm.$el.querySelector('.preview')).toBeFalsy();
+ });
+
+ it('does not render if not isMini', () => {
+ RepoStore.openedFiles = [];
+
+ const vm = createComponent();
+
+ expect(vm.$el.innerHTML).toBeFalsy();
+ });
+});
diff --git a/spec/javascripts/repo/components/repo_file_options_spec.js b/spec/javascripts/repo/components/repo_file_options_spec.js
new file mode 100644
index 00000000000..9759b4bf12d
--- /dev/null
+++ b/spec/javascripts/repo/components/repo_file_options_spec.js
@@ -0,0 +1,33 @@
+import Vue from 'vue';
+import repoFileOptions from '~/repo/components/repo_file_options.vue';
+
+describe('RepoFileOptions', () => {
+ const projectName = 'projectName';
+
+ function createComponent(propsData) {
+ const RepoFileOptions = Vue.extend(repoFileOptions);
+
+ return new RepoFileOptions({
+ propsData,
+ }).$mount();
+ }
+
+ it('renders the title and new file/folder buttons if isMini is true', () => {
+ const vm = createComponent({
+ isMini: true,
+ projectName,
+ });
+
+ expect(vm.$el.classList.contains('repo-file-options')).toBeTruthy();
+ expect(vm.$el.querySelector('.title').textContent).toEqual(projectName);
+ });
+
+ it('does not render if isMini is false', () => {
+ const vm = createComponent({
+ isMini: false,
+ projectName,
+ });
+
+ expect(vm.$el.innerHTML).toBeFalsy();
+ });
+});
diff --git a/spec/javascripts/repo/components/repo_file_spec.js b/spec/javascripts/repo/components/repo_file_spec.js
new file mode 100644
index 00000000000..90616ae13ca
--- /dev/null
+++ b/spec/javascripts/repo/components/repo_file_spec.js
@@ -0,0 +1,136 @@
+import Vue from 'vue';
+import repoFile from '~/repo/components/repo_file.vue';
+
+describe('RepoFile', () => {
+ const updated = 'updated';
+ const file = {
+ icon: 'icon',
+ url: 'url',
+ name: 'name',
+ lastCommitMessage: 'message',
+ lastCommitUpdate: Date.now(),
+ level: 10,
+ };
+ const activeFile = {
+ url: 'url',
+ };
+
+ function createComponent(propsData) {
+ const RepoFile = Vue.extend(repoFile);
+
+ return new RepoFile({
+ propsData,
+ }).$mount();
+ }
+
+ beforeEach(() => {
+ spyOn(repoFile.mixins[0].methods, 'timeFormated').and.returnValue(updated);
+ });
+
+ it('renders link, icon, name and last commit details', () => {
+ const vm = createComponent({
+ file,
+ activeFile,
+ });
+ const name = vm.$el.querySelector('.repo-file-name');
+ const fileIcon = vm.$el.querySelector('.file-icon');
+
+ expect(vm.$el.classList.contains('active')).toBeTruthy();
+ expect(vm.$el.querySelector(`.${file.icon}`).style.marginLeft).toEqual('100px');
+ expect(name.title).toEqual(file.url);
+ expect(name.href).toMatch(`/${file.url}`);
+ expect(name.textContent).toEqual(file.name);
+ expect(vm.$el.querySelector('.commit-message').textContent).toBe(file.lastCommitMessage);
+ expect(vm.$el.querySelector('.commit-update').textContent).toBe(updated);
+ expect(fileIcon.classList.contains(file.icon)).toBeTruthy();
+ expect(fileIcon.style.marginLeft).toEqual(`${file.level * 10}px`);
+ });
+
+ it('does render if hasFiles is true and is loading tree', () => {
+ const vm = createComponent({
+ file,
+ activeFile,
+ loading: {
+ tree: true,
+ },
+ hasFiles: true,
+ });
+
+ expect(vm.$el.innerHTML).toBeTruthy();
+ expect(vm.$el.querySelector('.fa-spin.fa-spinner')).toBeFalsy();
+ });
+
+ it('renders a spinner if the file is loading', () => {
+ file.loading = true;
+ const vm = createComponent({
+ file,
+ activeFile,
+ loading: {
+ tree: true,
+ },
+ hasFiles: true,
+ });
+
+ expect(vm.$el.innerHTML).toBeTruthy();
+ expect(vm.$el.querySelector('.fa-spin.fa-spinner').style.marginLeft).toEqual(`${file.level * 10}px`);
+ });
+
+ it('does not render if loading tree', () => {
+ const vm = createComponent({
+ file,
+ activeFile,
+ loading: {
+ tree: true,
+ },
+ });
+
+ expect(vm.$el.innerHTML).toBeFalsy();
+ });
+
+ it('does not render commit message and datetime if mini', () => {
+ const vm = createComponent({
+ file,
+ activeFile,
+ isMini: true,
+ });
+
+ expect(vm.$el.querySelector('.commit-message')).toBeFalsy();
+ expect(vm.$el.querySelector('.commit-update')).toBeFalsy();
+ });
+
+ it('does not set active class if file is active file', () => {
+ const vm = createComponent({
+ file,
+ activeFile: {},
+ });
+
+ expect(vm.$el.classList.contains('active')).toBeFalsy();
+ });
+
+ it('fires linkClicked when the link is clicked', () => {
+ const vm = createComponent({
+ file,
+ activeFile,
+ });
+
+ spyOn(vm, 'linkClicked');
+
+ vm.$el.querySelector('.repo-file-name').click();
+
+ expect(vm.linkClicked).toHaveBeenCalledWith(file);
+ });
+
+ describe('methods', () => {
+ describe('linkClicked', () => {
+ const vm = jasmine.createSpyObj('vm', ['$emit']);
+
+ it('$emits linkclicked with file obj', () => {
+ const theFile = {};
+
+ repoFile.methods.linkClicked.call(vm, theFile);
+
+ expect(vm.$emit).toHaveBeenCalledWith('linkclicked', theFile);
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/repo/components/repo_loading_file_spec.js b/spec/javascripts/repo/components/repo_loading_file_spec.js
new file mode 100644
index 00000000000..d84f4c5609e
--- /dev/null
+++ b/spec/javascripts/repo/components/repo_loading_file_spec.js
@@ -0,0 +1,79 @@
+import Vue from 'vue';
+import repoLoadingFile from '~/repo/components/repo_loading_file.vue';
+
+describe('RepoLoadingFile', () => {
+ function createComponent(propsData) {
+ const RepoLoadingFile = Vue.extend(repoLoadingFile);
+
+ return new RepoLoadingFile({
+ propsData,
+ }).$mount();
+ }
+
+ function assertLines(lines) {
+ lines.forEach((line, n) => {
+ const index = n + 1;
+ expect(line.classList.contains(`line-of-code-${index}`)).toBeTruthy();
+ });
+ }
+
+ function assertColumns(columns) {
+ columns.forEach((column) => {
+ const container = column.querySelector('.animation-container');
+ const lines = [...container.querySelectorAll(':scope > div')];
+
+ expect(container).toBeTruthy();
+ expect(lines.length).toEqual(6);
+ assertLines(lines);
+ });
+ }
+
+ it('renders 3 columns of animated LoC', () => {
+ const vm = createComponent({
+ loading: {
+ tree: true,
+ },
+ hasFiles: false,
+ });
+ const columns = [...vm.$el.querySelectorAll('td')];
+
+ expect(columns.length).toEqual(3);
+ assertColumns(columns);
+ });
+
+ it('renders 1 column of animated LoC if isMini', () => {
+ const vm = createComponent({
+ loading: {
+ tree: true,
+ },
+ hasFiles: false,
+ isMini: true,
+ });
+ const columns = [...vm.$el.querySelectorAll('td')];
+
+ expect(columns.length).toEqual(1);
+ assertColumns(columns);
+ });
+
+ it('does not render if tree is not loading', () => {
+ const vm = createComponent({
+ loading: {
+ tree: false,
+ },
+ hasFiles: false,
+ });
+
+ expect(vm.$el.innerHTML).toBeFalsy();
+ });
+
+ it('does not render if hasFiles is true', () => {
+ const vm = createComponent({
+ loading: {
+ tree: true,
+ },
+ hasFiles: true,
+ });
+
+ expect(vm.$el.innerHTML).toBeFalsy();
+ });
+});
diff --git a/spec/javascripts/repo/components/repo_prev_directory_spec.js b/spec/javascripts/repo/components/repo_prev_directory_spec.js
new file mode 100644
index 00000000000..34dde545e6a
--- /dev/null
+++ b/spec/javascripts/repo/components/repo_prev_directory_spec.js
@@ -0,0 +1,43 @@
+import Vue from 'vue';
+import repoPrevDirectory from '~/repo/components/repo_prev_directory.vue';
+
+describe('RepoPrevDirectory', () => {
+ function createComponent(propsData) {
+ const RepoPrevDirectory = Vue.extend(repoPrevDirectory);
+
+ return new RepoPrevDirectory({
+ propsData,
+ }).$mount();
+ }
+
+ it('renders a prev dir link', () => {
+ const prevUrl = 'prevUrl';
+ const vm = createComponent({
+ prevUrl,
+ });
+ const link = vm.$el.querySelector('a');
+
+ spyOn(vm, 'linkClicked');
+
+ expect(link.href).toMatch(`/${prevUrl}`);
+ expect(link.textContent).toEqual('..');
+
+ link.click();
+
+ expect(vm.linkClicked).toHaveBeenCalledWith(prevUrl);
+ });
+
+ describe('methods', () => {
+ describe('linkClicked', () => {
+ const vm = jasmine.createSpyObj('vm', ['$emit']);
+
+ it('$emits linkclicked with file obj', () => {
+ const file = {};
+
+ repoPrevDirectory.methods.linkClicked.call(vm, file);
+
+ expect(vm.$emit).toHaveBeenCalledWith('linkclicked', file);
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/repo/components/repo_preview_spec.js b/spec/javascripts/repo/components/repo_preview_spec.js
new file mode 100644
index 00000000000..4920cf02083
--- /dev/null
+++ b/spec/javascripts/repo/components/repo_preview_spec.js
@@ -0,0 +1,23 @@
+import Vue from 'vue';
+import repoPreview from '~/repo/components/repo_preview.vue';
+import RepoStore from '~/repo/stores/repo_store';
+
+describe('RepoPreview', () => {
+ function createComponent() {
+ const RepoPreview = Vue.extend(repoPreview);
+
+ return new RepoPreview().$mount();
+ }
+
+ it('renders a div with the activeFile html', () => {
+ const activeFile = {
+ html: '<p class="file-content">html</p>',
+ };
+ RepoStore.activeFile = activeFile;
+
+ const vm = createComponent();
+
+ expect(vm.$el.tagName).toEqual('DIV');
+ expect(vm.$el.innerHTML).toContain(activeFile.html);
+ });
+});
diff --git a/spec/javascripts/repo/components/repo_sidebar_spec.js b/spec/javascripts/repo/components/repo_sidebar_spec.js
new file mode 100644
index 00000000000..edd27d3afb8
--- /dev/null
+++ b/spec/javascripts/repo/components/repo_sidebar_spec.js
@@ -0,0 +1,110 @@
+import Vue from 'vue';
+import Helper from '~/repo/helpers/repo_helper';
+import RepoService from '~/repo/services/repo_service';
+import RepoStore from '~/repo/stores/repo_store';
+import repoSidebar from '~/repo/components/repo_sidebar.vue';
+
+describe('RepoSidebar', () => {
+ function createComponent() {
+ const RepoSidebar = Vue.extend(repoSidebar);
+
+ return new RepoSidebar().$mount();
+ }
+
+ it('renders a sidebar', () => {
+ RepoStore.files = [{
+ id: 0,
+ }];
+ const vm = createComponent();
+ const thead = vm.$el.querySelector('thead');
+ const tbody = vm.$el.querySelector('tbody');
+
+ expect(vm.$el.id).toEqual('sidebar');
+ expect(vm.$el.classList.contains('sidebar-mini')).toBeFalsy();
+ expect(thead.querySelector('.name').textContent).toEqual('Name');
+ expect(thead.querySelector('.last-commit').textContent).toEqual('Last Commit');
+ expect(thead.querySelector('.last-update').textContent).toEqual('Last Update');
+ expect(tbody.querySelector('.repo-file-options')).toBeFalsy();
+ expect(tbody.querySelector('.prev-directory')).toBeFalsy();
+ expect(tbody.querySelector('.loading-file')).toBeFalsy();
+ expect(tbody.querySelector('.file')).toBeTruthy();
+ });
+
+ it('does not render a thead, renders repo-file-options and sets sidebar-mini class if isMini', () => {
+ RepoStore.openedFiles = [{
+ id: 0,
+ }];
+ const vm = createComponent();
+
+ expect(vm.$el.classList.contains('sidebar-mini')).toBeTruthy();
+ expect(vm.$el.querySelector('thead')).toBeFalsy();
+ expect(vm.$el.querySelector('tbody .repo-file-options')).toBeTruthy();
+ });
+
+ it('renders 5 loading files if tree is loading and not hasFiles', () => {
+ RepoStore.loading = {
+ tree: true,
+ };
+ RepoStore.files = [];
+ const vm = createComponent();
+
+ expect(vm.$el.querySelectorAll('tbody .loading-file').length).toEqual(5);
+ });
+
+ it('renders a prev directory if isRoot', () => {
+ RepoStore.files = [{
+ id: 0,
+ }];
+ RepoStore.isRoot = true;
+ const vm = createComponent();
+
+ expect(vm.$el.querySelector('tbody .prev-directory')).toBeTruthy();
+ });
+
+ describe('methods', () => {
+ describe('fileClicked', () => {
+ it('should fetch data for new file', () => {
+ spyOn(Helper, 'getContent').and.callThrough();
+ const file1 = {
+ id: 0,
+ url: '',
+ };
+ RepoStore.files = [file1];
+ RepoStore.isRoot = true;
+ const vm = createComponent();
+
+ vm.fileClicked(file1);
+
+ expect(Helper.getContent).toHaveBeenCalledWith(file1);
+ });
+
+ it('should hide files in directory if already open', () => {
+ spyOn(RepoStore, 'removeChildFilesOfTree').and.callThrough();
+ const file1 = {
+ id: 0,
+ type: 'tree',
+ url: '',
+ opened: true,
+ };
+ RepoStore.files = [file1];
+ RepoStore.isRoot = true;
+ const vm = createComponent();
+
+ vm.fileClicked(file1);
+
+ expect(RepoStore.removeChildFilesOfTree).toHaveBeenCalledWith(file1);
+ });
+ });
+
+ describe('goToPreviousDirectoryClicked', () => {
+ it('should hide files in directory if already open', () => {
+ const prevUrl = 'foo/bar';
+ const vm = createComponent();
+
+ vm.goToPreviousDirectoryClicked(prevUrl);
+
+ expect(RepoService.url).toEqual(prevUrl);
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/repo/components/repo_tab_spec.js b/spec/javascripts/repo/components/repo_tab_spec.js
new file mode 100644
index 00000000000..a3b2d5dea82
--- /dev/null
+++ b/spec/javascripts/repo/components/repo_tab_spec.js
@@ -0,0 +1,70 @@
+import Vue from 'vue';
+import repoTab from '~/repo/components/repo_tab.vue';
+
+describe('RepoTab', () => {
+ function createComponent(propsData) {
+ const RepoTab = Vue.extend(repoTab);
+
+ return new RepoTab({
+ propsData,
+ }).$mount();
+ }
+
+ it('renders a close link and a name link', () => {
+ const tab = {
+ url: 'url',
+ name: 'name',
+ };
+ const vm = createComponent({
+ tab,
+ });
+ const close = vm.$el.querySelector('.close');
+ const name = vm.$el.querySelector(`a[title="${tab.url}"]`);
+
+ spyOn(vm, 'xClicked');
+ spyOn(vm, 'tabClicked');
+
+ expect(close.querySelector('.fa-times')).toBeTruthy();
+ expect(name.textContent.trim()).toEqual(tab.name);
+
+ close.click();
+ name.click();
+
+ expect(vm.xClicked).toHaveBeenCalledWith(tab);
+ expect(vm.tabClicked).toHaveBeenCalledWith(tab);
+ });
+
+ it('renders an fa-circle icon if tab is changed', () => {
+ const tab = {
+ url: 'url',
+ name: 'name',
+ changed: true,
+ };
+ const vm = createComponent({
+ tab,
+ });
+
+ expect(vm.$el.querySelector('.close .fa-circle')).toBeTruthy();
+ });
+
+ describe('methods', () => {
+ describe('xClicked', () => {
+ const vm = jasmine.createSpyObj('vm', ['$emit']);
+
+ it('returns undefined and does not $emit if file is changed', () => {
+ const file = { changed: true };
+ const returnVal = repoTab.methods.xClicked.call(vm, file);
+
+ expect(returnVal).toBeUndefined();
+ expect(vm.$emit).not.toHaveBeenCalled();
+ });
+
+ it('$emits xclicked event with file obj', () => {
+ const file = { changed: false };
+ repoTab.methods.xClicked.call(vm, file);
+
+ expect(vm.$emit).toHaveBeenCalledWith('xclicked', file);
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/repo/components/repo_tabs_spec.js b/spec/javascripts/repo/components/repo_tabs_spec.js
new file mode 100644
index 00000000000..60459e90c48
--- /dev/null
+++ b/spec/javascripts/repo/components/repo_tabs_spec.js
@@ -0,0 +1,53 @@
+import Vue from 'vue';
+import RepoStore from '~/repo/stores/repo_store';
+import repoTabs from '~/repo/components/repo_tabs.vue';
+
+describe('RepoTabs', () => {
+ const openedFiles = [{
+ id: 0,
+ active: true,
+ }, {
+ id: 1,
+ }];
+
+ function createComponent() {
+ const RepoTabs = Vue.extend(repoTabs);
+
+ return new RepoTabs().$mount();
+ }
+
+ it('renders a list of tabs', () => {
+ RepoStore.openedFiles = openedFiles;
+
+ const vm = createComponent();
+ const tabs = [...vm.$el.querySelectorAll(':scope > li')];
+
+ expect(vm.$el.id).toEqual('tabs');
+ expect(tabs.length).toEqual(3);
+ expect(tabs[0].classList.contains('active')).toBeTruthy();
+ expect(tabs[1].classList.contains('active')).toBeFalsy();
+ expect(tabs[2].classList.contains('tabs-divider')).toBeTruthy();
+ });
+
+ it('does not render a tabs list if not isMini', () => {
+ RepoStore.openedFiles = [];
+
+ const vm = createComponent();
+
+ expect(vm.$el.innerHTML).toBeFalsy();
+ });
+
+ describe('methods', () => {
+ describe('xClicked', () => {
+ it('calls removeFromOpenedFiles with file obj', () => {
+ const file = {};
+
+ spyOn(RepoStore, 'removeFromOpenedFiles');
+
+ repoTabs.methods.xClicked(file);
+
+ expect(RepoStore.removeFromOpenedFiles).toHaveBeenCalledWith(file);
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/repo/monaco_loader_spec.js b/spec/javascripts/repo/monaco_loader_spec.js
new file mode 100644
index 00000000000..be6e779c50f
--- /dev/null
+++ b/spec/javascripts/repo/monaco_loader_spec.js
@@ -0,0 +1,17 @@
+/* global __webpack_public_path__ */
+import monacoContext from 'monaco-editor/dev/vs/loader';
+
+describe('MonacoLoader', () => {
+ it('calls require.config and exports require', () => {
+ spyOn(monacoContext.require, 'config');
+
+ const monacoLoader = require('~/repo/monaco_loader'); // eslint-disable-line global-require
+
+ expect(monacoContext.require.config).toHaveBeenCalledWith({
+ paths: {
+ vs: `${__webpack_public_path__}monaco-editor/vs`, // eslint-disable-line camelcase
+ },
+ });
+ expect(monacoLoader.default).toBe(monacoContext.require);
+ });
+});
diff --git a/spec/javascripts/repo/services/repo_service_spec.js b/spec/javascripts/repo/services/repo_service_spec.js
new file mode 100644
index 00000000000..d74e6a67b1e
--- /dev/null
+++ b/spec/javascripts/repo/services/repo_service_spec.js
@@ -0,0 +1,121 @@
+import axios from 'axios';
+import RepoService from '~/repo/services/repo_service';
+
+describe('RepoService', () => {
+ it('has default json format param', () => {
+ expect(RepoService.options.params.format).toBe('json');
+ });
+
+ describe('buildParams', () => {
+ let newParams;
+ const url = 'url';
+
+ beforeEach(() => {
+ newParams = {};
+
+ spyOn(Object, 'assign').and.returnValue(newParams);
+ });
+
+ it('clones params', () => {
+ const params = RepoService.buildParams(url);
+
+ expect(Object.assign).toHaveBeenCalledWith({}, RepoService.options.params);
+
+ expect(params).toBe(newParams);
+ });
+
+ it('sets and returns viewer params to richif urlIsRichBlob is true', () => {
+ spyOn(RepoService, 'urlIsRichBlob').and.returnValue(true);
+
+ const params = RepoService.buildParams(url);
+
+ expect(params.viewer).toEqual('rich');
+ });
+
+ it('returns params urlIsRichBlob is false', () => {
+ spyOn(RepoService, 'urlIsRichBlob').and.returnValue(false);
+
+ const params = RepoService.buildParams(url);
+
+ expect(params.viewer).toBeUndefined();
+ });
+
+ it('calls urlIsRichBlob with the objects url prop if no url arg is provided', () => {
+ spyOn(RepoService, 'urlIsRichBlob');
+ RepoService.url = url;
+
+ RepoService.buildParams();
+
+ expect(RepoService.urlIsRichBlob).toHaveBeenCalledWith(url);
+ });
+ });
+
+ describe('urlIsRichBlob', () => {
+ it('returns true for md extension', () => {
+ const isRichBlob = RepoService.urlIsRichBlob('url.md');
+
+ expect(isRichBlob).toBeTruthy();
+ });
+
+ it('returns false for js extension', () => {
+ const isRichBlob = RepoService.urlIsRichBlob('url.js');
+
+ expect(isRichBlob).toBeFalsy();
+ });
+ });
+
+ describe('getContent', () => {
+ const params = {};
+ const url = 'url';
+ const requestPromise = Promise.resolve();
+
+ beforeEach(() => {
+ spyOn(RepoService, 'buildParams').and.returnValue(params);
+ spyOn(axios, 'get').and.returnValue(requestPromise);
+ });
+
+ it('calls buildParams and axios.get', () => {
+ const request = RepoService.getContent(url);
+
+ expect(RepoService.buildParams).toHaveBeenCalledWith(url);
+ expect(axios.get).toHaveBeenCalledWith(url, {
+ params,
+ });
+ expect(request).toBe(requestPromise);
+ });
+
+ it('uses object url prop if no url arg is provided', () => {
+ RepoService.url = url;
+
+ RepoService.getContent();
+
+ expect(axios.get).toHaveBeenCalledWith(url, {
+ params,
+ });
+ });
+ });
+
+ describe('getBase64Content', () => {
+ const url = 'url';
+ const response = { data: 'data' };
+
+ beforeEach(() => {
+ spyOn(RepoService, 'bufferToBase64');
+ spyOn(axios, 'get').and.returnValue(Promise.resolve(response));
+ });
+
+ it('calls axios.get and bufferToBase64 on completion', (done) => {
+ const request = RepoService.getBase64Content(url);
+
+ expect(axios.get).toHaveBeenCalledWith(url, {
+ responseType: 'arraybuffer',
+ });
+ expect(request).toEqual(jasmine.any(Promise));
+
+ request.then(() => {
+ expect(RepoService.bufferToBase64).toHaveBeenCalledWith(response.data);
+ done();
+ }).catch(done.fail);
+ });
+ });
+});
diff --git a/spec/javascripts/sidebar/confidential_edit_buttons_spec.js b/spec/javascripts/sidebar/confidential_edit_buttons_spec.js
new file mode 100644
index 00000000000..482be466aad
--- /dev/null
+++ b/spec/javascripts/sidebar/confidential_edit_buttons_spec.js
@@ -0,0 +1,39 @@
+import Vue from 'vue';
+import editFormButtons from '~/sidebar/components/confidential/edit_form_buttons.vue';
+
+describe('Edit Form Buttons', () => {
+ let vm1;
+ let vm2;
+
+ beforeEach(() => {
+ const Component = Vue.extend(editFormButtons);
+ const toggleForm = () => { };
+ const updateConfidentialAttribute = () => { };
+
+ vm1 = new Component({
+ propsData: {
+ isConfidential: true,
+ toggleForm,
+ updateConfidentialAttribute,
+ },
+ }).$mount();
+
+ vm2 = new Component({
+ propsData: {
+ isConfidential: false,
+ toggleForm,
+ updateConfidentialAttribute,
+ },
+ }).$mount();
+ });
+
+ it('renders on or off text based on confidentiality', () => {
+ expect(
+ vm1.$el.innerHTML.includes('Turn Off'),
+ ).toBe(true);
+
+ expect(
+ vm2.$el.innerHTML.includes('Turn On'),
+ ).toBe(true);
+ });
+});
diff --git a/spec/javascripts/sidebar/confidential_edit_form_buttons_spec.js b/spec/javascripts/sidebar/confidential_edit_form_buttons_spec.js
new file mode 100644
index 00000000000..724f5126945
--- /dev/null
+++ b/spec/javascripts/sidebar/confidential_edit_form_buttons_spec.js
@@ -0,0 +1,39 @@
+import Vue from 'vue';
+import editForm from '~/sidebar/components/confidential/edit_form.vue';
+
+describe('Edit Form Dropdown', () => {
+ let vm1;
+ let vm2;
+
+ beforeEach(() => {
+ const Component = Vue.extend(editForm);
+ const toggleForm = () => { };
+ const updateConfidentialAttribute = () => { };
+
+ vm1 = new Component({
+ propsData: {
+ isConfidential: true,
+ toggleForm,
+ updateConfidentialAttribute,
+ },
+ }).$mount();
+
+ vm2 = new Component({
+ propsData: {
+ isConfidential: false,
+ toggleForm,
+ updateConfidentialAttribute,
+ },
+ }).$mount();
+ });
+
+ it('renders on the appropriate warning text', () => {
+ expect(
+ vm1.$el.innerHTML.includes('You are going to turn off the confidentiality.'),
+ ).toBe(true);
+
+ expect(
+ vm2.$el.innerHTML.includes('You are going to turn on the confidentiality.'),
+ ).toBe(true);
+ });
+});
diff --git a/spec/javascripts/sidebar/confidential_issue_sidebar_spec.js b/spec/javascripts/sidebar/confidential_issue_sidebar_spec.js
new file mode 100644
index 00000000000..90eac1ed1ab
--- /dev/null
+++ b/spec/javascripts/sidebar/confidential_issue_sidebar_spec.js
@@ -0,0 +1,65 @@
+import Vue from 'vue';
+import confidentialIssueSidebar from '~/sidebar/components/confidential/confidential_issue_sidebar.vue';
+
+describe('Confidential Issue Sidebar Block', () => {
+ let vm1;
+ let vm2;
+
+ beforeEach(() => {
+ const Component = Vue.extend(confidentialIssueSidebar);
+ const service = {
+ update: () => new Promise((resolve, reject) => {
+ resolve(true);
+ reject('failed!');
+ }),
+ };
+
+ vm1 = new Component({
+ propsData: {
+ isConfidential: true,
+ isEditable: true,
+ service,
+ },
+ }).$mount();
+
+ vm2 = new Component({
+ propsData: {
+ isConfidential: false,
+ isEditable: false,
+ service,
+ },
+ }).$mount();
+ });
+
+ it('shows if confidential and/or editable', () => {
+ expect(
+ vm1.$el.innerHTML.includes('Edit'),
+ ).toBe(true);
+
+ expect(
+ vm1.$el.innerHTML.includes('This issue is confidential'),
+ ).toBe(true);
+
+ expect(
+ vm2.$el.innerHTML.includes('None'),
+ ).toBe(true);
+ });
+
+ it('displays the edit form when editable', (done) => {
+ expect(vm1.edit).toBe(false);
+
+ vm1.$el.querySelector('.confidential-edit').click();
+
+ expect(vm1.edit).toBe(true);
+
+ setTimeout(() => {
+ expect(
+ vm1.$el
+ .innerHTML
+ .includes('You are going to turn off the confidentiality.'),
+ ).toBe(true);
+
+ done();
+ });
+ });
+});
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js
index ab8a3f6c64c..7ee998c8fce 100644
--- a/spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js
@@ -1,7 +1,6 @@
import Vue from 'vue';
import deploymentComponent from '~/vue_merge_request_widget/components/mr_widget_deployment';
import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service';
-import { statusIconEntityMap } from '~/vue_shared/ci_status_icons';
const deploymentMockData = [
{
@@ -43,15 +42,6 @@ describe('MRWidgetDeployment', () => {
});
});
- describe('computed', () => {
- describe('svg', () => {
- it('should have the proper SVG icon', () => {
- const vm = createComponent(deploymentMockData);
- expect(vm.svg).toEqual(statusIconEntityMap.icon_status_success);
- });
- });
- });
-
describe('methods', () => {
let vm = createComponent();
const deployment = deploymentMockData[0];
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js
index 6adcbc73ed7..2ae3adc1f93 100644
--- a/spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js
@@ -52,10 +52,10 @@ const createComponent = () => {
};
const messages = {
- loadingMetrics: 'Loading deployment statistics.',
+ loadingMetrics: 'Loading deployment statistics',
hasMetrics: 'Memory usage unchanged from 0MB to 0MB',
- loadFailed: 'Failed to load deployment statistics.',
- metricsUnavailable: 'Deployment statistics are not available currently.',
+ loadFailed: 'Failed to load deployment statistics',
+ metricsUnavailable: 'Deployment statistics are not available currently',
};
describe('MemoryUsage', () => {
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js
index 647b59520f8..c763487d12f 100644
--- a/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js
@@ -81,13 +81,12 @@ describe('MRWidgetPipeline', () => {
expect(el.querySelectorAll('.ci-status-icon.ci-status-icon-success').length).toEqual(1);
expect(el.querySelector('.pipeline-id').textContent).toContain(`#${pipeline.id}`);
expect(el.innerText).toContain('passed');
- expect(el.innerText).toContain('with stages');
expect(el.querySelector('.pipeline-id').getAttribute('href')).toEqual(pipeline.path);
expect(el.querySelectorAll('.stage-container').length).toEqual(2);
expect(el.querySelector('.js-ci-error')).toEqual(null);
expect(el.querySelector('.js-commit-link').getAttribute('href')).toEqual(pipeline.commit.commit_path);
expect(el.querySelector('.js-commit-link').textContent).toContain(pipeline.commit.short_id);
- expect(el.querySelector('.js-mr-coverage').textContent).toContain(`Coverage ${pipeline.coverage}%.`);
+ expect(el.querySelector('.js-mr-coverage').textContent).toContain(`Coverage ${pipeline.coverage}%`);
});
it('should list single stage', (done) => {
@@ -95,7 +94,6 @@ describe('MRWidgetPipeline', () => {
Vue.nextTick(() => {
expect(el.querySelectorAll('.stage-container button').length).toEqual(1);
- expect(el.innerText).toContain('with stage');
done();
});
});
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_related_links_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_related_links_spec.js
index f6e0c3dfb74..f86fb6a0b4b 100644
--- a/spec/javascripts/vue_mr_widget/components/mr_widget_related_links_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/mr_widget_related_links_spec.js
@@ -22,15 +22,16 @@ describe('MRWidgetRelatedLinks', () => {
});
describe('computed', () => {
+ const data = {
+ relatedLinks: {
+ closing: '/foo',
+ mentioned: '/foo',
+ assignToMe: '/foo',
+ },
+ };
+
describe('hasLinks', () => {
it('should return correct value when we have links reference', () => {
- const data = {
- relatedLinks: {
- closing: '/foo',
- mentioned: '/foo',
- assignToMe: '/foo',
- },
- };
const vm = createComponent(data);
expect(vm.hasLinks).toBeTruthy();
@@ -44,44 +45,24 @@ describe('MRWidgetRelatedLinks', () => {
expect(vm.hasLinks).toBeFalsy();
});
});
- });
-
- describe('methods', () => {
- const data = {
- relatedLinks: {
- closing: '<a href="#">#23</a> and <a>#42</a>',
- mentioned: '<a href="#">#7</a>',
- },
- };
- const vm = createComponent(data);
-
- describe('hasMultipleIssues', () => {
- it('should return true if the given text has multiple issues', () => {
- expect(vm.hasMultipleIssues(data.relatedLinks.closing)).toBeTruthy();
- });
-
- it('should return false if the given text has one issue', () => {
- expect(vm.hasMultipleIssues(data.relatedLinks.mentioned)).toBeFalsy();
- });
- });
- describe('issueLabel', () => {
- it('should return true if the given text has multiple issues', () => {
- expect(vm.issueLabel('closing')).toEqual('issues');
- });
-
- it('should return false if the given text has one issue', () => {
- expect(vm.issueLabel('mentioned')).toEqual('issue');
+ describe('closesText', () => {
+ it('returns correct text for open merge request', () => {
+ data.state = 'open';
+ const vm = createComponent(data);
+ expect(vm.closesText).toEqual('Closes');
});
- });
- describe('verbLabel', () => {
- it('should return true if the given text has multiple issues', () => {
- expect(vm.verbLabel('closing')).toEqual('are');
+ it('returns correct text for closed merge request', () => {
+ data.state = 'closed';
+ const vm = createComponent(data);
+ expect(vm.closesText).toEqual('Did not close');
});
- it('should return false if the given text has one issue', () => {
- expect(vm.verbLabel('mentioned')).toEqual('is');
+ it('returns correct tense for merged request', () => {
+ data.state = 'merged';
+ const vm = createComponent(data);
+ expect(vm.closesText).toEqual('Closed');
});
});
});
@@ -95,8 +76,8 @@ describe('MRWidgetRelatedLinks', () => {
});
const content = vm.$el.textContent.replace(/\n(\s)+/g, ' ').trim();
- expect(content).toContain('Closes issues #23 and #42');
- expect(content).not.toContain('mentioned');
+ expect(content).toContain('Closes #23 and #42');
+ expect(content).not.toContain('Mentions');
});
it('should have only have mentioned issues text', () => {
@@ -106,8 +87,7 @@ describe('MRWidgetRelatedLinks', () => {
},
});
- expect(vm.$el.innerText).toContain('issue #7');
- expect(vm.$el.innerText).toContain('is mentioned but will not be closed.');
+ expect(vm.$el.innerText).toContain('Mentions #7');
expect(vm.$el.innerText).not.toContain('Closes');
});
@@ -120,9 +100,8 @@ describe('MRWidgetRelatedLinks', () => {
});
const content = vm.$el.textContent.replace(/\n(\s)+/g, ' ').trim();
- expect(content).toContain('Closes issue #7.');
- expect(content).toContain('issues #23 and #42');
- expect(content).toContain('are mentioned but will not be closed.');
+ expect(content).toContain('Closes #7');
+ expect(content).toContain('Mentions #23 and #42');
});
it('should have assing issues link', () => {
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_archived_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_archived_spec.js
index cac2f561a0b..4869fb17d96 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_archived_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_archived_spec.js
@@ -12,7 +12,7 @@ describe('MRWidgetArchived', () => {
expect(el.classList.contains('mr-widget-body')).toBeTruthy();
expect(el.querySelector('button').classList.contains('btn-success')).toBeTruthy();
expect(el.querySelector('button').disabled).toBeTruthy();
- expect(el.innerText).toContain('This project is archived, write access has been disabled.');
+ expect(el.innerText).toContain('This project is archived, write access has been disabled');
});
});
});
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js
index 47b4ba893e0..6042d7384d5 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js
@@ -24,8 +24,8 @@ describe('MRWidgetAutoMergeFailed', () => {
it('should have correct elements', () => {
expect(vm.$el.classList.contains('mr-widget-body')).toBeTruthy();
- expect(vm.$el.querySelector('button').getAttribute('disabled')).toBeTruthy();
- expect(vm.$el.innerText).toContain('This merge request failed to be merged automatically.');
+ expect(vm.$el.querySelector('button').getAttribute('disabled')).toBeFalsy();
+ expect(vm.$el.innerText).toContain('This merge request failed to be merged automatically');
expect(vm.$el.innerText).toContain(mergeError);
});
});
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_checking_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_checking_spec.js
index 3be11d47227..6b7aa935ad3 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_checking_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_checking_spec.js
@@ -12,7 +12,7 @@ describe('MRWidgetChecking', () => {
expect(el.classList.contains('mr-widget-body')).toBeTruthy();
expect(el.querySelector('button').classList.contains('btn-success')).toBeTruthy();
expect(el.querySelector('button').disabled).toBeTruthy();
- expect(el.innerText).toContain('Checking ability to merge automatically.');
+ expect(el.innerText).toContain('Checking ability to merge automatically');
expect(el.querySelector('i')).toBeDefined();
});
});
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_conflicts_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_conflicts_spec.js
index e7ae85caec4..3b7b7d93662 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_conflicts_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_conflicts_spec.js
@@ -29,15 +29,16 @@ describe('MRWidgetConflicts', () => {
describe('template', () => {
it('should have correct elements', () => {
const el = createComponent().$el;
- const resolveButton = el.querySelectorAll('.btn-group .btn')[0];
- const mergeLocallyButton = el.querySelectorAll('.btn-group .btn')[1];
+ const resolveButton = el.querySelector('.js-resolve-conflicts-button');
+ const mergeButton = el.querySelector('.mr-widget-body .btn');
+ const mergeLocallyButton = el.querySelector('.js-merge-locally-button');
- expect(el.textContent).toContain('There are merge conflicts.');
+ expect(el.textContent).toContain('There are merge conflicts');
expect(el.textContent).not.toContain('ask someone with write access');
expect(el.querySelector('.btn-success').disabled).toBeTruthy();
- expect(el.querySelectorAll('.btn-group .btn').length).toBe(2);
expect(resolveButton.textContent).toContain('Resolve conflicts');
expect(resolveButton.getAttribute('href')).toEqual(path);
+ expect(mergeButton.textContent).toContain('Merge');
expect(mergeLocallyButton.textContent).toContain('Merge locally');
});
@@ -59,8 +60,8 @@ describe('MRWidgetConflicts', () => {
it('should not have action buttons', (done) => {
Vue.nextTick(() => {
expect(vm.$el.querySelectorAll('.btn').length).toBe(1);
- expect(vm.$el.querySelector('a.js-resolve-conflicts-button')).toEqual(null);
- expect(vm.$el.querySelector('a.js-merge-locally-button')).toEqual(null);
+ expect(vm.$el.querySelector('.js-resolve-conflicts-button')).toEqual(null);
+ expect(vm.$el.querySelector('.js-merge-locally-button')).toEqual(null);
done();
});
});
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_failed_to_merge_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_failed_to_merge_spec.js
index 587b83430d9..cef365eec8a 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_failed_to_merge_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_failed_to_merge_spec.js
@@ -94,7 +94,7 @@ describe('MRWidgetFailedToMerge', () => {
expect(el.querySelector('button').innerText).toContain('Merge');
expect(el.querySelector('.js-refresh-button').innerText).toContain('Refresh now');
expect(el.querySelector('.js-refresh-label')).toEqual(null);
- expect(el.innerText).not.toContain('Refreshing now...');
+ expect(el.innerText).not.toContain('Refreshing now');
setTimeout(() => {
expect(el.innerText).toContain('Refreshing in 9 seconds');
done();
@@ -115,7 +115,7 @@ describe('MRWidgetFailedToMerge', () => {
vm.refresh();
Vue.nextTick(() => {
expect(el.innerText).not.toContain('Merge failed. Refreshing');
- expect(el.innerText).toContain('Refreshing now...');
+ expect(el.innerText).toContain('Refreshing now');
});
});
});
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_locked_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_locked_spec.js
index fb2ef606604..237035648cf 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_locked_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_locked_spec.js
@@ -1,10 +1,10 @@
import Vue from 'vue';
-import lockedComponent from '~/vue_merge_request_widget/components/states/mr_widget_locked';
+import mergingComponent from '~/vue_merge_request_widget/components/states/mr_widget_merging';
-describe('MRWidgetLocked', () => {
+describe('MRWidgetMerging', () => {
describe('props', () => {
it('should have props', () => {
- const { mr } = lockedComponent.props;
+ const { mr } = mergingComponent.props;
expect(mr.type instanceof Object).toBeTruthy();
expect(mr.required).toBeTruthy();
@@ -13,7 +13,7 @@ describe('MRWidgetLocked', () => {
describe('template', () => {
it('should have correct elements', () => {
- const Component = Vue.extend(lockedComponent);
+ const Component = Vue.extend(mergingComponent);
const mr = {
targetBranchPath: '/branch-path',
targetBranch: 'branch',
@@ -24,7 +24,7 @@ describe('MRWidgetLocked', () => {
}).$el;
expect(el.classList.contains('mr-widget-body')).toBeTruthy();
- expect(el.innerText).toContain('it is locked');
+ expect(el.innerText).toContain('This merge request is in the process of being merged');
expect(el.innerText).toContain('changes will be merged into');
expect(el.querySelector('.label-branch a').getAttribute('href')).toEqual(mr.targetBranchPath);
expect(el.querySelector('.label-branch a').textContent).toContain(mr.targetBranch);
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_merge_when_pipeline_succeeds_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_merge_when_pipeline_succeeds_spec.js
index 8d8b90cea16..9a71d0b47d7 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_merge_when_pipeline_succeeds_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_merge_when_pipeline_succeeds_spec.js
@@ -162,10 +162,10 @@ describe('MRWidgetMergeWhenPipelineSucceeds', () => {
it('should have correct elements', () => {
expect(el.classList.contains('mr-widget-body')).toBeTruthy();
- expect(el.innerText).toContain('to be merged automatically when the pipeline succeeds.');
+ expect(el.innerText).toContain('to be merged automatically when the pipeline succeeds');
expect(el.innerText).toContain('The changes will be merged into');
expect(el.innerText).toContain(targetBranch);
- expect(el.innerText).toContain('The source branch will not be removed.');
+ expect(el.innerText).toContain('The source branch will not be removed');
expect(el.querySelector('.js-cancel-auto-merge').innerText).toContain('Cancel automatic merge');
expect(el.querySelector('.js-cancel-auto-merge').getAttribute('disabled')).toBeFalsy();
expect(el.querySelector('.js-remove-source-branch').innerText).toContain('Remove source branch');
@@ -186,8 +186,8 @@ describe('MRWidgetMergeWhenPipelineSucceeds', () => {
Vue.nextTick(() => {
const normalizedText = el.innerText.replace(/\s+/g, ' ');
- expect(normalizedText).toContain('The source branch will be removed.');
- expect(normalizedText).not.toContain('The source branch will not be removed.');
+ expect(normalizedText).toContain('The source branch will be removed');
+ expect(normalizedText).not.toContain('The source branch will not be removed');
done();
});
});
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_merged_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_merged_spec.js
index 6628010112d..afaa750199a 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_merged_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_merged_spec.js
@@ -142,19 +142,19 @@ describe('MRWidgetMerged', () => {
expect(el.querySelector('.js-mr-widget-author')).toBeDefined();
expect(el.innerText).toContain('The changes were merged into');
expect(el.innerText).toContain(targetBranch);
- expect(el.innerText).toContain('The source branch has been removed.');
+ expect(el.innerText).toContain('The source branch has been removed');
expect(el.innerText).toContain('Revert');
expect(el.innerText).toContain('Cherry-pick');
- expect(el.innerText).not.toContain('You can remove source branch now.');
- expect(el.innerText).not.toContain('The source branch is being removed.');
+ expect(el.innerText).not.toContain('You can remove source branch now');
+ expect(el.innerText).not.toContain('The source branch is being removed');
});
it('should not show source branch removed text', (done) => {
vm.mr.sourceBranchRemoved = false;
Vue.nextTick(() => {
- expect(el.innerText).toContain('You can remove source branch now.');
- expect(el.innerText).not.toContain('The source branch has been removed.');
+ expect(el.innerText).toContain('You can remove source branch now');
+ expect(el.innerText).not.toContain('The source branch has been removed');
done();
});
});
@@ -164,9 +164,9 @@ describe('MRWidgetMerged', () => {
vm.mr.sourceBranchRemoved = false;
Vue.nextTick(() => {
- expect(el.innerText).toContain('The source branch is being removed.');
- expect(el.innerText).not.toContain('You can remove source branch now.');
- expect(el.innerText).not.toContain('The source branch has been removed.');
+ expect(el.innerText).toContain('The source branch is being removed');
+ expect(el.innerText).not.toContain('You can remove source branch now');
+ expect(el.innerText).not.toContain('The source branch has been removed');
done();
});
});
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_missing_branch_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_missing_branch_spec.js
index 98674d12afb..720effb5c1c 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_missing_branch_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_missing_branch_spec.js
@@ -49,7 +49,7 @@ describe('MRWidgetMissingBranch', () => {
expect(el.classList.contains('mr-widget-body')).toBeTruthy();
expect(el.querySelector('button').getAttribute('disabled')).toBeTruthy();
expect(content).toContain('source branch does not exist.');
- expect(content).toContain('Please restore the source branch or use a different source branch.');
+ expect(content).toContain('Please restore it or use a different source branch');
});
});
});
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_not_allowed_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_not_allowed_spec.js
index 61e00f4cf79..33f20ab132d 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_not_allowed_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_not_allowed_spec.js
@@ -11,7 +11,7 @@ describe('MRWidgetNotAllowed', () => {
expect(vm.$el.classList.contains('mr-widget-body')).toBeTruthy();
expect(vm.$el.querySelector('button').getAttribute('disabled')).toBeTruthy();
expect(vm.$el.innerText).toContain('Ready to be merged automatically.');
- expect(vm.$el.innerText).toContain('Ask someone with write access to this repository to merge this request.');
+ expect(vm.$el.innerText).toContain('Ask someone with write access to this repository to merge this request');
});
});
});
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_pipeline_blocked_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_pipeline_blocked_spec.js
index b293d118571..d0702f9f503 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_pipeline_blocked_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_pipeline_blocked_spec.js
@@ -10,7 +10,7 @@ describe('MRWidgetPipelineBlocked', () => {
it('should have correct elements', () => {
expect(vm.$el.classList.contains('mr-widget-body')).toBeTruthy();
expect(vm.$el.querySelector('button').getAttribute('disabled')).toBeTruthy();
- expect(vm.$el.innerText).toContain('Pipeline blocked. The pipeline for this merge request requires a manual action to proceed.');
+ expect(vm.$el.innerText).toContain('Pipeline blocked. The pipeline for this merge request requires a manual action to proceed');
});
});
});
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_pipeline_failed_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_pipeline_failed_spec.js
index 807fba705d4..78bac1c61a5 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_pipeline_failed_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_pipeline_failed_spec.js
@@ -10,7 +10,7 @@ describe('MRWidgetPipelineFailed', () => {
it('should have correct elements', () => {
expect(vm.$el.classList.contains('mr-widget-body')).toBeTruthy();
expect(vm.$el.querySelector('button').getAttribute('disabled')).toBeTruthy();
- expect(vm.$el.innerText).toContain('The pipeline for this merge request failed. Please retry the job or push a new commit to fix the failure.');
+ expect(vm.$el.innerText).toContain('The pipeline for this merge request failed. Please retry the job or push a new commit to fix the failure');
});
});
});
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
index 732b516badd..c607c9746a4 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
@@ -72,7 +72,7 @@ describe('MRWidgetReadyToMerge', () => {
const withDesc = 'Include description in commit message';
const withoutDesc = "Don't include description in commit message";
- it('should return message wit description', () => {
+ it('should return message with description', () => {
expect(vm.commitMessageLinkTitle).toEqual(withDesc);
});
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_sha_mismatch_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_sha_mismatch_spec.js
index 5fb1d69a8b3..4c67504b642 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_sha_mismatch_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_sha_mismatch_spec.js
@@ -10,7 +10,7 @@ describe('MRWidgetSHAMismatch', () => {
it('should have correct elements', () => {
expect(vm.$el.classList.contains('mr-widget-body')).toBeTruthy();
expect(vm.$el.querySelector('button').getAttribute('disabled')).toBeTruthy();
- expect(vm.$el.innerText).toContain('The source branch HEAD has recently changed. Please reload the page and review the changes before merging.');
+ expect(vm.$el.innerText).toContain('The source branch HEAD has recently changed. Please reload the page and review the changes before merging');
});
});
});
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_wip_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_wip_spec.js
index 45bd1a69964..2cb3aaa6951 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_wip_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_wip_spec.js
@@ -78,7 +78,7 @@ describe('MRWidgetWIP', () => {
it('should have correct elements', () => {
expect(el.classList.contains('mr-widget-body')).toBeTruthy();
- expect(el.innerText).toContain('This merge request is currently Work In Progress and therefore unable to merge');
+ expect(el.innerText).toContain('This is a Work in Progress');
expect(el.querySelector('button').getAttribute('disabled')).toBeTruthy();
expect(el.querySelector('button').innerText).toContain('Merge');
expect(el.querySelector('.js-remove-wip').innerText).toContain('Resolve WIP status');
diff --git a/spec/javascripts/vue_mr_widget/mock_data.js b/spec/javascripts/vue_mr_widget/mock_data.js
index e6f96d5588b..0795d0aaa82 100644
--- a/spec/javascripts/vue_mr_widget/mock_data.js
+++ b/spec/javascripts/vue_mr_widget/mock_data.js
@@ -20,7 +20,6 @@ export default {
"human_time_estimate": null,
"human_total_time_spent": null,
"in_progress_merge_commit_sha": null,
- "locked_at": null,
"merge_commit_sha": "53027d060246c8f47e4a9310fb332aa52f221775",
"merge_error": null,
"merge_params": {
@@ -30,6 +29,7 @@ export default {
"merge_user_id": null,
"merge_when_pipeline_succeeds": false,
"source_branch": "daaaa",
+ "source_branch_link": "daaaa",
"source_project_id": 19,
"target_branch": "master",
"target_project_id": 19,
diff --git a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js
index 3a0c50b750f..669ee248bf1 100644
--- a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js
+++ b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js
@@ -342,7 +342,7 @@ describe('mrWidgetOptions', () => {
expect(comps['mr-widget-related-links']).toBeDefined();
expect(comps['mr-widget-merged']).toBeDefined();
expect(comps['mr-widget-closed']).toBeDefined();
- expect(comps['mr-widget-locked']).toBeDefined();
+ expect(comps['mr-widget-merging']).toBeDefined();
expect(comps['mr-widget-failed-to-merge']).toBeDefined();
expect(comps['mr-widget-wip']).toBeDefined();
expect(comps['mr-widget-archived']).toBeDefined();
diff --git a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
index 5db77566513..ebd6c79077e 100644
--- a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
@@ -3,57 +3,57 @@ require 'spec_helper'
describe Banzai::Filter::MilestoneReferenceFilter do
include FilterSpecHelper
- let(:project) { create(:project, :public) }
- let(:milestone) { create(:milestone, project: project) }
- let(:reference) { milestone.to_reference }
+ let(:group) { create(:group, :public) }
+ let(:project) { create(:project, :public, group: group) }
it 'requires project context' do
expect { described_class.call('') }.to raise_error(ArgumentError, /:project/)
end
- %w(pre code a style).each do |elem|
- it "ignores valid references contained inside '#{elem}' element" do
- exp = act = "<#{elem}>milestone #{milestone.to_reference}</#{elem}>"
- expect(reference_filter(act).to_html).to eq exp
+ shared_examples 'reference parsing' do
+ %w(pre code a style).each do |elem|
+ it "ignores valid references contained inside '#{elem}' element" do
+ exp = act = "<#{elem}>milestone #{reference}</#{elem}>"
+ expect(reference_filter(act).to_html).to eq exp
+ end
end
- end
- it 'includes default classes' do
- doc = reference_filter("Milestone #{reference}")
- expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-milestone has-tooltip'
- end
+ it 'includes default classes' do
+ doc = reference_filter("Milestone #{reference}")
- it 'includes a data-project attribute' do
- doc = reference_filter("Milestone #{reference}")
- link = doc.css('a').first
+ expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-milestone has-tooltip'
+ end
- expect(link).to have_attribute('data-project')
- expect(link.attr('data-project')).to eq project.id.to_s
- end
+ it 'includes a data-project attribute' do
+ doc = reference_filter("Milestone #{reference}")
+ link = doc.css('a').first
- it 'includes a data-milestone attribute' do
- doc = reference_filter("See #{reference}")
- link = doc.css('a').first
+ expect(link).to have_attribute('data-project')
+ expect(link.attr('data-project')).to eq project.id.to_s
+ end
- expect(link).to have_attribute('data-milestone')
- expect(link.attr('data-milestone')).to eq milestone.id.to_s
- end
+ it 'includes a data-milestone attribute' do
+ doc = reference_filter("See #{reference}")
+ link = doc.css('a').first
+
+ expect(link).to have_attribute('data-milestone')
+ expect(link.attr('data-milestone')).to eq milestone.id.to_s
+ end
- it 'supports an :only_path context' do
- doc = reference_filter("Milestone #{reference}", only_path: true)
- link = doc.css('a').first.attr('href')
+ it 'supports an :only_path context' do
+ doc = reference_filter("Milestone #{reference}", only_path: true)
+ link = doc.css('a').first.attr('href')
- expect(link).not_to match %r(https?://)
- expect(link).to eq urls
- .project_milestone_path(project, milestone)
+ expect(link).not_to match %r(https?://)
+ expect(link).to eq urls.milestone_path(milestone)
+ end
end
- context 'Integer-based references' do
+ shared_examples 'Integer-based references' do
it 'links to a valid reference' do
doc = reference_filter("See #{reference}")
- expect(doc.css('a').first.attr('href')).to eq urls
- .project_milestone_url(project, milestone)
+ expect(doc.css('a').first.attr('href')).to eq urls.milestone_url(milestone)
end
it 'links with adjacent text' do
@@ -68,15 +68,17 @@ describe Banzai::Filter::MilestoneReferenceFilter do
end
end
- context 'String-based single-word references' do
- let(:milestone) { create(:milestone, name: 'gfm', project: project) }
+ shared_examples 'String-based single-word references' do
let(:reference) { "#{Milestone.reference_prefix}#{milestone.name}" }
+ before do
+ milestone.update!(name: 'gfm')
+ end
+
it 'links to a valid reference' do
doc = reference_filter("See #{reference}")
- expect(doc.css('a').first.attr('href')).to eq urls
- .project_milestone_url(project, milestone)
+ expect(doc.css('a').first.attr('href')).to eq urls.milestone_url(milestone)
expect(doc.text).to eq 'See gfm'
end
@@ -92,15 +94,17 @@ describe Banzai::Filter::MilestoneReferenceFilter do
end
end
- context 'String-based multi-word references in quotes' do
- let(:milestone) { create(:milestone, name: 'gfm references', project: project) }
+ shared_examples 'String-based multi-word references in quotes' do
let(:reference) { milestone.to_reference(format: :name) }
+ before do
+ milestone.update!(name: 'gfm references')
+ end
+
it 'links to a valid reference' do
doc = reference_filter("See #{reference}")
- expect(doc.css('a').first.attr('href')).to eq urls
- .project_milestone_url(project, milestone)
+ expect(doc.css('a').first.attr('href')).to eq urls.milestone_url(milestone)
expect(doc.text).to eq 'See gfm references'
end
@@ -116,23 +120,27 @@ describe Banzai::Filter::MilestoneReferenceFilter do
end
end
- describe 'referencing a milestone in a link href' do
- let(:reference) { %Q{<a href="#{milestone.to_reference}">Milestone</a>} }
+ shared_examples 'referencing a milestone in a link href' do
+ let(:unquoted_reference) { "#{Milestone.reference_prefix}#{milestone.name}" }
+ let(:link_reference) { %Q{<a href="#{unquoted_reference}">Milestone</a>} }
+
+ before do
+ milestone.update!(name: 'gfm')
+ end
it 'links to a valid reference' do
- doc = reference_filter("See #{reference}")
+ doc = reference_filter("See #{link_reference}")
- expect(doc.css('a').first.attr('href')).to eq urls
- .project_milestone_url(project, milestone)
+ expect(doc.css('a').first.attr('href')).to eq urls.milestone_url(milestone)
end
it 'links with adjacent text' do
- doc = reference_filter("Milestone (#{reference}.)")
+ doc = reference_filter("Milestone (#{link_reference}.)")
expect(doc.to_html).to match(%r(\(<a.+>Milestone</a>\.\)))
end
it 'includes a data-project attribute' do
- doc = reference_filter("Milestone #{reference}")
+ doc = reference_filter("Milestone #{link_reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-project')
@@ -140,7 +148,7 @@ describe Banzai::Filter::MilestoneReferenceFilter do
end
it 'includes a data-milestone attribute' do
- doc = reference_filter("See #{reference}")
+ doc = reference_filter("See #{link_reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-milestone')
@@ -148,7 +156,35 @@ describe Banzai::Filter::MilestoneReferenceFilter do
end
end
- describe 'cross-project / cross-namespace complete reference' do
+ shared_examples 'linking to a milestone as the entire link' do
+ let(:unquoted_reference) { "#{Milestone.reference_prefix}#{milestone.name}" }
+ let(:link) { urls.milestone_url(milestone) }
+ let(:link_reference) { %Q{<a href="#{link}">#{link}</a>} }
+
+ it 'replaces the link text with the milestone reference' do
+ doc = reference_filter("See #{link}")
+
+ expect(doc.css('a').first.text).to eq(unquoted_reference)
+ end
+
+ it 'includes a data-project attribute' do
+ doc = reference_filter("Milestone #{link_reference}")
+ link = doc.css('a').first
+
+ expect(link).to have_attribute('data-project')
+ expect(link.attr('data-project')).to eq project.id.to_s
+ end
+
+ it 'includes a data-milestone attribute' do
+ doc = reference_filter("See #{link_reference}")
+ link = doc.css('a').first
+
+ expect(link).to have_attribute('data-milestone')
+ expect(link.attr('data-milestone')).to eq milestone.id.to_s
+ end
+ end
+
+ shared_examples 'cross-project / cross-namespace complete reference' do
let(:namespace) { create(:namespace) }
let(:another_project) { create(:project, :public, namespace: namespace) }
let(:milestone) { create(:milestone, project: another_project) }
@@ -184,7 +220,7 @@ describe Banzai::Filter::MilestoneReferenceFilter do
end
end
- describe 'cross-project / same-namespace complete reference' do
+ shared_examples 'cross-project / same-namespace complete reference' do
let(:namespace) { create(:namespace) }
let(:project) { create(:project, :public, namespace: namespace) }
let(:another_project) { create(:project, :public, namespace: namespace) }
@@ -221,7 +257,7 @@ describe Banzai::Filter::MilestoneReferenceFilter do
end
end
- describe 'cross project shorthand reference' do
+ shared_examples 'cross project shorthand reference' do
let(:namespace) { create(:namespace) }
let(:project) { create(:project, :public, namespace: namespace) }
let(:another_project) { create(:project, :public, namespace: namespace) }
@@ -258,27 +294,53 @@ describe Banzai::Filter::MilestoneReferenceFilter do
end
end
- describe 'cross project milestone references' do
- let(:another_project) { create(:project, :public) }
- let(:project_path) { another_project.full_path }
- let(:milestone) { create(:milestone, project: another_project) }
- let(:reference) { milestone.to_reference(project) }
+ context 'project milestones' do
+ let(:milestone) { create(:milestone, project: project) }
+ let(:reference) { milestone.to_reference }
- let!(:result) { reference_filter("See #{reference}") }
+ include_examples 'reference parsing'
- it 'points to referenced project milestone page' do
- expect(result.css('a').first.attr('href')).to eq urls
- .project_milestone_url(another_project, milestone)
+ it_behaves_like 'Integer-based references'
+ it_behaves_like 'String-based single-word references'
+ it_behaves_like 'String-based multi-word references in quotes'
+ it_behaves_like 'referencing a milestone in a link href'
+ it_behaves_like 'cross-project / cross-namespace complete reference'
+ it_behaves_like 'cross-project / same-namespace complete reference'
+ it_behaves_like 'cross project shorthand reference'
+ end
+
+ context 'group milestones' do
+ let(:milestone) { create(:milestone, group: group) }
+ let(:reference) { milestone.to_reference(format: :name) }
+
+ include_examples 'reference parsing'
+
+ it_behaves_like 'String-based single-word references'
+ it_behaves_like 'String-based multi-word references in quotes'
+ it_behaves_like 'referencing a milestone in a link href'
+
+ it 'does not support references by IID' do
+ doc = reference_filter("See #{Milestone.reference_prefix}#{milestone.iid}")
+
+ expect(doc.css('a')).to be_empty
end
- it 'contains cross project content' do
- expect(result.css('a').first.text).to eq "#{milestone.name} in #{project_path}"
+ it 'does not support references by link' do
+ doc = reference_filter("See #{urls.milestone_url(milestone)}")
+
+ expect(doc.css('a').first.text).to eq(urls.milestone_url(milestone))
end
- it 'escapes the name attribute' do
- allow_any_instance_of(Milestone).to receive(:title).and_return(%{"></a>whatever<a title="})
- doc = reference_filter("See #{reference}")
- expect(doc.css('a').first.text).to eq "#{milestone.name} in #{project_path}"
+ it 'does not support cross-project references' do
+ another_group = create(:group)
+ another_project = create(:project, :public, group: group)
+ project_reference = another_project.to_reference(project)
+
+ milestone.update!(group: another_group)
+
+ doc = reference_filter("See #{project_reference}#{reference}")
+
+ expect(doc.css('a')).to be_empty
end
end
end
diff --git a/spec/lib/bitbucket/paginator_spec.rb b/spec/lib/bitbucket/paginator_spec.rb
index 2c972da682e..bdf10a5e2a2 100644
--- a/spec/lib/bitbucket/paginator_spec.rb
+++ b/spec/lib/bitbucket/paginator_spec.rb
@@ -15,7 +15,7 @@ describe Bitbucket::Paginator do
expect(paginator.items).to match(['item_2'])
allow(paginator).to receive(:fetch_next_page).and_return(nil)
- expect{ paginator.items }.to raise_error(StopIteration)
+ expect { paginator.items }.to raise_error(StopIteration)
end
end
end
diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
index ed571a2ba05..ee28387cd48 100644
--- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
+++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
@@ -1323,11 +1323,11 @@ EOT
describe "Error handling" do
it "fails to parse YAML" do
- expect{GitlabCiYamlProcessor.new("invalid: yaml: test")}.to raise_error(Psych::SyntaxError)
+ expect {GitlabCiYamlProcessor.new("invalid: yaml: test")}.to raise_error(Psych::SyntaxError)
end
it "indicates that object is invalid" do
- expect{GitlabCiYamlProcessor.new("invalid_yaml")}.to raise_error(GitlabCiYamlProcessor::ValidationError)
+ expect {GitlabCiYamlProcessor.new("invalid_yaml")}.to raise_error(GitlabCiYamlProcessor::ValidationError)
end
it "returns errors if tags parameter is invalid" do
diff --git a/spec/lib/event_filter_spec.rb b/spec/lib/event_filter_spec.rb
index b0efcab47fb..87ae6b6cf01 100644
--- a/spec/lib/event_filter_spec.rb
+++ b/spec/lib/event_filter_spec.rb
@@ -5,7 +5,7 @@ describe EventFilter do
let(:source_user) { create(:user) }
let!(:public_project) { create(:project, :public) }
- let!(:push_event) { create(:event, :pushed, project: public_project, target: public_project, author: source_user) }
+ let!(:push_event) { create(:push_event, project: public_project, author: source_user) }
let!(:merged_event) { create(:event, :merged, project: public_project, target: public_project, author: source_user) }
let!(:created_event) { create(:event, :created, project: public_project, target: public_project, author: source_user) }
let!(:updated_event) { create(:event, :updated, project: public_project, target: public_project, author: source_user) }
diff --git a/spec/lib/extracts_path_spec.rb b/spec/lib/extracts_path_spec.rb
index 867f7d55af7..e13406d1972 100644
--- a/spec/lib/extracts_path_spec.rb
+++ b/spec/lib/extracts_path_spec.rb
@@ -56,7 +56,7 @@ describe ExtractsPath do
context 'subclass overrides get_id' do
it 'uses ref returned by get_id' do
- allow_any_instance_of(self.class).to receive(:get_id){ '38008cb17ce1466d8fec2dfa6f6ab8dcfe5cf49e' }
+ allow_any_instance_of(self.class).to receive(:get_id) { '38008cb17ce1466d8fec2dfa6f6ab8dcfe5cf49e' }
assign_ref_vars
diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb
index 8f57e73e40d..4a498e79c87 100644
--- a/spec/lib/gitlab/auth_spec.rb
+++ b/spec/lib/gitlab/auth_spec.rb
@@ -313,7 +313,8 @@ describe Gitlab::Auth do
def full_authentication_abilities
read_authentication_abilities + [
:push_code,
- :create_container_image
+ :create_container_image,
+ :admin_container_image
]
end
end
diff --git a/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb b/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb
new file mode 100644
index 00000000000..7cd2ce82eda
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb
@@ -0,0 +1,199 @@
+require 'spec_helper'
+
+describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits do
+ describe '#perform' do
+ set(:merge_request) { create(:merge_request) }
+ set(:merge_request_diff) { merge_request.merge_request_diff }
+ let(:updated_merge_request_diff) { MergeRequestDiff.find(merge_request_diff.id) }
+
+ def diffs_to_hashes(diffs)
+ diffs.as_json(only: Gitlab::Git::Diff::SERIALIZE_KEYS).map(&:with_indifferent_access)
+ end
+
+ def quote_yaml(value)
+ MergeRequestDiff.connection.quote(YAML.dump(value))
+ end
+
+ def convert_to_yaml(merge_request_diff_id, commits, diffs)
+ MergeRequestDiff.where(id: merge_request_diff_id).update_all(
+ "st_commits = #{quote_yaml(commits)}, st_diffs = #{quote_yaml(diffs)}"
+ )
+ end
+
+ shared_examples 'updated MR diff' do
+ before do
+ convert_to_yaml(merge_request_diff.id, commits, diffs)
+
+ MergeRequestDiffCommit.delete_all
+ MergeRequestDiffFile.delete_all
+
+ subject.perform(merge_request_diff.id, merge_request_diff.id)
+ end
+
+ it 'creates correct entries in the merge_request_diff_commits table' do
+ expect(updated_merge_request_diff.merge_request_diff_commits.count).to eq(commits.count)
+ expect(updated_merge_request_diff.commits.map(&:to_hash)).to eq(commits)
+ end
+
+ it 'creates correct entries in the merge_request_diff_files table' do
+ expect(updated_merge_request_diff.merge_request_diff_files.count).to eq(expected_diffs.count)
+ expect(diffs_to_hashes(updated_merge_request_diff.raw_diffs)).to eq(expected_diffs)
+ end
+
+ it 'sets the st_commits and st_diffs columns to nil' do
+ expect(updated_merge_request_diff.st_commits_before_type_cast).to be_nil
+ expect(updated_merge_request_diff.st_diffs_before_type_cast).to be_nil
+ end
+ end
+
+ context 'when the diff IDs passed do not exist' do
+ it 'does not raise' do
+ expect { subject.perform(0, 0) }.not_to raise_exception
+ end
+ end
+
+ context 'when the merge request diff has no serialised commits or diffs' do
+ before do
+ merge_request_diff.update(st_commits: nil, st_diffs: nil)
+ end
+
+ it 'does not raise' do
+ expect { subject.perform(merge_request_diff.id, merge_request_diff.id) }
+ .not_to raise_exception
+ end
+ end
+
+ context 'processing multiple merge request diffs' do
+ let(:start_id) { described_class::MergeRequestDiff.minimum(:id) }
+ let(:stop_id) { described_class::MergeRequestDiff.maximum(:id) }
+
+ before do
+ merge_request.reload_diff(true)
+
+ convert_to_yaml(start_id, merge_request_diff.commits, merge_request_diff.diffs)
+ convert_to_yaml(stop_id, updated_merge_request_diff.commits, updated_merge_request_diff.diffs)
+
+ MergeRequestDiffCommit.delete_all
+ MergeRequestDiffFile.delete_all
+ end
+
+ context 'when BUFFER_ROWS is exceeded' do
+ before do
+ stub_const("#{described_class}::BUFFER_ROWS", 1)
+ end
+
+ it 'updates and continues' do
+ expect(described_class::MergeRequestDiff).to receive(:transaction).twice
+
+ subject.perform(start_id, stop_id)
+ end
+ end
+
+ context 'when BUFFER_ROWS is not exceeded' do
+ it 'only updates once' do
+ expect(described_class::MergeRequestDiff).to receive(:transaction).once
+
+ subject.perform(start_id, stop_id)
+ end
+ end
+ end
+
+ context 'when the merge request diff update fails' do
+ before do
+ allow(described_class::MergeRequestDiff)
+ .to receive(:update_all).and_raise(ActiveRecord::Rollback)
+ end
+
+ it 'does not add any diff commits' do
+ expect { subject.perform(merge_request_diff.id, merge_request_diff.id) }
+ .not_to change { MergeRequestDiffCommit.count }
+ end
+
+ it 'does not add any diff files' do
+ expect { subject.perform(merge_request_diff.id, merge_request_diff.id) }
+ .not_to change { MergeRequestDiffFile.count }
+ end
+ end
+
+ context 'when the merge request diff has valid commits and diffs' do
+ let(:commits) { merge_request_diff.commits.map(&:to_hash) }
+ let(:diffs) { diffs_to_hashes(merge_request_diff.merge_request_diff_files) }
+ let(:expected_diffs) { diffs }
+
+ include_examples 'updated MR diff'
+ end
+
+ context 'when the merge request diffs do not have too_large set' do
+ let(:commits) { merge_request_diff.commits.map(&:to_hash) }
+ let(:expected_diffs) { diffs_to_hashes(merge_request_diff.merge_request_diff_files) }
+
+ let(:diffs) do
+ expected_diffs.map { |diff| diff.except(:too_large) }
+ end
+
+ include_examples 'updated MR diff'
+ end
+
+ context 'when the merge request diffs have binary content' do
+ let(:commits) { merge_request_diff.commits.map(&:to_hash) }
+ let(:expected_diffs) { diffs }
+
+ # The start of a PDF created by Illustrator
+ let(:binary_string) do
+ "\x25\x50\x44\x46\x2d\x31\x2e\x35\x0d\x25\xe2\xe3\xcf\xd3\x0d\x0a".force_encoding(Encoding::BINARY)
+ end
+
+ let(:diffs) do
+ [
+ {
+ 'diff' => binary_string,
+ 'new_path' => 'path',
+ 'old_path' => 'path',
+ 'a_mode' => '100644',
+ 'b_mode' => '100644',
+ 'new_file' => false,
+ 'renamed_file' => false,
+ 'deleted_file' => false,
+ 'too_large' => false
+ }
+ ]
+ end
+
+ include_examples 'updated MR diff'
+ end
+
+ context 'when the merge request diff has commits, but no diffs' do
+ let(:commits) { merge_request_diff.commits.map(&:to_hash) }
+ let(:diffs) { [] }
+ let(:expected_diffs) { diffs }
+
+ include_examples 'updated MR diff'
+ end
+
+ context 'when the merge request diffs have invalid content' do
+ let(:commits) { merge_request_diff.commits.map(&:to_hash) }
+ let(:diffs) { ['--broken-diff'] }
+ let(:expected_diffs) { [] }
+
+ include_examples 'updated MR diff'
+ end
+
+ context 'when the merge request diffs are Rugged::Patch instances' do
+ let(:commits) { merge_request_diff.commits.map(&:to_hash) }
+ let(:first_commit) { merge_request.project.repository.commit(merge_request_diff.head_commit_sha) }
+ let(:diffs) { first_commit.rugged_diff_from_parent.patches }
+ let(:expected_diffs) { [] }
+
+ include_examples 'updated MR diff'
+ end
+
+ context 'when the merge request diffs are Rugged::Diff::Delta instances' do
+ let(:commits) { merge_request_diff.commits.map(&:to_hash) }
+ let(:first_commit) { merge_request.project.repository.commit(merge_request_diff.head_commit_sha) }
+ let(:diffs) { first_commit.rugged_diff_from_parent.deltas }
+ let(:expected_diffs) { [] }
+
+ include_examples 'updated MR diff'
+ end
+ end
+end
diff --git a/spec/lib/gitlab/background_migration/migrate_events_to_push_event_payloads_spec.rb b/spec/lib/gitlab/background_migration/migrate_events_to_push_event_payloads_spec.rb
new file mode 100644
index 00000000000..87f45619e7a
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/migrate_events_to_push_event_payloads_spec.rb
@@ -0,0 +1,423 @@
+require 'spec_helper'
+
+describe Gitlab::BackgroundMigration::MigrateEventsToPushEventPayloads::Event do
+ describe '#commit_title' do
+ it 'returns nil when there are no commits' do
+ expect(described_class.new.commit_title).to be_nil
+ end
+
+ it 'returns nil when there are commits without commit messages' do
+ event = described_class.new
+
+ allow(event).to receive(:commits).and_return([{ id: '123' }])
+
+ expect(event.commit_title).to be_nil
+ end
+
+ it 'returns the commit message when it is less than 70 characters long' do
+ event = described_class.new
+
+ allow(event).to receive(:commits).and_return([{ message: 'Hello world' }])
+
+ expect(event.commit_title).to eq('Hello world')
+ end
+
+ it 'returns the first line of a commit message if multiple lines are present' do
+ event = described_class.new
+
+ allow(event).to receive(:commits).and_return([{ message: "Hello\n\nworld" }])
+
+ expect(event.commit_title).to eq('Hello')
+ end
+
+ it 'truncates the commit to 70 characters when it is too long' do
+ event = described_class.new
+
+ allow(event).to receive(:commits).and_return([{ message: 'a' * 100 }])
+
+ expect(event.commit_title).to eq(('a' * 67) + '...')
+ end
+ end
+
+ describe '#commit_from_sha' do
+ it 'returns nil when pushing to a new ref' do
+ event = described_class.new
+
+ allow(event).to receive(:create?).and_return(true)
+
+ expect(event.commit_from_sha).to be_nil
+ end
+
+ it 'returns the ID of the first commit when pushing to an existing ref' do
+ event = described_class.new
+
+ allow(event).to receive(:create?).and_return(false)
+ allow(event).to receive(:data).and_return(before: '123')
+
+ expect(event.commit_from_sha).to eq('123')
+ end
+ end
+
+ describe '#commit_to_sha' do
+ it 'returns nil when removing an existing ref' do
+ event = described_class.new
+
+ allow(event).to receive(:remove?).and_return(true)
+
+ expect(event.commit_to_sha).to be_nil
+ end
+
+ it 'returns the ID of the last commit when pushing to an existing ref' do
+ event = described_class.new
+
+ allow(event).to receive(:remove?).and_return(false)
+ allow(event).to receive(:data).and_return(after: '123')
+
+ expect(event.commit_to_sha).to eq('123')
+ end
+ end
+
+ describe '#data' do
+ it 'returns the deserialized data' do
+ event = described_class.new(data: { before: '123' })
+
+ expect(event.data).to eq(before: '123')
+ end
+
+ it 'returns an empty hash when no data is present' do
+ event = described_class.new
+
+ expect(event.data).to eq({})
+ end
+ end
+
+ describe '#commits' do
+ it 'returns an Array of commits' do
+ event = described_class.new(data: { commits: [{ id: '123' }] })
+
+ expect(event.commits).to eq([{ id: '123' }])
+ end
+
+ it 'returns an empty array when no data is present' do
+ event = described_class.new
+
+ expect(event.commits).to eq([])
+ end
+ end
+
+ describe '#commit_count' do
+ it 'returns the number of commits' do
+ event = described_class.new(data: { total_commits_count: 2 })
+
+ expect(event.commit_count).to eq(2)
+ end
+
+ it 'returns 0 when no data is present' do
+ event = described_class.new
+
+ expect(event.commit_count).to eq(0)
+ end
+ end
+
+ describe '#ref' do
+ it 'returns the name of the ref' do
+ event = described_class.new(data: { ref: 'refs/heads/master' })
+
+ expect(event.ref).to eq('refs/heads/master')
+ end
+ end
+
+ describe '#trimmed_ref_name' do
+ it 'returns the trimmed ref name for a branch' do
+ event = described_class.new(data: { ref: 'refs/heads/master' })
+
+ expect(event.trimmed_ref_name).to eq('master')
+ end
+
+ it 'returns the trimmed ref name for a tag' do
+ event = described_class.new(data: { ref: 'refs/tags/v1.2' })
+
+ expect(event.trimmed_ref_name).to eq('v1.2')
+ end
+ end
+
+ describe '#create?' do
+ it 'returns true when creating a new ref' do
+ event = described_class.new(data: { before: described_class::BLANK_REF })
+
+ expect(event.create?).to eq(true)
+ end
+
+ it 'returns false when pushing to an existing ref' do
+ event = described_class.new(data: { before: '123' })
+
+ expect(event.create?).to eq(false)
+ end
+ end
+
+ describe '#remove?' do
+ it 'returns true when removing an existing ref' do
+ event = described_class.new(data: { after: described_class::BLANK_REF })
+
+ expect(event.remove?).to eq(true)
+ end
+
+ it 'returns false when pushing to an existing ref' do
+ event = described_class.new(data: { after: '123' })
+
+ expect(event.remove?).to eq(false)
+ end
+ end
+
+ describe '#push_action' do
+ let(:event) { described_class.new }
+
+ it 'returns :created when creating a new ref' do
+ allow(event).to receive(:create?).and_return(true)
+
+ expect(event.push_action).to eq(:created)
+ end
+
+ it 'returns :removed when removing an existing ref' do
+ allow(event).to receive(:create?).and_return(false)
+ allow(event).to receive(:remove?).and_return(true)
+
+ expect(event.push_action).to eq(:removed)
+ end
+
+ it 'returns :pushed when pushing to an existing ref' do
+ allow(event).to receive(:create?).and_return(false)
+ allow(event).to receive(:remove?).and_return(false)
+
+ expect(event.push_action).to eq(:pushed)
+ end
+ end
+
+ describe '#ref_type' do
+ let(:event) { described_class.new }
+
+ it 'returns :tag for a tag' do
+ allow(event).to receive(:ref).and_return('refs/tags/1.2')
+
+ expect(event.ref_type).to eq(:tag)
+ end
+
+ it 'returns :branch for a branch' do
+ allow(event).to receive(:ref).and_return('refs/heads/1.2')
+
+ expect(event.ref_type).to eq(:branch)
+ end
+ end
+end
+
+describe Gitlab::BackgroundMigration::MigrateEventsToPushEventPayloads do
+ let(:migration) { described_class.new }
+ let(:project) { create(:project_empty_repo) }
+ let(:author) { create(:user) }
+
+ # We can not rely on FactoryGirl as the state of Event may change in ways that
+ # the background migration does not expect, hence we use the Event class of
+ # the migration itself.
+ def create_push_event(project, author, data = nil)
+ klass = Gitlab::BackgroundMigration::MigrateEventsToPushEventPayloads::Event
+
+ klass.create!(
+ action: klass::PUSHED,
+ project_id: project.id,
+ author_id: author.id,
+ data: data
+ )
+ end
+
+ # The background migration relies on a temporary table, hence we're migrating
+ # to a specific version of the database where said table is still present.
+ before :all do
+ ActiveRecord::Migration.verbose = false
+
+ ActiveRecord::Migrator
+ .migrate(ActiveRecord::Migrator.migrations_paths, 20170608152748)
+ end
+
+ after :all do
+ ActiveRecord::Migrator.migrate(ActiveRecord::Migrator.migrations_paths)
+
+ ActiveRecord::Migration.verbose = true
+ end
+
+ describe '#perform' do
+ it 'returns if data should not be migrated' do
+ allow(migration).to receive(:migrate?).and_return(false)
+
+ expect(migration).not_to receive(:find_events)
+
+ migration.perform(1, 10)
+ end
+
+ it 'migrates the range of events if data is to be migrated' do
+ event1 = create_push_event(project, author, { commits: [] })
+ event2 = create_push_event(project, author, { commits: [] })
+
+ allow(migration).to receive(:migrate?).and_return(true)
+
+ expect(migration).to receive(:process_event).twice
+
+ migration.perform(event1.id, event2.id)
+ end
+ end
+
+ describe '#process_event' do
+ it 'processes a regular event' do
+ event = double(:event, push_event?: false)
+
+ expect(migration).to receive(:replicate_event)
+ expect(migration).not_to receive(:create_push_event_payload)
+
+ migration.process_event(event)
+ end
+
+ it 'processes a push event' do
+ event = double(:event, push_event?: true)
+
+ expect(migration).to receive(:replicate_event)
+ expect(migration).to receive(:create_push_event_payload)
+
+ migration.process_event(event)
+ end
+ end
+
+ describe '#replicate_event' do
+ it 'replicates the event to the "events_for_migration" table' do
+ event = create_push_event(
+ project,
+ author,
+ data: { commits: [] },
+ title: 'bla'
+ )
+
+ attributes = event
+ .attributes.with_indifferent_access.except(:title, :data)
+
+ expect(described_class::EventForMigration)
+ .to receive(:create!)
+ .with(attributes)
+
+ migration.replicate_event(event)
+ end
+ end
+
+ describe '#create_push_event_payload' do
+ let(:push_data) do
+ {
+ commits: [],
+ ref: 'refs/heads/master',
+ before: '156e0e9adc587a383a7eeb5b21ddecb9044768a8',
+ after: '0' * 40,
+ total_commits_count: 1
+ }
+ end
+
+ let(:event) do
+ create_push_event(project, author, push_data)
+ end
+
+ before do
+ # The foreign key in push_event_payloads at this point points to the
+ # "events_for_migration" table so we need to make sure a row exists in
+ # said table.
+ migration.replicate_event(event)
+ end
+
+ it 'creates a push event payload for an event' do
+ payload = migration.create_push_event_payload(event)
+
+ expect(PushEventPayload.count).to eq(1)
+ expect(payload.valid?).to eq(true)
+ end
+
+ it 'does not create push event payloads for removed events' do
+ allow(event).to receive(:id).and_return(-1)
+
+ payload = migration.create_push_event_payload(event)
+
+ expect(payload).to be_nil
+ expect(PushEventPayload.count).to eq(0)
+ end
+
+ it 'encodes and decodes the commit IDs from and to binary data' do
+ payload = migration.create_push_event_payload(event)
+ packed = migration.pack(push_data[:before])
+
+ expect(payload.commit_from).to eq(packed)
+ expect(payload.commit_to).to be_nil
+ end
+ end
+
+ describe '#find_events' do
+ it 'returns the events for the given ID range' do
+ event1 = create_push_event(project, author, { commits: [] })
+ event2 = create_push_event(project, author, { commits: [] })
+ event3 = create_push_event(project, author, { commits: [] })
+ events = migration.find_events(event1.id, event2.id)
+
+ expect(events.length).to eq(2)
+ expect(events.pluck(:id)).not_to include(event3.id)
+ end
+ end
+
+ describe '#migrate?' do
+ it 'returns true when data should be migrated' do
+ allow(described_class::Event)
+ .to receive(:table_exists?).and_return(true)
+
+ allow(described_class::PushEventPayload)
+ .to receive(:table_exists?).and_return(true)
+
+ allow(described_class::EventForMigration)
+ .to receive(:table_exists?).and_return(true)
+
+ expect(migration.migrate?).to eq(true)
+ end
+
+ it 'returns false if the "events" table does not exist' do
+ allow(described_class::Event)
+ .to receive(:table_exists?).and_return(false)
+
+ expect(migration.migrate?).to eq(false)
+ end
+
+ it 'returns false if the "push_event_payloads" table does not exist' do
+ allow(described_class::Event)
+ .to receive(:table_exists?).and_return(true)
+
+ allow(described_class::PushEventPayload)
+ .to receive(:table_exists?).and_return(false)
+
+ expect(migration.migrate?).to eq(false)
+ end
+
+ it 'returns false when the "events_for_migration" table does not exist' do
+ allow(described_class::Event)
+ .to receive(:table_exists?).and_return(true)
+
+ allow(described_class::PushEventPayload)
+ .to receive(:table_exists?).and_return(true)
+
+ allow(described_class::EventForMigration)
+ .to receive(:table_exists?).and_return(false)
+
+ expect(migration.migrate?).to eq(false)
+ end
+ end
+
+ describe '#pack' do
+ it 'packs a SHA1 into a 20 byte binary string' do
+ packed = migration.pack('156e0e9adc587a383a7eeb5b21ddecb9044768a8')
+
+ expect(packed.bytesize).to eq(20)
+ end
+
+ it 'returns nil if the input value is nil' do
+ expect(migration.pack(nil)).to be_nil
+ end
+ end
+end
diff --git a/spec/lib/gitlab/background_migration/move_personal_snippet_files_spec.rb b/spec/lib/gitlab/background_migration/move_personal_snippet_files_spec.rb
new file mode 100644
index 00000000000..ee60e498b59
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/move_personal_snippet_files_spec.rb
@@ -0,0 +1,72 @@
+require 'spec_helper'
+
+describe Gitlab::BackgroundMigration::MovePersonalSnippetFiles do
+ let(:test_dir) { File.join(Rails.root, 'tmp', 'tests', 'move_snippet_files_test') }
+ let(:old_uploads_dir) { File.join('uploads', 'system', 'personal_snippet') }
+ let(:new_uploads_dir) { File.join('uploads', '-', 'system', 'personal_snippet') }
+ let(:snippet) do
+ snippet = create(:personal_snippet)
+ create_upload_for_snippet(snippet)
+ snippet.update_attributes!(description: markdown_linking_file(snippet))
+ snippet
+ end
+
+ let(:migration) { described_class.new }
+
+ before do
+ allow(migration).to receive(:base_directory) { test_dir }
+ end
+
+ describe '#perform' do
+ it 'moves the file on the disk' do
+ expected_path = File.join(test_dir, new_uploads_dir, snippet.id.to_s, "secret#{snippet.id}", 'upload.txt')
+
+ migration.perform(old_uploads_dir, new_uploads_dir)
+
+ expect(File.exist?(expected_path)).to be_truthy
+ end
+
+ it 'updates the markdown of the snippet' do
+ expected_path = File.join(new_uploads_dir, snippet.id.to_s, "secret#{snippet.id}", 'upload.txt')
+ expected_markdown = "[an upload](#{expected_path})"
+
+ migration.perform(old_uploads_dir, new_uploads_dir)
+
+ expect(snippet.reload.description).to eq(expected_markdown)
+ end
+
+ it 'updates the markdown of notes' do
+ expected_path = File.join(new_uploads_dir, snippet.id.to_s, "secret#{snippet.id}", 'upload.txt')
+ expected_markdown = "with [an upload](#{expected_path})"
+
+ note = create(:note_on_personal_snippet, noteable: snippet, note: "with #{markdown_linking_file(snippet)}")
+
+ migration.perform(old_uploads_dir, new_uploads_dir)
+
+ expect(note.reload.note).to eq(expected_markdown)
+ end
+ end
+
+ def create_upload_for_snippet(snippet)
+ snippet_path = path_for_file_in_snippet(snippet)
+ path = File.join(old_uploads_dir, snippet.id.to_s, snippet_path)
+ absolute_path = File.join(test_dir, path)
+
+ FileUtils.mkdir_p(File.dirname(absolute_path))
+ FileUtils.touch(absolute_path)
+
+ create(:upload, model: snippet, path: snippet_path, uploader: PersonalFileUploader)
+ end
+
+ def path_for_file_in_snippet(snippet)
+ secret = "secret#{snippet.id}"
+ filename = 'upload.txt'
+
+ File.join(secret, filename)
+ end
+
+ def markdown_linking_file(snippet)
+ path = File.join(old_uploads_dir, snippet.id.to_s, path_for_file_in_snippet(snippet))
+ "[an upload](#{path})"
+ end
+end
diff --git a/spec/lib/gitlab/bitbucket_import/importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importer_spec.rb
index d7d6a37f7cf..a66347ead76 100644
--- a/spec/lib/gitlab/bitbucket_import/importer_spec.rb
+++ b/spec/lib/gitlab/bitbucket_import/importer_spec.rb
@@ -54,7 +54,7 @@ describe Gitlab::BitbucketImport::Importer do
create(
:project,
import_source: project_identifier,
- import_data: ProjectImportData.new(credentials: data)
+ import_data_attributes: { credentials: data }
)
end
diff --git a/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb b/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb
index d7f7b26740d..ae5b31dc12d 100644
--- a/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb
+++ b/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb
@@ -15,7 +15,7 @@ describe Gitlab::BitbucketImport::ProjectCreator do
has_wiki?: false)
end
- let(:namespace){ create(:group, owner: user) }
+ let(:namespace) { create(:group, owner: user) }
let(:token) { "asdasd12345" }
let(:secret) { "sekrettt" }
let(:access_params) { { bitbucket_access_token: token, bitbucket_access_token_secret: secret } }
diff --git a/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb b/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb
index f43d89d7ccd..16704ff5e77 100644
--- a/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb
+++ b/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb
@@ -48,8 +48,9 @@ describe Gitlab::Cache::Ci::ProjectPipelineStatus, :clean_gitlab_redis_cache do
described_class.load_in_batch_for_projects([project_without_status])
end
- it 'only connects to redis_cache twice' do
- # Once to load, once to store in the cache
+ it 'only connects to redis twice' do
+ # Stub circuitbreaker so it doesn't count the redis connections in there
+ stub_circuit_breaker(project_without_status)
expect(Gitlab::Redis::Cache).to receive(:with).exactly(2).and_call_original
described_class.load_in_batch_for_projects([project_without_status])
@@ -301,4 +302,13 @@ describe Gitlab::Cache::Ci::ProjectPipelineStatus, :clean_gitlab_redis_cache do
end
end
end
+
+ def stub_circuit_breaker(project)
+ fake_circuitbreaker = double
+ allow(fake_circuitbreaker).to receive(:perform).and_yield
+ allow(project.repository.raw_repository)
+ .to receive(:circuit_breaker).and_return(fake_circuitbreaker)
+ allow(project.repository)
+ .to receive(:circuit_breaker).and_return(fake_circuitbreaker)
+ end
end
diff --git a/spec/lib/gitlab/checks/force_push_spec.rb b/spec/lib/gitlab/checks/force_push_spec.rb
index 6c4cfa1203e..f8c8b83a3ac 100644
--- a/spec/lib/gitlab/checks/force_push_spec.rb
+++ b/spec/lib/gitlab/checks/force_push_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe Gitlab::Checks::ForcePush do
let(:project) { create(:project, :repository) }
- context "exit code checking" do
+ context "exit code checking", skip_gitaly_mock: true do
it "does not raise a runtime error if the `popen` call to git returns a zero exit code" do
allow(Gitlab::Popen).to receive(:popen).and_return(['normal output', 0])
diff --git a/spec/lib/gitlab/contributions_calendar_spec.rb b/spec/lib/gitlab/contributions_calendar_spec.rb
index 9217d48087e..f1655854486 100644
--- a/spec/lib/gitlab/contributions_calendar_spec.rb
+++ b/spec/lib/gitlab/contributions_calendar_spec.rb
@@ -22,12 +22,14 @@ describe Gitlab::ContributionsCalendar do
end
end
- let(:today) { Time.now.to_date }
+ let(:today) { Time.now.utc.to_date }
+ let(:yesterday) { today - 1.day }
+ let(:tomorrow) { today + 1.day }
let(:last_week) { today - 7.days }
let(:last_year) { today - 1.year }
before do
- travel_to today
+ travel_to Time.now.utc.end_of_day
end
after do
@@ -38,7 +40,7 @@ describe Gitlab::ContributionsCalendar do
described_class.new(contributor, current_user)
end
- def create_event(project, day)
+ def create_event(project, day, hour = 0)
@targets ||= {}
@targets[project] ||= create(:issue, project: project, author: contributor)
@@ -47,7 +49,7 @@ describe Gitlab::ContributionsCalendar do
action: Event::CREATED,
target: @targets[project],
author: contributor,
- created_at: day
+ created_at: DateTime.new(day.year, day.month, day.day, hour)
)
end
@@ -68,6 +70,34 @@ describe Gitlab::ContributionsCalendar do
expect(calendar(user).activity_dates[today]).to eq(0)
expect(calendar(contributor).activity_dates[today]).to eq(2)
end
+
+ context "when events fall under different dates depending on the time zone" do
+ before do
+ create_event(public_project, today, 1)
+ create_event(public_project, today, 4)
+ create_event(public_project, today, 10)
+ create_event(public_project, today, 16)
+ create_event(public_project, today, 23)
+ end
+
+ it "renders correct event counts within the UTC timezone" do
+ Time.use_zone('UTC') do
+ expect(calendar.activity_dates).to eq(today => 5)
+ end
+ end
+
+ it "renders correct event counts within the Sydney timezone" do
+ Time.use_zone('Sydney') do
+ expect(calendar.activity_dates).to eq(today => 3, tomorrow => 2)
+ end
+ end
+
+ it "renders correct event counts within the US Central timezone" do
+ Time.use_zone('Central Time (US & Canada)') do
+ expect(calendar.activity_dates).to eq(yesterday => 2, today => 3)
+ end
+ end
+ end
end
describe '#events_by_date' do
diff --git a/spec/lib/gitlab/daemon_spec.rb b/spec/lib/gitlab/daemon_spec.rb
new file mode 100644
index 00000000000..2bdb0dfc736
--- /dev/null
+++ b/spec/lib/gitlab/daemon_spec.rb
@@ -0,0 +1,103 @@
+require 'spec_helper'
+
+describe Gitlab::Daemon do
+ subject { described_class.new }
+
+ before do
+ allow(subject).to receive(:start_working)
+ allow(subject).to receive(:stop_working)
+ end
+
+ describe '.instance' do
+ before do
+ allow(Kernel).to receive(:at_exit)
+ end
+
+ after do
+ described_class.instance_variable_set(:@instance, nil)
+ end
+
+ it 'provides instance of Daemon' do
+ expect(described_class.instance).to be_instance_of(described_class)
+ end
+
+ it 'subsequent invocations provide the same instance' do
+ expect(described_class.instance).to eq(described_class.instance)
+ end
+
+ it 'creates at_exit hook when instance is created' do
+ expect(described_class.instance).not_to be_nil
+
+ expect(Kernel).to have_received(:at_exit)
+ end
+ end
+
+ describe 'when Daemon is enabled' do
+ before do
+ allow(subject).to receive(:enabled?).and_return(true)
+ end
+
+ describe 'when Daemon is stopped' do
+ describe '#start' 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)
+ end
+ end
+
+ describe '#stop' do
+ it "doesn't shutdown stopped Daemon" do
+ expect { subject.stop }.not_to change { subject.thread? }
+
+ expect(subject).not_to have_received(:start_working)
+ end
+ end
+ end
+
+ describe 'when Daemon is running' do
+ before do
+ subject.start.join
+ end
+
+ describe '#start' 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
+ end
+ end
+
+ describe '#stop' do
+ it 'shutdowns Daemon' do
+ expect { subject.stop }.to change { subject.thread? }.from(true).to(false)
+
+ expect(subject).to have_received(:stop_working)
+ end
+ end
+ end
+ end
+
+ describe 'when Daemon is disabled' do
+ before do
+ allow(subject).to receive(:enabled?).and_return(false)
+ end
+
+ describe '#start' do
+ it "doesn't start working" do
+ expect(subject.start).to be_nil
+ expect { subject.start }.not_to change { subject.thread? }
+
+ expect(subject).not_to have_received(:start_working)
+ end
+ end
+
+ describe '#stop' do
+ it "doesn't stop working" do
+ expect { subject.stop }.not_to change { subject.thread? }
+
+ expect(subject).not_to have_received(:stop_working)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/data_builder/note_spec.rb b/spec/lib/gitlab/data_builder/note_spec.rb
index 6415e4083d6..aaa42566a4d 100644
--- a/spec/lib/gitlab/data_builder/note_spec.rb
+++ b/spec/lib/gitlab/data_builder/note_spec.rb
@@ -6,7 +6,7 @@ describe Gitlab::DataBuilder::Note do
let(:data) { described_class.build(note, user) }
let(:fixed_time) { Time.at(1425600000) } # Avoid time precision errors
- before(:each) do
+ before do
expect(data).to have_key(:object_attributes)
expect(data[:object_attributes]).to have_key(:url)
expect(data[:object_attributes][:url])
diff --git a/spec/lib/gitlab/database_spec.rb b/spec/lib/gitlab/database_spec.rb
index c5f9aecd867..5fa94999d25 100644
--- a/spec/lib/gitlab/database_spec.rb
+++ b/spec/lib/gitlab/database_spec.rb
@@ -51,6 +51,28 @@ describe Gitlab::Database do
end
end
+ describe '.join_lateral_supported?' do
+ it 'returns false when using MySQL' do
+ allow(described_class).to receive(:postgresql?).and_return(false)
+
+ expect(described_class.join_lateral_supported?).to eq(false)
+ end
+
+ it 'returns false when using PostgreSQL 9.2' do
+ allow(described_class).to receive(:postgresql?).and_return(true)
+ 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(:postgresql?).and_return(true)
+ allow(described_class).to receive(:version).and_return('9.3.0')
+
+ expect(described_class.join_lateral_supported?).to eq(true)
+ end
+ end
+
describe '.nulls_last_order' do
context 'when using PostgreSQL' do
before do
diff --git a/spec/lib/gitlab/email/handler/create_note_handler_spec.rb b/spec/lib/gitlab/email/handler/create_note_handler_spec.rb
index 0127b012c91..d0fa16ce4d1 100644
--- a/spec/lib/gitlab/email/handler/create_note_handler_spec.rb
+++ b/spec/lib/gitlab/email/handler/create_note_handler_spec.rb
@@ -36,15 +36,6 @@ describe Gitlab::Email::Handler::CreateNoteHandler do
end
end
- context "when the email was auto generated" do
- let!(:mail_key) { '636ca428858779856c226bb145ef4fad' }
- let!(:email_raw) { fixture_file("emails/auto_reply.eml") }
-
- it "raises an AutoGeneratedEmailError" do
- expect { receiver.execute }.to raise_error(Gitlab::Email::AutoGeneratedEmailError)
- end
- end
-
context "when the noteable could not be found" do
before do
noteable.destroy
diff --git a/spec/lib/gitlab/email/receiver_spec.rb b/spec/lib/gitlab/email/receiver_spec.rb
index 88565ea5311..59f43abf26d 100644
--- a/spec/lib/gitlab/email/receiver_spec.rb
+++ b/spec/lib/gitlab/email/receiver_spec.rb
@@ -28,14 +28,6 @@ describe Gitlab::Email::Receiver do
it "raises an UnknownIncomingEmail error" do
expect { receiver.execute }.to raise_error(Gitlab::Email::UnknownIncomingEmail)
end
-
- context "and the email contains no references header" do
- let(:email_raw) { fixture_file("emails/auto_reply.eml").gsub(mail_key, "!!!") }
-
- it "raises an UnknownIncomingEmail error" do
- expect { receiver.execute }.to raise_error(Gitlab::Email::UnknownIncomingEmail)
- end
- end
end
context "when the email is blank" do
@@ -45,4 +37,12 @@ describe Gitlab::Email::Receiver do
expect { receiver.execute }.to raise_error(Gitlab::Email::EmptyEmailError)
end
end
+
+ context "when the email was auto generated" do
+ let(:email_raw) { fixture_file("emails/auto_reply.eml") }
+
+ it "raises an AutoGeneratedEmailError" do
+ expect { receiver.execute }.to raise_error(Gitlab::Email::AutoGeneratedEmailError)
+ end
+ end
end
diff --git a/spec/lib/gitlab/encoding_helper_spec.rb b/spec/lib/gitlab/encoding_helper_spec.rb
index 1482ef7132d..8b14b227e65 100644
--- a/spec/lib/gitlab/encoding_helper_spec.rb
+++ b/spec/lib/gitlab/encoding_helper_spec.rb
@@ -30,6 +30,53 @@ describe Gitlab::EncodingHelper do
it 'leaves binary string as is' do
expect(ext_class.encode!(binary_string)).to eq(binary_string)
end
+
+ context 'with corrupted diff' do
+ let(:corrupted_diff) do
+ with_empty_bare_repository do |repo|
+ content = File.read(Rails.root.join(
+ 'spec/fixtures/encoding/Japanese.md').to_s)
+ commit_a = commit(repo, 'Japanese.md', content)
+ commit_b = commit(repo, 'Japanese.md',
+ content.sub('[TODO: Link]', '[ç¾åœ¨ä½œæ¥­ä¸­ã§ã™: Link]'))
+
+ repo.diff(commit_a, commit_b).each_line.map(&:content).join
+ end
+ end
+
+ let(:cleaned_diff) do
+ corrupted_diff.dup.force_encoding('UTF-8')
+ .encode!('UTF-8', invalid: :replace, replace: '')
+ end
+
+ let(:encoded_diff) do
+ described_class.encode!(corrupted_diff.dup)
+ end
+
+ it 'does not corrupt data but remove invalid characters' do
+ expect(encoded_diff).to eq(cleaned_diff)
+ end
+
+ def commit(repo, path, content)
+ oid = repo.write(content, :blob)
+ index = repo.index
+
+ index.read_tree(repo.head.target.tree) unless repo.empty?
+
+ index.add(path: path, oid: oid, mode: 0100644)
+ user = { name: 'Test', email: 'test@example.com' }
+
+ Rugged::Commit.create(
+ repo,
+ tree: index.write_tree(repo),
+ author: user,
+ committer: user,
+ message: "Update #{path}",
+ parents: repo.empty? ? [] : [repo.head.target].compact,
+ update_ref: 'HEAD'
+ )
+ end
+ end
end
describe '#encode_utf8' do
diff --git a/spec/lib/gitlab/git/blame_spec.rb b/spec/lib/gitlab/git/blame_spec.rb
index 66c016d14b3..800c245b130 100644
--- a/spec/lib/gitlab/git/blame_spec.rb
+++ b/spec/lib/gitlab/git/blame_spec.rb
@@ -7,63 +7,73 @@ describe Gitlab::Git::Blame, seed_helper: true do
Gitlab::Git::Blame.new(repository, SeedRepo::Commit::ID, "CONTRIBUTING.md")
end
- context "each count" do
- it do
- data = []
- blame.each do |commit, line|
- data << {
- commit: commit,
- line: line
- }
- end
-
- expect(data.size).to eq(95)
- expect(data.first[:commit]).to be_kind_of(Gitlab::Git::Commit)
- expect(data.first[:line]).to eq("# Contribute to GitLab")
- expect(data.first[:line]).to be_utf8
- end
- end
+ shared_examples 'blaming a file' do
+ context "each count" do
+ it do
+ data = []
+ blame.each do |commit, line|
+ data << {
+ commit: commit,
+ line: line
+ }
+ end
- context "ISO-8859 encoding" do
- let(:blame) do
- Gitlab::Git::Blame.new(repository, SeedRepo::EncodingCommit::ID, "encoding/iso8859.txt")
+ expect(data.size).to eq(95)
+ expect(data.first[:commit]).to be_kind_of(Gitlab::Git::Commit)
+ expect(data.first[:line]).to eq("# Contribute to GitLab")
+ expect(data.first[:line]).to be_utf8
+ end
end
- it 'converts to UTF-8' do
- data = []
- blame.each do |commit, line|
- data << {
- commit: commit,
- line: line
- }
+ context "ISO-8859 encoding" do
+ let(:blame) do
+ Gitlab::Git::Blame.new(repository, SeedRepo::EncodingCommit::ID, "encoding/iso8859.txt")
end
- expect(data.size).to eq(1)
- expect(data.first[:commit]).to be_kind_of(Gitlab::Git::Commit)
- expect(data.first[:line]).to eq("Ä ü")
- expect(data.first[:line]).to be_utf8
- end
- end
+ it 'converts to UTF-8' do
+ data = []
+ blame.each do |commit, line|
+ data << {
+ commit: commit,
+ line: line
+ }
+ end
- context "unknown encoding" do
- let(:blame) do
- Gitlab::Git::Blame.new(repository, SeedRepo::EncodingCommit::ID, "encoding/iso8859.txt")
+ expect(data.size).to eq(1)
+ expect(data.first[:commit]).to be_kind_of(Gitlab::Git::Commit)
+ expect(data.first[:line]).to eq("Ä ü")
+ expect(data.first[:line]).to be_utf8
+ end
end
- it 'converts to UTF-8' do
- expect(CharlockHolmes::EncodingDetector).to receive(:detect).and_return(nil)
- data = []
- blame.each do |commit, line|
- data << {
+ context "unknown encoding" do
+ let(:blame) do
+ Gitlab::Git::Blame.new(repository, SeedRepo::EncodingCommit::ID, "encoding/iso8859.txt")
+ end
+
+ it 'converts to UTF-8' do
+ expect(CharlockHolmes::EncodingDetector).to receive(:detect).and_return(nil)
+ data = []
+ blame.each do |commit, line|
+ data << {
commit: commit,
line: line
- }
- end
+ }
+ end
- expect(data.size).to eq(1)
- expect(data.first[:commit]).to be_kind_of(Gitlab::Git::Commit)
- expect(data.first[:line]).to eq(" ")
- expect(data.first[:line]).to be_utf8
+ expect(data.size).to eq(1)
+ expect(data.first[:commit]).to be_kind_of(Gitlab::Git::Commit)
+ expect(data.first[:line]).to eq(" ")
+ expect(data.first[:line]).to be_utf8
+ end
end
end
+
+ context 'when Gitaly blame feature is enabled' do
+ it_behaves_like 'blaming a file'
+ end
+
+ context 'when Gitaly blame feature is disabled', skip_gitaly_mock: true do
+ it_behaves_like 'blaming a file'
+ end
end
diff --git a/spec/lib/gitlab/git/blob_spec.rb b/spec/lib/gitlab/git/blob_spec.rb
index 18320bb23b9..dfab0c2fe85 100644
--- a/spec/lib/gitlab/git/blob_spec.rb
+++ b/spec/lib/gitlab/git/blob_spec.rb
@@ -152,6 +152,77 @@ describe Gitlab::Git::Blob, seed_helper: true do
end
end
+ describe '.batch' do
+ let(:blob_references) do
+ [
+ [SeedRepo::Commit::ID, "files/ruby/popen.rb"],
+ [SeedRepo::Commit::ID, 'six']
+ ]
+ end
+
+ subject { described_class.batch(repository, blob_references) }
+
+ it { expect(subject.size).to eq(blob_references.size) }
+
+ context 'first blob' do
+ let(:blob) { subject[0] }
+
+ it { expect(blob.id).to eq(SeedRepo::RubyBlob::ID) }
+ it { expect(blob.name).to eq(SeedRepo::RubyBlob::NAME) }
+ it { expect(blob.path).to eq("files/ruby/popen.rb") }
+ it { expect(blob.commit_id).to eq(SeedRepo::Commit::ID) }
+ it { expect(blob.data[0..10]).to eq(SeedRepo::RubyBlob::CONTENT[0..10]) }
+ it { expect(blob.size).to eq(669) }
+ it { expect(blob.mode).to eq("100644") }
+ end
+
+ context 'second blob' do
+ let(:blob) { subject[1] }
+
+ it { expect(blob.id).to eq('409f37c4f05865e4fb208c771485f211a22c4c2d') }
+ it { expect(blob.data).to eq('') }
+ it 'does not mark the blob as binary' do
+ expect(blob).not_to be_binary
+ end
+ end
+
+ context 'limiting' do
+ subject { described_class.batch(repository, blob_references, blob_size_limit: blob_size_limit) }
+
+ context 'default' do
+ let(:blob_size_limit) { nil }
+
+ it 'limits to MAX_DATA_DISPLAY_SIZE' do
+ stub_const('Gitlab::Git::Blob::MAX_DATA_DISPLAY_SIZE', 100)
+
+ expect(subject.first.data.size).to eq(100)
+ end
+ end
+
+ context 'positive' do
+ let(:blob_size_limit) { 10 }
+
+ it { expect(subject.first.data.size).to eq(10) }
+ end
+
+ context 'zero' do
+ let(:blob_size_limit) { 0 }
+
+ it { expect(subject.first.data).to eq('') }
+ end
+
+ context 'negative' do
+ let(:blob_size_limit) { -1 }
+
+ it 'ignores MAX_DATA_DISPLAY_SIZE' do
+ stub_const('Gitlab::Git::Blob::MAX_DATA_DISPLAY_SIZE', 100)
+
+ expect(subject.first.data.size).to eq(669)
+ end
+ end
+ end
+ end
+
describe 'encoding' do
context 'file with russian text' do
let(:blob) { Gitlab::Git::Blob.find(repository, SeedRepo::Commit::ID, "encoding/russian.rb") }
diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb
index 730fdb112d9..c531d4b055f 100644
--- a/spec/lib/gitlab/git/commit_spec.rb
+++ b/spec/lib/gitlab/git/commit_spec.rb
@@ -2,7 +2,7 @@ require "spec_helper"
describe Gitlab::Git::Commit, seed_helper: true do
let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH) }
- let(:commit) { Gitlab::Git::Commit.find(repository, SeedRepo::Commit::ID) }
+ let(:commit) { described_class.find(repository, SeedRepo::Commit::ID) }
let(:rugged_commit) do
repository.rugged.lookup(SeedRepo::Commit::ID)
end
@@ -24,7 +24,7 @@ describe Gitlab::Git::Commit, seed_helper: true do
}
@parents = [repo.head.target]
- @gitlab_parents = @parents.map { |c| Gitlab::Git::Commit.decorate(c) }
+ @gitlab_parents = @parents.map { |c| described_class.decorate(repository, c) }
@tree = @parents.first.tree
sha = Rugged::Commit.create(
@@ -38,7 +38,7 @@ describe Gitlab::Git::Commit, seed_helper: true do
)
@raw_commit = repo.lookup(sha)
- @commit = Gitlab::Git::Commit.new(@raw_commit)
+ @commit = described_class.new(repository, @raw_commit)
end
it { expect(@commit.short_id).to eq(@raw_commit.oid[0..10]) }
@@ -66,6 +66,7 @@ describe Gitlab::Git::Commit, seed_helper: true do
describe "Commit info from gitaly commit" do
let(:id) { 'f00' }
+ let(:parent_ids) { %w(b45 b46) }
let(:subject) { "My commit".force_encoding('ASCII-8BIT') }
let(:body) { subject + "My body".force_encoding('ASCII-8BIT') }
let(:committer) do
@@ -88,10 +89,11 @@ describe Gitlab::Git::Commit, seed_helper: true do
subject: subject,
body: body,
author: author,
- committer: committer
+ committer: committer,
+ parent_ids: parent_ids
)
end
- let(:commit) { described_class.new(Gitlab::GitalyClient::Commit.new(repository, gitaly_commit)) }
+ let(:commit) { described_class.new(repository, gitaly_commit) }
it { expect(commit.short_id).to eq(id[0..10]) }
it { expect(commit.id).to eq(id) }
@@ -102,6 +104,7 @@ describe Gitlab::Git::Commit, seed_helper: true do
it { expect(commit.author_name).to eq(author.name) }
it { expect(commit.committer_name).to eq(committer.name) }
it { expect(commit.committer_email).to eq(committer.email) }
+ it { expect(commit.parent_ids).to eq(parent_ids) }
context 'no body' do
let(:body) { "".force_encoding('ASCII-8BIT') }
@@ -113,45 +116,45 @@ describe Gitlab::Git::Commit, seed_helper: true do
context 'Class methods' do
describe '.find' do
it "should return first head commit if without params" do
- expect(Gitlab::Git::Commit.last(repository).id).to eq(
- repository.raw.head.target.oid
+ expect(described_class.last(repository).id).to eq(
+ repository.rugged.head.target.oid
)
end
it "should return valid commit" do
- expect(Gitlab::Git::Commit.find(repository, SeedRepo::Commit::ID)).to be_valid_commit
+ expect(described_class.find(repository, SeedRepo::Commit::ID)).to be_valid_commit
end
it "should return valid commit for tag" do
- expect(Gitlab::Git::Commit.find(repository, 'v1.0.0').id).to eq('6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9')
+ expect(described_class.find(repository, 'v1.0.0').id).to eq('6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9')
end
it "should return nil for non-commit ids" do
blob = Gitlab::Git::Blob.find(repository, SeedRepo::Commit::ID, "files/ruby/popen.rb")
- expect(Gitlab::Git::Commit.find(repository, blob.id)).to be_nil
+ expect(described_class.find(repository, blob.id)).to be_nil
end
it "should return nil for parent of non-commit object" do
blob = Gitlab::Git::Blob.find(repository, SeedRepo::Commit::ID, "files/ruby/popen.rb")
- expect(Gitlab::Git::Commit.find(repository, "#{blob.id}^")).to be_nil
+ expect(described_class.find(repository, "#{blob.id}^")).to be_nil
end
it "should return nil for nonexisting ids" do
- expect(Gitlab::Git::Commit.find(repository, "+123_4532530XYZ")).to be_nil
+ expect(described_class.find(repository, "+123_4532530XYZ")).to be_nil
end
context 'with broken repo' do
let(:repository) { Gitlab::Git::Repository.new('default', TEST_BROKEN_REPO_PATH) }
it 'returns nil' do
- expect(Gitlab::Git::Commit.find(repository, SeedRepo::Commit::ID)).to be_nil
+ expect(described_class.find(repository, SeedRepo::Commit::ID)).to be_nil
end
end
end
describe '.last_for_path' do
context 'no path' do
- subject { Gitlab::Git::Commit.last_for_path(repository, 'master') }
+ subject { described_class.last_for_path(repository, 'master') }
describe '#id' do
subject { super().id }
@@ -160,7 +163,7 @@ describe Gitlab::Git::Commit, seed_helper: true do
end
context 'path' do
- subject { Gitlab::Git::Commit.last_for_path(repository, 'master', 'files/ruby') }
+ subject { described_class.last_for_path(repository, 'master', 'files/ruby') }
describe '#id' do
subject { super().id }
@@ -169,7 +172,7 @@ describe Gitlab::Git::Commit, seed_helper: true do
end
context 'ref + path' do
- subject { Gitlab::Git::Commit.last_for_path(repository, SeedRepo::Commit::ID, 'encoding') }
+ subject { described_class.last_for_path(repository, SeedRepo::Commit::ID, 'encoding') }
describe '#id' do
subject { super().id }
@@ -181,7 +184,7 @@ describe Gitlab::Git::Commit, seed_helper: true do
describe '.where' do
context 'path is empty string' do
subject do
- commits = Gitlab::Git::Commit.where(
+ commits = described_class.where(
repo: repository,
ref: 'master',
path: '',
@@ -199,7 +202,7 @@ describe Gitlab::Git::Commit, seed_helper: true do
context 'path is nil' do
subject do
- commits = Gitlab::Git::Commit.where(
+ commits = described_class.where(
repo: repository,
ref: 'master',
path: nil,
@@ -217,7 +220,7 @@ describe Gitlab::Git::Commit, seed_helper: true do
context 'ref is branch name' do
subject do
- commits = Gitlab::Git::Commit.where(
+ commits = described_class.where(
repo: repository,
ref: 'master',
path: 'files',
@@ -237,7 +240,7 @@ describe Gitlab::Git::Commit, seed_helper: true do
context 'ref is commit id' do
subject do
- commits = Gitlab::Git::Commit.where(
+ commits = described_class.where(
repo: repository,
ref: "874797c3a73b60d2187ed6e2fcabd289ff75171e",
path: 'files',
@@ -257,7 +260,7 @@ describe Gitlab::Git::Commit, seed_helper: true do
context 'ref is tag' do
subject do
- commits = Gitlab::Git::Commit.where(
+ commits = described_class.where(
repo: repository,
ref: 'v1.0.0',
path: 'files',
@@ -278,7 +281,7 @@ describe Gitlab::Git::Commit, seed_helper: true do
describe '.between' do
subject do
- commits = Gitlab::Git::Commit.between(repository, SeedRepo::Commit::PARENT_ID, SeedRepo::Commit::ID)
+ commits = described_class.between(repository, SeedRepo::Commit::PARENT_ID, SeedRepo::Commit::ID)
commits.map { |c| c.id }
end
@@ -294,12 +297,12 @@ describe Gitlab::Git::Commit, seed_helper: true do
it 'should return a return a collection of commits' do
commits = described_class.find_all(repository)
- expect(commits).to all( be_a_kind_of(Gitlab::Git::Commit) )
+ expect(commits).to all( be_a_kind_of(described_class) )
end
context 'max_count' do
subject do
- commits = Gitlab::Git::Commit.find_all(
+ commits = described_class.find_all(
repository,
max_count: 50
)
@@ -322,7 +325,7 @@ describe Gitlab::Git::Commit, seed_helper: true do
context 'ref + max_count + skip' do
subject do
- commits = Gitlab::Git::Commit.find_all(
+ commits = described_class.find_all(
repository,
ref: 'master',
max_count: 50,
@@ -374,7 +377,7 @@ describe Gitlab::Git::Commit, seed_helper: true do
end
describe '#init_from_rugged' do
- let(:gitlab_commit) { Gitlab::Git::Commit.new(rugged_commit) }
+ let(:gitlab_commit) { described_class.new(repository, rugged_commit) }
subject { gitlab_commit }
describe '#id' do
@@ -384,7 +387,7 @@ describe Gitlab::Git::Commit, seed_helper: true do
end
describe '#init_from_hash' do
- let(:commit) { Gitlab::Git::Commit.new(sample_commit_hash) }
+ let(:commit) { described_class.new(repository, sample_commit_hash) }
subject { commit }
describe '#id' do
@@ -451,7 +454,7 @@ describe Gitlab::Git::Commit, seed_helper: true do
end
describe '#ref_names' do
- let(:commit) { Gitlab::Git::Commit.find(repository, 'master') }
+ let(:commit) { described_class.find(repository, 'master') }
subject { commit.ref_names(repository) }
it 'has 1 element' do
diff --git a/spec/lib/gitlab/git/diff_collection_spec.rb b/spec/lib/gitlab/git/diff_collection_spec.rb
index 0cfb210e390..3494f0cc98d 100644
--- a/spec/lib/gitlab/git/diff_collection_spec.rb
+++ b/spec/lib/gitlab/git/diff_collection_spec.rb
@@ -384,7 +384,7 @@ describe Gitlab::Git::DiffCollection, seed_helper: true do
context 'when go over safe limits on files' do
let(:iterator) { [fake_diff(1, 1)] * 4 }
- before(:each) do
+ before do
stub_const('Gitlab::Git::DiffCollection::DEFAULT_LIMITS', { max_files: 2, max_lines: max_lines })
end
@@ -409,7 +409,7 @@ describe Gitlab::Git::DiffCollection, seed_helper: true do
]
end
- before(:each) do
+ before do
stub_const('Gitlab::Git::DiffCollection::DEFAULT_LIMITS', { max_files: max_files, max_lines: 80 })
end
@@ -434,7 +434,7 @@ describe Gitlab::Git::DiffCollection, seed_helper: true do
]
end
- before(:each) do
+ before do
stub_const('Gitlab::Git::DiffCollection::DEFAULT_LIMITS', { max_files: max_files, max_lines: 80 })
end
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index 8e4a1f31ced..8d24d6f55b8 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -22,7 +22,6 @@ describe Gitlab::Git::Repository, seed_helper: true do
describe "Respond to" do
subject { repository }
- it { is_expected.to respond_to(:raw) }
it { is_expected.to respond_to(:rugged) }
it { is_expected.to respond_to(:root_ref) }
it { is_expected.to respond_to(:tags) }
@@ -55,6 +54,20 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
describe "#rugged" do
+ describe 'when storage is broken', broken_storage: true do
+ it 'raises a storage exception when storage is not available' do
+ broken_repo = described_class.new('broken', 'a/path.git')
+
+ expect { broken_repo.rugged }.to raise_error(Gitlab::Git::Storage::Inaccessible)
+ end
+ end
+
+ it 'raises a no repository exception when there is no repo' do
+ broken_repo = described_class.new('default', 'a/path.git')
+
+ expect { broken_repo.rugged }.to raise_error(Gitlab::Git::Repository::NoRepository)
+ end
+
context 'with no Git env stored' do
before do
expect(Gitlab::Git::Env).to receive(:all).and_return({})
@@ -361,20 +374,20 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
describe '#commit_count' do
- shared_examples 'counting commits' do
+ shared_examples 'simple commit counting' do
it { expect(repository.commit_count("master")).to eq(25) }
it { expect(repository.commit_count("feature")).to eq(9) }
end
context 'when Gitaly commit_count feature is enabled' do
- it_behaves_like 'counting commits'
+ it_behaves_like 'simple commit counting'
it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::CommitService, :commit_count do
subject { repository.commit_count('master') }
end
end
context 'when Gitaly commit_count feature is disabled', skip_gitaly_mock: true do
- it_behaves_like 'counting commits'
+ it_behaves_like 'simple commit counting'
end
end
@@ -409,11 +422,11 @@ describe Gitlab::Git::Repository, seed_helper: true do
it "should fail if we create an existing branch" do
@repo.create_branch('duplicated_branch', 'master')
- expect{@repo.create_branch('duplicated_branch', 'master')}.to raise_error("Branch duplicated_branch already exists")
+ expect {@repo.create_branch('duplicated_branch', 'master')}.to raise_error("Branch duplicated_branch already exists")
end
it "should fail if we create a branch from a non existing ref" do
- expect{@repo.create_branch('branch_based_in_wrong_ref', 'master_2_the_revenge')}.to raise_error("Invalid reference master_2_the_revenge")
+ expect {@repo.create_branch('branch_based_in_wrong_ref', 'master_2_the_revenge')}.to raise_error("Invalid reference master_2_the_revenge")
end
after(:all) do
@@ -492,17 +505,22 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
describe "#log" do
- commit_with_old_name = nil
- commit_with_new_name = nil
- rename_commit = nil
+ let(:commit_with_old_name) do
+ Gitlab::Git::Commit.decorate(repository, @commit_with_old_name_id)
+ end
+ let(:commit_with_new_name) do
+ Gitlab::Git::Commit.decorate(repository, @commit_with_new_name_id)
+ end
+ let(:rename_commit) do
+ Gitlab::Git::Commit.decorate(repository, @rename_commit_id)
+ end
before(:context) do
# Add new commits so that there's a renamed file in the commit history
repo = Gitlab::Git::Repository.new('default', TEST_REPO_PATH).rugged
-
- commit_with_old_name = Gitlab::Git::Commit.decorate(new_commit_edit_old_file(repo))
- rename_commit = Gitlab::Git::Commit.decorate(new_commit_move_file(repo))
- commit_with_new_name = Gitlab::Git::Commit.decorate(new_commit_edit_new_file(repo))
+ @commit_with_old_name_id = new_commit_edit_old_file(repo)
+ @rename_commit_id = new_commit_move_file(repo)
+ @commit_with_new_name_id = new_commit_edit_new_file(repo)
end
after(:context) do
@@ -741,7 +759,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
let(:options) { { ref: 'master', path: ['PROCESS.md', 'README.md'] } }
def commit_files(commit)
- commit.diff_from_parent.deltas.flat_map do |delta|
+ commit.rugged_diff_from_parent.deltas.flat_map do |delta|
[delta.old_file[:path], delta.new_file[:path]].uniq.compact
end
end
@@ -757,13 +775,13 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
end
- describe "#commits_between" do
+ describe "#rugged_commits_between" do
context 'two SHAs' do
let(:first_sha) { 'b0e52af38d7ea43cf41d8a6f2471351ac036d6c9' }
let(:second_sha) { '0e50ec4d3c7ce42ab74dda1d422cb2cbffe1e326' }
it 'returns the number of commits between' do
- expect(repository.commits_between(first_sha, second_sha).count).to eq(3)
+ expect(repository.rugged_commits_between(first_sha, second_sha).count).to eq(3)
end
end
@@ -772,11 +790,11 @@ describe Gitlab::Git::Repository, seed_helper: true do
let(:branch) { 'master' }
it 'returns the number of commits between a sha and a branch' do
- expect(repository.commits_between(sha, branch).count).to eq(5)
+ expect(repository.rugged_commits_between(sha, branch).count).to eq(5)
end
it 'returns the number of commits between a branch and a sha' do
- expect(repository.commits_between(branch, sha).count).to eq(0) # sha is before branch
+ expect(repository.rugged_commits_between(branch, sha).count).to eq(0) # sha is before branch
end
end
@@ -785,7 +803,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
let(:second_branch) { 'master' }
it 'returns the number of commits between' do
- expect(repository.commits_between(first_branch, second_branch).count).to eq(17)
+ expect(repository.rugged_commits_between(first_branch, second_branch).count).to eq(17)
end
end
end
@@ -797,28 +815,38 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
describe '#count_commits' do
- context 'with after timestamp' do
- it 'returns the number of commits after timestamp' do
- options = { ref: 'master', limit: nil, after: Time.iso8601('2013-03-03T20:15:01+00:00') }
+ shared_examples 'extended commit counting' do
+ context 'with after timestamp' do
+ it 'returns the number of commits after timestamp' do
+ options = { ref: 'master', limit: nil, after: Time.iso8601('2013-03-03T20:15:01+00:00') }
- expect(repository.count_commits(options)).to eq(25)
+ expect(repository.count_commits(options)).to eq(25)
+ end
end
- end
- context 'with before timestamp' do
- it 'returns the number of commits after timestamp' do
- options = { ref: 'feature', limit: nil, before: Time.iso8601('2015-03-03T20:15:01+00:00') }
+ context 'with before timestamp' do
+ it 'returns the number of commits before timestamp' do
+ options = { ref: 'feature', limit: nil, before: Time.iso8601('2015-03-03T20:15:01+00:00') }
+
+ expect(repository.count_commits(options)).to eq(9)
+ end
+ end
- expect(repository.count_commits(options)).to eq(9)
+ context 'with path' do
+ it 'returns the number of commits with path ' do
+ options = { ref: 'master', limit: nil, path: "encoding" }
+
+ expect(repository.count_commits(options)).to eq(2)
+ end
end
end
- context 'with path' do
- it 'returns the number of commits with path ' do
- options = { ref: 'master', limit: nil, path: "encoding" }
+ context 'when Gitaly count_commits feature is enabled' do
+ it_behaves_like 'extended commit counting'
+ end
- expect(repository.count_commits(options)).to eq(2)
- end
+ context 'when Gitaly count_commits feature is disabled', skip_gitaly_mock: true do
+ it_behaves_like 'extended commit counting'
end
end
@@ -974,7 +1002,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
expect(master_file_paths).to include("files/html/500.html")
end
- it "dose not read submodule directory and empty directory of master branch" do
+ it "does not read submodule directory and empty directory of master branch" do
expect(master_file_paths).not_to include("six")
end
@@ -995,7 +1023,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
context "with no .gitattrbutes" do
- before(:each) do
+ before do
repository.copy_gitattributes("master")
end
@@ -1003,13 +1031,13 @@ describe Gitlab::Git::Repository, seed_helper: true do
expect(File.exist?(attributes_path)).to be_falsey
end
- after(:each) do
+ after do
FileUtils.rm_rf(attributes_path)
end
end
context "with .gitattrbutes" do
- before(:each) do
+ before do
repository.copy_gitattributes("gitattributes")
end
@@ -1022,13 +1050,13 @@ describe Gitlab::Git::Repository, seed_helper: true do
expect(contents).to eq("*.md binary\n")
end
- after(:each) do
+ after do
FileUtils.rm_rf(attributes_path)
end
end
context "with updated .gitattrbutes" do
- before(:each) do
+ before do
repository.copy_gitattributes("gitattributes")
repository.copy_gitattributes("gitattributes-updated")
end
@@ -1042,13 +1070,13 @@ describe Gitlab::Git::Repository, seed_helper: true do
expect(contents).to eq("*.txt binary\n")
end
- after(:each) do
+ after do
FileUtils.rm_rf(attributes_path)
end
end
context "with no .gitattrbutes in HEAD but with previous info/attributes" do
- before(:each) do
+ before do
repository.copy_gitattributes("gitattributes")
repository.copy_gitattributes("master")
end
@@ -1057,7 +1085,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
expect(File.exist?(attributes_path)).to be_falsey
end
- after(:each) do
+ after do
FileUtils.rm_rf(attributes_path)
end
end
diff --git a/spec/lib/gitlab/git/storage/circuit_breaker_spec.rb b/spec/lib/gitlab/git/storage/circuit_breaker_spec.rb
new file mode 100644
index 00000000000..9d1763b96ad
--- /dev/null
+++ b/spec/lib/gitlab/git/storage/circuit_breaker_spec.rb
@@ -0,0 +1,290 @@
+require 'spec_helper'
+
+describe Gitlab::Git::Storage::CircuitBreaker, clean_gitlab_redis_shared_state: true, broken_storage: true do
+ let(:circuit_breaker) { described_class.new('default') }
+ let(:hostname) { Gitlab::Environment.hostname }
+ let(:cache_key) { "storage_accessible:default:#{hostname}" }
+
+ def value_from_redis(name)
+ Gitlab::Git::Storage.redis.with do |redis|
+ redis.hmget(cache_key, name)
+ end.first
+ end
+
+ def set_in_redis(name, value)
+ Gitlab::Git::Storage.redis.with do |redis|
+ redis.hmset(cache_key, name, value)
+ end.first
+ end
+
+ describe '.reset_all!' do
+ it 'clears all entries form redis' do
+ set_in_redis(:failure_count, 10)
+
+ described_class.reset_all!
+
+ key_exists = Gitlab::Git::Storage.redis.with { |redis| redis.exists(cache_key) }
+
+ expect(key_exists).to be_falsey
+ end
+ end
+
+ describe '.for_storage' do
+ it 'only builds a single circuitbreaker per storage' do
+ expect(described_class).to receive(:new).once.and_call_original
+
+ breaker = described_class.for_storage('default')
+
+ expect(breaker).to be_a(described_class)
+ expect(described_class.for_storage('default')).to eq(breaker)
+ end
+ end
+
+ describe '#initialize' do
+ it 'assigns the settings' do
+ expect(circuit_breaker.hostname).to eq(hostname)
+ expect(circuit_breaker.storage).to eq('default')
+ expect(circuit_breaker.storage_path).to eq(TestEnv.repos_path)
+ expect(circuit_breaker.failure_count_threshold).to eq(10)
+ expect(circuit_breaker.failure_wait_time).to eq(30)
+ expect(circuit_breaker.failure_reset_time).to eq(1800)
+ expect(circuit_breaker.storage_timeout).to eq(5)
+ end
+ end
+
+ describe '#perform' do
+ it 'raises an exception with retry time when the circuit is open' do
+ allow(circuit_breaker).to receive(:circuit_broken?).and_return(true)
+
+ expect { |b| circuit_breaker.perform(&b) }
+ .to raise_error(Gitlab::Git::Storage::CircuitOpen)
+ end
+
+ it 'yields the block' do
+ expect { |b| circuit_breaker.perform(&b) }
+ .to yield_control
+ end
+
+ it 'checks if the storage is available' do
+ expect(circuit_breaker).to receive(:check_storage_accessible!)
+
+ circuit_breaker.perform { 'hello world' }
+ end
+
+ it 'returns the value of the block' do
+ result = circuit_breaker.perform { 'return value' }
+
+ expect(result).to eq('return value')
+ end
+
+ it 'raises possible errors' do
+ expect { circuit_breaker.perform { raise Rugged::OSError.new('Broken') } }
+ .to raise_error(Rugged::OSError)
+ end
+
+ context 'with the feature disabled' do
+ it 'returns the block without checking accessibility' do
+ stub_feature_flags(git_storage_circuit_breaker: false)
+
+ expect(circuit_breaker).not_to receive(:circuit_broken?)
+
+ result = circuit_breaker.perform { 'hello' }
+
+ expect(result).to eq('hello')
+ end
+ end
+ end
+
+ describe '#circuit_broken?' do
+ it 'is closed when there is no last failure' do
+ set_in_redis(:last_failure, nil)
+ set_in_redis(:failure_count, 0)
+
+ expect(circuit_breaker.circuit_broken?).to be_falsey
+ end
+
+ it 'is open when there was a recent failure' do
+ Timecop.freeze do
+ set_in_redis(:last_failure, 1.second.ago.to_f)
+ set_in_redis(:failure_count, 1)
+
+ expect(circuit_breaker.circuit_broken?).to be_truthy
+ end
+ end
+
+ it 'is open when there are to many failures' do
+ set_in_redis(:last_failure, 1.day.ago.to_f)
+ set_in_redis(:failure_count, 200)
+
+ expect(circuit_breaker.circuit_broken?).to be_truthy
+ end
+ end
+
+ describe "storage_available?" do
+ context 'when the storage is available' do
+ it 'tracks that the storage was accessible an raises the error' do
+ expect(circuit_breaker).to receive(:track_storage_accessible)
+
+ circuit_breaker.storage_available?
+ end
+
+ it 'only performs the check once' do
+ expect(Gitlab::Git::Storage::ForkedStorageCheck)
+ .to receive(:storage_available?).once.and_call_original
+
+ 2.times { circuit_breaker.storage_available? }
+ end
+ end
+
+ context 'when storage is not available' do
+ let(:circuit_breaker) { described_class.new('broken') }
+
+ it 'tracks that the storage was inaccessible' do
+ expect(circuit_breaker).to receive(:track_storage_inaccessible)
+
+ circuit_breaker.storage_available?
+ end
+ end
+ end
+
+ describe '#check_storage_accessible!' do
+ it 'raises an exception with retry time when the circuit is open' do
+ allow(circuit_breaker).to receive(:circuit_broken?).and_return(true)
+
+ expect { circuit_breaker.check_storage_accessible! }
+ .to raise_error do |exception|
+ expect(exception).to be_kind_of(Gitlab::Git::Storage::CircuitOpen)
+ expect(exception.retry_after).to eq(30)
+ end
+ end
+
+ context 'when the storage is not available' do
+ let(:circuit_breaker) { described_class.new('broken') }
+
+ it 'raises an error' do
+ expect(circuit_breaker).to receive(:track_storage_inaccessible)
+
+ expect { circuit_breaker.check_storage_accessible! }
+ .to raise_error do |exception|
+ expect(exception).to be_kind_of(Gitlab::Git::Storage::Inaccessible)
+ expect(exception.retry_after).to eq(30)
+ end
+ end
+ end
+ end
+
+ describe '#track_storage_inaccessible' do
+ around do |example|
+ Timecop.freeze { example.run }
+ end
+
+ it 'records the failure time in redis' do
+ circuit_breaker.track_storage_inaccessible
+
+ failure_time = value_from_redis(:last_failure)
+
+ expect(Time.at(failure_time.to_i)).to be_within(1.second).of(Time.now)
+ end
+
+ it 'sets the failure time on the breaker without reloading' do
+ circuit_breaker.track_storage_inaccessible
+
+ expect(circuit_breaker).not_to receive(:get_failure_info)
+ expect(circuit_breaker.last_failure).to eq(Time.now)
+ end
+
+ it 'increments the failure count in redis' do
+ set_in_redis(:failure_count, 10)
+
+ circuit_breaker.track_storage_inaccessible
+
+ expect(value_from_redis(:failure_count).to_i).to be(11)
+ end
+
+ it 'increments the failure count on the breaker without reloading' do
+ set_in_redis(:failure_count, 10)
+
+ circuit_breaker.track_storage_inaccessible
+
+ expect(circuit_breaker).not_to receive(:get_failure_info)
+ expect(circuit_breaker.failure_count).to eq(11)
+ end
+ end
+
+ describe '#track_storage_accessible' do
+ it 'sets the failure count to zero in redis' do
+ set_in_redis(:failure_count, 10)
+
+ circuit_breaker.track_storage_accessible
+
+ expect(value_from_redis(:failure_count).to_i).to be(0)
+ end
+
+ it 'sets the failure count to zero on the breaker without reloading' do
+ set_in_redis(:failure_count, 10)
+
+ circuit_breaker.track_storage_accessible
+
+ expect(circuit_breaker).not_to receive(:get_failure_info)
+ expect(circuit_breaker.failure_count).to eq(0)
+ end
+
+ it 'removes the last failure time from redis' do
+ set_in_redis(:last_failure, Time.now.to_i)
+
+ circuit_breaker.track_storage_accessible
+
+ expect(circuit_breaker).not_to receive(:get_failure_info)
+ expect(circuit_breaker.last_failure).to be_nil
+ end
+
+ it 'removes the last failure time from the breaker without reloading' do
+ set_in_redis(:last_failure, Time.now.to_i)
+
+ circuit_breaker.track_storage_accessible
+
+ expect(value_from_redis(:last_failure)).to be_empty
+ end
+
+ it 'wont connect to redis when there are no failures' do
+ expect(Gitlab::Git::Storage.redis).to receive(:with).once
+ .and_call_original
+ expect(circuit_breaker).to receive(:track_storage_accessible)
+ .and_call_original
+
+ circuit_breaker.track_storage_accessible
+ end
+ end
+
+ describe '#no_failures?' do
+ it 'is false when a failure was tracked' do
+ set_in_redis(:last_failure, Time.now.to_i)
+ set_in_redis(:failure_count, 1)
+
+ expect(circuit_breaker.no_failures?).to be_falsey
+ end
+ end
+
+ describe '#last_failure' do
+ it 'returns the last failure time' do
+ time = Time.parse("2017-05-26 17:52:30")
+ set_in_redis(:last_failure, time.to_i)
+
+ expect(circuit_breaker.last_failure).to eq(time)
+ end
+ end
+
+ describe '#failure_count' do
+ it 'returns the failure count' do
+ set_in_redis(:failure_count, 7)
+
+ expect(circuit_breaker.failure_count).to eq(7)
+ end
+ end
+
+ describe '#cache_key' do
+ it 'includes storage and host' do
+ expect(circuit_breaker.cache_key).to eq(cache_key)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/git/storage/forked_storage_check_spec.rb b/spec/lib/gitlab/git/storage/forked_storage_check_spec.rb
new file mode 100644
index 00000000000..12366151f44
--- /dev/null
+++ b/spec/lib/gitlab/git/storage/forked_storage_check_spec.rb
@@ -0,0 +1,58 @@
+require 'spec_helper'
+
+describe Gitlab::Git::Storage::ForkedStorageCheck, skip_database_cleaner: true do
+ let(:existing_path) do
+ existing_path = TestEnv.repos_path
+ FileUtils.mkdir_p(existing_path)
+ existing_path
+ end
+
+ describe '.storage_accessible?' do
+ it 'detects when a storage is not available' do
+ expect(described_class.storage_available?('/non/existant/path')).to be_falsey
+ end
+
+ it 'detects when a storage is available' do
+ expect(described_class.storage_available?(existing_path)).to be_truthy
+ end
+
+ it 'returns false when the check takes to long' do
+ # We're forking a process here that takes too long
+ # It will be killed it's parent process will be killed by it's parent
+ # and waited for inside `Gitlab::Git::Storage::ForkedStorageCheck.timeout_check`
+ allow(described_class).to receive(:check_filesystem_in_process) do
+ Process.spawn("sleep 10")
+ end
+ result = true
+
+ runtime = Benchmark.realtime do
+ result = described_class.storage_available?(existing_path, 0.5)
+ end
+
+ expect(result).to be_falsey
+ expect(runtime).to be < 1.0
+ end
+
+ describe 'when using paths with spaces' do
+ let(:test_dir) { Rails.root.join('tmp', 'tests', 'storage_check') }
+ let(:path_with_spaces) { File.join(test_dir, 'path with spaces') }
+
+ around do |example|
+ FileUtils.mkdir_p(path_with_spaces)
+ example.run
+ FileUtils.rm_r(test_dir)
+ end
+
+ it 'works for paths with spaces' do
+ expect(described_class.storage_available?(path_with_spaces)).to be_truthy
+ end
+
+ it 'works for a realpath with spaces' do
+ symlink_location = File.join(test_dir, 'a symlink')
+ FileUtils.ln_s(path_with_spaces, symlink_location)
+
+ expect(described_class.storage_available?(symlink_location)).to be_truthy
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/git/storage/health_spec.rb b/spec/lib/gitlab/git/storage/health_spec.rb
new file mode 100644
index 00000000000..2d3af387971
--- /dev/null
+++ b/spec/lib/gitlab/git/storage/health_spec.rb
@@ -0,0 +1,87 @@
+require 'spec_helper'
+
+describe Gitlab::Git::Storage::Health, clean_gitlab_redis_shared_state: true, broken_storage: true do
+ let(:host1_key) { 'storage_accessible:broken:web01' }
+ let(:host2_key) { 'storage_accessible:default:kiq01' }
+
+ def set_in_redis(cache_key, value)
+ Gitlab::Git::Storage.redis.with do |redis|
+ redis.hmset(cache_key, :failure_count, value)
+ end.first
+ end
+
+ describe '.for_failing_storages' do
+ it 'only includes health status for failures' do
+ set_in_redis(host1_key, 10)
+ set_in_redis(host2_key, 0)
+
+ expect(described_class.for_failing_storages.map(&:storage_name))
+ .to contain_exactly('broken')
+ end
+ end
+
+ describe '.load_for_keys' do
+ let(:subject) do
+ results = Gitlab::Git::Storage.redis.with do |redis|
+ fake_future = double
+ allow(fake_future).to receive(:value).and_return([host1_key])
+ described_class.load_for_keys({ 'broken' => fake_future }, redis)
+ end
+
+ # Make sure the `Redis#future is loaded
+ results.inject({}) do |result, (name, info)|
+ info.each { |i| i[:failure_count] = i[:failure_count].value.to_i }
+
+ result[name] = info
+
+ result
+ end
+ end
+
+ it 'loads when there is no info in redis' do
+ expect(subject).to eq('broken' => [{ name: host1_key, failure_count: 0 }])
+ end
+
+ it 'reads the correct values for a storage from redis' do
+ set_in_redis(host1_key, 5)
+ set_in_redis(host2_key, 7)
+
+ expect(subject).to eq('broken' => [{ name: host1_key, failure_count: 5 }])
+ end
+ end
+
+ describe '.for_all_storages' do
+ it 'loads health status for all configured storages' do
+ healths = described_class.for_all_storages
+
+ expect(healths.map(&:storage_name)).to contain_exactly('default', 'broken')
+ end
+ end
+
+ describe '#failing_info' do
+ it 'only contains storages that have failures' do
+ health = described_class.new('broken', [{ name: host1_key, failure_count: 0 },
+ { name: host2_key, failure_count: 3 }])
+
+ expect(health.failing_info).to contain_exactly({ name: host2_key, failure_count: 3 })
+ end
+ end
+
+ describe '#total_failures' do
+ it 'sums up all the failures' do
+ health = described_class.new('broken', [{ name: host1_key, failure_count: 2 },
+ { name: host2_key, failure_count: 3 }])
+
+ expect(health.total_failures).to eq(5)
+ end
+ end
+
+ describe '#failing_on_hosts' do
+ it 'collects only the failing hostnames' do
+ health = described_class.new('broken', [{ name: host1_key, failure_count: 2 },
+ { name: host2_key, failure_count: 0 }])
+
+ expect(health.failing_on_hosts).to contain_exactly('web01')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
index d71e0f84c65..7fe698fcb18 100644
--- a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
@@ -30,7 +30,7 @@ describe Gitlab::GitalyClient::CommitService do
context 'when a commit does not have a parent' do
it 'sends an RPC request with empty tree ref as left commit' do
- initial_commit = project.commit('1a0b36b3cdad1d2ee32457c102a8c0b7056fa863')
+ initial_commit = project.commit('1a0b36b3cdad1d2ee32457c102a8c0b7056fa863').raw
request = Gitaly::CommitDiffRequest.new(
repository: repository_message,
left_commit_id: '4b825dc642cb6eb9a060e54bf8d69288fbee4904',
@@ -46,18 +46,10 @@ describe Gitlab::GitalyClient::CommitService do
end
end
- it 'returns a Gitlab::Git::DiffCollection' do
+ it 'returns a Gitlab::GitalyClient::DiffStitcher' do
ret = client.diff_from_parent(commit)
- expect(ret).to be_kind_of(Gitlab::Git::DiffCollection)
- end
-
- it 'passes options to Gitlab::Git::DiffCollection' do
- options = { max_files: 31, max_lines: 13, from_gitaly: true }
-
- expect(Gitlab::Git::DiffCollection).to receive(:new).with(kind_of(Enumerable), options)
-
- client.diff_from_parent(commit, options)
+ expect(ret).to be_kind_of(Gitlab::GitalyClient::DiffStitcher)
end
end
@@ -120,4 +112,18 @@ describe Gitlab::GitalyClient::CommitService do
client.tree_entries(repository, revision, path)
end
end
+
+ describe '#find_commit' do
+ let(:revision) { '4b825dc642cb6eb9a060e54bf8d69288fbee4904' }
+ it 'sends an RPC request' do
+ request = Gitaly::FindCommitRequest.new(
+ repository: repository_message, revision: revision
+ )
+
+ expect_any_instance_of(Gitaly::CommitService::Stub).to receive(:find_commit)
+ .with(request, kind_of(Hash)).and_return(double(commit: nil))
+
+ described_class.new(repository).find_commit(revision)
+ end
+ end
end
diff --git a/spec/lib/gitlab/gitaly_client/repository_service_spec.rb b/spec/lib/gitlab/gitaly_client/repository_service_spec.rb
deleted file mode 100644
index 5c9c4ed1d7c..00000000000
--- a/spec/lib/gitlab/gitaly_client/repository_service_spec.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::GitalyClient::RepositoryService do
- set(:project) { create(:project) }
- let(:storage_name) { project.repository_storage }
- let(:relative_path) { project.path_with_namespace + '.git' }
- let(:client) { described_class.new(project.repository) }
-
- describe '#exists?' do
- it 'sends an exists message' do
- expect_any_instance_of(Gitaly::RepositoryService::Stub)
- .to receive(:exists)
- .with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
- .and_call_original
-
- client.exists?
- end
- end
-end
diff --git a/spec/lib/gitlab/gitlab_import/project_creator_spec.rb b/spec/lib/gitlab/gitlab_import/project_creator_spec.rb
index da48d8f0670..82548c7fd31 100644
--- a/spec/lib/gitlab/gitlab_import/project_creator_spec.rb
+++ b/spec/lib/gitlab/gitlab_import/project_creator_spec.rb
@@ -12,7 +12,7 @@ describe Gitlab::GitlabImport::ProjectCreator do
owner: { name: "john" }
}.with_indifferent_access
end
- let(:namespace){ create(:group, owner: user) }
+ let(:namespace) { create(:group, owner: user) }
let(:token) { "asdffg" }
let(:access_params) { { gitlab_access_token: token } }
diff --git a/spec/lib/gitlab/google_code_import/project_creator_spec.rb b/spec/lib/gitlab/google_code_import/project_creator_spec.rb
index aad53938d52..8d5b60d50de 100644
--- a/spec/lib/gitlab/google_code_import/project_creator_spec.rb
+++ b/spec/lib/gitlab/google_code_import/project_creator_spec.rb
@@ -9,7 +9,7 @@ describe Gitlab::GoogleCodeImport::ProjectCreator do
"repositoryUrls" => ["https://vim.googlecode.com/git/"]
)
end
- let(:namespace){ create(:group, owner: user) }
+ let(:namespace) { create(:group, owner: user) }
before do
namespace.add_owner(user)
diff --git a/spec/lib/gitlab/gpg_spec.rb b/spec/lib/gitlab/gpg_spec.rb
index 8041518117d..30ad033b204 100644
--- a/spec/lib/gitlab/gpg_spec.rb
+++ b/spec/lib/gitlab/gpg_spec.rb
@@ -43,6 +43,58 @@ describe Gitlab::Gpg do
).to eq []
end
end
+
+ describe '.current_home_dir' do
+ let(:default_home_dir) { GPGME::Engine.dirinfo('homedir') }
+
+ it 'returns the default value when no explicit home dir has been set' do
+ expect(described_class.current_home_dir).to eq default_home_dir
+ end
+
+ it 'returns the explicitely set home dir' do
+ GPGME::Engine.home_dir = '/tmp/gpg'
+
+ expect(described_class.current_home_dir).to eq '/tmp/gpg'
+
+ GPGME::Engine.home_dir = GPGME::Engine.dirinfo('homedir')
+ end
+
+ it 'returns the default value when explicitely setting the home dir to nil' do
+ GPGME::Engine.home_dir = nil
+
+ expect(described_class.current_home_dir).to eq default_home_dir
+ end
+ end
+
+ describe '.using_tmp_keychain' do
+ it "the second thread does not change the first thread's directory" do
+ thread1 = Thread.new do
+ described_class.using_tmp_keychain do
+ dir = described_class.current_home_dir
+ sleep 0.1
+ expect(described_class.current_home_dir).to eq dir
+ end
+ end
+
+ thread2 = Thread.new do
+ described_class.using_tmp_keychain do
+ sleep 0.2
+ end
+ end
+
+ thread1.join
+ thread2.join
+ end
+
+ it 'allows recursive execution in the same thread' do
+ expect do
+ described_class.using_tmp_keychain do
+ described_class.using_tmp_keychain do
+ end
+ end
+ end.not_to raise_error(ThreadError)
+ end
+ end
end
describe Gitlab::Gpg::CurrentKeyChain do
diff --git a/spec/lib/gitlab/health_checks/fs_shards_check_spec.rb b/spec/lib/gitlab/health_checks/fs_shards_check_spec.rb
index 8abc4320c59..f5c9680bf59 100644
--- a/spec/lib/gitlab/health_checks/fs_shards_check_spec.rb
+++ b/spec/lib/gitlab/health_checks/fs_shards_check_spec.rb
@@ -44,6 +44,15 @@ describe Gitlab::HealthChecks::FsShardsCheck do
describe '#readiness' do
subject { described_class.readiness }
+ context 'storage has a tripped circuitbreaker', broken_storage: true do
+ let(:repository_storages) { ['broken'] }
+ let(:storages_paths) do
+ Gitlab.config.repositories.storages
+ end
+
+ it { is_expected.to include(result_class.new(false, 'circuitbreaker tripped', shard: 'broken')) }
+ end
+
context 'storage points to not existing folder' do
let(:storages_paths) do
{
@@ -51,6 +60,10 @@ describe Gitlab::HealthChecks::FsShardsCheck do
}.with_indifferent_access
end
+ before do
+ allow(described_class).to receive(:storage_circuitbreaker_test) { true }
+ end
+
it { is_expected.to include(result_class.new(false, 'cannot stat storage', shard: :default)) }
end
@@ -93,12 +106,6 @@ describe Gitlab::HealthChecks::FsShardsCheck do
}.with_indifferent_access
end
- # Unsolved intermittent failure in CI https://gitlab.com/gitlab-org/gitlab-ce/issues/31128
- around(:each) do |example| # rubocop:disable RSpec/AroundBlock
- times_to_try = ENV['CI'] ? 4 : 1
- example.run_with_retry retry: times_to_try
- end
-
it 'provides metrics' do
metrics = described_class.metrics
@@ -109,6 +116,7 @@ describe Gitlab::HealthChecks::FsShardsCheck do
expect(metrics).to include(an_object_having_attributes(name: :filesystem_access_latency_seconds, value: be >= 0))
expect(metrics).to include(an_object_having_attributes(name: :filesystem_read_latency_seconds, value: be >= 0))
expect(metrics).to include(an_object_having_attributes(name: :filesystem_write_latency_seconds, value: be >= 0))
+ expect(metrics).to include(an_object_having_attributes(name: :filesystem_circuitbreaker_latency_seconds, value: be >= 0))
end
end
@@ -127,6 +135,7 @@ describe Gitlab::HealthChecks::FsShardsCheck do
expect(metrics).to include(an_object_having_attributes(name: :filesystem_access_latency_seconds, value: be >= 0))
expect(metrics).to include(an_object_having_attributes(name: :filesystem_read_latency_seconds, value: be >= 0))
expect(metrics).to include(an_object_having_attributes(name: :filesystem_write_latency_seconds, value: be >= 0))
+ expect(metrics).to include(an_object_having_attributes(name: :filesystem_circuitbreaker_latency_seconds, value: be >= 0))
end
it 'cleans up files used for metrics' do
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index 6a41afe0c25..8da02b0cf00 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -22,6 +22,7 @@ events:
- author
- project
- target
+- push_event_payload
notes:
- award_emoji
- project
@@ -272,3 +273,5 @@ timelogs:
- issue
- merge_request
- user
+push_event_payload:
+- event
diff --git a/spec/lib/gitlab/import_export/attribute_cleaner_spec.rb b/spec/lib/gitlab/import_export/attribute_cleaner_spec.rb
index 574748756bd..cd5a1b2982b 100644
--- a/spec/lib/gitlab/import_export/attribute_cleaner_spec.rb
+++ b/spec/lib/gitlab/import_export/attribute_cleaner_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe Gitlab::ImportExport::AttributeCleaner do
- let(:relation_class){ double('relation_class').as_null_object }
+ let(:relation_class) { double('relation_class').as_null_object }
let(:unsafe_hash) do
{
'id' => 101,
diff --git a/spec/lib/gitlab/import_export/file_importer_spec.rb b/spec/lib/gitlab/import_export/file_importer_spec.rb
index 690c7625c52..162b776e107 100644
--- a/spec/lib/gitlab/import_export/file_importer_spec.rb
+++ b/spec/lib/gitlab/import_export/file_importer_spec.rb
@@ -5,6 +5,7 @@ describe Gitlab::ImportExport::FileImporter do
let(:export_path) { "#{Dir.tmpdir}/file_importer_spec" }
let(:valid_file) { "#{shared.export_path}/valid.json" }
let(:symlink_file) { "#{shared.export_path}/invalid.json" }
+ let(:hidden_symlink_file) { "#{shared.export_path}/.hidden" }
let(:subfolder_symlink_file) { "#{shared.export_path}/subfolder/invalid.json" }
before do
@@ -25,6 +26,10 @@ describe Gitlab::ImportExport::FileImporter do
expect(File.exist?(symlink_file)).to be false
end
+ it 'removes hidden symlinks in root folder' do
+ expect(File.exist?(hidden_symlink_file)).to be false
+ end
+
it 'removes symlinks in subfolders' do
expect(File.exist?(subfolder_symlink_file)).to be false
end
diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json
index 469a014e4d2..4e631e13410 100644
--- a/spec/lib/gitlab/import_export/project.json
+++ b/spec/lib/gitlab/import_export/project.json
@@ -2534,7 +2534,6 @@
"iid": 9,
"description": null,
"position": 0,
- "locked_at": null,
"updated_by_id": null,
"merge_error": null,
"merge_params": {
@@ -2983,7 +2982,6 @@
"iid": 8,
"description": null,
"position": 0,
- "locked_at": null,
"updated_by_id": null,
"merge_error": null,
"merge_params": {
@@ -3267,7 +3265,6 @@
"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,
- "locked_at": null,
"updated_by_id": null,
"merge_error": null,
"merge_params": {
@@ -3551,7 +3548,6 @@
"iid": 6,
"description": "Dicta magnam non voluptates nam dignissimos nostrum deserunt. Dolorum et suscipit iure quae doloremque. Necessitatibus saepe aut labore sed.",
"position": 0,
- "locked_at": null,
"updated_by_id": null,
"merge_error": null,
"merge_params": {
@@ -4241,7 +4237,6 @@
"iid": 5,
"description": "Est eaque quasi qui qui. Similique voluptatem impedit iusto ratione reprehenderit. Itaque est illum ut nulla aut.",
"position": 0,
- "locked_at": null,
"updated_by_id": null,
"merge_error": null,
"merge_params": {
@@ -4789,7 +4784,6 @@
"iid": 4,
"description": "Nam magnam odit velit rerum. Sapiente dolore sunt saepe debitis. Culpa maiores ut ad dolores dolorem et.",
"position": 0,
- "locked_at": null,
"updated_by_id": null,
"merge_error": null,
"merge_params": {
@@ -5288,7 +5282,6 @@
"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,
- "locked_at": null,
"updated_by_id": null,
"merge_error": null,
"merge_params": {
@@ -5548,7 +5541,6 @@
"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,
- "locked_at": null,
"updated_by_id": null,
"merge_error": null,
"merge_params": {
@@ -6238,7 +6230,6 @@
"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,
- "locked_at": null,
"updated_by_id": null,
"merge_error": null,
"merge_params": {
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index 11f4c16ff96..ae3b0173160 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -36,6 +36,14 @@ Event:
- updated_at
- action
- author_id
+PushEventPayload:
+- commit_count
+- action
+- ref_type
+- commit_from
+- commit_to
+- ref
+- commit_title
Note:
- id
- note
@@ -145,7 +153,6 @@ MergeRequest:
- iid
- description
- position
-- locked_at
- updated_by_id
- merge_error
- merge_params
diff --git a/spec/lib/gitlab/import_sources_spec.rb b/spec/lib/gitlab/import_sources_spec.rb
index b3b5e5e7e33..c5725f47453 100644
--- a/spec/lib/gitlab/import_sources_spec.rb
+++ b/spec/lib/gitlab/import_sources_spec.rb
@@ -56,7 +56,7 @@ describe Gitlab::ImportSources do
describe '.importer' do
import_sources = {
- 'github' => Gitlab::GithubImport::Importer,
+ 'github' => Github::Import,
'bitbucket' => Gitlab::BitbucketImport::Importer,
'gitlab' => Gitlab::GitlabImport::Importer,
'google_code' => Gitlab::GoogleCodeImport::Importer,
diff --git a/spec/lib/gitlab/key_fingerprint_spec.rb b/spec/lib/gitlab/key_fingerprint_spec.rb
index d7bebaca675..f5fd5a96bc9 100644
--- a/spec/lib/gitlab/key_fingerprint_spec.rb
+++ b/spec/lib/gitlab/key_fingerprint_spec.rb
@@ -1,12 +1,82 @@
-require "spec_helper"
+require 'spec_helper'
-describe Gitlab::KeyFingerprint do
- let(:key) { "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=" }
- let(:fingerprint) { "3f:a2:ee:de:b5:de:53:c3:aa:2f:9c:45:24:4c:47:7b" }
+describe Gitlab::KeyFingerprint, lib: true do
+ KEYS = {
+ rsa:
+ 'example.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC5z65PwQ1GE6foJgwk' \
+ '9rmQi/glaXbUeVa5uvQpnZ3Z5+forcI7aTngh3aZ/H2UDP2L70TGy7kKNyp0J3a8/OdG' \
+ 'Z08y5yi3JlbjFARO1NyoFEjw2H1SJxeJ43L6zmvTlu+hlK1jSAlidl7enS0ufTlzEEj4' \
+ 'iJcuTPKdVzKRgZuTRVm9woWNVKqIrdRC0rJiTinERnfSAp/vNYERMuaoN4oJt8p/NEek' \
+ 'rmFoDsQOsyDW5RAnCnjWUU+jFBKDpfkJQ1U2n6BjJewC9dl6ODK639l3yN4WOLZEk4tN' \
+ 'UysfbGeF3rmMeflaD6O1Jplpv3YhwVGFNKa7fMq6k3Z0tszTJPYh',
+ ecdsa:
+ 'example.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAI' \
+ 'bmlzdHAyNTYAAABBBKTJy43NZzJSfNxpv/e2E6Zy3qoHoTQbmOsU5FEfpWfWa1MdTeXQ' \
+ 'YvKOi+qz/1AaNx6BK421jGu74JCDJtiZWT8=',
+ ed25519:
+ '@revoked example.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAfuCHKVTjq' \
+ 'uxvt6CM6tdG4SLp1Btn/nOeHHE5UOzRdf',
+ dss:
+ 'example.com ssh-dss AAAAB3NzaC1kc3MAAACBAP1/U4EddRIpUt9KnC7s5Of2EbdS' \
+ 'PO9EAMMeP4C2USZpRV1AIlH7WT2NWPq/xfW6MPbLm1Vs14E7gB00b/JmYLdrmVClpJ+f' \
+ '6AR7ECLCT7up1/63xhv4O1fnxqimFQ8E+4P208UewwI1VBNaFpEy9nXzrith1yrv8iID' \
+ 'GZ3RSAHHAAAAFQCXYFCPFSMLzLKSuYKi64QL8Fgc9QAAAIEA9+GghdabPd7LvKtcNrhX' \
+ 'uXmUr7v6OuqC+VdMCz0HgmdRWVeOutRZT+ZxBxCBgLRJFnEj6EwoFhO3zwkyjMim4TwW' \
+ 'eotUfI0o4KOuHiuzpnWRbqN/C/ohNWLx+2J6ASQ7zKTxvqhRkImog9/hWuWfBpKLZl6A' \
+ 'e1UlZAFMO/7PSSoAAACBAJcQ4JODqhuGbXIEpqxetm7PWbdbCcr3y/GzIZ066pRovpL6' \
+ 'qm3qCVIym4cyChxWwb8qlyCIi+YRUUWm1z/wiBYT2Vf3S4FXBnyymCkKEaV/EY7+jd4X' \
+ '1bXI58OD2u+bLCB/sInM4fGB8CZUIWT9nJH0Ve9jJUge2ms348/QOJ1+'
+ }.freeze
- describe "#fingerprint" do
- it "generates the key's fingerprint" do
- expect(described_class.new(key).fingerprint).to eq(fingerprint)
+ MD5_FINGERPRINTS = {
+ rsa: '06:b2:8a:92:df:0e:11:2c:ca:7b:8f:a4:ba:6e:4b:fd',
+ ecdsa: '45:ff:5b:98:9a:b6:8a:41:13:c1:30:8b:09:5e:7b:4e',
+ ed25519: '2e:65:6a:c8:cf:bf:b2:8b:9a:bd:6d:9f:11:5c:12:16',
+ dss: '57:98:86:02:5f:9c:f4:9b:ad:5a:1e:51:92:0e:fd:2b'
+ }.freeze
+
+ BIT_COUNTS = {
+ rsa: 2048,
+ ecdsa: 256,
+ ed25519: 256,
+ dss: 1024
+ }.freeze
+
+ describe '#type' do
+ KEYS.each do |type, key|
+ it "calculates the type of #{type} keys" do
+ calculated_type = described_class.new(key).type
+
+ expect(calculated_type).to eq(type.to_s.upcase)
+ end
+ end
+ end
+
+ describe '#fingerprint' do
+ KEYS.each do |type, key|
+ it "calculates the MD5 fingerprint for #{type} keys" do
+ fp = described_class.new(key).fingerprint
+
+ expect(fp).to eq(MD5_FINGERPRINTS[type])
+ end
+ end
+ end
+
+ describe '#bits' do
+ KEYS.each do |type, key|
+ it "calculates the number of bits in #{type} keys" do
+ bits = described_class.new(key).bits
+
+ expect(bits).to eq(BIT_COUNTS[type])
+ end
+ end
+ end
+
+ describe '#key' do
+ it 'carries the unmodified key data' do
+ key = described_class.new(KEYS[:rsa]).key
+
+ expect(key).to eq(KEYS[:rsa])
end
end
end
diff --git a/spec/lib/gitlab/ldap/config_spec.rb b/spec/lib/gitlab/ldap/config_spec.rb
index 292ec064a67..ca2213cd112 100644
--- a/spec/lib/gitlab/ldap/config_spec.rb
+++ b/spec/lib/gitlab/ldap/config_spec.rb
@@ -7,7 +7,7 @@ describe Gitlab::LDAP::Config do
describe '#initialize' do
it 'requires a provider' do
- expect{ described_class.new }.to raise_error ArgumentError
+ expect { described_class.new }.to raise_error ArgumentError
end
it 'works' do
@@ -15,7 +15,7 @@ describe Gitlab::LDAP::Config do
end
it 'raises an error if a unknown provider is used' do
- expect{ described_class.new 'unknown' }.to raise_error(RuntimeError)
+ expect { described_class.new 'unknown' }.to raise_error(RuntimeError)
end
end
diff --git a/spec/lib/gitlab/ldap/user_spec.rb b/spec/lib/gitlab/ldap/user_spec.rb
index 175ceec44d7..5100a5a609e 100644
--- a/spec/lib/gitlab/ldap/user_spec.rb
+++ b/spec/lib/gitlab/ldap/user_spec.rb
@@ -61,12 +61,12 @@ describe Gitlab::LDAP::User do
it "finds the user if already existing" do
create(:omniauth_user, extern_uid: 'my-uid', provider: 'ldapmain')
- expect{ ldap_user.save }.not_to change{ User.count }
+ expect { ldap_user.save }.not_to change { User.count }
end
it "connects to existing non-ldap user if the email matches" do
existing_user = create(:omniauth_user, email: 'john@example.com', provider: "twitter")
- expect{ ldap_user.save }.not_to change{ User.count }
+ expect { ldap_user.save }.not_to change { User.count }
existing_user.reload
expect(existing_user.ldap_identity.extern_uid).to eql 'my-uid'
@@ -75,7 +75,7 @@ describe Gitlab::LDAP::User do
it 'connects to existing ldap user if the extern_uid changes' do
existing_user = create(:omniauth_user, email: 'john@example.com', extern_uid: 'old-uid', provider: 'ldapmain')
- expect{ ldap_user.save }.not_to change{ User.count }
+ expect { ldap_user.save }.not_to change { User.count }
existing_user.reload
expect(existing_user.ldap_identity.extern_uid).to eql 'my-uid'
@@ -85,7 +85,7 @@ describe Gitlab::LDAP::User do
it 'connects to existing ldap user if the extern_uid changes and email address has upper case characters' do
existing_user = create(:omniauth_user, email: 'john@example.com', extern_uid: 'old-uid', provider: 'ldapmain')
- expect{ ldap_user_upper_case.save }.not_to change{ User.count }
+ expect { ldap_user_upper_case.save }.not_to change { User.count }
existing_user.reload
expect(existing_user.ldap_identity.extern_uid).to eql 'my-uid'
@@ -106,7 +106,7 @@ describe Gitlab::LDAP::User do
end
it "creates a new user if not found" do
- expect{ ldap_user.save }.to change{ User.count }.by(1)
+ expect { ldap_user.save }.to change { User.count }.by(1)
end
context 'when signup is disabled' do
diff --git a/spec/lib/gitlab/metrics/influx_sampler_spec.rb b/spec/lib/gitlab/metrics/influx_sampler_spec.rb
index 0bc68d64276..999a9536d82 100644
--- a/spec/lib/gitlab/metrics/influx_sampler_spec.rb
+++ b/spec/lib/gitlab/metrics/influx_sampler_spec.rb
@@ -11,7 +11,7 @@ describe Gitlab::Metrics::InfluxSampler do
it 'runs once and gathers a sample at a given interval' do
expect(sampler).to receive(:sleep).with(a_kind_of(Numeric)).twice
expect(sampler).to receive(:sample).once
- expect(sampler).to receive(:running).and_return(false, true, false)
+ expect(sampler).to receive(:running).and_return(true, false)
sampler.start.join
end
diff --git a/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb b/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb
index 461b1e4182a..ebe66948a91 100644
--- a/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb
+++ b/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb
@@ -4,10 +4,6 @@ describe Gitlab::Metrics::RequestsRackMiddleware do
let(:app) { double('app') }
subject { described_class.new(app) }
- around do |example|
- Timecop.freeze { example.run }
- end
-
describe '#call' do
let(:status) { 100 }
let(:env) { { 'REQUEST_METHOD' => 'GET' } }
@@ -28,16 +24,14 @@ describe Gitlab::Metrics::RequestsRackMiddleware do
subject.call(env)
end
- it 'measures execution time' do
- execution_time = 10
- allow(app).to receive(:call) do |*args|
- Timecop.freeze(execution_time.seconds)
- [200, nil, nil]
- end
+ RSpec::Matchers.define :a_positive_execution_time do
+ match { |actual| actual > 0 }
+ end
- expect(described_class).to receive_message_chain(:http_request_duration_seconds, :observe).with({ status: 200, method: 'get' }, execution_time)
+ it 'measures execution time' do
+ expect(described_class).to receive_message_chain(:http_request_duration_seconds, :observe).with({ status: 200, method: 'get' }, a_positive_execution_time)
- subject.call(env)
+ Timecop.scale(3600) { subject.call(env) }
end
end
diff --git a/spec/lib/gitlab/metrics/sidekiq_metrics_exporter_spec.rb b/spec/lib/gitlab/metrics/sidekiq_metrics_exporter_spec.rb
new file mode 100644
index 00000000000..6721e02fb85
--- /dev/null
+++ b/spec/lib/gitlab/metrics/sidekiq_metrics_exporter_spec.rb
@@ -0,0 +1,101 @@
+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
+ )
+ 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/o_auth/user_spec.rb b/spec/lib/gitlab/o_auth/user_spec.rb
index 6c84c4a0c1e..15edb820908 100644
--- a/spec/lib/gitlab/o_auth/user_spec.rb
+++ b/spec/lib/gitlab/o_auth/user_spec.rb
@@ -147,7 +147,7 @@ describe Gitlab::OAuth::User do
end
it 'throws an error' do
- expect{ oauth_user.save }.to raise_error StandardError
+ expect { oauth_user.save }.to raise_error StandardError
end
end
@@ -157,7 +157,7 @@ describe Gitlab::OAuth::User do
end
it 'throws an error' do
- expect{ oauth_user.save }.to raise_error StandardError
+ expect { oauth_user.save }.to raise_error StandardError
end
end
end
diff --git a/spec/lib/gitlab/project_template_spec.rb b/spec/lib/gitlab/project_template_spec.rb
new file mode 100644
index 00000000000..12e75cdd5d0
--- /dev/null
+++ b/spec/lib/gitlab/project_template_spec.rb
@@ -0,0 +1,63 @@
+require 'spec_helper'
+
+describe Gitlab::ProjectTemplate do
+ describe '.all' do
+ it 'returns a all templates' do
+ expected = [
+ described_class.new('rails', 'Ruby on Rails')
+ ]
+
+ expect(described_class.all).to be_an(Array)
+ expect(described_class.all).to eq(expected)
+ end
+ end
+
+ describe '.find' do
+ subject { described_class.find(query) }
+
+ context 'when there is a match' do
+ let(:query) { :rails }
+
+ it { is_expected.to be_a(described_class) }
+ end
+
+ context 'when there is no match' do
+ let(:query) { 'no-match' }
+
+ it { is_expected.to be(nil) }
+ end
+ end
+
+ describe 'instance methods' do
+ subject { described_class.new('phoenix', 'Phoenix Framework') }
+
+ it { is_expected.to respond_to(:logo, :file, :archive_path) }
+ end
+
+ describe 'validate all templates' do
+ set(:admin) { create(:admin) }
+
+ described_class.all.each do |template|
+ it "#{template.name} has a valid archive" do
+ archive = template.archive_path
+
+ expect(File.exist?(archive)).to be(true)
+ end
+
+ context 'with valid parameters' do
+ it 'can be imported' do
+ params = {
+ template_name: template.name,
+ namespace_id: admin.namespace.id,
+ path: template.name
+ }
+
+ project = Projects::CreateFromTemplateService.new(admin, params).execute
+
+ expect(project).to be_valid
+ expect(project).to be_persisted
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/request_context_spec.rb b/spec/lib/gitlab/request_context_spec.rb
index e272bdb9284..8a28ad0e597 100644
--- a/spec/lib/gitlab/request_context_spec.rb
+++ b/spec/lib/gitlab/request_context_spec.rb
@@ -7,7 +7,7 @@ describe Gitlab::RequestContext do
let(:env) { Hash.new }
context 'when RequestStore::Middleware is used' do
- around(:each) do |example|
+ around do |example|
RequestStore::Middleware.new(-> (env) { example.run }).call({})
end
diff --git a/spec/lib/gitlab/saml/user_spec.rb b/spec/lib/gitlab/saml/user_spec.rb
index 2827a18515e..19710029224 100644
--- a/spec/lib/gitlab/saml/user_spec.rb
+++ b/spec/lib/gitlab/saml/user_spec.rb
@@ -109,7 +109,7 @@ describe Gitlab::Saml::User do
end
it 'does not throw an error' do
- expect{ saml_user.save }.not_to raise_error
+ expect { saml_user.save }.not_to raise_error
end
end
@@ -119,7 +119,7 @@ describe Gitlab::Saml::User do
end
it 'throws an error' do
- expect{ saml_user.save }.to raise_error StandardError
+ expect { saml_user.save }.to raise_error StandardError
end
end
end
diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb
index b90d8dede0f..2345874cf10 100644
--- a/spec/lib/gitlab/shell_spec.rb
+++ b/spec/lib/gitlab/shell_spec.rb
@@ -174,20 +174,94 @@ describe Gitlab::Shell do
end
describe '#fetch_remote' do
+ def fetch_remote(ssh_auth = nil)
+ gitlab_shell.fetch_remote('current/storage', 'project/path', 'new/storage', ssh_auth: ssh_auth)
+ end
+
+ def expect_popen(vars = {})
+ popen_args = [
+ projects_path,
+ 'fetch-remote',
+ 'current/storage',
+ 'project/path.git',
+ 'new/storage',
+ Gitlab.config.gitlab_shell.git_timeout.to_s
+ ]
+
+ expect(Gitlab::Popen).to receive(:popen).with(popen_args, nil, popen_vars.merge(vars))
+ end
+
+ def build_ssh_auth(opts = {})
+ defaults = {
+ ssh_import?: true,
+ ssh_key_auth?: false,
+ ssh_known_hosts: nil,
+ ssh_private_key: nil
+ }
+
+ double(:ssh_auth, defaults.merge(opts))
+ end
+
it 'returns true when the command succeeds' do
- expect(Gitlab::Popen).to receive(:popen)
- .with([projects_path, 'fetch-remote', 'current/storage', 'project/path.git', 'new/storage', '800'],
- nil, popen_vars).and_return([nil, 0])
+ expect_popen.and_return([nil, 0])
- expect(gitlab_shell.fetch_remote('current/storage', 'project/path', 'new/storage')).to be true
+ expect(fetch_remote).to be_truthy
end
it 'raises an exception when the command fails' do
- expect(Gitlab::Popen).to receive(:popen)
- .with([projects_path, 'fetch-remote', 'current/storage', 'project/path.git', 'new/storage', '800'],
- nil, popen_vars).and_return(["error", 1])
+ expect_popen.and_return(["error", 1])
+
+ expect { fetch_remote }.to raise_error(Gitlab::Shell::Error, "error")
+ end
+
+ context 'SSH auth' do
+ it 'passes the SSH key if specified' do
+ expect_popen('GITLAB_SHELL_SSH_KEY' => 'foo').and_return([nil, 0])
+
+ ssh_auth = build_ssh_auth(ssh_key_auth?: true, ssh_private_key: 'foo')
+
+ expect(fetch_remote(ssh_auth)).to be_truthy
+ end
+
+ it 'does not pass an empty SSH key' do
+ expect_popen.and_return([nil, 0])
+
+ ssh_auth = build_ssh_auth(ssh_key_auth: true, ssh_private_key: '')
+
+ expect(fetch_remote(ssh_auth)).to be_truthy
+ end
+
+ it 'does not pass the key unless SSH key auth is to be used' do
+ expect_popen.and_return([nil, 0])
+
+ ssh_auth = build_ssh_auth(ssh_key_auth: false, ssh_private_key: 'foo')
+
+ expect(fetch_remote(ssh_auth)).to be_truthy
+ end
+
+ it 'passes the known_hosts data if specified' do
+ expect_popen('GITLAB_SHELL_KNOWN_HOSTS' => 'foo').and_return([nil, 0])
+
+ ssh_auth = build_ssh_auth(ssh_known_hosts: 'foo')
+
+ expect(fetch_remote(ssh_auth)).to be_truthy
+ end
+
+ it 'does not pass empty known_hosts data' do
+ expect_popen.and_return([nil, 0])
+
+ ssh_auth = build_ssh_auth(ssh_known_hosts: '')
+
+ expect(fetch_remote(ssh_auth)).to be_truthy
+ end
+
+ it 'does not pass known_hosts data unless SSH is to be used' do
+ expect_popen(popen_vars).and_return([nil, 0])
+
+ ssh_auth = build_ssh_auth(ssh_import?: false, ssh_known_hosts: 'foo')
- expect { gitlab_shell.fetch_remote('current/storage', 'project/path', 'new/storage') }.to raise_error(Gitlab::Shell::Error, "error")
+ expect(fetch_remote(ssh_auth)).to be_truthy
+ end
end
end
diff --git a/spec/lib/gitlab/sql/union_spec.rb b/spec/lib/gitlab/sql/union_spec.rb
index 5346881444d..baf8f6644bf 100644
--- a/spec/lib/gitlab/sql/union_spec.rb
+++ b/spec/lib/gitlab/sql/union_spec.rb
@@ -19,7 +19,7 @@ describe Gitlab::SQL::Union do
empty_relation = User.none
union = described_class.new([empty_relation, relation_1, relation_2])
- expect{User.where("users.id IN (#{union.to_sql})").to_a}.not_to raise_error
+ expect {User.where("users.id IN (#{union.to_sql})").to_a}.not_to raise_error
expect(union.to_sql).to eq("#{to_sql(relation_1)}\nUNION\n#{to_sql(relation_2)}")
end
end
diff --git a/spec/lib/gitlab/template/issue_template_spec.rb b/spec/lib/gitlab/template/issue_template_spec.rb
index 6e0b1075a89..7098499f996 100644
--- a/spec/lib/gitlab/template/issue_template_spec.rb
+++ b/spec/lib/gitlab/template/issue_template_spec.rb
@@ -1,41 +1,28 @@
require 'spec_helper'
describe Gitlab::Template::IssueTemplate do
- subject { described_class }
-
- let(:user) { create(:user) }
-
- let(:project) do
- create(:project,
- :repository,
- create_template: {
- user: user,
- access: Gitlab::Access::MASTER,
- path: 'issue_templates'
- })
- end
+ let(:project) { create(:project, :repository, create_templates: :issue) }
describe '.all' do
it 'strips the md suffix' do
- expect(subject.all(project).first.name).not_to end_with('.issue_template')
+ expect(described_class.all(project).first.name).not_to end_with('.issue_template')
end
it 'combines the globals and rest' do
- all = subject.all(project).map(&:name)
+ all = described_class.all(project).map(&:name)
expect(all).to include('bug')
expect(all).to include('feature_proposal')
- expect(all).to include('template_test')
end
end
describe '.find' do
it 'returns nil if the file does not exist' do
- expect { subject.find('mepmep-yadida', project) }.to raise_error(Gitlab::Template::Finders::RepoTemplateFinder::FileNotFoundError)
+ expect { described_class.find('mepmep-yadida', project) }.to raise_error(Gitlab::Template::Finders::RepoTemplateFinder::FileNotFoundError)
end
it 'returns the issue object of a valid file' do
- ruby = subject.find('bug', project)
+ ruby = described_class.find('bug', project)
expect(ruby).to be_a described_class
expect(ruby.name).to eq('bug')
@@ -44,21 +31,17 @@ describe Gitlab::Template::IssueTemplate do
describe '.by_category' do
it 'return array of templates' do
- all = subject.by_category('', project).map(&:name)
+ all = described_class.by_category('', project).map(&:name)
expect(all).to include('bug')
expect(all).to include('feature_proposal')
- expect(all).to include('template_test')
end
context 'when repo is bare or empty' do
let(:empty_project) { create(:project) }
- before do
- empty_project.add_user(user, Gitlab::Access::MASTER)
- end
-
it "returns empty array" do
- templates = subject.by_category('', empty_project)
+ templates = described_class.by_category('', empty_project)
+
expect(templates).to be_empty
end
end
@@ -66,26 +49,23 @@ describe Gitlab::Template::IssueTemplate do
describe '#content' do
it 'loads the full file' do
- issue_template = subject.new('.gitlab/issue_templates/bug.md', project)
+ issue_template = described_class.new('.gitlab/issue_templates/bug.md', project)
expect(issue_template.name).to eq 'bug'
expect(issue_template.content).to eq('something valid')
end
it 'raises error when file is not found' do
- issue_template = subject.new('.gitlab/issue_templates/bugnot.md', project)
+ issue_template = described_class.new('.gitlab/issue_templates/bugnot.md', project)
expect { issue_template.content }.to raise_error(Gitlab::Template::Finders::RepoTemplateFinder::FileNotFoundError)
end
context "when repo is empty" do
let(:empty_project) { create(:project) }
- before do
- empty_project.add_user(user, Gitlab::Access::MASTER)
- end
-
it "raises file not found" do
- issue_template = subject.new('.gitlab/issue_templates/not_existent.md', empty_project)
+ issue_template = described_class.new('.gitlab/issue_templates/not_existent.md', empty_project)
+
expect { issue_template.content }.to raise_error(Gitlab::Template::Finders::RepoTemplateFinder::FileNotFoundError)
end
end
diff --git a/spec/lib/gitlab/template/merge_request_template_spec.rb b/spec/lib/gitlab/template/merge_request_template_spec.rb
index b952274cd24..bd7ff64aa8a 100644
--- a/spec/lib/gitlab/template/merge_request_template_spec.rb
+++ b/spec/lib/gitlab/template/merge_request_template_spec.rb
@@ -1,41 +1,28 @@
require 'spec_helper'
describe Gitlab::Template::MergeRequestTemplate do
- subject { described_class }
-
- let(:user) { create(:user) }
-
- let(:project) do
- create(:project,
- :repository,
- create_template: {
- user: user,
- access: Gitlab::Access::MASTER,
- path: 'merge_request_templates'
- })
- end
+ let(:project) { create(:project, :repository, create_templates: :merge_request) }
describe '.all' do
it 'strips the md suffix' do
- expect(subject.all(project).first.name).not_to end_with('.issue_template')
+ expect(described_class.all(project).first.name).not_to end_with('.issue_template')
end
it 'combines the globals and rest' do
- all = subject.all(project).map(&:name)
+ all = described_class.all(project).map(&:name)
expect(all).to include('bug')
expect(all).to include('feature_proposal')
- expect(all).to include('template_test')
end
end
describe '.find' do
it 'returns nil if the file does not exist' do
- expect { subject.find('mepmep-yadida', project) }.to raise_error(Gitlab::Template::Finders::RepoTemplateFinder::FileNotFoundError)
+ expect { described_class.find('mepmep-yadida', project) }.to raise_error(Gitlab::Template::Finders::RepoTemplateFinder::FileNotFoundError)
end
it 'returns the merge request object of a valid file' do
- ruby = subject.find('bug', project)
+ ruby = described_class.find('bug', project)
expect(ruby).to be_a described_class
expect(ruby.name).to eq('bug')
@@ -44,21 +31,17 @@ describe Gitlab::Template::MergeRequestTemplate do
describe '.by_category' do
it 'return array of templates' do
- all = subject.by_category('', project).map(&:name)
+ all = described_class.by_category('', project).map(&:name)
expect(all).to include('bug')
expect(all).to include('feature_proposal')
- expect(all).to include('template_test')
end
context 'when repo is bare or empty' do
let(:empty_project) { create(:project) }
- before do
- empty_project.add_user(user, Gitlab::Access::MASTER)
- end
-
it "returns empty array" do
- templates = subject.by_category('', empty_project)
+ templates = described_class.by_category('', empty_project)
+
expect(templates).to be_empty
end
end
@@ -66,26 +49,23 @@ describe Gitlab::Template::MergeRequestTemplate do
describe '#content' do
it 'loads the full file' do
- issue_template = subject.new('.gitlab/merge_request_templates/bug.md', project)
+ issue_template = described_class.new('.gitlab/merge_request_templates/bug.md', project)
expect(issue_template.name).to eq 'bug'
expect(issue_template.content).to eq('something valid')
end
it 'raises error when file is not found' do
- issue_template = subject.new('.gitlab/merge_request_templates/bugnot.md', project)
+ issue_template = described_class.new('.gitlab/merge_request_templates/bugnot.md', project)
expect { issue_template.content }.to raise_error(Gitlab::Template::Finders::RepoTemplateFinder::FileNotFoundError)
end
context "when repo is empty" do
let(:empty_project) { create(:project) }
- before do
- empty_project.add_user(user, Gitlab::Access::MASTER)
- end
-
it "raises file not found" do
- issue_template = subject.new('.gitlab/merge_request_templates/not_existent.md', empty_project)
+ issue_template = described_class.new('.gitlab/merge_request_templates/not_existent.md', empty_project)
+
expect { issue_template.content }.to raise_error(Gitlab::Template::Finders::RepoTemplateFinder::FileNotFoundError)
end
end
diff --git a/spec/lib/gitlab/url_blocker_spec.rb b/spec/lib/gitlab/url_blocker_spec.rb
index f5b4882815f..f18823b61ef 100644
--- a/spec/lib/gitlab/url_blocker_spec.rb
+++ b/spec/lib/gitlab/url_blocker_spec.rb
@@ -20,6 +20,34 @@ describe Gitlab::UrlBlocker do
expect(described_class.blocked_url?('https://gitlab.com:25/foo/foo.git')).to be true
end
+ it 'returns true for a non-alphanumeric hostname' do
+ stub_resolv
+
+ aggregate_failures do
+ expect(described_class).to be_blocked_url('ssh://-oProxyCommand=whoami/a')
+
+ # The leading character here is a Unicode "soft hyphen"
+ expect(described_class).to be_blocked_url('ssh://­oProxyCommand=whoami/a')
+
+ # Unicode alphanumerics are allowed
+ expect(described_class).not_to be_blocked_url('ssh://ÄŸitlab.com/a')
+ end
+ end
+
+ it 'returns true for a non-alphanumeric username' do
+ stub_resolv
+
+ aggregate_failures do
+ expect(described_class).to be_blocked_url('ssh://-oProxyCommand=whoami@example.com/a')
+
+ # The leading character here is a Unicode "soft hyphen"
+ expect(described_class).to be_blocked_url('ssh://­oProxyCommand=whoami@example.com/a')
+
+ # Unicode alphanumerics are allowed
+ expect(described_class).not_to be_blocked_url('ssh://ÄŸitlab@example.com/a')
+ end
+ end
+
it 'returns true for invalid URL' do
expect(described_class.blocked_url?('http://:8080')).to be true
end
@@ -28,4 +56,10 @@ describe Gitlab::UrlBlocker do
expect(described_class.blocked_url?('https://gitlab.com/foo/foo.git')).to be false
end
end
+
+ # Resolv does not support resolving UTF-8 domain names
+ # See https://bugs.ruby-lang.org/issues/4270
+ def stub_resolv
+ allow(Resolv).to receive(:getaddresses).and_return([])
+ end
end
diff --git a/spec/lib/gitlab/version_info_spec.rb b/spec/lib/gitlab/version_info_spec.rb
index e7e1a92ae54..c8a1e433d59 100644
--- a/spec/lib/gitlab/version_info_spec.rb
+++ b/spec/lib/gitlab/version_info_spec.rb
@@ -50,8 +50,8 @@ describe 'Gitlab::VersionInfo' do
context 'unknown' do
it { expect(@unknown).not_to be @v0_0_1 }
it { expect(@unknown).not_to be Gitlab::VersionInfo.new }
- it { expect{@unknown > @v0_0_1}.to raise_error(ArgumentError) }
- it { expect{@unknown < @v0_0_1}.to raise_error(ArgumentError) }
+ it { expect {@unknown > @v0_0_1}.to raise_error(ArgumentError) }
+ it { expect {@unknown < @v0_0_1}.to raise_error(ArgumentError) }
end
context 'parse' do
diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb
index 654397ccffb..e78892d4232 100644
--- a/spec/lib/gitlab/workhorse_spec.rb
+++ b/spec/lib/gitlab/workhorse_spec.rb
@@ -217,7 +217,9 @@ describe Gitlab::Workhorse do
it 'includes a Repository param' do
repo_param = { Repository: {
storage_name: 'default',
- relative_path: project.full_path + '.git'
+ relative_path: project.full_path + '.git',
+ git_object_directory: '',
+ git_alternate_object_directories: []
} }
expect(subject).to include(repo_param)
diff --git a/spec/lib/json_web_token/rsa_token_spec.rb b/spec/lib/json_web_token/rsa_token_spec.rb
index e7022bd06f8..d6edc964844 100644
--- a/spec/lib/json_web_token/rsa_token_spec.rb
+++ b/spec/lib/json_web_token/rsa_token_spec.rb
@@ -27,7 +27,7 @@ describe JSONWebToken::RSAToken do
subject { JWT.decode(rsa_encoded, rsa_key) }
- it { expect{subject}.not_to raise_error }
+ it { expect {subject}.not_to raise_error }
it { expect(subject.first).to include('key' => 'value') }
it do
expect(subject.second).to eq(
@@ -41,7 +41,7 @@ describe JSONWebToken::RSAToken do
let(:new_key) { OpenSSL::PKey::RSA.generate(512) }
subject { JWT.decode(rsa_encoded, new_key) }
- it { expect{subject}.to raise_error(JWT::DecodeError) }
+ it { expect {subject}.to raise_error(JWT::DecodeError) }
end
end
end
diff --git a/spec/lib/mattermost/session_spec.rb b/spec/lib/mattermost/session_spec.rb
index be3908e8f6a..3db19d06305 100644
--- a/spec/lib/mattermost/session_spec.rb
+++ b/spec/lib/mattermost/session_spec.rb
@@ -20,9 +20,10 @@ describe Mattermost::Session, type: :request do
describe '#with session' do
let(:location) { 'http://location.tld' }
+ let(:cookie_header) {'MMOAUTH=taskik8az7rq8k6rkpuas7htia; Path=/;'}
let!(:stub) do
WebMock.stub_request(:get, "#{mattermost_url}/api/v3/oauth/gitlab/login")
- .to_return(headers: { 'location' => location }, status: 307)
+ .to_return(headers: { 'location' => location, 'Set-Cookie' => cookie_header }, status: 307)
end
context 'without oauth uri' do
@@ -34,9 +35,9 @@ describe Mattermost::Session, type: :request do
context 'with oauth_uri' do
let!(:doorkeeper) do
Doorkeeper::Application.create(
- name: "GitLab Mattermost",
+ name: 'GitLab Mattermost',
redirect_uri: "#{mattermost_url}/signup/gitlab/complete\n#{mattermost_url}/login/gitlab/complete",
- scopes: "")
+ scopes: '')
end
context 'without token_uri' do
diff --git a/spec/lib/rspec_flaky/example_spec.rb b/spec/lib/rspec_flaky/example_spec.rb
new file mode 100644
index 00000000000..5b4fd5ddf3e
--- /dev/null
+++ b/spec/lib/rspec_flaky/example_spec.rb
@@ -0,0 +1,89 @@
+require 'spec_helper'
+
+describe RspecFlaky::Example do
+ let(:example_attrs) do
+ {
+ id: 'spec/foo/bar_spec.rb:2',
+ metadata: {
+ file_path: 'spec/foo/bar_spec.rb',
+ line_number: 2,
+ full_description: 'hello world'
+ },
+ execution_result: double(status: 'passed', exception: 'BOOM!'),
+ attempts: 1
+ }
+ end
+ let(:rspec_example) { double(example_attrs) }
+
+ describe '#initialize' do
+ shared_examples 'a valid Example instance' do
+ it 'returns valid attributes' do
+ example = described_class.new(args)
+
+ expect(example.example_id).to eq(example_attrs[:id])
+ end
+ end
+
+ context 'when given an Rspec::Core::Example that responds to #example' do
+ let(:args) { double(example: rspec_example) }
+
+ it_behaves_like 'a valid Example instance'
+ end
+
+ context 'when given an Rspec::Core::Example that does not respond to #example' do
+ let(:args) { rspec_example }
+
+ it_behaves_like 'a valid Example instance'
+ end
+ end
+
+ subject { described_class.new(rspec_example) }
+
+ describe '#uid' do
+ it 'returns a hash of the full description' do
+ expect(subject.uid).to eq(Digest::MD5.hexdigest("#{subject.description}-#{subject.file}"))
+ end
+ end
+
+ describe '#example_id' do
+ it 'returns the ID of the RSpec::Core::Example' do
+ expect(subject.example_id).to eq(rspec_example.id)
+ end
+ end
+
+ describe '#attempts' do
+ it 'returns the attempts of the RSpec::Core::Example' do
+ expect(subject.attempts).to eq(rspec_example.attempts)
+ end
+ end
+
+ describe '#file' do
+ it 'returns the metadata[:file_path] of the RSpec::Core::Example' do
+ expect(subject.file).to eq(rspec_example.metadata[:file_path])
+ end
+ end
+
+ describe '#line' do
+ it 'returns the metadata[:line_number] of the RSpec::Core::Example' do
+ expect(subject.line).to eq(rspec_example.metadata[:line_number])
+ end
+ end
+
+ describe '#description' do
+ it 'returns the metadata[:full_description] of the RSpec::Core::Example' do
+ expect(subject.description).to eq(rspec_example.metadata[:full_description])
+ end
+ end
+
+ describe '#status' do
+ it 'returns the execution_result.status of the RSpec::Core::Example' do
+ expect(subject.status).to eq(rspec_example.execution_result.status)
+ end
+ end
+
+ describe '#exception' do
+ it 'returns the execution_result.exception of the RSpec::Core::Example' do
+ expect(subject.exception).to eq(rspec_example.execution_result.exception)
+ end
+ end
+end
diff --git a/spec/lib/rspec_flaky/flaky_example_spec.rb b/spec/lib/rspec_flaky/flaky_example_spec.rb
new file mode 100644
index 00000000000..cbfc1e538ab
--- /dev/null
+++ b/spec/lib/rspec_flaky/flaky_example_spec.rb
@@ -0,0 +1,104 @@
+require 'spec_helper'
+
+describe RspecFlaky::FlakyExample do
+ let(:flaky_example_attrs) do
+ {
+ example_id: 'spec/foo/bar_spec.rb:2',
+ file: 'spec/foo/bar_spec.rb',
+ line: 2,
+ description: 'hello world',
+ first_flaky_at: 1234,
+ last_flaky_at: 2345,
+ last_attempts_count: 2,
+ flaky_reports: 1
+ }
+ end
+ let(:example_attrs) do
+ {
+ uid: 'abc123',
+ example_id: flaky_example_attrs[:example_id],
+ file: flaky_example_attrs[:file],
+ line: flaky_example_attrs[:line],
+ description: flaky_example_attrs[:description],
+ status: 'passed',
+ exception: 'BOOM!',
+ attempts: flaky_example_attrs[:last_attempts_count]
+ }
+ end
+ let(:example) { double(example_attrs) }
+
+ describe '#initialize' do
+ shared_examples 'a valid FlakyExample instance' do
+ it 'returns valid attributes' do
+ flaky_example = described_class.new(args)
+
+ expect(flaky_example.uid).to eq(flaky_example_attrs[:uid])
+ expect(flaky_example.example_id).to eq(flaky_example_attrs[:example_id])
+ end
+ end
+
+ context 'when given an Rspec::Example' do
+ let(:args) { example }
+
+ it_behaves_like 'a valid FlakyExample instance'
+ end
+
+ context 'when given a hash' do
+ let(:args) { flaky_example_attrs }
+
+ it_behaves_like 'a valid FlakyExample instance'
+ end
+ end
+
+ describe '#to_h' do
+ before do
+ # Stub these env variables otherwise specs don't behave the same on the CI
+ stub_env('CI_PROJECT_URL', nil)
+ stub_env('CI_JOB_ID', nil)
+ end
+
+ shared_examples 'a valid FlakyExample hash' do
+ let(:additional_attrs) { {} }
+
+ it 'returns a valid hash' do
+ flaky_example = described_class.new(args)
+ final_hash = flaky_example_attrs
+ .merge(last_flaky_at: instance_of(Time), last_flaky_job: nil)
+ .merge(additional_attrs)
+
+ expect(flaky_example.to_h).to match(hash_including(final_hash))
+ end
+ end
+
+ context 'when given an Rspec::Example' do
+ let(:args) { example }
+
+ context 'when run locally' do
+ it_behaves_like 'a valid FlakyExample hash' do
+ let(:additional_attrs) do
+ { first_flaky_at: instance_of(Time) }
+ end
+ end
+ end
+
+ context 'when run on the CI' do
+ before do
+ stub_env('CI_PROJECT_URL', 'https://gitlab.com/gitlab-org/gitlab-ce')
+ stub_env('CI_JOB_ID', 42)
+ end
+
+ it_behaves_like 'a valid FlakyExample hash' do
+ let(:additional_attrs) do
+ { first_flaky_at: instance_of(Time), last_flaky_job: "https://gitlab.com/gitlab-org/gitlab-ce/-/jobs/42" }
+ end
+ end
+ end
+ end
+
+ context 'when given a hash' do
+ let(:args) { flaky_example_attrs }
+
+ it_behaves_like 'a valid FlakyExample hash'
+ end
+ end
+end
diff --git a/spec/lib/rspec_flaky/listener_spec.rb b/spec/lib/rspec_flaky/listener_spec.rb
new file mode 100644
index 00000000000..0e193bf408b
--- /dev/null
+++ b/spec/lib/rspec_flaky/listener_spec.rb
@@ -0,0 +1,178 @@
+require 'spec_helper'
+
+describe RspecFlaky::Listener do
+ let(:flaky_example_report) do
+ {
+ 'abc123' => {
+ example_id: 'spec/foo/bar_spec.rb:2',
+ file: 'spec/foo/bar_spec.rb',
+ line: 2,
+ description: 'hello world',
+ first_flaky_at: 1234,
+ last_flaky_at: instance_of(Time),
+ last_attempts_count: 2,
+ flaky_reports: 1,
+ last_flaky_job: nil
+ }
+ }
+ end
+ let(:example_attrs) do
+ {
+ id: 'spec/foo/baz_spec.rb:3',
+ metadata: {
+ file_path: 'spec/foo/baz_spec.rb',
+ line_number: 3,
+ full_description: 'hello GitLab'
+ },
+ execution_result: double(status: 'passed', exception: nil)
+ }
+ end
+
+ before do
+ # Stub these env variables otherwise specs don't behave the same on the CI
+ stub_env('CI_PROJECT_URL', nil)
+ stub_env('CI_JOB_ID', nil)
+ end
+
+ describe '#initialize' do
+ shared_examples 'a valid Listener instance' do
+ let(:expected_all_flaky_examples) { {} }
+
+ it 'returns a valid Listener instance' do
+ listener = described_class.new
+
+ expect(listener.to_report(listener.all_flaky_examples))
+ .to match(hash_including(expected_all_flaky_examples))
+ expect(listener.new_flaky_examples).to eq({})
+ end
+ end
+
+ context 'when no report file exists' do
+ it_behaves_like 'a valid Listener instance'
+ end
+
+ context 'when a report file exists and set by ALL_FLAKY_RSPEC_REPORT_PATH' do
+ let(:report_file) do
+ Tempfile.new(%w[rspec_flaky_report .json]).tap do |f|
+ f.write(JSON.pretty_generate(flaky_example_report))
+ f.rewind
+ end
+ end
+
+ before do
+ stub_env('ALL_FLAKY_RSPEC_REPORT_PATH', report_file.path)
+ end
+
+ after do
+ report_file.close
+ report_file.unlink
+ end
+
+ it_behaves_like 'a valid Listener instance' do
+ let(:expected_all_flaky_examples) { flaky_example_report }
+ end
+ end
+ end
+
+ describe '#example_passed' do
+ let(:rspec_example) { double(example_attrs) }
+ let(:notification) { double(example: rspec_example) }
+
+ shared_examples 'a non-flaky example' do
+ it 'does not change the flaky examples hash' do
+ expect { subject.example_passed(notification) }
+ .not_to change { subject.all_flaky_examples }
+ end
+ end
+
+ describe 'when the RSpec example does not respond to attempts' do
+ it_behaves_like 'a non-flaky example'
+ end
+
+ describe 'when the RSpec example has 1 attempt' do
+ let(:rspec_example) { double(example_attrs.merge(attempts: 1)) }
+
+ it_behaves_like 'a non-flaky example'
+ end
+
+ describe 'when the RSpec example has 2 attempts' do
+ let(:rspec_example) { double(example_attrs.merge(attempts: 2)) }
+ let(:expected_new_flaky_example) do
+ {
+ example_id: 'spec/foo/baz_spec.rb:3',
+ file: 'spec/foo/baz_spec.rb',
+ line: 3,
+ description: 'hello GitLab',
+ first_flaky_at: instance_of(Time),
+ last_flaky_at: instance_of(Time),
+ last_attempts_count: 2,
+ flaky_reports: 1,
+ last_flaky_job: nil
+ }
+ end
+
+ it 'does not change the flaky examples hash' do
+ expect { subject.example_passed(notification) }
+ .to change { subject.all_flaky_examples }
+
+ new_example = RspecFlaky::Example.new(rspec_example)
+
+ expect(subject.all_flaky_examples[new_example.uid].to_h)
+ .to match(hash_including(expected_new_flaky_example))
+ end
+ end
+ end
+
+ describe '#dump_summary' do
+ let(:rspec_example) { double(example_attrs) }
+ let(:notification) { double(example: rspec_example) }
+
+ context 'when a report file path is set by ALL_FLAKY_RSPEC_REPORT_PATH' do
+ let(:report_file_path) { Rails.root.join('tmp', 'rspec_flaky_report.json') }
+
+ before do
+ stub_env('ALL_FLAKY_RSPEC_REPORT_PATH', report_file_path)
+ FileUtils.rm(report_file_path) if File.exist?(report_file_path)
+ end
+
+ after do
+ FileUtils.rm(report_file_path) if File.exist?(report_file_path)
+ end
+
+ context 'when FLAKY_RSPEC_GENERATE_REPORT == "false"' do
+ before do
+ stub_env('FLAKY_RSPEC_GENERATE_REPORT', 'false')
+ end
+
+ it 'does not write the report file' do
+ subject.example_passed(notification)
+
+ subject.dump_summary(nil)
+
+ expect(File.exist?(report_file_path)).to be(false)
+ end
+ end
+
+ context 'when FLAKY_RSPEC_GENERATE_REPORT == "true"' do
+ before do
+ stub_env('FLAKY_RSPEC_GENERATE_REPORT', 'true')
+ end
+
+ it 'writes the report file' do
+ subject.example_passed(notification)
+
+ subject.dump_summary(nil)
+
+ expect(File.exist?(report_file_path)).to be(true)
+ end
+ end
+ end
+ end
+
+ describe '#to_report' do
+ it 'transforms the internal hash to a JSON-ready hash' do
+ expect(subject.to_report('abc123' => RspecFlaky::FlakyExample.new(flaky_example_report['abc123'])))
+ .to match(hash_including(flaky_example_report))
+ end
+ end
+end
diff --git a/spec/lib/system_check/simple_executor_spec.rb b/spec/lib/system_check/simple_executor_spec.rb
index 025ea2673b4..4de5da984ba 100644
--- a/spec/lib/system_check/simple_executor_spec.rb
+++ b/spec/lib/system_check/simple_executor_spec.rb
@@ -240,7 +240,7 @@ describe SystemCheck::SimpleExecutor do
context 'when there is an exception' do
it 'rescues the exception' do
- expect{ subject.run_check(BugousCheck) }.not_to raise_exception
+ expect { subject.run_check(BugousCheck) }.not_to raise_exception
end
end
end
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index e36d7a1800c..1fa59ebd22b 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -545,7 +545,7 @@ describe Notify do
let(:note_author) { create(:user, name: 'author_name') }
let(:note) { create(:note, project: project, author: note_author) }
- before :each do
+ before do
allow(Note).to receive(:find).with(note.id).and_return(note)
end
@@ -661,7 +661,7 @@ describe Notify do
let(:project) { create(:project, :repository) }
let(:note_author) { create(:user, name: 'author_name') }
- before :each do
+ before do
allow(Note).to receive(:find).with(note.id).and_return(note)
end
@@ -779,7 +779,7 @@ describe Notify do
context 'items that are noteable, the email for a diff discussion note' do
let(:note_author) { create(:user, name: 'author_name') }
- before :each do
+ before do
allow(Note).to receive(:find).with(note.id).and_return(note)
end
diff --git a/spec/migrations/calculate_conv_dev_index_percentages_spec.rb b/spec/migrations/calculate_conv_dev_index_percentages_spec.rb
new file mode 100644
index 00000000000..597d8eab51c
--- /dev/null
+++ b/spec/migrations/calculate_conv_dev_index_percentages_spec.rb
@@ -0,0 +1,41 @@
+# encoding: utf-8
+
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20170803090603_calculate_conv_dev_index_percentages.rb')
+
+describe CalculateConvDevIndexPercentages, truncate: true do
+ let(:migration) { described_class.new }
+ let!(:conv_dev_index) do
+ create(:conversational_development_index_metric,
+ leader_notes: 0,
+ instance_milestones: 0,
+ percentage_issues: 0,
+ percentage_notes: 0,
+ percentage_milestones: 0,
+ percentage_boards: 0,
+ percentage_merge_requests: 0,
+ percentage_ci_pipelines: 0,
+ percentage_environments: 0,
+ percentage_deployments: 0,
+ percentage_projects_prometheus_active: 0,
+ percentage_service_desk_issues: 0)
+ end
+
+ describe '#up' do
+ it 'calculates percentages correctly' do
+ migration.up
+ conv_dev_index.reload
+
+ expect(conv_dev_index.percentage_issues).to be_within(0.1).of(13.3)
+ expect(conv_dev_index.percentage_notes).to be_zero # leader 0
+ expect(conv_dev_index.percentage_milestones).to be_zero # instance 0
+ expect(conv_dev_index.percentage_boards).to be_within(0.1).of(62.4)
+ expect(conv_dev_index.percentage_merge_requests).to eq(50.0)
+ expect(conv_dev_index.percentage_ci_pipelines).to be_within(0.1).of(19.3)
+ expect(conv_dev_index.percentage_environments).to be_within(0.1).of(66.7)
+ expect(conv_dev_index.percentage_deployments).to be_within(0.1).of(64.2)
+ expect(conv_dev_index.percentage_projects_prometheus_active).to be_within(0.1).of(98.2)
+ expect(conv_dev_index.percentage_service_desk_issues).to be_within(0.1).of(84.0)
+ end
+ end
+end
diff --git a/spec/migrations/clean_upload_symlinks_spec.rb b/spec/migrations/clean_upload_symlinks_spec.rb
index cecb3ddac53..26653b9c008 100644
--- a/spec/migrations/clean_upload_symlinks_spec.rb
+++ b/spec/migrations/clean_upload_symlinks_spec.rb
@@ -5,7 +5,7 @@ describe CleanUploadSymlinks do
let(:migration) { described_class.new }
let(:test_dir) { File.join(Rails.root, "tmp", "tests", "move_uploads_test") }
let(:uploads_dir) { File.join(test_dir, "public", "uploads") }
- let(:new_uploads_dir) { File.join(uploads_dir, "system") }
+ let(:new_uploads_dir) { File.join(uploads_dir, "-", "system") }
let(:original_path) { File.join(new_uploads_dir, 'user') }
let(:symlink_path) { File.join(uploads_dir, 'user') }
diff --git a/spec/migrations/migrate_process_commit_worker_jobs_spec.rb b/spec/migrations/migrate_process_commit_worker_jobs_spec.rb
index cf2d5827306..e5793a3c0ee 100644
--- a/spec/migrations/migrate_process_commit_worker_jobs_spec.rb
+++ b/spec/migrations/migrate_process_commit_worker_jobs_spec.rb
@@ -6,7 +6,7 @@ require Rails.root.join('db', 'migrate', '20161124141322_migrate_process_commit_
describe MigrateProcessCommitWorkerJobs do
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
- let(:commit) { project.commit.raw.raw_commit }
+ let(:commit) { project.commit.raw.rugged_commit }
describe 'Project' do
describe 'find_including_path' do
diff --git a/spec/migrations/move_personal_snippets_files_spec.rb b/spec/migrations/move_personal_snippets_files_spec.rb
index 8505c7bf3e3..1a319eccc0d 100644
--- a/spec/migrations/move_personal_snippets_files_spec.rb
+++ b/spec/migrations/move_personal_snippets_files_spec.rb
@@ -5,7 +5,7 @@ describe MovePersonalSnippetsFiles do
let(:migration) { described_class.new }
let(:test_dir) { File.join(Rails.root, "tmp", "tests", "move_snippet_files_test") }
let(:uploads_dir) { File.join(test_dir, 'uploads') }
- let(:new_uploads_dir) { File.join(uploads_dir, 'system') }
+ let(:new_uploads_dir) { File.join(uploads_dir, '-', 'system') }
before do
allow(CarrierWave).to receive(:root).and_return(test_dir)
@@ -42,7 +42,7 @@ describe MovePersonalSnippetsFiles do
describe 'updating the markdown' do
it 'includes the new path when the file exists' do
secret = "secret#{snippet.id}"
- file_location = "/uploads/system/personal_snippet/#{snippet.id}/#{secret}/picture.jpg"
+ file_location = "/uploads/-/system/personal_snippet/#{snippet.id}/#{secret}/picture.jpg"
migration.up
@@ -60,7 +60,7 @@ describe MovePersonalSnippetsFiles do
it 'updates the note markdown' do
secret = "secret#{snippet.id}"
- file_location = "/uploads/system/personal_snippet/#{snippet.id}/#{secret}/picture.jpg"
+ file_location = "/uploads/-/system/personal_snippet/#{snippet.id}/#{secret}/picture.jpg"
markdown = markdown_linking_file('picture.jpg', snippet)
note = create(:note_on_personal_snippet, noteable: snippet, note: "with #{markdown}")
@@ -108,7 +108,7 @@ describe MovePersonalSnippetsFiles do
it 'keeps the markdown as is when the file is missing' do
secret = "secret#{snippet_with_missing_file.id}"
- file_location = "/uploads/system/personal_snippet/#{snippet_with_missing_file.id}/#{secret}/picture.jpg"
+ file_location = "/uploads/-/system/personal_snippet/#{snippet_with_missing_file.id}/#{secret}/picture.jpg"
migration.down
@@ -167,7 +167,7 @@ describe MovePersonalSnippetsFiles do
def markdown_linking_file(filename, snippet, in_new_path: false)
markdown = "![#{filename.split('.')[0]}]"
markdown += '(/uploads'
- markdown += '/system' if in_new_path
+ markdown += '/-/system' if in_new_path
markdown += "/#{model_file_path(filename, snippet)})"
markdown
end
diff --git a/spec/migrations/move_system_upload_folder_spec.rb b/spec/migrations/move_system_upload_folder_spec.rb
index b622b4e9536..d3180477db3 100644
--- a/spec/migrations/move_system_upload_folder_spec.rb
+++ b/spec/migrations/move_system_upload_folder_spec.rb
@@ -33,6 +33,15 @@ describe MoveSystemUploadFolder do
expect(File.symlink?(File.join(test_base, 'system'))).to be_truthy
expect(File.exist?(File.join(test_base, 'system', 'file'))).to be_truthy
end
+
+ it 'does not move if the target directory already exists' do
+ FileUtils.mkdir_p(File.join(test_base, '-', 'system'))
+
+ expect(FileUtils).not_to receive(:mv)
+ expect(migration).to receive(:say).with(/already exists. No need to redo the move/)
+
+ migration.up
+ end
end
describe '#down' do
@@ -58,5 +67,14 @@ describe MoveSystemUploadFolder do
expect(File.directory?(File.join(test_base, 'system'))).to be_truthy
expect(File.symlink?(File.join(test_base, 'system'))).to be_falsey
end
+
+ it 'does not move if the old directory already exists' do
+ FileUtils.mkdir_p(File.join(test_base, 'system'))
+
+ expect(FileUtils).not_to receive(:mv)
+ expect(migration).to receive(:say).with(/already exists and is not a symlink, no need to revert/)
+
+ migration.down
+ end
end
end
diff --git a/spec/migrations/move_uploads_to_system_dir_spec.rb b/spec/migrations/move_uploads_to_system_dir_spec.rb
index 37d66452447..ca11a2004c5 100644
--- a/spec/migrations/move_uploads_to_system_dir_spec.rb
+++ b/spec/migrations/move_uploads_to_system_dir_spec.rb
@@ -5,7 +5,7 @@ describe MoveUploadsToSystemDir do
let(:migration) { described_class.new }
let(:test_dir) { File.join(Rails.root, "tmp", "move_uploads_test") }
let(:uploads_dir) { File.join(test_dir, "public", "uploads") }
- let(:new_uploads_dir) { File.join(uploads_dir, "system") }
+ let(:new_uploads_dir) { File.join(uploads_dir, "-", "system") }
before do
FileUtils.remove_dir(test_dir) if File.directory?(test_dir)
diff --git a/spec/migrations/rename_system_namespaces_spec.rb b/spec/migrations/rename_system_namespaces_spec.rb
deleted file mode 100644
index 747694cbe33..00000000000
--- a/spec/migrations/rename_system_namespaces_spec.rb
+++ /dev/null
@@ -1,254 +0,0 @@
-require "spec_helper"
-require Rails.root.join("db", "migrate", "20170316163800_rename_system_namespaces.rb")
-
-describe RenameSystemNamespaces, truncate: true do
- let(:migration) { described_class.new }
- let(:test_dir) { File.join(Rails.root, "tmp", "tests", "rename_namespaces_test") }
- let(:uploads_dir) { File.join(test_dir, "public", "uploads") }
- let(:system_namespace) do
- namespace = build(:namespace, path: "system")
- namespace.save(validate: false)
- namespace
- end
-
- def save_invalid_routable(routable)
- routable.__send__(:prepare_route)
- routable.save(validate: false)
- end
-
- before do
- FileUtils.remove_dir(test_dir) if File.directory?(test_dir)
- FileUtils.mkdir_p(uploads_dir)
- FileUtils.remove_dir(TestEnv.repos_path) if File.directory?(TestEnv.repos_path)
- allow(migration).to receive(:say)
- allow(migration).to receive(:uploads_dir).and_return(uploads_dir)
- end
-
- describe "#system_namespace" do
- it "only root namespaces called with path `system`" do
- system_namespace
- system_namespace_with_parent = build(:namespace, path: 'system', parent: create(:namespace))
- system_namespace_with_parent.save(validate: false)
-
- expect(migration.system_namespace.id).to eq(system_namespace.id)
- end
- end
-
- describe "#up" do
- before do
- system_namespace
- end
-
- it "doesn't break if there are no namespaces called system" do
- Namespace.delete_all
-
- migration.up
- end
-
- it "renames namespaces called system" do
- migration.up
-
- expect(system_namespace.reload.path).to eq("system0")
- end
-
- it "renames the route to the namespace" do
- migration.up
-
- expect(system_namespace.reload.full_path).to eq("system0")
- end
-
- it "renames the route for projects of the namespace" do
- project = build(:project, :repository, path: "project-path", namespace: system_namespace)
- save_invalid_routable(project)
-
- migration.up
-
- expect(project.route.reload.path).to eq("system0/project-path")
- end
-
- it "doesn't touch routes of namespaces that look like system" do
- namespace = create(:group, path: 'systemlookalike')
- project = create(:project, :repository, namespace: namespace, path: 'the-project')
-
- migration.up
-
- expect(project.route.reload.path).to eq('systemlookalike/the-project')
- expect(namespace.route.reload.path).to eq('systemlookalike')
- end
-
- it "moves the the repository for a project in the namespace" do
- project = build(:project, :repository, namespace: system_namespace, path: "system-project")
- save_invalid_routable(project)
- TestEnv.copy_repo(project,
- bare_repo: TestEnv.factory_repo_path_bare,
- refs: TestEnv::BRANCH_SHA)
- expected_repo = File.join(TestEnv.repos_path, "system0", "system-project.git")
-
- migration.up
-
- expect(File.directory?(expected_repo)).to be(true)
- end
-
- it "moves the uploads for the namespace" do
- allow(migration).to receive(:move_namespace_folders).with(Settings.pages.path, "system", "system0")
- expect(migration).to receive(:move_namespace_folders).with(uploads_dir, "system", "system0")
-
- migration.up
- end
-
- it "moves the pages for the namespace" do
- allow(migration).to receive(:move_namespace_folders).with(uploads_dir, "system", "system0")
- expect(migration).to receive(:move_namespace_folders).with(Settings.pages.path, "system", "system0")
-
- migration.up
- end
-
- describe "clears the markdown cache for projects in the system namespace" do
- let!(:project) do
- project = build(:project, :repository, namespace: system_namespace)
- save_invalid_routable(project)
- project
- end
-
- it 'removes description_html from projects' do
- migration.up
-
- expect(project.reload.description_html).to be_nil
- end
-
- it 'removes issue descriptions' do
- issue = create(:issue, project: project, description_html: 'Issue description')
-
- migration.up
-
- expect(issue.reload.description_html).to be_nil
- end
-
- it 'removes merge request descriptions' do
- merge_request = create(:merge_request,
- source_project: project,
- target_project: project,
- description_html: 'MergeRequest description')
-
- migration.up
-
- expect(merge_request.reload.description_html).to be_nil
- end
-
- it 'removes note html' do
- note = create(:note,
- project: project,
- noteable: create(:issue, project: project),
- note_html: 'note description')
-
- migration.up
-
- expect(note.reload.note_html).to be_nil
- end
-
- it 'removes milestone description' do
- milestone = create(:milestone,
- project: project,
- description_html: 'milestone description')
-
- migration.up
-
- expect(milestone.reload.description_html).to be_nil
- end
- end
-
- context "system namespace -> subgroup -> system0 project" do
- it "updates the route of the project correctly" do
- subgroup = build(:group, path: "subgroup", parent: system_namespace)
- save_invalid_routable(subgroup)
- project = build(:project, :repository, path: "system0", namespace: subgroup)
- save_invalid_routable(project)
-
- migration.up
-
- expect(project.route.reload.path).to eq("system0/subgroup/system0")
- end
- end
- end
-
- describe "#move_repositories" do
- let(:namespace) { create(:group, name: "hello-group") }
- it "moves a project for a namespace" do
- create(:project, :repository, namespace: namespace, path: "hello-project")
- expected_path = File.join(TestEnv.repos_path, "bye-group", "hello-project.git")
-
- migration.move_repositories(namespace, "hello-group", "bye-group")
-
- expect(File.directory?(expected_path)).to be(true)
- end
-
- it "moves a namespace in a subdirectory correctly" do
- child_namespace = create(:group, name: "sub-group", parent: namespace)
- create(:project, :repository, namespace: child_namespace, path: "hello-project")
-
- expected_path = File.join(TestEnv.repos_path, "hello-group", "renamed-sub-group", "hello-project.git")
-
- migration.move_repositories(child_namespace, "hello-group/sub-group", "hello-group/renamed-sub-group")
-
- expect(File.directory?(expected_path)).to be(true)
- end
-
- it "moves a parent namespace with subdirectories" do
- child_namespace = create(:group, name: "sub-group", parent: namespace)
- create(:project, :repository, namespace: child_namespace, path: "hello-project")
- expected_path = File.join(TestEnv.repos_path, "renamed-group", "sub-group", "hello-project.git")
-
- migration.move_repositories(child_namespace, "hello-group", "renamed-group")
-
- expect(File.directory?(expected_path)).to be(true)
- end
- end
-
- describe "#move_namespace_folders" do
- it "moves a namespace with files" do
- source = File.join(uploads_dir, "parent-group", "sub-group")
- FileUtils.mkdir_p(source)
- destination = File.join(uploads_dir, "parent-group", "moved-group")
- FileUtils.touch(File.join(source, "test.txt"))
- expected_file = File.join(destination, "test.txt")
-
- migration.move_namespace_folders(uploads_dir, File.join("parent-group", "sub-group"), File.join("parent-group", "moved-group"))
-
- expect(File.exist?(expected_file)).to be(true)
- end
-
- it "moves a parent namespace uploads" do
- source = File.join(uploads_dir, "parent-group", "sub-group")
- FileUtils.mkdir_p(source)
- destination = File.join(uploads_dir, "moved-parent", "sub-group")
- FileUtils.touch(File.join(source, "test.txt"))
- expected_file = File.join(destination, "test.txt")
-
- migration.move_namespace_folders(uploads_dir, "parent-group", "moved-parent")
-
- expect(File.exist?(expected_file)).to be(true)
- end
- end
-
- describe "#child_ids_for_parent" do
- it "collects child ids for all levels" do
- parent = create(:group)
- first_child = create(:group, parent: parent)
- second_child = create(:group, parent: parent)
- third_child = create(:group, parent: second_child)
- all_ids = [parent.id, first_child.id, second_child.id, third_child.id]
-
- collected_ids = migration.child_ids_for_parent(parent, ids: [parent.id])
-
- expect(collected_ids).to contain_exactly(*all_ids)
- end
- end
-
- describe "#remove_last_ocurrence" do
- it "removes only the last occurance of a string" do
- input = "this/is/system/namespace/with/system"
-
- expect(migration.remove_last_occurrence(input, "system")).to eq("this/is/system/namespace/with/")
- end
- end
-end
diff --git a/spec/migrations/schedule_merge_request_diff_migrations_spec.rb b/spec/migrations/schedule_merge_request_diff_migrations_spec.rb
new file mode 100644
index 00000000000..f95bd6e3511
--- /dev/null
+++ b/spec/migrations/schedule_merge_request_diff_migrations_spec.rb
@@ -0,0 +1,59 @@
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20170703130158_schedule_merge_request_diff_migrations')
+
+describe ScheduleMergeRequestDiffMigrations, :migration, :sidekiq do
+ matcher :be_scheduled_migration do |time, *expected|
+ match do |migration|
+ BackgroundMigrationWorker.jobs.any? do |job|
+ job['args'] == [migration, expected] &&
+ job['at'].to_i == time.to_i
+ end
+ end
+
+ failure_message do |migration|
+ "Migration `#{migration}` with args `#{expected.inspect}` not scheduled!"
+ end
+ end
+
+ let(:merge_request_diffs) { table(:merge_request_diffs) }
+ let(:merge_requests) { table(:merge_requests) }
+ let(:projects) { table(:projects) }
+
+ before do
+ stub_const("#{described_class.name}::BATCH_SIZE", 1)
+
+ projects.create!(id: 1, name: 'gitlab', path: 'gitlab')
+
+ merge_requests.create!(id: 1, target_project_id: 1, source_project_id: 1, target_branch: 'feature', source_branch: 'master')
+
+ merge_request_diffs.create!(id: 1, merge_request_id: 1, st_commits: YAML.dump([]), st_diffs: nil)
+ merge_request_diffs.create!(id: 2, merge_request_id: 1, st_commits: nil, st_diffs: YAML.dump([]))
+ merge_request_diffs.create!(id: 3, merge_request_id: 1, st_commits: nil, st_diffs: nil)
+ merge_request_diffs.create!(id: 4, merge_request_id: 1, st_commits: YAML.dump([]), st_diffs: YAML.dump([]))
+ end
+
+ it 'correctly schedules background migrations' do
+ Sidekiq::Testing.fake! do
+ Timecop.freeze do
+ migrate!
+
+ expect(described_class::MIGRATION).to be_scheduled_migration(5.minutes.from_now, 1, 1)
+ expect(described_class::MIGRATION).to be_scheduled_migration(10.minutes.from_now, 2, 2)
+ expect(described_class::MIGRATION).to be_scheduled_migration(15.minutes.from_now, 4, 4)
+ expect(BackgroundMigrationWorker.jobs.size).to eq 3
+ end
+ end
+ end
+
+ it 'schedules background migrations' do
+ Sidekiq::Testing.inline! do
+ non_empty = 'st_commits IS NOT NULL OR st_diffs IS NOT NULL'
+
+ expect(merge_request_diffs.where(non_empty).count).to eq 3
+
+ migrate!
+
+ expect(merge_request_diffs.where(non_empty).count).to eq 0
+ end
+ end
+end
diff --git a/spec/migrations/update_upload_paths_to_system_spec.rb b/spec/migrations/update_upload_paths_to_system_spec.rb
index 11412005b72..0a45c5ea32d 100644
--- a/spec/migrations/update_upload_paths_to_system_spec.rb
+++ b/spec/migrations/update_upload_paths_to_system_spec.rb
@@ -11,7 +11,7 @@ describe UpdateUploadPathsToSystem do
describe "#uploads_to_switch_to_new_path" do
it "contains only uploads with the old path for the correct models" do
_upload_for_other_type = create(:upload, model: create(:ci_pipeline), path: "uploads/ci_pipeline/avatar.jpg")
- _upload_with_system_path = create(:upload, model: create(:project), path: "uploads/system/project/avatar.jpg")
+ _upload_with_system_path = create(:upload, model: create(:project), path: "uploads/-/system/project/avatar.jpg")
_upload_with_other_path = create(:upload, model: create(:project), path: "thelongsecretforafileupload/avatar.jpg")
old_upload = create(:upload, model: create(:project), path: "uploads/project/avatar.jpg")
group_upload = create(:upload, model: create(:group), path: "uploads/group/avatar.jpg")
@@ -23,7 +23,7 @@ describe UpdateUploadPathsToSystem do
describe "#uploads_to_switch_to_old_path" do
it "contains only uploads with the new path for the correct models" do
_upload_for_other_type = create(:upload, model: create(:ci_pipeline), path: "uploads/ci_pipeline/avatar.jpg")
- upload_with_system_path = create(:upload, model: create(:project), path: "uploads/system/project/avatar.jpg")
+ upload_with_system_path = create(:upload, model: create(:project), path: "uploads/-/system/project/avatar.jpg")
_upload_with_other_path = create(:upload, model: create(:project), path: "thelongsecretforafileupload/avatar.jpg")
_old_upload = create(:upload, model: create(:project), path: "uploads/project/avatar.jpg")
@@ -37,13 +37,13 @@ describe UpdateUploadPathsToSystem do
migration.up
- expect(old_upload.reload.path).to eq("uploads/system/project/avatar.jpg")
+ expect(old_upload.reload.path).to eq("uploads/-/system/project/avatar.jpg")
end
end
describe "#down", truncate: true do
it "updates the new system patsh to the old paths" do
- new_upload = create(:upload, model: create(:project), path: "uploads/system/project/avatar.jpg")
+ new_upload = create(:upload, model: create(:project), path: "uploads/-/system/project/avatar.jpg")
migration.down
diff --git a/spec/models/appearance_spec.rb b/spec/models/appearance_spec.rb
index 7cd3a84d592..b5d5d58697b 100644
--- a/spec/models/appearance_spec.rb
+++ b/spec/models/appearance_spec.rb
@@ -9,4 +9,39 @@ RSpec.describe Appearance do
it { is_expected.to validate_presence_of(:description) }
it { is_expected.to have_many(:uploads).dependent(:destroy) }
+
+ describe '.current', :use_clean_rails_memory_store_caching do
+ let!(:appearance) { create(:appearance) }
+
+ it 'returns the current appearance row' do
+ expect(described_class.current).to eq(appearance)
+ end
+
+ it 'caches the result' do
+ expect(described_class).to receive(:first).once
+
+ 2.times { described_class.current }
+ end
+ end
+
+ describe '#flush_redis_cache' do
+ it 'flushes the cache in Redis' do
+ appearance = create(:appearance)
+
+ expect(Rails.cache).to receive(:delete).with(described_class::CACHE_KEY)
+
+ appearance.flush_redis_cache
+ end
+ end
+
+ describe '#single_appearance_row' do
+ it 'adds an error when more than 1 row exists' do
+ create(:appearance)
+
+ new_row = build(:appearance)
+ new_row.save
+
+ expect(new_row.valid?).to eq(false)
+ end
+ end
end
diff --git a/spec/models/broadcast_message_spec.rb b/spec/models/broadcast_message_spec.rb
index a8ca1d110e4..3369aef1d3e 100644
--- a/spec/models/broadcast_message_spec.rb
+++ b/spec/models/broadcast_message_spec.rb
@@ -20,7 +20,7 @@ describe BroadcastMessage do
it { is_expected.not_to allow_value('000').for(:font) }
end
- describe '.current' do
+ describe '.current', :use_clean_rails_memory_store_caching do
it 'returns message if time match' do
message = create(:broadcast_message)
@@ -45,6 +45,14 @@ describe BroadcastMessage do
expect(described_class.current).to be_empty
end
+
+ it 'caches the output of the query' do
+ create(:broadcast_message)
+
+ expect(described_class).to receive(:where).and_call_original.once
+
+ 2.times { described_class.current }
+ end
end
describe '#active?' do
@@ -102,4 +110,14 @@ describe BroadcastMessage do
end
end
end
+
+ describe '#flush_redis_cache' do
+ it 'flushes the Redis cache' do
+ message = create(:broadcast_message)
+
+ expect(Rails.cache).to receive(:delete).with(described_class::CACHE_KEY)
+
+ message.flush_redis_cache
+ end
+ end
end
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index f63ff19c2fc..ac75c6501ee 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -1,8 +1,6 @@
require 'spec_helper'
-describe Ci::Pipeline do
- include EmailHelpers
-
+describe Ci::Pipeline, :mailer do
let(:user) { create(:user) }
let(:project) { create(:project) }
@@ -1248,8 +1246,6 @@ describe Ci::Pipeline do
pipeline.user.global_notification_setting
.update(level: 'custom', failed_pipeline: true, success_pipeline: true)
- reset_delivered_emails!
-
perform_enqueued_jobs do
pipeline.enqueue
pipeline.run
diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb
index 08693b5da33..c18c635d811 100644
--- a/spec/models/commit_spec.rb
+++ b/spec/models/commit_spec.rb
@@ -33,7 +33,6 @@ describe Commit do
describe '#to_reference' do
let(:project) { create(:project, :repository, path: 'sample-project') }
- let(:commit) { project.commit }
it 'returns a String reference to the object' do
expect(commit.to_reference).to eq commit.id
@@ -47,7 +46,6 @@ describe Commit do
describe '#reference_link_text' do
let(:project) { create(:project, :repository, path: 'sample-project') }
- let(:commit) { project.commit }
it 'returns a String reference to the object' do
expect(commit.reference_link_text).to eq commit.short_id
@@ -191,7 +189,7 @@ eos
it { expect(data).to be_a(Hash) }
it { expect(data[:message]).to include('adds bar folder and branch-test text file to check Repository merged_to_root_ref method') }
- it { expect(data[:timestamp]).to eq('2016-09-27T14:37:46+00:00') }
+ it { expect(data[:timestamp]).to eq('2016-09-27T14:37:46Z') }
it { expect(data[:added]).to eq(["bar/branch-test.txt"]) }
it { expect(data[:modified]).to eq([]) }
it { expect(data[:removed]).to eq([]) }
diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb
index 6fb4794ea5f..8c4a366ef8f 100644
--- a/spec/models/commit_status_spec.rb
+++ b/spec/models/commit_status_spec.rb
@@ -425,7 +425,7 @@ describe CommitStatus do
end
it "raise exception when trying to update" do
- expect{ commit_status.save }.to raise_error(ActiveRecord::StaleObjectError)
+ expect { commit_status.save }.to raise_error(ActiveRecord::StaleObjectError)
end
end
diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb
index 0137f71be8f..dfbe1a7c192 100644
--- a/spec/models/concerns/issuable_spec.rb
+++ b/spec/models/concerns/issuable_spec.rb
@@ -300,7 +300,7 @@ describe Issuable do
let(:bug) { create(:label, project: project, title: 'bug') }
let(:issue) { create(:issue, project: project) }
- before(:each) do
+ before do
issue.labels << bug
end
@@ -402,7 +402,7 @@ describe Issuable do
let(:issue2) { create(:issue, title: "Bugfix2", project: project) }
let(:issue3) { create(:issue, title: "Feature1", project: project) }
- before(:each) do
+ before do
issue1.labels << bug
issue1.labels << feature
issue2.labels << bug
diff --git a/spec/models/concerns/reactive_caching_spec.rb b/spec/models/concerns/reactive_caching_spec.rb
index 5f9b7e0a367..a5d505af001 100644
--- a/spec/models/concerns/reactive_caching_spec.rb
+++ b/spec/models/concerns/reactive_caching_spec.rb
@@ -31,7 +31,7 @@ describe ReactiveCaching, :use_clean_rails_memory_store_caching do
let(:now) { Time.now.utc }
- around(:each) do |example|
+ around do |example|
Timecop.freeze(now) { example.run }
end
diff --git a/spec/models/conversational_development_index/metric_spec.rb b/spec/models/conversational_development_index/metric_spec.rb
new file mode 100644
index 00000000000..b3193619503
--- /dev/null
+++ b/spec/models/conversational_development_index/metric_spec.rb
@@ -0,0 +1,11 @@
+require 'rails_helper'
+
+describe ConversationalDevelopmentIndex::Metric do
+ let(:conv_dev_index) { create(:conversational_development_index_metric) }
+
+ describe '#percentage_score' do
+ it 'returns stored percentage score' do
+ expect(conv_dev_index.percentage_score('issues')).to eq(13.331)
+ end
+ end
+end
diff --git a/spec/models/deploy_key_spec.rb b/spec/models/deploy_key_spec.rb
index 2aece75b817..3d7283e2164 100644
--- a/spec/models/deploy_key_spec.rb
+++ b/spec/models/deploy_key_spec.rb
@@ -1,8 +1,6 @@
require 'spec_helper'
-describe DeployKey do
- include EmailHelpers
-
+describe DeployKey, :mailer do
describe "Associations" do
it { is_expected.to have_many(:deploy_keys_projects) }
it { is_expected.to have_many(:projects) }
diff --git a/spec/models/event_collection_spec.rb b/spec/models/event_collection_spec.rb
new file mode 100644
index 00000000000..e0a87c18cc7
--- /dev/null
+++ b/spec/models/event_collection_spec.rb
@@ -0,0 +1,51 @@
+require 'spec_helper'
+
+describe EventCollection do
+ describe '#to_a' do
+ let(:project) { create(:project_empty_repo) }
+ let(:projects) { Project.where(id: project.id) }
+ let(:user) { create(:user) }
+
+ before do
+ 20.times do
+ event = create(:push_event, project: project, author: user)
+
+ create(:push_event_payload, event: event)
+ end
+
+ create(:closed_issue_event, project: project, author: user)
+ end
+
+ it 'returns an Array of events' do
+ events = described_class.new(projects).to_a
+
+ expect(events).to be_an_instance_of(Array)
+ end
+
+ it 'applies a limit to the number of events' do
+ events = described_class.new(projects).to_a
+
+ expect(events.length).to eq(20)
+ end
+
+ it 'can paginate through events' do
+ events = described_class.new(projects, offset: 20).to_a
+
+ expect(events.length).to eq(1)
+ end
+
+ it 'returns an empty Array when crossing the maximum page number' do
+ events = described_class.new(projects, limit: 1, offset: 15).to_a
+
+ expect(events).to be_empty
+ end
+
+ it 'allows filtering of events using an EventFilter' do
+ filter = EventFilter.new(EventFilter.issue)
+ events = described_class.new(projects, filter: filter).to_a
+
+ expect(events.length).to eq(1)
+ expect(events[0].action).to eq(Event::CLOSED)
+ end
+ end
+end
diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb
index d86bf1a90a9..ff3224dd298 100644
--- a/spec/models/event_spec.rb
+++ b/spec/models/event_spec.rb
@@ -304,27 +304,15 @@ describe Event do
end
end
- def create_push_event(project, user, attrs = {})
- data = {
- before: Gitlab::Git::BLANK_SHA,
- after: "0220c11b9a3e6c69dc8fd35321254ca9a7b98f7e",
- ref: "refs/heads/master",
- user_id: user.id,
- user_name: user.name,
- repository: {
- name: project.name,
- url: "localhost/rubinius",
- description: "",
- homepage: "localhost/rubinius",
- private: true
- }
- }
-
- described_class.create({
- project: project,
- action: described_class::PUSHED,
- data: data,
- author_id: user.id
- }.merge!(attrs))
+ def create_push_event(project, user)
+ event = create(:push_event, project: project, author: user)
+
+ create(:push_event_payload,
+ event: event,
+ commit_to: '1cf19a015df3523caf0a1f9d40c98a267d6a2fc2',
+ commit_count: 0,
+ ref: 'master')
+
+ event
end
end
diff --git a/spec/models/gpg_key_spec.rb b/spec/models/gpg_key_spec.rb
index 59c074199db..e48f20bf53b 100644
--- a/spec/models/gpg_key_spec.rb
+++ b/spec/models/gpg_key_spec.rb
@@ -114,9 +114,7 @@ describe GpgKey do
end
end
- describe 'notification' do
- include EmailHelpers
-
+ describe 'notification', :mailer do
let(:user) { create(:user) }
it 'sends a notification' do
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index fa22eee3dea..9203f6562f2 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -57,18 +57,14 @@ describe Issue do
end
describe '#closed_at' do
- after do
- Timecop.return
- end
-
- let!(:now) { Timecop.freeze(Time.now) }
-
it 'sets closed_at to Time.now when issue is closed' do
issue = create(:issue, state: 'opened')
+ expect(issue.closed_at).to be_nil
+
issue.close
- expect(issue.closed_at).to eq(now)
+ expect(issue.closed_at).to be_present
end
end
@@ -191,14 +187,10 @@ describe Issue do
end
it 'returns the merge request to close this issue' do
- mr
-
expect(issue.closed_by_merge_requests(mr.author)).to eq([mr])
end
it "returns an empty array when the merge request is closed already" do
- closed_mr
-
expect(issue.closed_by_merge_requests(closed_mr.author)).to eq([])
end
@@ -354,7 +346,7 @@ describe Issue do
subject { create(:issue, project: create(:project, :repository)) }
let(:backref_text) { "issue #{subject.to_reference}" }
- let(:set_mentionable_text) { ->(txt){ subject.description = txt } }
+ let(:set_mentionable_text) { ->(txt) { subject.description = txt } }
end
it_behaves_like 'a Taskable' do
diff --git a/spec/models/key_spec.rb b/spec/models/key_spec.rb
index d41717d0223..3508391c721 100644
--- a/spec/models/key_spec.rb
+++ b/spec/models/key_spec.rb
@@ -1,8 +1,6 @@
require 'spec_helper'
-describe Key do
- include EmailHelpers
-
+describe Key, :mailer do
describe "Associations" do
it { is_expected.to belong_to(:user) }
end
@@ -85,24 +83,16 @@ describe Key do
expect(build(:key)).to be_valid
end
- it 'rejects an unfingerprintable key that contains a space' do
+ it 'accepts a key with newline charecters after stripping them' do
key = build(:key)
-
- # Not always the middle, but close enough
- key.key = key.key[0..100] + ' ' + key.key[101..-1]
-
- expect(key).not_to be_valid
+ key.key = key.key.insert(100, "\n")
+ key.key = key.key.insert(40, "\r\n")
+ expect(key).to be_valid
end
it 'rejects the unfingerprintable key (not a key)' do
expect(build(:key, key: 'ssh-rsa an-invalid-key==')).not_to be_valid
end
-
- it 'rejects the multiple line key' do
- key = build(:key)
- key.key.tr!(' ', "\n")
- expect(key).not_to be_valid
- end
end
context 'callbacks' do
diff --git a/spec/models/members/project_member_spec.rb b/spec/models/members/project_member_spec.rb
index f1d1f37c78a..fa3e80ba062 100644
--- a/spec/models/members/project_member_spec.rb
+++ b/spec/models/members/project_member_spec.rb
@@ -149,7 +149,7 @@ describe ProjectMember do
describe 'notifications' do
describe '#after_accept_request' do
it 'calls NotificationService.new_project_member' do
- member = create(:project_member, user: build_stubbed(:user), requested_at: Time.now)
+ member = create(:project_member, user: create(:user), requested_at: Time.now)
expect_any_instance_of(NotificationService).to receive(:new_project_member)
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 3402c260f27..026bdbd26d1 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -681,7 +681,7 @@ describe MergeRequest do
end
it 'does not crash' do
- expect{ subject.diverged_commits_count }.not_to raise_error
+ expect { subject.diverged_commits_count }.not_to raise_error
end
it 'returns 0' do
@@ -714,7 +714,7 @@ describe MergeRequest do
end
describe 'caching' do
- before(:example) do
+ before do
allow(Rails).to receive(:cache).and_return(ActiveSupport::Cache::MemoryStore.new)
end
@@ -753,7 +753,7 @@ describe MergeRequest do
subject { create(:merge_request, :simple) }
let(:backref_text) { "merge request #{subject.to_reference}" }
- let(:set_mentionable_text) { ->(txt){ subject.description = txt } }
+ let(:set_mentionable_text) { ->(txt) { subject.description = txt } }
end
it_behaves_like 'a Taskable' do
@@ -1369,6 +1369,32 @@ describe MergeRequest do
end
end
+ describe '#merge_ongoing?' do
+ it 'returns true when merge process is ongoing for merge_jid' do
+ merge_request = create(:merge_request, merge_jid: 'foo')
+
+ allow(Gitlab::SidekiqStatus).to receive(:num_running).with(['foo']).and_return(1)
+
+ expect(merge_request.merge_ongoing?).to be(true)
+ end
+
+ it 'returns false when no merge process running for merge_jid' do
+ merge_request = build(:merge_request, merge_jid: 'foo')
+
+ allow(Gitlab::SidekiqStatus).to receive(:num_running).with(['foo']).and_return(0)
+
+ expect(merge_request.merge_ongoing?).to be(false)
+ end
+
+ it 'returns false when merge_jid is nil' do
+ merge_request = build(:merge_request, merge_jid: nil)
+
+ expect(Gitlab::SidekiqStatus).not_to receive(:num_running)
+
+ expect(merge_request.merge_ongoing?).to be(false)
+ end
+ end
+
describe "#closed_without_fork?" do
let(:project) { create(:project) }
let(:fork_project) { create(:project, forked_from_project: project) }
diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb
index b48aa9558d5..d3da0107d5c 100644
--- a/spec/models/milestone_spec.rb
+++ b/spec/models/milestone_spec.rb
@@ -230,16 +230,40 @@ describe Milestone do
end
describe '#to_reference' do
- let(:project) { build(:project, name: 'sample-project') }
- let(:milestone) { build(:milestone, iid: 1, project: project) }
+ let(:group) { build_stubbed(:group) }
+ let(:project) { build_stubbed(:project, name: 'sample-project') }
+ let(:another_project) { build_stubbed(:project, name: 'another-project', namespace: project.namespace) }
+
+ context 'for a project milestone' do
+ let(:milestone) { build_stubbed(:milestone, iid: 1, project: project, name: 'milestone') }
+
+ it 'returns a String reference to the object' do
+ expect(milestone.to_reference).to eq '%1'
+ end
+
+ it 'returns a reference by name when the format is set to :name' do
+ expect(milestone.to_reference(format: :name)).to eq '%"milestone"'
+ end
- it 'returns a String reference to the object' do
- expect(milestone.to_reference).to eq "%1"
+ it 'supports a cross-project reference' do
+ expect(milestone.to_reference(another_project)).to eq 'sample-project%1'
+ end
end
- it 'supports a cross-project reference' do
- another_project = build(:project, name: 'another-project', namespace: project.namespace)
- expect(milestone.to_reference(another_project)).to eq "sample-project%1"
+ context 'for a group milestone' do
+ let(:milestone) { build_stubbed(:milestone, iid: 1, group: group, name: 'milestone') }
+
+ it 'returns nil with the default format' do
+ expect(milestone.to_reference).to be_nil
+ end
+
+ it 'returns a reference by name when the format is set to :name' do
+ expect(milestone.to_reference(format: :name)).to eq '%"milestone"'
+ end
+
+ it 'does not supports cross-project references' do
+ expect(milestone.to_reference(another_project, format: :name)).to eq '%"milestone"'
+ end
end
end
diff --git a/spec/models/project_services/drone_ci_service_spec.rb b/spec/models/project_services/drone_ci_service_spec.rb
index 5b0f24ce306..d8972aff407 100644
--- a/spec/models/project_services/drone_ci_service_spec.rb
+++ b/spec/models/project_services/drone_ci_service_spec.rb
@@ -43,7 +43,7 @@ describe DroneCiService, :use_clean_rails_memory_store_caching do
let(:build_page) { "#{drone_url}/gitlab/#{path}/redirect/commits/#{sha}?branch=#{branch}" }
let(:commit_status_path) { "#{drone_url}/gitlab/#{path}/commits/#{sha}?branch=#{branch}&access_token=#{token}" }
- before(:each) do
+ before do
allow(drone).to receive_messages(
project_id: project.id,
project: project,
diff --git a/spec/models/project_services/hipchat_service_spec.rb b/spec/models/project_services/hipchat_service_spec.rb
index 7614bb897e8..23db29cb541 100644
--- a/spec/models/project_services/hipchat_service_spec.rb
+++ b/spec/models/project_services/hipchat_service_spec.rb
@@ -36,7 +36,7 @@ describe HipchatService do
Gitlab::DataBuilder::Push.build_sample(project, user)
end
- before(:each) do
+ before do
allow(hipchat).to receive_messages(
project_id: project.id,
project: project,
diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb
index 204a00778a7..63bf131cfc5 100644
--- a/spec/models/project_services/jira_service_spec.rb
+++ b/spec/models/project_services/jira_service_spec.rb
@@ -153,6 +153,15 @@ describe JiraService do
expect(WebMock).not_to have_requested(:post, @remote_link_url)
end
+ it "does not send comment or remote links to issues with unknown resolution" do
+ allow_any_instance_of(JIRA::Resource::Issue).to receive(:respond_to?).with(:resolution).and_return(false)
+
+ @jira_service.close_issue(merge_request, ExternalIssue.new("JIRA-123", project))
+
+ expect(WebMock).not_to have_requested(:post, @comment_url)
+ expect(WebMock).not_to have_requested(:post, @remote_link_url)
+ end
+
it "references the GitLab commit/merge request" do
stub_config_setting(base_url: custom_base_url)
diff --git a/spec/models/project_services/pipelines_email_service_spec.rb b/spec/models/project_services/pipelines_email_service_spec.rb
index 03932895b0e..5faab9ba38b 100644
--- a/spec/models/project_services/pipelines_email_service_spec.rb
+++ b/spec/models/project_services/pipelines_email_service_spec.rb
@@ -1,8 +1,6 @@
require 'spec_helper'
-describe PipelinesEmailService do
- include EmailHelpers
-
+describe PipelinesEmailService, :mailer do
let(:pipeline) do
create(:ci_pipeline, project: project, sha: project.commit('master').sha)
end
@@ -14,10 +12,6 @@ describe PipelinesEmailService do
Gitlab::DataBuilder::Pipeline.build(pipeline)
end
- before do
- reset_delivered_emails!
- end
-
describe 'Validations' do
context 'when service is active' do
before do
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 8f951605954..d9ab44dc49f 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -485,7 +485,7 @@ describe Project do
describe 'last_activity' do
it 'alias last_activity to last_event' do
- last_event = create(:event, project: project)
+ last_event = create(:event, :closed, project: project)
expect(project.last_activity).to eq(last_event)
end
@@ -493,7 +493,7 @@ describe Project do
describe 'last_activity_date' do
it 'returns the creation date of the project\'s last event if present' do
- new_event = create(:event, project: project, created_at: Time.now)
+ new_event = create(:event, :closed, project: project, created_at: Time.now)
project.reload
expect(project.last_activity_at.to_i).to eq(new_event.created_at.to_i)
@@ -651,7 +651,7 @@ describe Project do
let(:ext_project) { create(:redmine_project) }
context 'on existing projects with no value for has_external_issue_tracker' do
- before(:each) do
+ before do
project.update_column(:has_external_issue_tracker, nil)
ext_project.update_column(:has_external_issue_tracker, nil)
end
@@ -1301,7 +1301,7 @@ describe Project do
subject { project.rename_repo }
- it { expect{subject}.to raise_error(StandardError) }
+ it { expect {subject}.to raise_error(StandardError) }
end
end
@@ -1832,6 +1832,11 @@ describe Project do
describe '#change_head' do
let(:project) { create(:project, :repository) }
+ it 'returns error if branch does not exist' do
+ expect(project.change_head('unexisted-branch')).to be false
+ expect(project.errors.size).to eq(1)
+ end
+
it 'calls the before_change_head and after_change_head methods' do
expect(project.repository).to receive(:before_change_head)
expect(project.repository).to receive(:after_change_head)
diff --git a/spec/models/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb
index 6e33431bbe9..953df7746eb 100644
--- a/spec/models/project_wiki_spec.rb
+++ b/spec/models/project_wiki_spec.rb
@@ -223,7 +223,12 @@ describe ProjectWiki do
before do
create_page("update-page", "some content")
@gollum_page = subject.wiki.paged("update-page")
- subject.update_page(@gollum_page, "some other content", :markdown, "updated page")
+ subject.update_page(
+ @gollum_page,
+ content: "some other content",
+ format: :markdown,
+ message: "updated page"
+ )
@page = subject.pages.first.page
end
@@ -240,7 +245,12 @@ describe ProjectWiki do
end
it 'updates project activity' do
- subject.update_page(@gollum_page, 'Yet more content', :markdown, 'Updated page again')
+ subject.update_page(
+ @gollum_page,
+ content: 'Yet more content',
+ format: :markdown,
+ message: 'Updated page again'
+ )
project.reload
diff --git a/spec/models/push_event_payload_spec.rb b/spec/models/push_event_payload_spec.rb
new file mode 100644
index 00000000000..a049ad35584
--- /dev/null
+++ b/spec/models/push_event_payload_spec.rb
@@ -0,0 +1,16 @@
+require 'spec_helper'
+
+describe PushEventPayload do
+ describe 'saving payloads' do
+ it 'does not allow commit messages longer than 70 characters' do
+ event = create(:push_event)
+ payload = build(:push_event_payload, event: event)
+
+ expect(payload).to be_valid
+
+ payload.commit_title = 'a' * 100
+
+ expect(payload).not_to be_valid
+ end
+ end
+end
diff --git a/spec/models/push_event_spec.rb b/spec/models/push_event_spec.rb
new file mode 100644
index 00000000000..532fb024261
--- /dev/null
+++ b/spec/models/push_event_spec.rb
@@ -0,0 +1,202 @@
+require 'spec_helper'
+
+describe PushEvent do
+ let(:payload) { PushEventPayload.new }
+
+ let(:event) do
+ event = described_class.new
+
+ allow(event).to receive(:push_event_payload).and_return(payload)
+
+ event
+ end
+
+ describe '.sti_name' do
+ it 'returns Event::PUSHED' do
+ expect(described_class.sti_name).to eq(Event::PUSHED)
+ end
+ end
+
+ describe '#push?' do
+ it 'returns true' do
+ expect(event).to be_push
+ end
+ end
+
+ describe '#push_with_commits?' do
+ it 'returns true when both the first and last commit are present' do
+ allow(event).to receive(:commit_from).and_return('123')
+ allow(event).to receive(:commit_to).and_return('456')
+
+ expect(event).to be_push_with_commits
+ end
+
+ it 'returns false when the first commit is missing' do
+ allow(event).to receive(:commit_to).and_return('456')
+
+ expect(event).not_to be_push_with_commits
+ end
+
+ it 'returns false when the last commit is missing' do
+ allow(event).to receive(:commit_from).and_return('123')
+
+ expect(event).not_to be_push_with_commits
+ end
+ end
+
+ describe '#tag?' do
+ it 'returns true when pushing to a tag' do
+ allow(payload).to receive(:tag?).and_return(true)
+
+ expect(event).to be_tag
+ end
+
+ it 'returns false when pushing to a branch' do
+ allow(payload).to receive(:tag?).and_return(false)
+
+ expect(event).not_to be_tag
+ end
+ end
+
+ describe '#branch?' do
+ it 'returns true when pushing to a branch' do
+ allow(payload).to receive(:branch?).and_return(true)
+
+ expect(event).to be_branch
+ end
+
+ it 'returns false when pushing to a tag' do
+ allow(payload).to receive(:branch?).and_return(false)
+
+ expect(event).not_to be_branch
+ end
+ end
+
+ describe '#valid_push?' do
+ it 'returns true if a ref exists' do
+ allow(payload).to receive(:ref).and_return('master')
+
+ expect(event).to be_valid_push
+ end
+
+ it 'returns false when no ref is present' do
+ expect(event).not_to be_valid_push
+ end
+ end
+
+ describe '#new_ref?' do
+ it 'returns true when pushing a new ref' do
+ allow(payload).to receive(:created?).and_return(true)
+
+ expect(event).to be_new_ref
+ end
+
+ it 'returns false when pushing to an existing ref' do
+ allow(payload).to receive(:created?).and_return(false)
+
+ expect(event).not_to be_new_ref
+ end
+ end
+
+ describe '#rm_ref?' do
+ it 'returns true when removing an existing ref' do
+ allow(payload).to receive(:removed?).and_return(true)
+
+ expect(event).to be_rm_ref
+ end
+
+ it 'returns false when pushing to an existing ref' do
+ allow(payload).to receive(:removed?).and_return(false)
+
+ expect(event).not_to be_rm_ref
+ end
+ end
+
+ describe '#commit_from' do
+ it 'returns the first commit SHA' do
+ allow(payload).to receive(:commit_from).and_return('123')
+
+ expect(event.commit_from).to eq('123')
+ end
+ end
+
+ describe '#commit_to' do
+ it 'returns the last commit SHA' do
+ allow(payload).to receive(:commit_to).and_return('123')
+
+ expect(event.commit_to).to eq('123')
+ end
+ end
+
+ describe '#ref_name' do
+ it 'returns the name of the ref' do
+ allow(payload).to receive(:ref).and_return('master')
+
+ expect(event.ref_name).to eq('master')
+ end
+ end
+
+ describe '#ref_type' do
+ it 'returns the type of the ref' do
+ allow(payload).to receive(:ref_type).and_return('branch')
+
+ expect(event.ref_type).to eq('branch')
+ end
+ end
+
+ describe '#branch_name' do
+ it 'returns the name of the branch' do
+ allow(payload).to receive(:ref).and_return('master')
+
+ expect(event.branch_name).to eq('master')
+ end
+ end
+
+ describe '#tag_name' do
+ it 'returns the name of the tag' do
+ allow(payload).to receive(:ref).and_return('1.2')
+
+ expect(event.tag_name).to eq('1.2')
+ end
+ end
+
+ describe '#commit_title' do
+ it 'returns the commit message' do
+ allow(payload).to receive(:commit_title).and_return('foo')
+
+ expect(event.commit_title).to eq('foo')
+ end
+ end
+
+ describe '#commit_id' do
+ it 'returns the SHA of the last commit if present' do
+ allow(event).to receive(:commit_to).and_return('123')
+
+ expect(event.commit_id).to eq('123')
+ end
+
+ it 'returns the SHA of the first commit if the last commit is not present' do
+ allow(event).to receive(:commit_to).and_return(nil)
+ allow(event).to receive(:commit_from).and_return('123')
+
+ expect(event.commit_id).to eq('123')
+ end
+ end
+
+ describe '#commits_count' do
+ it 'returns the number of commits' do
+ allow(payload).to receive(:commit_count).and_return(1)
+
+ expect(event.commits_count).to eq(1)
+ end
+ end
+
+ describe '#validate_push_action' do
+ it 'adds an error when the action is not PUSHED' do
+ event.action = Event::CREATED
+ event.validate_push_action
+
+ expect(event.errors.count).to eq(1)
+ end
+ end
+end
diff --git a/spec/models/redirect_route_spec.rb b/spec/models/redirect_route_spec.rb
index 80943877095..106ae59af29 100644
--- a/spec/models/redirect_route_spec.rb
+++ b/spec/models/redirect_route_spec.rb
@@ -20,8 +20,16 @@ describe RedirectRoute do
let!(:redirect4) { group.redirect_routes.create(path: 'gitlabb/test/foo/bar') }
let!(:redirect5) { group.redirect_routes.create(path: 'gitlabb/test/baz') }
- it 'returns correct routes' do
- expect(described_class.matching_path_and_descendants('gitlabb/test')).to match_array([redirect2, redirect3, redirect4, redirect5])
+ context 'when the redirect route matches with same casing' do
+ it 'returns correct routes' do
+ expect(described_class.matching_path_and_descendants('gitlabb/test')).to match_array([redirect2, redirect3, redirect4, redirect5])
+ end
+ end
+
+ context 'when the redirect route matches with different casing' do
+ it 'returns correct routes' do
+ expect(described_class.matching_path_and_descendants('GitLABB/test')).to match_array([redirect2, redirect3, redirect4, redirect5])
+ end
end
end
end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 764f548be45..4926d5d6c49 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -1,11 +1,12 @@
require 'spec_helper'
-describe Repository do
+describe Repository, models: true do
include RepoHelpers
TestBlob = Struct.new(:path)
let(:project) { create(:project, :repository) }
let(:repository) { project.repository }
+ let(:broken_repository) { create(:project, :broken_storage).repository }
let(:user) { create(:user) }
let(:commit_options) do
@@ -27,12 +28,27 @@ describe Repository do
let(:author_email) { 'user@example.org' }
let(:author_name) { 'John Doe' }
+ def expect_to_raise_storage_error
+ expect { yield }.to raise_error do |exception|
+ storage_exceptions = [Gitlab::Git::Storage::Inaccessible, Gitlab::Git::CommandError, GRPC::Unavailable]
+ expect(exception.class).to be_in(storage_exceptions)
+ end
+ end
+
describe '#branch_names_contains' do
subject { repository.branch_names_contains(sample_commit.id) }
it { is_expected.to include('master') }
it { is_expected.not_to include('feature') }
it { is_expected.not_to include('fix') }
+
+ describe 'when storage is broken', broken_storage: true do
+ it 'should raise a storage error' do
+ expect_to_raise_storage_error do
+ broken_repository.branch_names_contains(sample_commit.id)
+ end
+ end
+ end
end
describe '#tag_names_contains' do
@@ -139,24 +155,60 @@ describe Repository do
end
describe '#last_commit_for_path' do
- subject { repository.last_commit_for_path(sample_commit.id, '.gitignore').id }
+ shared_examples 'getting last commit for path' do
+ subject { repository.last_commit_for_path(sample_commit.id, '.gitignore').id }
+
+ it { is_expected.to eq('c1acaa58bbcbc3eafe538cb8274ba387047b69f8') }
+
+ describe 'when storage is broken', broken_storage: true do
+ it 'should raise a storage error' do
+ expect_to_raise_storage_error do
+ broken_repository.last_commit_id_for_path(sample_commit.id, '.gitignore')
+ end
+ end
+ end
+ end
+
+ context 'when Gitaly feature last_commit_for_path is enabled' do
+ it_behaves_like 'getting last commit for path'
+ end
- it { is_expected.to eq('c1acaa58bbcbc3eafe538cb8274ba387047b69f8') }
+ context 'when Gitaly feature last_commit_for_path is disabled', skip_gitaly_mock: true do
+ it_behaves_like 'getting last commit for path'
+ end
end
describe '#last_commit_id_for_path' do
- subject { repository.last_commit_id_for_path(sample_commit.id, '.gitignore') }
+ shared_examples 'getting last commit ID for path' do
+ subject { repository.last_commit_id_for_path(sample_commit.id, '.gitignore') }
+
+ it "returns last commit id for a given path" do
+ is_expected.to eq('c1acaa58bbcbc3eafe538cb8274ba387047b69f8')
+ end
+
+ it "caches last commit id for a given path" do
+ cache = repository.send(:cache)
+ key = "last_commit_id_for_path:#{sample_commit.id}:#{Digest::SHA1.hexdigest('.gitignore')}"
- it "returns last commit id for a given path" do
- is_expected.to eq('c1acaa58bbcbc3eafe538cb8274ba387047b69f8')
+ expect(cache).to receive(:fetch).with(key).and_return('c1acaa5')
+ is_expected.to eq('c1acaa5')
+ end
+
+ describe 'when storage is broken', broken_storage: true do
+ it 'should raise a storage error' do
+ expect_to_raise_storage_error do
+ broken_repository.last_commit_for_path(sample_commit.id, '.gitignore').id
+ end
+ end
+ end
end
- it "caches last commit id for a given path" do
- cache = repository.send(:cache)
- key = "last_commit_id_for_path:#{sample_commit.id}:#{Digest::SHA1.hexdigest('.gitignore')}"
+ context 'when Gitaly feature last_commit_for_path is enabled' do
+ it_behaves_like 'getting last commit ID for path'
+ end
- expect(cache).to receive(:fetch).with(key).and_return('c1acaa5')
- is_expected.to eq('c1acaa5')
+ context 'when Gitaly feature last_commit_for_path is disabled', skip_gitaly_mock: true do
+ it_behaves_like 'getting last commit ID for path'
end
end
@@ -182,19 +234,37 @@ describe Repository do
end
describe '#find_commits_by_message' do
- it 'returns commits with messages containing a given string' do
- commit_ids = repository.find_commits_by_message('submodule').map(&:id)
+ shared_examples 'finding commits by message' do
+ it 'returns commits with messages containing a given string' do
+ commit_ids = repository.find_commits_by_message('submodule').map(&:id)
+
+ expect(commit_ids).to include(
+ '5937ac0a7beb003549fc5fd26fc247adbce4a52e',
+ '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9',
+ 'cfe32cf61b73a0d5e9f13e774abde7ff789b1660'
+ )
+ expect(commit_ids).not_to include('913c66a37b4a45b9769037c55c2d238bd0942d2e')
+ end
+
+ it 'is case insensitive' do
+ commit_ids = repository.find_commits_by_message('SUBMODULE').map(&:id)
+
+ expect(commit_ids).to include('5937ac0a7beb003549fc5fd26fc247adbce4a52e')
+ end
+ end
- expect(commit_ids).to include('5937ac0a7beb003549fc5fd26fc247adbce4a52e')
- expect(commit_ids).to include('6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9')
- expect(commit_ids).to include('cfe32cf61b73a0d5e9f13e774abde7ff789b1660')
- expect(commit_ids).not_to include('913c66a37b4a45b9769037c55c2d238bd0942d2e')
+ context 'when Gitaly commits_by_message feature is enabled' do
+ it_behaves_like 'finding commits by message'
end
- it 'is case insensitive' do
- commit_ids = repository.find_commits_by_message('SUBMODULE').map(&:id)
+ context 'when Gitaly commits_by_message feature is disabled', skip_gitaly_mock: true do
+ it_behaves_like 'finding commits by message'
+ end
- expect(commit_ids).to include('5937ac0a7beb003549fc5fd26fc247adbce4a52e')
+ describe 'when storage is broken', broken_storage: true do
+ it 'should raise a storage error' do
+ expect_to_raise_storage_error { broken_repository.find_commits_by_message('s') }
+ end
end
end
@@ -521,6 +591,14 @@ describe Repository do
expect(results).to match_array([])
end
+ describe 'when storage is broken', broken_storage: true do
+ it 'should raise a storage error' do
+ expect_to_raise_storage_error do
+ broken_repository.search_files_by_content('feature', 'master')
+ end
+ end
+ end
+
describe 'result' do
subject { results.first }
@@ -549,6 +627,22 @@ describe Repository do
expect(results).to match_array([])
end
+
+ describe 'when storage is broken', broken_storage: true do
+ it 'should raise a storage error' do
+ expect_to_raise_storage_error { broken_repository.search_files_by_name('files', 'master') }
+ end
+ end
+ end
+
+ describe '#fetch_ref' do
+ describe 'when storage is broken', broken_storage: true do
+ it 'should raise a storage error' do
+ path = broken_repository.path_to_repo
+
+ expect_to_raise_storage_error { broken_repository.fetch_ref(path, '1', '2') }
+ end
+ end
end
describe '#create_ref' do
@@ -867,6 +961,27 @@ describe Repository do
end
end
+ context 'when temporary ref failed to be created from other project' do
+ let(:target_project) { create(:project, :empty_repo) }
+
+ before do
+ expect(target_project.repository).to receive(:run_git)
+ end
+
+ it 'raises Rugged::ReferenceError' do
+ raise_reference_error = raise_error(Rugged::ReferenceError) do |err|
+ expect(err.cause).to be_nil
+ end
+
+ expect do
+ GitOperationService.new(user, target_project.repository)
+ .with_branch('feature',
+ start_project: project,
+ &:itself)
+ end.to raise_reference_error
+ end
+ end
+
context 'when the update adds more than one commit' do
let(:old_rev) { '33f3729a45c02fc67d00adb1b8bca394b0e761d9' }
@@ -966,6 +1081,12 @@ describe Repository do
expect(repository.exists?).to eq(false)
end
+
+ context 'with broken storage', broken_storage: true do
+ it 'should raise a storage error' do
+ expect_to_raise_storage_error { broken_repository.exists? }
+ end
+ end
end
describe '#exists?' do
@@ -1124,7 +1245,7 @@ describe Repository do
end
describe 'skip_merges option' do
- subject { repository.commits(Gitlab::Git::BRANCH_REF_PREFIX + "'test'", limit: 100, skip_merges: true).map{ |k| k.id } }
+ subject { repository.commits(Gitlab::Git::BRANCH_REF_PREFIX + "'test'", limit: 100, skip_merges: true).map { |k| k.id } }
it { is_expected.not_to include('e56497bb5f03a90a51293fc6d516788730953899') }
end
diff --git a/spec/models/route_spec.rb b/spec/models/route_spec.rb
index bdacc60fb53..fece370c03f 100644
--- a/spec/models/route_spec.rb
+++ b/spec/models/route_spec.rb
@@ -145,45 +145,71 @@ describe Route do
describe '#delete_conflicting_redirects' do
context 'when a redirect route with the same path exists' do
- let!(:redirect1) { route.create_redirect(route.path) }
+ context 'when the redirect route has matching case' do
+ let!(:redirect1) { route.create_redirect(route.path) }
- it 'deletes the redirect' do
- route.delete_conflicting_redirects
- expect(route.conflicting_redirects).to be_empty
+ it 'deletes the redirect' do
+ expect do
+ route.delete_conflicting_redirects
+ end.to change { RedirectRoute.count }.by(-1)
+ end
+
+ context 'when redirect routes with paths descending from the route path exists' do
+ let!(:redirect2) { route.create_redirect("#{route.path}/foo") }
+ let!(:redirect3) { route.create_redirect("#{route.path}/foo/bar") }
+ let!(:redirect4) { route.create_redirect("#{route.path}/baz/quz") }
+ let!(:other_redirect) { route.create_redirect("other") }
+
+ it 'deletes all redirects with paths that descend from the route path' do
+ expect do
+ route.delete_conflicting_redirects
+ end.to change { RedirectRoute.count }.by(-4)
+ end
+ end
end
- context 'when redirect routes with paths descending from the route path exists' do
- let!(:redirect2) { route.create_redirect("#{route.path}/foo") }
- let!(:redirect3) { route.create_redirect("#{route.path}/foo/bar") }
- let!(:redirect4) { route.create_redirect("#{route.path}/baz/quz") }
- let!(:other_redirect) { route.create_redirect("other") }
+ context 'when the redirect route is differently cased' do
+ let!(:redirect1) { route.create_redirect(route.path.upcase) }
- it 'deletes all redirects with paths that descend from the route path' do
- route.delete_conflicting_redirects
- expect(route.conflicting_redirects).to be_empty
+ it 'deletes the redirect' do
+ expect do
+ route.delete_conflicting_redirects
+ end.to change { RedirectRoute.count }.by(-1)
end
end
end
end
describe '#conflicting_redirects' do
+ it 'returns an ActiveRecord::Relation' do
+ expect(route.conflicting_redirects).to be_an(ActiveRecord::Relation)
+ end
+
context 'when a redirect route with the same path exists' do
- let!(:redirect1) { route.create_redirect(route.path) }
+ context 'when the redirect route has matching case' do
+ let!(:redirect1) { route.create_redirect(route.path) }
- it 'returns the redirect route' do
- expect(route.conflicting_redirects).to be_an(ActiveRecord::Relation)
- expect(route.conflicting_redirects).to match_array([redirect1])
+ it 'returns the redirect route' do
+ expect(route.conflicting_redirects).to match_array([redirect1])
+ end
+
+ context 'when redirect routes with paths descending from the route path exists' do
+ let!(:redirect2) { route.create_redirect("#{route.path}/foo") }
+ let!(:redirect3) { route.create_redirect("#{route.path}/foo/bar") }
+ let!(:redirect4) { route.create_redirect("#{route.path}/baz/quz") }
+ let!(:other_redirect) { route.create_redirect("other") }
+
+ it 'returns the redirect routes' do
+ expect(route.conflicting_redirects).to match_array([redirect1, redirect2, redirect3, redirect4])
+ end
+ end
end
- context 'when redirect routes with paths descending from the route path exists' do
- let!(:redirect2) { route.create_redirect("#{route.path}/foo") }
- let!(:redirect3) { route.create_redirect("#{route.path}/foo/bar") }
- let!(:redirect4) { route.create_redirect("#{route.path}/baz/quz") }
- let!(:other_redirect) { route.create_redirect("other") }
+ context 'when the redirect route is differently cased' do
+ let!(:redirect1) { route.create_redirect(route.path.upcase) }
- it 'returns the redirect routes' do
- expect(route.conflicting_redirects).to be_an(ActiveRecord::Relation)
- expect(route.conflicting_redirects).to match_array([redirect1, redirect2, redirect3, redirect4])
+ it 'returns the redirect route' do
+ expect(route.conflicting_redirects).to match_array([redirect1])
end
end
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index a6bd6052006..97bb91a6ac8 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -118,6 +118,17 @@ describe User do
expect(user).to validate_uniqueness_of(:username).case_insensitive
end
+
+ context 'when username is changed' do
+ let(:user) { build_stubbed(:user, username: 'old_path', namespace: build_stubbed(:namespace)) }
+
+ it 'validates move_dir is allowed for the namespace' do
+ expect(user.namespace).to receive(:any_project_has_container_registry_tags?).and_return(true)
+ user.username = 'new_path'
+ expect(user).to be_invalid
+ expect(user.errors.messages[:username].first).to match('cannot be changed if a personal project has container registry tags')
+ end
+ end
end
it { is_expected.to validate_presence_of(:projects_limit) }
@@ -1280,7 +1291,7 @@ describe User do
let!(:project2) { create(:project, forked_from_project: project3) }
let!(:project3) { create(:project) }
let!(:merge_request) { create(:merge_request, source_project: project2, target_project: project3, author: subject) }
- let!(:push_event) { create(:event, :pushed, project: project1, target: project1, author: subject) }
+ let!(:push_event) { create(:push_event, project: project1, author: subject) }
let!(:merge_event) { create(:event, :created, project: project3, target: merge_request, author: subject) }
before do
@@ -1322,10 +1333,18 @@ describe User do
subject { create(:user) }
let!(:project1) { create(:project, :repository) }
let!(:project2) { create(:project, :repository, forked_from_project: project1) }
- let!(:push_data) do
- Gitlab::DataBuilder::Push.build_sample(project2, subject)
+
+ let!(:push_event) do
+ event = create(:push_event, project: project2, author: subject)
+
+ create(:push_event_payload,
+ event: event,
+ commit_to: '1cf19a015df3523caf0a1f9d40c98a267d6a2fc2',
+ commit_count: 0,
+ ref: 'master')
+
+ event
end
- let!(:push_event) { create(:event, :pushed, project: project2, target: project1, author: subject, data: push_data) }
before do
project1.team << [subject, :master]
@@ -1352,8 +1371,13 @@ describe User do
expect(subject.recent_push(project1)).to eq(nil)
expect(subject.recent_push(project2)).to eq(push_event)
- push_data1 = Gitlab::DataBuilder::Push.build_sample(project1, subject)
- push_event1 = create(:event, :pushed, project: project1, target: project1, author: subject, data: push_data1)
+ push_event1 = create(:push_event, project: project1, author: subject)
+
+ create(:push_event_payload,
+ event: push_event1,
+ commit_to: '1cf19a015df3523caf0a1f9d40c98a267d6a2fc2',
+ commit_count: 0,
+ ref: 'master')
expect(subject.recent_push([project1, project2])).to eq(push_event1) # Newest
end
@@ -1976,4 +2000,28 @@ describe User do
expect(user.allow_password_authentication?).to be_falsey
end
end
+
+ describe '#personal_projects_count' do
+ it 'returns the number of personal projects using a single query' do
+ user = build(:user)
+ projects = double(:projects, count: 1)
+
+ expect(user).to receive(:personal_projects).once.and_return(projects)
+
+ 2.times do
+ expect(user.personal_projects_count).to eq(1)
+ end
+ end
+ end
+
+ describe '#projects_limit_left' do
+ it 'returns the number of projects that can be created by the user' do
+ user = build(:user)
+
+ allow(user).to receive(:projects_limit).and_return(10)
+ allow(user).to receive(:personal_projects_count).and_return(5)
+
+ expect(user.projects_limit_left).to eq(5)
+ end
+ end
end
diff --git a/spec/models/wiki_page_spec.rb b/spec/models/wiki_page_spec.rb
index b7eb412a8de..40a222be24d 100644
--- a/spec/models/wiki_page_spec.rb
+++ b/spec/models/wiki_page_spec.rb
@@ -178,12 +178,12 @@ describe WikiPage do
end
it "updates the content of the page" do
- @page.update("new content")
+ @page.update(content: "new content")
@page = wiki.find_page(title)
end
it "returns true" do
- expect(@page.update("more content")).to be_truthy
+ expect(@page.update(content: "more content")).to be_truthy
end
end
end
@@ -195,29 +195,42 @@ describe WikiPage do
end
after do
- destroy_page("Update")
+ destroy_page(@page.title)
end
context "with valid attributes" do
it "updates the content of the page" do
- @page.update("new content")
+ new_content = "new content"
+
+ @page.update(content: new_content)
@page = wiki.find_page("Update")
+
+ expect(@page.content).to eq("new content")
+ end
+
+ it "updates the title of the page" do
+ new_title = "Index v.1.2.4"
+
+ @page.update(title: new_title)
+ @page = wiki.find_page(new_title)
+
+ expect(@page.title).to eq(new_title)
end
it "returns true" do
- expect(@page.update("more content")).to be_truthy
+ expect(@page.update(content: "more content")).to be_truthy
end
end
context 'with same last commit sha' do
it 'returns true' do
- expect(@page.update('more content', last_commit_sha: @page.last_commit_sha)).to be_truthy
+ expect(@page.update(content: 'more content', last_commit_sha: @page.last_commit_sha)).to be_truthy
end
end
context 'with different last commit sha' do
it 'raises exception' do
- expect { @page.update('more content', last_commit_sha: 'xxx') }.to raise_error(WikiPage::PageChangedError)
+ expect { @page.update(content: 'more content', last_commit_sha: 'xxx') }.to raise_error(WikiPage::PageChangedError)
end
end
end
@@ -249,7 +262,7 @@ describe WikiPage do
end
it "returns an array of all commits for the page" do
- 3.times { |i| @page.update("content #{i}") }
+ 3.times { |i| @page.update(content: "content #{i}") }
expect(@page.versions.count).to eq(4)
end
end
@@ -294,7 +307,7 @@ describe WikiPage do
before do
create_page('Update', 'content')
@page = wiki.find_page('Update')
- 3.times { |i| @page.update("content #{i}") }
+ 3.times { |i| @page.update(content: "content #{i}") }
end
after do
@@ -338,7 +351,7 @@ describe WikiPage do
end
it 'returns false for updated wiki page' do
- updated_wiki_page = original_wiki_page.update("Updated content")
+ updated_wiki_page = original_wiki_page.update(content: "Updated content")
expect(original_wiki_page).not_to eq(updated_wiki_page)
end
end
@@ -360,7 +373,7 @@ describe WikiPage do
it 'is changed after page updated' do
last_commit_sha_before_update = @page.last_commit_sha
- @page.update("new content")
+ @page.update(content: "new content")
@page = wiki.find_page("Update")
expect(@page.last_commit_sha).not_to eq last_commit_sha_before_update
diff --git a/spec/presenters/conversational_development_index/metric_presenter_spec.rb b/spec/presenters/conversational_development_index/metric_presenter_spec.rb
index 1e015c71f5b..81eb05a9a6b 100644
--- a/spec/presenters/conversational_development_index/metric_presenter_spec.rb
+++ b/spec/presenters/conversational_development_index/metric_presenter_spec.rb
@@ -8,9 +8,9 @@ describe ConversationalDevelopmentIndex::MetricPresenter do
it 'includes instance score, leader score and percentage score' do
issues_card = subject.cards.first
- expect(issues_card.instance_score).to eq 1.234
- expect(issues_card.leader_score).to eq 9.256
- expect(issues_card.percentage_score).to be_within(0.1).of(13.3)
+ expect(issues_card.instance_score).to eq(1.234)
+ expect(issues_card.leader_score).to eq(9.256)
+ expect(issues_card.percentage_score).to eq(13.331)
end
end
diff --git a/spec/requests/api/circuit_breakers_spec.rb b/spec/requests/api/circuit_breakers_spec.rb
new file mode 100644
index 00000000000..76521e55994
--- /dev/null
+++ b/spec/requests/api/circuit_breakers_spec.rb
@@ -0,0 +1,57 @@
+require 'spec_helper'
+
+describe API::CircuitBreakers do
+ let(:user) { create(:user) }
+ let(:admin) { create(:admin) }
+
+ describe 'GET circuit_breakers/repository_storage' do
+ it 'returns a 401 for anonymous users' do
+ get api('/circuit_breakers/repository_storage')
+
+ expect(response).to have_http_status(401)
+ end
+
+ it 'returns a 403 for users' do
+ get api('/circuit_breakers/repository_storage', user)
+
+ expect(response).to have_http_status(403)
+ end
+
+ it 'returns an Array of storages' do
+ expect(Gitlab::Git::Storage::Health).to receive(:for_all_storages) do
+ [Gitlab::Git::Storage::Health.new('broken', [{ name: 'prefix:broken:web01', failure_count: 4 }])]
+ end
+
+ get api('/circuit_breakers/repository_storage', admin)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_kind_of(Array)
+ expect(json_response.first['storage_name']).to eq('broken')
+ expect(json_response.first['failing_on_hosts']).to eq(['web01'])
+ expect(json_response.first['total_failures']).to eq(4)
+ end
+
+ describe 'GET circuit_breakers/repository_storage/failing' do
+ it 'returns an array of failing storages' do
+ expect(Gitlab::Git::Storage::Health).to receive(:for_failing_storages) do
+ [Gitlab::Git::Storage::Health.new('broken', [{ name: 'prefix:broken:web01', failure_count: 4 }])]
+ end
+
+ get api('/circuit_breakers/repository_storage/failing', admin)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_kind_of(Array)
+ end
+ end
+ end
+
+ describe 'DELETE circuit_breakers/repository_storage' do
+ it 'clears all circuit_breakers' do
+ expect(Gitlab::Git::Storage::CircuitBreaker).to receive(:reset_all!)
+
+ delete api('/circuit_breakers/repository_storage', admin)
+
+ expect(response).to have_http_status(204)
+ end
+ end
+end
diff --git a/spec/requests/api/commit_statuses_spec.rb b/spec/requests/api/commit_statuses_spec.rb
index 8b62aa268d9..3c02e6302b4 100644
--- a/spec/requests/api/commit_statuses_spec.rb
+++ b/spec/requests/api/commit_statuses_spec.rb
@@ -44,8 +44,8 @@ describe API::CommitStatuses do
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(statuses_id).to contain_exactly(status3.id, status4.id, status5.id, status6.id)
- json_response.sort_by!{ |status| status['id'] }
- expect(json_response.map{ |status| status['allow_failure'] }).to eq([true, false, false, false])
+ json_response.sort_by! { |status| status['id'] }
+ expect(json_response.map { |status| status['allow_failure'] }).to eq([true, false, false, false])
end
end
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index 0dad547735d..992a6e8d76a 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -3,29 +3,27 @@ require 'mime/types'
describe API::Commits do
let(:user) { create(:user) }
- let(:user2) { create(:user) }
- let!(:project) { create(:project, :repository, creator: user, namespace: user.namespace) }
- let!(:guest) { create(:project_member, :guest, user: user2, project: project) }
- let!(:note) { create(:note_on_commit, author: user, project: project, commit_id: project.repository.commit.id, note: 'a comment on a commit') }
- let!(:another_note) { create(:note_on_commit, author: user, project: project, commit_id: project.repository.commit.id, note: 'another comment on a commit') }
+ let(:guest) { create(:user).tap { |u| project.add_guest(u) } }
+ let(:project) { create(:project, :repository, creator: user, path: 'my.project') }
+ let(:branch_with_dot) { project.repository.find_branch('ends-with.json') }
+ let(:branch_with_slash) { project.repository.find_branch('improve/awesome') }
+
+ let(:project_id) { project.id }
+ let(:current_user) { nil }
before do
- project.team << [user, :reporter]
+ project.add_master(user)
end
- describe "List repository commits" do
- context "authorized user" do
- before do
- project.team << [user2, :reporter]
- end
-
+ describe 'GET /projects/:id/repository/commits' do
+ context 'authorized user' do
it "returns project commits" do
commit = project.repository.commit
- get api("/projects/#{project.id}/repository/commits", user)
+ get api("/projects/#{project_id}/repository/commits", user)
expect(response).to have_http_status(200)
- expect(json_response).to be_an Array
+ expect(response).to match_response_schema('public_api/v4/commits')
expect(json_response.first['id']).to eq(commit.id)
expect(json_response.first['committer_name']).to eq(commit.committer_name)
expect(json_response.first['committer_email']).to eq(commit.committer_email)
@@ -34,7 +32,7 @@ describe API::Commits do
it 'include correct pagination headers' do
commit_count = project.repository.count_commits(ref: 'master').to_s
- get api("/projects/#{project.id}/repository/commits", user)
+ get api("/projects/#{project_id}/repository/commits", user)
expect(response).to include_pagination_headers
expect(response.headers['X-Total']).to eq(commit_count)
@@ -44,8 +42,9 @@ describe API::Commits do
context "unauthorized user" do
it "does not return project commits" do
- get api("/projects/#{project.id}/repository/commits")
- expect(response).to have_http_status(401)
+ get api("/projects/#{project_id}/repository/commits")
+
+ expect(response).to have_http_status(404)
end
end
@@ -54,7 +53,7 @@ describe API::Commits do
commits = project.repository.commits("master")
after = commits.second.created_at
- get api("/projects/#{project.id}/repository/commits?since=#{after.utc.iso8601}", user)
+ get api("/projects/#{project_id}/repository/commits?since=#{after.utc.iso8601}", user)
expect(json_response.size).to eq 2
expect(json_response.first["id"]).to eq(commits.first.id)
@@ -66,7 +65,7 @@ describe API::Commits do
after = commits.second.created_at
commit_count = project.repository.count_commits(ref: 'master', after: after).to_s
- get api("/projects/#{project.id}/repository/commits?since=#{after.utc.iso8601}", user)
+ get api("/projects/#{project_id}/repository/commits?since=#{after.utc.iso8601}", user)
expect(response).to include_pagination_headers
expect(response.headers['X-Total']).to eq(commit_count)
@@ -79,7 +78,7 @@ describe API::Commits do
commits = project.repository.commits("master")
before = commits.second.created_at
- get api("/projects/#{project.id}/repository/commits?until=#{before.utc.iso8601}", user)
+ get api("/projects/#{project_id}/repository/commits?until=#{before.utc.iso8601}", user)
if commits.size >= 20
expect(json_response.size).to eq(20)
@@ -96,7 +95,7 @@ describe API::Commits do
before = commits.second.created_at
commit_count = project.repository.count_commits(ref: 'master', before: before).to_s
- get api("/projects/#{project.id}/repository/commits?until=#{before.utc.iso8601}", user)
+ get api("/projects/#{project_id}/repository/commits?until=#{before.utc.iso8601}", user)
expect(response).to include_pagination_headers
expect(response.headers['X-Total']).to eq(commit_count)
@@ -106,7 +105,7 @@ describe API::Commits do
context "invalid xmlschema date parameters" do
it "returns an invalid parameter error message" do
- get api("/projects/#{project.id}/repository/commits?since=invalid-date", user)
+ get api("/projects/#{project_id}/repository/commits?since=invalid-date", user)
expect(response).to have_http_status(400)
expect(json_response['error']).to eq('since is invalid')
@@ -118,7 +117,7 @@ describe API::Commits do
path = 'files/ruby/popen.rb'
commit_count = project.repository.count_commits(ref: 'master', path: path).to_s
- get api("/projects/#{project.id}/repository/commits?path=#{path}", user)
+ get api("/projects/#{project_id}/repository/commits?path=#{path}", user)
expect(json_response.size).to eq(3)
expect(json_response.first["id"]).to eq("570e7b2abdd848b95f2f578043fc23bd6f6fd24d")
@@ -130,7 +129,7 @@ describe API::Commits do
path = 'files/ruby/popen.rb'
commit_count = project.repository.count_commits(ref: 'master', path: path).to_s
- get api("/projects/#{project.id}/repository/commits?path=#{path}", user)
+ get api("/projects/#{project_id}/repository/commits?path=#{path}", user)
expect(response).to include_pagination_headers
expect(response.headers['X-Total']).to eq(commit_count)
@@ -143,7 +142,7 @@ describe API::Commits do
let(:per_page) { 5 }
let(:ref_name) { 'master' }
let!(:request) do
- get api("/projects/#{project.id}/repository/commits?page=#{page}&per_page=#{per_page}&ref_name=#{ref_name}", user)
+ get api("/projects/#{project_id}/repository/commits?page=#{page}&per_page=#{per_page}&ref_name=#{ref_name}", user)
end
it 'returns correct headers' do
@@ -181,10 +180,10 @@ describe API::Commits do
end
describe "POST /projects/:id/repository/commits" do
- let!(:url) { "/projects/#{project.id}/repository/commits" }
+ let!(:url) { "/projects/#{project_id}/repository/commits" }
it 'returns a 403 unauthorized for user without permissions' do
- post api(url, user2)
+ post api(url, guest)
expect(response).to have_http_status(403)
end
@@ -227,7 +226,7 @@ describe API::Commits do
it 'a new file in project repo' do
post api(url, user), valid_c_params
- expect(response).to have_http_status(201)
+ expect(response).to have_gitlab_http_status(201)
expect(json_response['title']).to eq(message)
expect(json_response['committer_name']).to eq(user.name)
expect(json_response['committer_email']).to eq(user.email)
@@ -453,13 +452,17 @@ describe API::Commits do
end
end
- describe "Get a single commit" do
- context "authorized user" do
- it "returns a commit by sha" do
- get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user)
+ describe 'GET /projects/:id/repository/commits/:sha' do
+ let(:commit) { project.repository.commit }
+ let(:commit_id) { commit.id }
+ let(:route) { "/projects/#{project_id}/repository/commits/#{commit_id}" }
- expect(response).to have_http_status(200)
- commit = project.repository.commit
+ shared_examples_for 'ref commit' do
+ it 'returns the ref last commit' do
+ get api(route, current_user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to match_response_schema('public_api/v4/commit/detail')
expect(json_response['id']).to eq(commit.id)
expect(json_response['short_id']).to eq(commit.short_id)
expect(json_response['title']).to eq(commit.title)
@@ -474,222 +477,539 @@ describe API::Commits do
expect(json_response['stats']['additions']).to eq(commit.stats.additions)
expect(json_response['stats']['deletions']).to eq(commit.stats.deletions)
expect(json_response['stats']['total']).to eq(commit.stats.total)
+ expect(json_response['status']).to be_nil
end
- it "returns a 404 error if not found" do
- get api("/projects/#{project.id}/repository/commits/invalid_sha", user)
- expect(response).to have_http_status(404)
+ context 'when ref does not exist' do
+ let(:commit_id) { 'unknown' }
+
+ it_behaves_like '404 response' do
+ let(:request) { get api(route, current_user) }
+ let(:message) { '404 Commit Not Found' }
+ end
end
- it "returns nil for commit without CI" do
- get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user)
+ context 'when repository is disabled' do
+ include_context 'disabled repository'
- expect(response).to have_http_status(200)
- expect(json_response['status']).to be_nil
+ it_behaves_like '403 response' do
+ let(:request) { get api(route, current_user) }
+ end
end
+ end
- it "returns status for CI" do
- pipeline = project.pipelines.create(source: :push, ref: 'master', sha: project.repository.commit.sha)
- pipeline.update(status: 'success')
+ context 'when unauthenticated', 'and project is public' do
+ let(:project) { create(:project, :public, :repository) }
- get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user)
+ it_behaves_like 'ref commit'
+ end
- expect(response).to have_http_status(200)
- expect(json_response['status']).to eq(pipeline.status)
+ context 'when unauthenticated', 'and project is private' do
+ it_behaves_like '404 response' do
+ let(:request) { get api(route) }
+ let(:message) { '404 Project Not Found' }
end
+ end
- it "returns status for CI when pipeline is created" do
- project.pipelines.create(source: :push, ref: 'master', sha: project.repository.commit.sha)
+ context 'when authenticated', 'as a master' do
+ let(:current_user) { user }
- get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user)
+ it_behaves_like 'ref commit'
- expect(response).to have_http_status(200)
- expect(json_response['status']).to eq("created")
+ context 'when branch contains a dot' do
+ let(:commit) { project.repository.commit(branch_with_dot.name) }
+ let(:commit_id) { branch_with_dot.name }
+
+ it_behaves_like 'ref commit'
end
- end
- context "unauthorized user" do
- it "does not return the selected commit" do
- get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}")
- expect(response).to have_http_status(401)
+ context 'when branch contains a slash' do
+ let(:commit_id) { branch_with_slash.name }
+
+ it_behaves_like '404 response' do
+ let(:request) { get api(route, current_user) }
+ end
+ end
+
+ context 'when branch contains an escaped slash' do
+ let(:commit) { project.repository.commit(branch_with_slash.name) }
+ let(:commit_id) { CGI.escape(branch_with_slash.name) }
+
+ it_behaves_like 'ref commit'
+ end
+
+ context 'requesting with the escaped project full path' do
+ let(:project_id) { CGI.escape(project.full_path) }
+
+ it_behaves_like 'ref commit'
+
+ context 'when branch contains a dot' do
+ let(:commit) { project.repository.commit(branch_with_dot.name) }
+ let(:commit_id) { branch_with_dot.name }
+
+ it_behaves_like 'ref commit'
+ end
+ end
+
+ context 'when the ref has a pipeline' do
+ let!(:pipeline) { project.pipelines.create(source: :push, ref: 'master', sha: commit.sha) }
+
+ it 'includes a "created" status' do
+ get api(route, current_user)
+
+ expect(response).to have_http_status(200)
+ expect(response).to match_response_schema('public_api/v4/commit/detail')
+ expect(json_response['status']).to eq('created')
+ end
+
+ context 'when pipeline succeeds' do
+ before do
+ pipeline.update(status: 'success')
+ end
+
+ it 'includes a "success" status' do
+ get api(route, current_user)
+
+ expect(response).to have_http_status(200)
+ expect(response).to match_response_schema('public_api/v4/commit/detail')
+ expect(json_response['status']).to eq('success')
+ end
+ end
end
end
end
- describe "Get the diff of a commit" do
- context "authorized user" do
- before do
- project.team << [user2, :reporter]
+ describe 'GET /projects/:id/repository/commits/:sha/diff' do
+ let(:commit) { project.repository.commit }
+ let(:commit_id) { commit.id }
+ let(:route) { "/projects/#{project_id}/repository/commits/#{commit_id}/diff" }
+
+ shared_examples_for 'ref diff' do
+ it 'returns the diff of the selected commit' do
+ get api(route, current_user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response.size).to be >= 1
+ expect(json_response.first.keys).to include 'diff'
end
- it "returns the diff of the selected commit" do
- get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/diff", user)
- expect(response).to have_http_status(200)
+ context 'when ref does not exist' do
+ let(:commit_id) { 'unknown' }
- expect(json_response).to be_an Array
- expect(json_response.length).to be >= 1
- expect(json_response.first.keys).to include "diff"
+ it_behaves_like '404 response' do
+ let(:request) { get api(route, current_user) }
+ let(:message) { '404 Commit Not Found' }
+ end
end
- it "returns a 404 error if invalid commit" do
- get api("/projects/#{project.id}/repository/commits/invalid_sha/diff", user)
- expect(response).to have_http_status(404)
+ context 'when repository is disabled' do
+ include_context 'disabled repository'
+
+ it_behaves_like '403 response' do
+ let(:request) { get api(route, current_user) }
+ end
end
end
- context "unauthorized user" do
- it "does not return the diff of the selected commit" do
- get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/diff")
- expect(response).to have_http_status(401)
+ context 'when unauthenticated', 'and project is public' do
+ let(:project) { create(:project, :public, :repository) }
+
+ it_behaves_like 'ref diff'
+ end
+
+ context 'when unauthenticated', 'and project is private' do
+ it_behaves_like '404 response' do
+ let(:request) { get api(route) }
+ let(:message) { '404 Project Not Found' }
+ end
+ end
+
+ context 'when authenticated', 'as a master' do
+ let(:current_user) { user }
+
+ it_behaves_like 'ref diff'
+
+ context 'when branch contains a dot' do
+ let(:commit_id) { branch_with_dot.name }
+
+ it_behaves_like 'ref diff'
+ end
+
+ context 'when branch contains a slash' do
+ let(:commit_id) { branch_with_slash.name }
+
+ it_behaves_like '404 response' do
+ let(:request) { get api(route, current_user) }
+ end
+ end
+
+ context 'when branch contains an escaped slash' do
+ let(:commit_id) { CGI.escape(branch_with_slash.name) }
+
+ it_behaves_like 'ref diff'
+ end
+
+ context 'requesting with the escaped project full path' do
+ let(:project_id) { CGI.escape(project.full_path) }
+
+ it_behaves_like 'ref diff'
+
+ context 'when branch contains a dot' do
+ let(:commit_id) { branch_with_dot.name }
+
+ it_behaves_like 'ref diff'
+ end
end
end
end
- describe 'Get the comments of a commit' do
- context 'authorized user' do
- it 'returns merge_request comments' do
- get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user)
- expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(2)
- expect(json_response.first['note']).to eq('a comment on a commit')
- expect(json_response.first['author']['id']).to eq(user.id)
+ describe 'GET /projects/:id/repository/commits/:sha/comments' do
+ let(:commit) { project.repository.commit }
+ let(:commit_id) { commit.id }
+ let(:route) { "/projects/#{project_id}/repository/commits/#{commit_id}/comments" }
+
+ shared_examples_for 'ref comments' do
+ context 'when ref exists' do
+ before do
+ create(:note_on_commit, author: user, project: project, commit_id: commit.id, note: 'a comment on a commit')
+ create(:note_on_commit, author: user, project: project, commit_id: commit.id, note: 'another comment on a commit')
+ end
+
+ it 'returns the diff of the selected commit' do
+ get api(route, current_user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to match_response_schema('public_api/v4/commit_notes')
+ expect(json_response.size).to eq(2)
+ expect(json_response.first['note']).to eq('a comment on a commit')
+ expect(json_response.first['author']['id']).to eq(user.id)
+ end
end
- it 'returns a 404 error if merge_request_id not found' do
- get api("/projects/#{project.id}/repository/commits/1234ab/comments", user)
- expect(response).to have_http_status(404)
+ context 'when ref does not exist' do
+ let(:commit_id) { 'unknown' }
+
+ it_behaves_like '404 response' do
+ let(:request) { get api(route, current_user) }
+ let(:message) { '404 Commit Not Found' }
+ end
+ end
+
+ context 'when repository is disabled' do
+ include_context 'disabled repository'
+
+ it_behaves_like '403 response' do
+ let(:request) { get api(route, current_user) }
+ end
+ end
+ end
+
+ context 'when unauthenticated', 'and project is public' do
+ let(:project) { create(:project, :public, :repository) }
+
+ it_behaves_like 'ref comments'
+ end
+
+ context 'when unauthenticated', 'and project is private' do
+ it_behaves_like '404 response' do
+ let(:request) { get api(route) }
+ let(:message) { '404 Project Not Found' }
end
end
- context 'unauthorized user' do
- it 'does not return the diff of the selected commit' do
- get api("/projects/#{project.id}/repository/commits/1234ab/comments")
- expect(response).to have_http_status(401)
+ context 'when authenticated', 'as a master' do
+ let(:current_user) { user }
+
+ it_behaves_like 'ref comments'
+
+ context 'when branch contains a dot' do
+ let(:commit) { project.repository.commit(branch_with_dot.name) }
+ let(:commit_id) { branch_with_dot.name }
+
+ it_behaves_like 'ref comments'
+ end
+
+ context 'when branch contains a slash' do
+ let(:commit) { project.repository.commit(branch_with_slash.name) }
+ let(:commit_id) { branch_with_slash.name }
+
+ it_behaves_like '404 response' do
+ let(:request) { get api(route, current_user) }
+ end
+ end
+
+ context 'when branch contains an escaped slash' do
+ let(:commit) { project.repository.commit(branch_with_slash.name) }
+ let(:commit_id) { CGI.escape(branch_with_slash.name) }
+
+ it_behaves_like 'ref comments'
+ end
+
+ context 'requesting with the escaped project full path' do
+ let(:project_id) { CGI.escape(project.full_path) }
+
+ it_behaves_like 'ref comments'
+
+ context 'when branch contains a dot' do
+ let(:commit) { project.repository.commit(branch_with_dot.name) }
+ let(:commit_id) { branch_with_dot.name }
+
+ it_behaves_like 'ref comments'
+ end
end
end
context 'when the commit is present on two projects' do
- let(:forked_project) { create(:project, :repository, creator: user2, namespace: user2.namespace) }
- let!(:forked_project_note) { create(:note_on_commit, author: user2, project: forked_project, commit_id: forked_project.repository.commit.id, note: 'a comment on a commit for fork') }
+ let(:forked_project) { create(:project, :repository, creator: guest, namespace: guest.namespace) }
+ let!(:forked_project_note) { create(:note_on_commit, author: guest, project: forked_project, commit_id: forked_project.repository.commit.id, note: 'a comment on a commit for fork') }
+ let(:project_id) { forked_project.id }
+ let(:commit_id) { forked_project.repository.commit.id }
it 'returns the comments for the target project' do
- get api("/projects/#{forked_project.id}/repository/commits/#{forked_project.repository.commit.id}/comments", user2)
+ get api(route, guest)
- expect(response).to have_http_status(200)
- expect(json_response.length).to eq(1)
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to match_response_schema('public_api/v4/commit_notes')
+ expect(json_response.size).to eq(1)
expect(json_response.first['note']).to eq('a comment on a commit for fork')
- expect(json_response.first['author']['id']).to eq(user2.id)
+ expect(json_response.first['author']['id']).to eq(guest.id)
end
end
end
describe 'POST :id/repository/commits/:sha/cherry_pick' do
- let(:master_pickable_commit) { project.commit('7d3b0f7cff5f37573aea97cebfd5692ea1689924') }
+ let(:commit) { project.commit('7d3b0f7cff5f37573aea97cebfd5692ea1689924') }
+ let(:commit_id) { commit.id }
+ let(:branch) { 'master' }
+ let(:route) { "/projects/#{project_id}/repository/commits/#{commit_id}/cherry_pick" }
+
+ shared_examples_for 'ref cherry-pick' do
+ context 'when ref exists' do
+ it 'cherry-picks the ref commit' do
+ post api(route, current_user), branch: branch
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(response).to match_response_schema('public_api/v4/commit/basic')
+ expect(json_response['title']).to eq(commit.title)
+ expect(json_response['message']).to eq(commit.message)
+ expect(json_response['author_name']).to eq(commit.author_name)
+ expect(json_response['committer_name']).to eq(user.name)
+ end
+ end
- context 'authorized user' do
- it 'cherry picks a commit' do
- post api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick", user), branch: 'master'
+ context 'when repository is disabled' do
+ include_context 'disabled repository'
- expect(response).to have_http_status(201)
- expect(json_response['title']).to eq(master_pickable_commit.title)
- expect(json_response['message']).to eq(master_pickable_commit.message)
- expect(json_response['author_name']).to eq(master_pickable_commit.author_name)
- expect(json_response['committer_name']).to eq(user.name)
+ it_behaves_like '403 response' do
+ let(:request) { post api(route, current_user), branch: 'master' }
+ end
end
+ end
- it 'returns 400 if commit is already included in the target branch' do
- post api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick", user), branch: 'markdown'
+ context 'when unauthenticated', 'and project is public' do
+ let(:project) { create(:project, :public, :repository) }
- expect(response).to have_http_status(400)
- expect(json_response['message']).to include('Sorry, we cannot cherry-pick this commit automatically.')
+ it_behaves_like '403 response' do
+ let(:request) { post api(route), branch: 'master' }
+ end
+ end
+
+ context 'when unauthenticated', 'and project is private' do
+ it_behaves_like '404 response' do
+ let(:request) { post api(route), branch: 'master' }
+ let(:message) { '404 Project Not Found' }
end
+ end
- it 'returns 400 if you are not allowed to push to the target branch' do
- project.team << [user2, :developer]
- protected_branch = create(:protected_branch, project: project, name: 'feature')
+ context 'when authenticated', 'as an owner' do
+ let(:current_user) { user }
- post api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick", user2), branch: protected_branch.name
+ it_behaves_like 'ref cherry-pick'
- expect(response).to have_http_status(400)
- expect(json_response['message']).to eq('You are not allowed to push into this branch')
+ context 'when ref does not exist' do
+ let(:commit_id) { 'unknown' }
+
+ it_behaves_like '404 response' do
+ let(:request) { post api(route, current_user), branch: 'master' }
+ let(:message) { '404 Commit Not Found' }
+ end
+ end
+
+ context 'when branch is missing' do
+ it_behaves_like '400 response' do
+ let(:request) { post api(route, current_user) }
+ end
end
- it 'returns 400 for missing parameters' do
- post api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick", user)
+ context 'when branch does not exist' do
+ it_behaves_like '404 response' do
+ let(:request) { post api(route, current_user), branch: 'foo' }
+ let(:message) { '404 Branch Not Found' }
+ end
+ end
- expect(response).to have_http_status(400)
- expect(json_response['error']).to eq('branch is missing')
+ context 'when commit is already included in the target branch' do
+ it_behaves_like '400 response' do
+ let(:request) { post api(route, current_user), branch: 'markdown' }
+ end
end
- it 'returns 404 if commit is not found' do
- post api("/projects/#{project.id}/repository/commits/abcd0123/cherry_pick", user), branch: 'master'
+ context 'when ref contains a dot' do
+ let(:commit) { project.repository.commit(branch_with_dot.name) }
+ let(:commit_id) { branch_with_dot.name }
- expect(response).to have_http_status(404)
- expect(json_response['message']).to eq('404 Commit Not Found')
+ it_behaves_like 'ref cherry-pick'
end
- it 'returns 404 if branch is not found' do
- post api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick", user), branch: 'foo'
+ context 'when ref contains a slash' do
+ let(:commit_id) { branch_with_slash.name }
- expect(response).to have_http_status(404)
- expect(json_response['message']).to eq('404 Branch Not Found')
+ it_behaves_like '404 response' do
+ let(:request) { post api(route, current_user), branch: 'master' }
+ end
end
- it 'returns 400 for missing parameters' do
- post api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick", user)
+ context 'requesting with the escaped project full path' do
+ let(:project_id) { CGI.escape(project.full_path) }
- expect(response).to have_http_status(400)
- expect(json_response['error']).to eq('branch is missing')
+ it_behaves_like 'ref cherry-pick'
+
+ context 'when ref contains a dot' do
+ let(:commit) { project.repository.commit(branch_with_dot.name) }
+ let(:commit_id) { branch_with_dot.name }
+
+ it_behaves_like 'ref cherry-pick'
+ end
end
end
- context 'unauthorized user' do
- it 'does not cherry pick the commit' do
- post api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick"), branch: 'master'
+ context 'when authenticated', 'as a developer' do
+ let(:current_user) { guest }
+
+ before do
+ project.add_developer(guest)
+ end
+
+ context 'when branch is protected' do
+ before do
+ create(:protected_branch, project: project, name: 'feature')
+ end
+
+ it 'returns 400 if you are not allowed to push to the target branch' do
+ post api(route, current_user), branch: 'feature'
- expect(response).to have_http_status(401)
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response['message']).to eq('You are not allowed to push into this branch')
+ end
end
end
end
- describe 'Post comment to commit' do
- context 'authorized user' do
- it 'returns comment' do
- post api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user), note: 'My comment'
- expect(response).to have_http_status(201)
- expect(json_response['note']).to eq('My comment')
- expect(json_response['path']).to be_nil
- expect(json_response['line']).to be_nil
- expect(json_response['line_type']).to be_nil
+ describe 'POST /projects/:id/repository/commits/:sha/comments' do
+ let(:commit) { project.repository.commit }
+ let(:commit_id) { commit.id }
+ let(:note) { 'My comment' }
+ let(:route) { "/projects/#{project_id}/repository/commits/#{commit_id}/comments" }
+
+ shared_examples_for 'ref new comment' do
+ context 'when ref exists' do
+ it 'creates the comment' do
+ post api(route, current_user), note: note
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(response).to match_response_schema('public_api/v4/commit_note')
+ expect(json_response['note']).to eq('My comment')
+ expect(json_response['path']).to be_nil
+ expect(json_response['line']).to be_nil
+ expect(json_response['line_type']).to be_nil
+ end
end
+ context 'when repository is disabled' do
+ include_context 'disabled repository'
+
+ it_behaves_like '403 response' do
+ let(:request) { post api(route, current_user), note: 'My comment' }
+ end
+ end
+ end
+
+ context 'when unauthenticated', 'and project is public' do
+ let(:project) { create(:project, :public, :repository) }
+
+ it_behaves_like '400 response' do
+ let(:request) { post api(route), note: 'My comment' }
+ end
+ end
+
+ context 'when unauthenticated', 'and project is private' do
+ it_behaves_like '404 response' do
+ let(:request) { post api(route), note: 'My comment' }
+ let(:message) { '404 Project Not Found' }
+ end
+ end
+
+ context 'when authenticated', 'as an owner' do
+ let(:current_user) { user }
+
+ it_behaves_like 'ref new comment'
+
it 'returns the inline comment' do
- post api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user), note: 'My comment', path: project.repository.commit.raw_diffs.first.new_path, line: 1, line_type: 'new'
+ post api(route, current_user), note: 'My comment', path: project.repository.commit.raw_diffs.first.new_path, line: 1, line_type: 'new'
- expect(response).to have_http_status(201)
+ expect(response).to have_gitlab_http_status(201)
+ expect(response).to match_response_schema('public_api/v4/commit_note')
expect(json_response['note']).to eq('My comment')
expect(json_response['path']).to eq(project.repository.commit.raw_diffs.first.new_path)
expect(json_response['line']).to eq(1)
expect(json_response['line_type']).to eq('new')
end
+ context 'when ref does not exist' do
+ let(:commit_id) { 'unknown' }
+
+ it_behaves_like '404 response' do
+ let(:request) { post api(route, current_user), note: 'My comment' }
+ let(:message) { '404 Commit Not Found' }
+ end
+ end
+
it 'returns 400 if note is missing' do
- post api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user)
- expect(response).to have_http_status(400)
+ post api(route, current_user)
+
+ expect(response).to have_gitlab_http_status(400)
end
- it 'returns 404 if note is attached to non existent commit' do
- post api("/projects/#{project.id}/repository/commits/1234ab/comments", user), note: 'My comment'
- expect(response).to have_http_status(404)
+ context 'when ref contains a dot' do
+ let(:commit_id) { branch_with_dot.name }
+
+ it_behaves_like 'ref new comment'
end
- end
- context 'unauthorized user' do
- it 'does not return the diff of the selected commit' do
- post api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments")
- expect(response).to have_http_status(401)
+ context 'when ref contains a slash' do
+ let(:commit_id) { branch_with_slash.name }
+
+ it_behaves_like '404 response' do
+ let(:request) { post api(route, current_user), note: 'My comment' }
+ end
+ end
+
+ context 'when ref contains an escaped slash' do
+ let(:commit_id) { CGI.escape(branch_with_slash.name) }
+
+ it_behaves_like 'ref new comment'
+ end
+
+ context 'requesting with the escaped project full path' do
+ let(:project_id) { CGI.escape(project.full_path) }
+
+ it_behaves_like 'ref new comment'
+
+ context 'when ref contains a dot' do
+ let(:commit_id) { branch_with_dot.name }
+
+ it_behaves_like 'ref new comment'
+ end
end
end
end
diff --git a/spec/requests/api/deploy_keys_spec.rb b/spec/requests/api/deploy_keys_spec.rb
index d032d72de9c..e497ec333a2 100644
--- a/spec/requests/api/deploy_keys_spec.rb
+++ b/spec/requests/api/deploy_keys_spec.rb
@@ -182,7 +182,7 @@ describe API::DeployKeys do
delete api("/projects/#{project.id}/deploy_keys/#{deploy_key.id}", admin)
expect(response).to have_http_status(204)
- end.to change{ project.deploy_keys.count }.by(-1)
+ end.to change { project.deploy_keys.count }.by(-1)
end
it 'returns 404 Not Found with invalid ID' do
diff --git a/spec/requests/api/environments_spec.rb b/spec/requests/api/environments_spec.rb
index 4c5ded7a492..87716c6fe3a 100644
--- a/spec/requests/api/environments_spec.rb
+++ b/spec/requests/api/environments_spec.rb
@@ -13,7 +13,14 @@ describe API::Environments do
describe 'GET /projects/:id/environments' do
context 'as member of the project' do
it 'returns project environments' do
- project_data_keys = %w(id http_url_to_repo web_url name name_with_namespace path path_with_namespace)
+ project_data_keys = %w(
+ id description default_branch tag_list
+ ssh_url_to_repo http_url_to_repo web_url
+ name name_with_namespace
+ path path_with_namespace
+ star_count forks_count
+ created_at last_activity_at
+ )
get api("/projects/#{project.id}/environments", user)
diff --git a/spec/requests/api/events_spec.rb b/spec/requests/api/events_spec.rb
index 7a847442469..a23d28994ce 100644
--- a/spec/requests/api/events_spec.rb
+++ b/spec/requests/api/events_spec.rb
@@ -59,6 +59,34 @@ describe API::Events do
expect(json_response.size).to eq(1)
end
+ context 'when the list of events includes push events' do
+ let(:event) do
+ create(:push_event, author: user, project: private_project)
+ end
+
+ let!(:payload) { create(:push_event_payload, event: event) }
+ let(:payload_hash) { json_response[0]['push_data'] }
+
+ before do
+ get api("/users/#{user.id}/events?action=pushed", user)
+ end
+
+ it 'responds with HTTP 200 OK' do
+ expect(response).to have_http_status(200)
+ end
+
+ it 'includes the push payload as a Hash' do
+ expect(payload_hash).to be_an_instance_of(Hash)
+ end
+
+ it 'includes the push payload details' do
+ expect(payload_hash['commit_count']).to eq(payload.commit_count)
+ expect(payload_hash['action']).to eq(payload.action)
+ expect(payload_hash['ref_type']).to eq(payload.ref_type)
+ expect(payload_hash['commit_to']).to eq(payload.commit_to)
+ end
+ end
+
context 'when there are multiple events from different projects' do
let(:second_note) { create(:note_on_issue, project: create(:project)) }
@@ -138,5 +166,35 @@ describe API::Events do
expect(response).to have_http_status(404)
end
end
+
+ context 'when exists some events' do
+ let(:merge_request1) { create(:merge_request, :closed, author: user, assignee: user, source_project: private_project, title: 'Test') }
+ let(:merge_request2) { create(:merge_request, :closed, author: user, assignee: user, source_project: private_project, title: 'Test') }
+
+ before do
+ create_event(merge_request1)
+ end
+
+ it 'avoids N+1 queries' do
+ control_count = ActiveRecord::QueryRecorder.new do
+ get api("/projects/#{private_project.id}/events", user), target_type: :merge_request
+ end.count
+
+ create_event(merge_request2)
+
+ expect do
+ get api("/projects/#{private_project.id}/events", user), target_type: :merge_request
+ end.not_to exceed_query_limit(control_count)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response.size).to eq(2)
+ expect(json_response.map { |r| r['target_id'] }).to match_array([merge_request1.id, merge_request2.id])
+ end
+
+ def create_event(target)
+ create(:event, project: private_project, author: user, target: target)
+ end
+ end
end
end
diff --git a/spec/requests/api/group_variables_spec.rb b/spec/requests/api/group_variables_spec.rb
index 402ea057cc5..2179790d098 100644
--- a/spec/requests/api/group_variables_spec.rb
+++ b/spec/requests/api/group_variables_spec.rb
@@ -88,7 +88,7 @@ describe API::GroupVariables do
it 'creates variable' do
expect do
post api("/groups/#{group.id}/variables", user), key: 'TEST_VARIABLE_2', value: 'VALUE_2', protected: true
- end.to change{group.variables.count}.by(1)
+ end.to change {group.variables.count}.by(1)
expect(response).to have_http_status(201)
expect(json_response['key']).to eq('TEST_VARIABLE_2')
@@ -99,7 +99,7 @@ describe API::GroupVariables do
it 'creates variable with optional attributes' do
expect do
post api("/groups/#{group.id}/variables", user), key: 'TEST_VARIABLE_2', value: 'VALUE_2'
- end.to change{group.variables.count}.by(1)
+ end.to change {group.variables.count}.by(1)
expect(response).to have_http_status(201)
expect(json_response['key']).to eq('TEST_VARIABLE_2')
@@ -110,7 +110,7 @@ describe API::GroupVariables do
it 'does not allow to duplicate variable key' do
expect do
post api("/groups/#{group.id}/variables", user), key: variable.key, value: 'VALUE_2'
- end.to change{group.variables.count}.by(0)
+ end.to change {group.variables.count}.by(0)
expect(response).to have_http_status(400)
end
@@ -192,7 +192,7 @@ describe API::GroupVariables do
delete api("/groups/#{group.id}/variables/#{variable.key}", user)
expect(response).to have_http_status(204)
- end.to change{group.variables.count}.by(-1)
+ end.to change {group.variables.count}.by(-1)
end
it 'responds with 404 Not Found if requesting non-existing variable' do
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index eba1db15da6..313c38cd29c 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -512,7 +512,7 @@ describe API::Groups do
let(:project) { create(:project) }
let(:project_path) { CGI.escape(project.full_path) }
- before(:each) do
+ before do
allow_any_instance_of(Projects::TransferService)
.to receive(:execute).and_return(true)
end
diff --git a/spec/requests/api/helpers_spec.rb b/spec/requests/api/helpers_spec.rb
index 7a1bd76af7a..d4006fe71a2 100644
--- a/spec/requests/api/helpers_spec.rb
+++ b/spec/requests/api/helpers_spec.rb
@@ -56,7 +56,7 @@ describe API::Helpers do
end
def doorkeeper_guard_returns(value)
- allow_any_instance_of(self.class).to receive(:doorkeeper_guard){ value }
+ allow_any_instance_of(self.class).to receive(:doorkeeper_guard) { value }
end
def error!(message, status, header)
@@ -161,7 +161,7 @@ describe API::Helpers do
describe "when authenticating using a user's private token" do
it "returns nil for an invalid token" do
env[API::APIGuard::PRIVATE_TOKEN_HEADER] = 'invalid token'
- allow_any_instance_of(self.class).to receive(:doorkeeper_guard){ false }
+ allow_any_instance_of(self.class).to receive(:doorkeeper_guard) { false }
expect(current_user).to be_nil
end
diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb
index 8a2de23716f..e9c30dba8d4 100644
--- a/spec/requests/api/internal_spec.rb
+++ b/spec/requests/api/internal_spec.rb
@@ -181,13 +181,12 @@ describe API::Internal do
describe "POST /internal/allowed", :clean_gitlab_redis_shared_state do
context "access granted" do
- before do
- project.team << [user, :developer]
- Timecop.freeze
+ around do |example|
+ Timecop.freeze { example.run }
end
- after do
- Timecop.return
+ before do
+ project.team << [user, :developer]
end
context 'with env passed as a JSON' do
diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb
index 60687db9316..7d120e4a234 100644
--- a/spec/requests/api/issues_spec.rb
+++ b/spec/requests/api/issues_spec.rb
@@ -1,8 +1,6 @@
require 'spec_helper'
-describe API::Issues do
- include EmailHelpers
-
+describe API::Issues, :mailer do
set(:user) { create(:user) }
set(:project) do
create(:project, :public, creator_id: user.id, namespace: user.namespace)
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index d8dfe71342d..0db645863fb 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -31,7 +31,7 @@ describe API::MergeRequests do
it 'returns authentication error' do
get api('/merge_requests')
- expect(response).to have_http_status(401)
+ expect(response).to have_gitlab_http_status(401)
end
end
@@ -43,7 +43,7 @@ describe API::MergeRequests do
it 'returns an array of all merge requests' do
get api('/merge_requests', user), scope: :all
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.map { |mr| mr['id'] })
@@ -56,7 +56,7 @@ describe API::MergeRequests do
get api('/merge_requests', user), scope: :all
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.map { |mr| mr['id'] })
@@ -68,7 +68,7 @@ describe API::MergeRequests do
get api('/merge_requests', user2)
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['id']).to eq(merge_request3.id)
@@ -79,7 +79,7 @@ describe API::MergeRequests do
get api('/merge_requests', user), author_id: user2.id, scope: :all
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['id']).to eq(merge_request3.id)
@@ -90,7 +90,7 @@ describe API::MergeRequests do
get api('/merge_requests', user), assignee_id: user2.id, scope: :all
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['id']).to eq(merge_request3.id)
@@ -101,7 +101,7 @@ describe API::MergeRequests do
get api('/merge_requests', user2), scope: 'assigned-to-me'
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['id']).to eq(merge_request3.id)
@@ -112,7 +112,7 @@ describe API::MergeRequests do
get api('/merge_requests', user2), scope: 'created-by-me'
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['id']).to eq(merge_request3.id)
@@ -125,7 +125,7 @@ describe API::MergeRequests do
it "returns authentication error" do
get api("/projects/#{project.id}/merge_requests")
- expect(response).to have_http_status(401)
+ expect(response).to have_gitlab_http_status(401)
end
end
@@ -145,7 +145,7 @@ describe API::MergeRequests do
it "returns an array of all merge_requests" do
get api("/projects/#{project.id}/merge_requests", user)
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(3)
@@ -166,7 +166,7 @@ describe API::MergeRequests do
it "returns an array of all merge_requests using simple mode" do
get api("/projects/#{project.id}/merge_requests?view=simple", user)
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
expect(json_response.last.keys).to match_array(%w(id iid title web_url created_at description project_id state updated_at))
expect(json_response).to be_an Array
@@ -182,7 +182,7 @@ describe API::MergeRequests do
it "returns an array of all merge_requests" do
get api("/projects/#{project.id}/merge_requests?state", user)
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(3)
@@ -192,7 +192,7 @@ describe API::MergeRequests do
it "returns an array of open merge_requests" do
get api("/projects/#{project.id}/merge_requests?state=opened", user)
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
@@ -202,7 +202,7 @@ describe API::MergeRequests do
it "returns an array of closed merge_requests" do
get api("/projects/#{project.id}/merge_requests?state=closed", user)
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
@@ -212,7 +212,7 @@ describe API::MergeRequests do
it "returns an array of merged merge_requests" do
get api("/projects/#{project.id}/merge_requests?state=merged", user)
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
@@ -222,7 +222,7 @@ describe API::MergeRequests do
it 'returns merge_request by "iids" array' do
get api("/projects/#{project.id}/merge_requests", user), iids: [merge_request.iid, merge_request_closed.iid]
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(2)
expect(json_response.first['title']).to eq merge_request_closed.title
@@ -232,14 +232,14 @@ describe API::MergeRequests do
it 'matches V4 response schema' do
get api("/projects/#{project.id}/merge_requests", user)
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(response).to match_response_schema('public_api/v4/merge_requests')
end
it 'returns an empty array if no issue matches milestone' do
get api("/projects/#{project.id}/merge_requests", user), milestone: '1.0.0'
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(0)
end
@@ -247,7 +247,7 @@ describe API::MergeRequests do
it 'returns an empty array if milestone does not exist' do
get api("/projects/#{project.id}/merge_requests", user), milestone: 'foo'
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(0)
end
@@ -262,7 +262,7 @@ describe API::MergeRequests do
it 'returns an array of merge requests matching state in milestone' do
get api("/projects/#{project.id}/merge_requests", user), milestone: '0.9', state: 'closed'
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['id']).to eq(merge_request_closed.id)
@@ -271,7 +271,7 @@ describe API::MergeRequests do
it 'returns an array of labeled merge requests' do
get api("/projects/#{project.id}/merge_requests?labels=#{label.title}", user)
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['labels']).to eq([label2.title, label.title])
@@ -280,7 +280,7 @@ describe API::MergeRequests do
it 'returns an array of labeled merge requests where all labels match' do
get api("/projects/#{project.id}/merge_requests?labels=#{label.title},foo,bar", user)
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(0)
end
@@ -288,11 +288,31 @@ describe API::MergeRequests do
it 'returns an empty array if no merge request matches labels' do
get api("/projects/#{project.id}/merge_requests?labels=foo,bar", user)
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(0)
end
+ it 'returns an array of labeled merge requests that are merged for a milestone' do
+ bug_label = create(:label, title: 'bug', color: '#FFAABB', project: project)
+
+ mr1 = create(:merge_request, state: "merged", source_project: project, target_project: project, milestone: milestone)
+ mr2 = create(:merge_request, state: "merged", source_project: project, target_project: project, milestone: milestone1)
+ mr3 = create(:merge_request, state: "closed", source_project: project, target_project: project, milestone: milestone1)
+ _mr = create(:merge_request, state: "merged", source_project: project, target_project: project, milestone: milestone1)
+
+ create(:label_link, label: bug_label, target: mr1)
+ create(:label_link, label: bug_label, target: mr2)
+ create(:label_link, label: bug_label, target: mr3)
+
+ get api("/projects/#{project.id}/merge_requests?labels=#{bug_label.title}&milestone=#{milestone1.title}&state=merged", user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(1)
+ expect(json_response.first['id']).to eq(mr2.id)
+ end
+
context "with ordering" do
before do
@mr_later = mr_with_later_created_and_updated_at_time
@@ -302,44 +322,44 @@ describe API::MergeRequests do
it "returns an array of merge_requests in ascending order" do
get api("/projects/#{project.id}/merge_requests?sort=asc", user)
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(3)
- response_dates = json_response.map{ |merge_request| merge_request['created_at'] }
+ response_dates = json_response.map { |merge_request| merge_request['created_at'] }
expect(response_dates).to eq(response_dates.sort)
end
it "returns an array of merge_requests in descending order" do
get api("/projects/#{project.id}/merge_requests?sort=desc", user)
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(3)
- response_dates = json_response.map{ |merge_request| merge_request['created_at'] }
+ response_dates = json_response.map { |merge_request| merge_request['created_at'] }
expect(response_dates).to eq(response_dates.sort.reverse)
end
it "returns an array of merge_requests ordered by updated_at" do
get api("/projects/#{project.id}/merge_requests?order_by=updated_at", user)
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(3)
- response_dates = json_response.map{ |merge_request| merge_request['updated_at'] }
+ response_dates = json_response.map { |merge_request| merge_request['updated_at'] }
expect(response_dates).to eq(response_dates.sort.reverse)
end
it "returns an array of merge_requests ordered by created_at" do
get api("/projects/#{project.id}/merge_requests?order_by=created_at&sort=asc", user)
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(3)
- response_dates = json_response.map{ |merge_request| merge_request['created_at'] }
+ response_dates = json_response.map { |merge_request| merge_request['created_at'] }
expect(response_dates).to eq(response_dates.sort)
end
end
@@ -350,7 +370,7 @@ describe API::MergeRequests do
it 'exposes known attributes' do
get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user)
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(json_response['id']).to eq(merge_request.id)
expect(json_response['iid']).to eq(merge_request.iid)
expect(json_response['project_id']).to eq(merge_request.project.id)
@@ -378,7 +398,7 @@ describe API::MergeRequests do
it "returns merge_request" do
get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user)
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(json_response['title']).to eq(merge_request.title)
expect(json_response['iid']).to eq(merge_request.iid)
expect(json_response['work_in_progress']).to eq(false)
@@ -389,13 +409,13 @@ describe API::MergeRequests do
it "returns a 404 error if merge_request_iid not found" do
get api("/projects/#{project.id}/merge_requests/999", user)
- expect(response).to have_http_status(404)
+ expect(response).to have_gitlab_http_status(404)
end
it "returns a 404 error if merge_request `id` is used instead of iid" do
get api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user)
- expect(response).to have_http_status(404)
+ expect(response).to have_gitlab_http_status(404)
end
context 'Work in Progress' do
@@ -403,7 +423,7 @@ describe API::MergeRequests do
it "returns merge_request" do
get api("/projects/#{project.id}/merge_requests/#{merge_request_wip.iid}", user)
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(json_response['work_in_progress']).to eq(true)
end
end
@@ -414,7 +434,7 @@ describe API::MergeRequests do
get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/commits", user)
commit = merge_request.commits.first
- expect(response.status).to eq 200
+ expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.size).to eq(merge_request.commits.size)
@@ -424,13 +444,13 @@ describe API::MergeRequests do
it 'returns a 404 when merge_request_iid not found' do
get api("/projects/#{project.id}/merge_requests/999/commits", user)
- expect(response).to have_http_status(404)
+ expect(response).to have_gitlab_http_status(404)
end
it 'returns a 404 when merge_request id is used instead of iid' do
get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/commits", user)
- expect(response).to have_http_status(404)
+ expect(response).to have_gitlab_http_status(404)
end
end
@@ -438,19 +458,19 @@ describe API::MergeRequests do
it 'returns the change information of the merge_request' do
get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/changes", user)
- expect(response.status).to eq 200
+ expect(response).to have_gitlab_http_status(200)
expect(json_response['changes'].size).to eq(merge_request.diffs.size)
end
it 'returns a 404 when merge_request_iid not found' do
get api("/projects/#{project.id}/merge_requests/999/changes", user)
- expect(response).to have_http_status(404)
+ expect(response).to have_gitlab_http_status(404)
end
it 'returns a 404 when merge_request id is used instead of iid' do
get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/changes", user)
- expect(response).to have_http_status(404)
+ expect(response).to have_gitlab_http_status(404)
end
end
@@ -465,7 +485,7 @@ describe API::MergeRequests do
labels: 'label, label2',
milestone_id: milestone.id
- expect(response).to have_http_status(201)
+ expect(response).to have_gitlab_http_status(201)
expect(json_response['title']).to eq('Test merge_request')
expect(json_response['labels']).to eq(%w(label label2))
expect(json_response['milestone']['id']).to eq(milestone.id)
@@ -475,25 +495,25 @@ describe API::MergeRequests do
it "returns 422 when source_branch equals target_branch" do
post api("/projects/#{project.id}/merge_requests", user),
title: "Test merge_request", source_branch: "master", target_branch: "master", author: user
- expect(response).to have_http_status(422)
+ expect(response).to have_gitlab_http_status(422)
end
it "returns 400 when source_branch is missing" do
post api("/projects/#{project.id}/merge_requests", user),
title: "Test merge_request", target_branch: "master", author: user
- expect(response).to have_http_status(400)
+ expect(response).to have_gitlab_http_status(400)
end
it "returns 400 when target_branch is missing" do
post api("/projects/#{project.id}/merge_requests", user),
title: "Test merge_request", source_branch: "markdown", author: user
- expect(response).to have_http_status(400)
+ expect(response).to have_gitlab_http_status(400)
end
it "returns 400 when title is missing" do
post api("/projects/#{project.id}/merge_requests", user),
target_branch: 'master', source_branch: 'markdown'
- expect(response).to have_http_status(400)
+ expect(response).to have_gitlab_http_status(400)
end
it 'allows special label names' do
@@ -503,7 +523,7 @@ describe API::MergeRequests do
target_branch: 'master',
author: user,
labels: 'label, label?, label&foo, ?, &'
- expect(response.status).to eq(201)
+ expect(response).to have_gitlab_http_status(201)
expect(json_response['labels']).to include 'label'
expect(json_response['labels']).to include 'label?'
expect(json_response['labels']).to include 'label&foo'
@@ -529,7 +549,7 @@ describe API::MergeRequests do
target_branch: 'master',
author: user
end.to change { MergeRequest.count }.by(0)
- expect(response).to have_http_status(409)
+ expect(response).to have_gitlab_http_status(409)
end
end
@@ -560,15 +580,17 @@ describe API::MergeRequests do
let!(:fork_project) { create(:project, forked_from_project: project, namespace: user2.namespace, creator_id: user2.id) }
let!(:unrelated_project) { create(:project, namespace: create(:user).namespace, creator_id: user2.id) }
- before :each do |each|
- fork_project.team << [user2, :reporter]
+ before do
+ fork_project.add_reporter(user2)
+
+ allow_any_instance_of(MergeRequest).to receive(:write_ref)
end
it "returns merge_request" do
post api("/projects/#{fork_project.id}/merge_requests", user2),
title: 'Test merge_request', source_branch: "feature_conflict", target_branch: "master",
author: user2, target_project_id: project.id, description: 'Test description for Test merge_request'
- expect(response).to have_http_status(201)
+ expect(response).to have_gitlab_http_status(201)
expect(json_response['title']).to eq('Test merge_request')
expect(json_response['description']).to eq('Test description for Test merge_request')
end
@@ -579,7 +601,7 @@ describe API::MergeRequests do
expect(fork_project.forked_from_project).to eq(project)
post api("/projects/#{fork_project.id}/merge_requests", user2),
title: 'Test merge_request', source_branch: "master", target_branch: "master", author: user2, target_project_id: project.id
- expect(response).to have_http_status(201)
+ expect(response).to have_gitlab_http_status(201)
expect(json_response['title']).to eq('Test merge_request')
end
@@ -593,25 +615,25 @@ describe API::MergeRequests do
author: user2,
target_project_id: project.id
- expect(response).to have_http_status(422)
+ expect(response).to have_gitlab_http_status(422)
end
it "returns 400 when source_branch is missing" do
post api("/projects/#{fork_project.id}/merge_requests", user2),
title: 'Test merge_request', target_branch: "master", author: user2, target_project_id: project.id
- expect(response).to have_http_status(400)
+ expect(response).to have_gitlab_http_status(400)
end
it "returns 400 when target_branch is missing" do
post api("/projects/#{fork_project.id}/merge_requests", user2),
title: 'Test merge_request', target_branch: "master", author: user2, target_project_id: project.id
- expect(response).to have_http_status(400)
+ expect(response).to have_gitlab_http_status(400)
end
it "returns 400 when title is missing" do
post api("/projects/#{fork_project.id}/merge_requests", user2),
target_branch: 'master', source_branch: 'markdown', author: user2, target_project_id: project.id
- expect(response).to have_http_status(400)
+ expect(response).to have_gitlab_http_status(400)
end
context 'when target_branch is specified' do
@@ -622,7 +644,7 @@ describe API::MergeRequests do
source_branch: 'markdown',
author: user,
target_project_id: fork_project.id
- expect(response).to have_http_status(422)
+ expect(response).to have_gitlab_http_status(422)
end
it 'returns 422 if targeting a different fork' do
@@ -632,14 +654,14 @@ describe API::MergeRequests do
source_branch: 'markdown',
author: user2,
target_project_id: unrelated_project.id
- expect(response).to have_http_status(422)
+ expect(response).to have_gitlab_http_status(422)
end
end
it "returns 201 when target_branch is specified and for the same project" do
post api("/projects/#{fork_project.id}/merge_requests", user2),
title: 'Test merge_request', target_branch: 'master', source_branch: 'markdown', author: user2, target_project_id: fork_project.id
- expect(response).to have_http_status(201)
+ expect(response).to have_gitlab_http_status(201)
end
end
end
@@ -654,7 +676,7 @@ describe API::MergeRequests do
it "denies the deletion of the merge request" do
delete api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", developer)
- expect(response).to have_http_status(403)
+ expect(response).to have_gitlab_http_status(403)
end
end
@@ -662,19 +684,19 @@ describe API::MergeRequests do
it "destroys the merge request owners can destroy" do
delete api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user)
- expect(response).to have_http_status(204)
+ expect(response).to have_gitlab_http_status(204)
end
it "returns 404 for an invalid merge request IID" do
delete api("/projects/#{project.id}/merge_requests/12345", user)
- expect(response).to have_http_status(404)
+ expect(response).to have_gitlab_http_status(404)
end
it "returns 404 if the merge request id is used instead of iid" do
delete api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user)
- expect(response).to have_http_status(404)
+ expect(response).to have_gitlab_http_status(404)
end
end
end
@@ -685,7 +707,7 @@ describe API::MergeRequests do
it "returns merge_request in case of success" do
put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user)
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
end
it "returns 406 if branch can't be merged" do
@@ -694,21 +716,21 @@ describe API::MergeRequests do
put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user)
- expect(response).to have_http_status(406)
+ expect(response).to have_gitlab_http_status(406)
expect(json_response['message']).to eq('Branch cannot be merged')
end
it "returns 405 if merge_request is not open" do
merge_request.close
put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user)
- expect(response).to have_http_status(405)
+ expect(response).to have_gitlab_http_status(405)
expect(json_response['message']).to eq('405 Method Not Allowed')
end
it "returns 405 if merge_request is a work in progress" do
merge_request.update_attribute(:title, "WIP: #{merge_request.title}")
put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user)
- expect(response).to have_http_status(405)
+ expect(response).to have_gitlab_http_status(405)
expect(json_response['message']).to eq('405 Method Not Allowed')
end
@@ -717,7 +739,7 @@ describe API::MergeRequests do
put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user)
- expect(response).to have_http_status(405)
+ expect(response).to have_gitlab_http_status(405)
expect(json_response['message']).to eq('405 Method Not Allowed')
end
@@ -725,21 +747,21 @@ describe API::MergeRequests do
user2 = create(:user)
project.team << [user2, :reporter]
put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user2)
- expect(response).to have_http_status(401)
+ expect(response).to have_gitlab_http_status(401)
expect(json_response['message']).to eq('401 Unauthorized')
end
it "returns 409 if the SHA parameter doesn't match" do
put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user), sha: merge_request.diff_head_sha.reverse
- expect(response).to have_http_status(409)
+ expect(response).to have_gitlab_http_status(409)
expect(json_response['message']).to start_with('SHA does not match HEAD of source branch')
end
it "succeeds if the SHA parameter matches" do
put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user), sha: merge_request.diff_head_sha
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
end
it "enables merge when pipeline succeeds if the pipeline is active" do
@@ -748,7 +770,7 @@ describe API::MergeRequests do
put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user), merge_when_pipeline_succeeds: true
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(json_response['title']).to eq('Test')
expect(json_response['merge_when_pipeline_succeeds']).to eq(true)
end
@@ -760,7 +782,7 @@ describe API::MergeRequests do
put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user), merge_when_pipeline_succeeds: true
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(json_response['title']).to eq('Test')
expect(json_response['merge_when_pipeline_succeeds']).to eq(true)
end
@@ -768,13 +790,13 @@ describe API::MergeRequests do
it "returns 404 for an invalid merge request IID" do
put api("/projects/#{project.id}/merge_requests/12345/merge", user)
- expect(response).to have_http_status(404)
+ expect(response).to have_gitlab_http_status(404)
end
it "returns 404 if the merge request id is used instead of iid" do
put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user)
- expect(response).to have_http_status(404)
+ expect(response).to have_gitlab_http_status(404)
end
end
@@ -783,39 +805,39 @@ describe API::MergeRequests do
it "returns merge_request" do
put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), state_event: "close"
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(json_response['state']).to eq('closed')
end
end
it "updates title and returns merge_request" do
put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), title: "New title"
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(json_response['title']).to eq('New title')
end
it "updates description and returns merge_request" do
put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), description: "New description"
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(json_response['description']).to eq('New description')
end
it "updates milestone_id and returns merge_request" do
put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), milestone_id: milestone.id
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(json_response['milestone']['id']).to eq(milestone.id)
end
it "returns merge_request with renamed target_branch" do
put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), target_branch: "wiki"
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(json_response['target_branch']).to eq('wiki')
end
it "returns merge_request that removes the source branch" do
put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), remove_source_branch: true
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(json_response['force_remove_source_branch']).to be_truthy
end
@@ -836,7 +858,7 @@ describe API::MergeRequests do
put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), state_event: 'close', title: nil
merge_request.reload
- expect(response).to have_http_status(400)
+ expect(response).to have_gitlab_http_status(400)
expect(merge_request.state).to eq('opened')
end
@@ -844,20 +866,20 @@ describe API::MergeRequests do
put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), state_event: 'close', target_branch: nil
merge_request.reload
- expect(response).to have_http_status(400)
+ expect(response).to have_gitlab_http_status(400)
expect(merge_request.state).to eq('opened')
end
it "returns 404 for an invalid merge request IID" do
put api("/projects/#{project.id}/merge_requests/12345", user), state_event: "close"
- expect(response).to have_http_status(404)
+ expect(response).to have_gitlab_http_status(404)
end
it "returns 404 if the merge request id is used instead of iid" do
put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), state_event: "close"
- expect(response).to have_http_status(404)
+ expect(response).to have_gitlab_http_status(404)
end
end
@@ -870,7 +892,7 @@ describe API::MergeRequests do
get api("/projects/#{project.id}/merge_requests/#{mr.iid}/closes_issues", user)
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
@@ -880,7 +902,7 @@ describe API::MergeRequests do
it 'returns an empty array when there are no issues to be closed' do
get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/closes_issues", user)
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(0)
@@ -896,7 +918,7 @@ describe API::MergeRequests do
get api("/projects/#{jira_project.id}/merge_requests/#{merge_request.iid}/closes_issues", user)
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(2)
@@ -916,19 +938,19 @@ describe API::MergeRequests do
get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/closes_issues", guest)
- expect(response).to have_http_status(403)
+ expect(response).to have_gitlab_http_status(403)
end
it "returns 404 for an invalid merge request IID" do
get api("/projects/#{project.id}/merge_requests/12345/closes_issues", user)
- expect(response).to have_http_status(404)
+ expect(response).to have_gitlab_http_status(404)
end
it "returns 404 if the merge request id is used instead of iid" do
get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/closes_issues", user)
- expect(response).to have_http_status(404)
+ expect(response).to have_gitlab_http_status(404)
end
end
@@ -936,26 +958,26 @@ describe API::MergeRequests do
it 'subscribes to a merge request' do
post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/subscribe", admin)
- expect(response).to have_http_status(201)
+ expect(response).to have_gitlab_http_status(201)
expect(json_response['subscribed']).to eq(true)
end
it 'returns 304 if already subscribed' do
post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/subscribe", user)
- expect(response).to have_http_status(304)
+ expect(response).to have_gitlab_http_status(304)
end
it 'returns 404 if the merge request is not found' do
post api("/projects/#{project.id}/merge_requests/123/subscribe", user)
- expect(response).to have_http_status(404)
+ expect(response).to have_gitlab_http_status(404)
end
it 'returns 404 if the merge request id is used instead of iid' do
post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscribe", user)
- expect(response).to have_http_status(404)
+ expect(response).to have_gitlab_http_status(404)
end
it 'returns 403 if user has no access to read code' do
@@ -964,7 +986,7 @@ describe API::MergeRequests do
post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/subscribe", guest)
- expect(response).to have_http_status(403)
+ expect(response).to have_gitlab_http_status(403)
end
end
@@ -972,26 +994,26 @@ describe API::MergeRequests do
it 'unsubscribes from a merge request' do
post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/unsubscribe", user)
- expect(response).to have_http_status(201)
+ expect(response).to have_gitlab_http_status(201)
expect(json_response['subscribed']).to eq(false)
end
it 'returns 304 if not subscribed' do
post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/unsubscribe", admin)
- expect(response).to have_http_status(304)
+ expect(response).to have_gitlab_http_status(304)
end
it 'returns 404 if the merge request is not found' do
post api("/projects/#{project.id}/merge_requests/123/unsubscribe", user)
- expect(response).to have_http_status(404)
+ expect(response).to have_gitlab_http_status(404)
end
it 'returns 404 if the merge request id is used instead of iid' do
post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/unsubscribe", user)
- expect(response).to have_http_status(404)
+ expect(response).to have_gitlab_http_status(404)
end
it 'returns 403 if user has no access to read code' do
@@ -1000,7 +1022,7 @@ describe API::MergeRequests do
post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/unsubscribe", guest)
- expect(response).to have_http_status(403)
+ expect(response).to have_gitlab_http_status(403)
end
end
diff --git a/spec/requests/api/pipeline_schedules_spec.rb b/spec/requests/api/pipeline_schedules_spec.rb
index 9ff2b782b52..1fc0ec528b9 100644
--- a/spec/requests/api/pipeline_schedules_spec.rb
+++ b/spec/requests/api/pipeline_schedules_spec.rb
@@ -53,7 +53,7 @@ describe API::PipelineSchedules do
it 'returns matched pipeline schedules' do
get api("/projects/#{project.id}/pipeline_schedules", developer), scope: target
- expect(json_response.map{ |r| r['active'] }).to all(eq(active?(target)))
+ expect(json_response.map { |r| r['active'] }).to all(eq(active?(target)))
end
end
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index b9ebf6c4c16..6cb27d16fe5 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -8,8 +8,8 @@ describe API::Projects do
let(:user2) { create(:user) }
let(:user3) { create(:user) }
let(:admin) { create(:admin) }
- let(:project) { create(:project, creator_id: user.id, namespace: user.namespace) }
- let(:project2) { create(:project, path: 'project2', creator_id: user.id, namespace: user.namespace) }
+ let(:project) { create(:project, namespace: user.namespace) }
+ let(:project2) { create(:project, path: 'project2', namespace: user.namespace) }
let(:snippet) { create(:project_snippet, :public, author: user, project: project, title: 'example') }
let(:project_member) { create(:project_member, :developer, user: user3, project: project) }
let(:user4) { create(:user) }
@@ -186,7 +186,14 @@ describe API::Projects do
context 'and with simple=true' do
it 'returns a simplified version of all the projects' do
- expected_keys = %w(id http_url_to_repo web_url name name_with_namespace path path_with_namespace)
+ expected_keys = %w(
+ id description default_branch tag_list
+ ssh_url_to_repo http_url_to_repo web_url
+ name name_with_namespace
+ path path_with_namespace
+ star_count forks_count
+ created_at last_activity_at
+ )
get api('/projects?simple=true', user)
@@ -689,6 +696,7 @@ describe API::Projects do
expect(response).to have_http_status(200)
expect(json_response['id']).to eq(public_project.id)
expect(json_response['description']).to eq(public_project.description)
+ expect(json_response['default_branch']).to eq(public_project.default_branch)
expect(json_response.keys).not_to include('permissions')
end
end
diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb
index edd6516cf34..e9ee3dd679d 100644
--- a/spec/requests/api/runner_spec.rb
+++ b/spec/requests/api/runner_spec.rb
@@ -683,7 +683,7 @@ describe API::Runner do
end
context 'when job has been updated recently' do
- it { expect{ patch_the_trace }.not_to change { job.updated_at }}
+ it { expect { patch_the_trace }.not_to change { job.updated_at }}
it "changes the job's trace" do
patch_the_trace
@@ -692,7 +692,7 @@ describe API::Runner do
end
context 'when Runner makes a force-patch' do
- it { expect{ force_patch_the_trace }.not_to change { job.updated_at }}
+ it { expect { force_patch_the_trace }.not_to change { job.updated_at }}
it "doesn't change the build.trace" do
force_patch_the_trace
diff --git a/spec/requests/api/runners_spec.rb b/spec/requests/api/runners_spec.rb
index 3a95db030d4..c8ff25f70fa 100644
--- a/spec/requests/api/runners_spec.rb
+++ b/spec/requests/api/runners_spec.rb
@@ -36,7 +36,7 @@ describe API::Runners do
it 'returns user available runners' do
get api('/runners', user)
- shared = json_response.any?{ |r| r['is_shared'] }
+ shared = json_response.any? { |r| r['is_shared'] }
expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
@@ -46,7 +46,7 @@ describe API::Runners do
it 'filters runners by scope' do
get api('/runners?scope=active', user)
- shared = json_response.any?{ |r| r['is_shared'] }
+ shared = json_response.any? { |r| r['is_shared'] }
expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
@@ -74,7 +74,7 @@ describe API::Runners do
it 'returns all runners' do
get api('/runners/all', admin)
- shared = json_response.any?{ |r| r['is_shared'] }
+ shared = json_response.any? { |r| r['is_shared'] }
expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
@@ -93,7 +93,7 @@ describe API::Runners do
it 'filters runners by scope' do
get api('/runners/all?scope=specific', admin)
- shared = json_response.any?{ |r| r['is_shared'] }
+ shared = json_response.any? { |r| r['is_shared'] }
expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
@@ -277,7 +277,7 @@ describe API::Runners do
delete api("/runners/#{shared_runner.id}", admin)
expect(response).to have_http_status(204)
- end.to change{ Ci::Runner.shared.count }.by(-1)
+ end.to change { Ci::Runner.shared.count }.by(-1)
end
end
@@ -287,7 +287,7 @@ describe API::Runners do
delete api("/runners/#{unused_specific_runner.id}", admin)
expect(response).to have_http_status(204)
- end.to change{ Ci::Runner.specific.count }.by(-1)
+ end.to change { Ci::Runner.specific.count }.by(-1)
end
it 'deletes used runner' do
@@ -295,7 +295,7 @@ describe API::Runners do
delete api("/runners/#{specific_runner.id}", admin)
expect(response).to have_http_status(204)
- end.to change{ Ci::Runner.specific.count }.by(-1)
+ end.to change { Ci::Runner.specific.count }.by(-1)
end
end
@@ -330,7 +330,7 @@ describe API::Runners do
delete api("/runners/#{specific_runner.id}", user)
expect(response).to have_http_status(204)
- end.to change{ Ci::Runner.specific.count }.by(-1)
+ end.to change { Ci::Runner.specific.count }.by(-1)
end
end
end
@@ -349,7 +349,7 @@ describe API::Runners do
it "returns project's runners" do
get api("/projects/#{project.id}/runners", user)
- shared = json_response.any?{ |r| r['is_shared'] }
+ shared = json_response.any? { |r| r['is_shared'] }
expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
@@ -385,14 +385,14 @@ describe API::Runners do
it 'enables specific runner' do
expect do
post api("/projects/#{project.id}/runners", user), runner_id: specific_runner2.id
- end.to change{ project.runners.count }.by(+1)
+ end.to change { project.runners.count }.by(+1)
expect(response).to have_http_status(201)
end
it 'avoids changes when enabling already enabled runner' do
expect do
post api("/projects/#{project.id}/runners", user), runner_id: specific_runner.id
- end.to change{ project.runners.count }.by(0)
+ end.to change { project.runners.count }.by(0)
expect(response).to have_http_status(409)
end
@@ -401,7 +401,7 @@ describe API::Runners do
expect do
post api("/projects/#{project.id}/runners", user), runner_id: specific_runner2.id
- end.to change{ project.runners.count }.by(0)
+ end.to change { project.runners.count }.by(0)
expect(response).to have_http_status(403)
end
@@ -416,7 +416,7 @@ describe API::Runners do
it 'enables any specific runner' do
expect do
post api("/projects/#{project.id}/runners", admin), runner_id: unused_specific_runner.id
- end.to change{ project.runners.count }.by(+1)
+ end.to change { project.runners.count }.by(+1)
expect(response).to have_http_status(201)
end
end
@@ -461,7 +461,7 @@ describe API::Runners do
delete api("/projects/#{project.id}/runners/#{two_projects_runner.id}", user)
expect(response).to have_http_status(204)
- end.to change{ project.runners.count }.by(-1)
+ end.to change { project.runners.count }.by(-1)
end
end
@@ -469,7 +469,7 @@ describe API::Runners do
it "does not disable project's runner" do
expect do
delete api("/projects/#{project.id}/runners/#{specific_runner.id}", user)
- end.to change{ project.runners.count }.by(0)
+ end.to change { project.runners.count }.by(0)
expect(response).to have_http_status(403)
end
end
diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb
index c3ed5cd8ece..97275b80d03 100644
--- a/spec/requests/api/settings_spec.rb
+++ b/spec/requests/api/settings_spec.rb
@@ -43,7 +43,9 @@ describe API::Settings, 'Settings' do
default_artifacts_expire_in: '2 days',
help_page_text: 'custom help text',
help_page_hide_commercial_content: true,
- help_page_support_url: 'http://example.com/help'
+ help_page_support_url: 'http://example.com/help',
+ project_export_enabled: false
+
expect(response).to have_http_status(200)
expect(json_response['default_projects_limit']).to eq(3)
expect(json_response['password_authentication_enabled']).to be_falsey
@@ -58,6 +60,7 @@ describe API::Settings, 'Settings' do
expect(json_response['help_page_text']).to eq('custom help text')
expect(json_response['help_page_hide_commercial_content']).to be_truthy
expect(json_response['help_page_support_url']).to eq('http://example.com/help')
+ expect(json_response['project_export_enabled']).to be_falsey
end
end
diff --git a/spec/requests/api/snippets_spec.rb b/spec/requests/api/snippets_spec.rb
index 373fab4d98a..d09b8bc42f1 100644
--- a/spec/requests/api/snippets_spec.rb
+++ b/spec/requests/api/snippets_spec.rb
@@ -52,10 +52,10 @@ describe API::Snippets do
expect(json_response.map { |snippet| snippet['id']} ).to contain_exactly(
public_snippet.id,
public_snippet_other.id)
- expect(json_response.map{ |snippet| snippet['web_url']} ).to include(
+ expect(json_response.map { |snippet| snippet['web_url']} ).to include(
"http://localhost/snippets/#{public_snippet.id}",
"http://localhost/snippets/#{public_snippet_other.id}")
- expect(json_response.map{ |snippet| snippet['raw_url']} ).to include(
+ expect(json_response.map { |snippet| snippet['raw_url']} ).to include(
"http://localhost/snippets/#{public_snippet.id}/raw",
"http://localhost/snippets/#{public_snippet_other.id}/raw")
end
diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb
index ef7d0c3ee41..9884c1ec206 100644
--- a/spec/requests/api/tags_spec.rb
+++ b/spec/requests/api/tags_spec.rb
@@ -1,66 +1,85 @@
require 'spec_helper'
-require 'mime/types'
describe API::Tags do
- include RepoHelpers
-
let(:user) { create(:user) }
- let(:user2) { create(:user) }
- let!(:project) { create(:project, :repository, creator: user) }
- let!(:master) { create(:project_member, :master, user: user, project: project) }
- let!(:guest) { create(:project_member, :guest, user: user2, project: project) }
+ let(:guest) { create(:user).tap { |u| project.add_guest(u) } }
+ let(:project) { create(:project, :repository, creator: user, path: 'my.project') }
+ let(:tag_name) { project.repository.find_tag('v1.1.0').name }
- describe "GET /projects/:id/repository/tags" do
- let(:tag_name) { project.repository.tag_names.sort.reverse.first }
- let(:description) { 'Awesome release!' }
+ let(:project_id) { project.id }
+ let(:current_user) { nil }
+
+ before do
+ project.add_master(user)
+ end
+
+ describe 'GET /projects/:id/repository/tags' do
+ let(:route) { "/projects/#{project_id}/repository/tags" }
shared_examples_for 'repository tags' do
it 'returns the repository tags' do
- get api("/projects/#{project.id}/repository/tags", current_user)
+ get api(route, current_user)
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to match_response_schema('public_api/v4/tags')
expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
expect(json_response.first['name']).to eq(tag_name)
end
- end
- context 'when unauthenticated' do
- it_behaves_like 'repository tags' do
- let(:project) { create(:project, :public, :repository) }
- let(:current_user) { nil }
+ context 'when repository is disabled' do
+ include_context 'disabled repository'
+
+ it_behaves_like '403 response' do
+ let(:request) { get api(route, current_user) }
+ end
end
end
- context 'when authenticated' do
- it_behaves_like 'repository tags' do
- let(:current_user) { user }
+ context 'when unauthenticated', 'and project is public' do
+ let(:project) { create(:project, :public, :repository) }
+
+ it_behaves_like 'repository tags'
+ end
+
+ context 'when unauthenticated', 'and project is private' do
+ it_behaves_like '404 response' do
+ let(:request) { get api(route) }
+ let(:message) { '404 Project Not Found' }
end
end
- context 'without releases' do
- it "returns an array of project tags" do
- get api("/projects/#{project.id}/repository/tags", user)
+ context 'when authenticated', 'as a master' do
+ let(:current_user) { user }
- expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.first['name']).to eq(tag_name)
+ it_behaves_like 'repository tags'
+
+ context 'requesting with the escaped project full path' do
+ let(:project_id) { CGI.escape(project.full_path) }
+
+ it_behaves_like 'repository tags'
+ end
+ end
+
+ context 'when authenticated', 'as a guest' do
+ it_behaves_like '403 response' do
+ let(:request) { get api(route, guest) }
end
end
context 'with releases' do
+ let(:description) { 'Awesome release!' }
+
before do
release = project.releases.find_or_initialize_by(tag: tag_name)
release.update_attributes(description: description)
end
- it "returns an array of project tags with release info" do
- get api("/projects/#{project.id}/repository/tags", user)
+ it 'returns an array of project tags with release info' do
+ get api(route, user)
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to match_response_schema('public_api/v4/tags')
expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
expect(json_response.first['name']).to eq(tag_name)
expect(json_response.first['message']).to eq('Version 1.1.0')
expect(json_response.first['release']['description']).to eq(description)
@@ -69,210 +88,342 @@ describe API::Tags do
end
describe 'GET /projects/:id/repository/tags/:tag_name' do
- let(:tag_name) { project.repository.tag_names.sort.reverse.first }
+ let(:route) { "/projects/#{project_id}/repository/tags/#{tag_name}" }
shared_examples_for 'repository tag' do
- it 'returns the repository tag' do
- get api("/projects/#{project.id}/repository/tags/#{tag_name}", current_user)
-
- expect(response).to have_http_status(200)
+ it 'returns the repository branch' do
+ get api(route, current_user)
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to match_response_schema('public_api/v4/tag')
expect(json_response['name']).to eq(tag_name)
end
- it 'returns 404 for an invalid tag name' do
- get api("/projects/#{project.id}/repository/tags/foobar", current_user)
+ context 'when tag does not exist' do
+ let(:tag_name) { 'unknown' }
- expect(response).to have_http_status(404)
+ it_behaves_like '404 response' do
+ let(:request) { get api(route, current_user) }
+ let(:message) { '404 Tag Not Found' }
+ end
+ end
+
+ context 'when repository is disabled' do
+ include_context 'disabled repository'
+
+ it_behaves_like '403 response' do
+ let(:request) { get api(route, current_user) }
+ end
end
end
- context 'when unauthenticated' do
- it_behaves_like 'repository tag' do
- let(:project) { create(:project, :public, :repository) }
- let(:current_user) { nil }
+ context 'when unauthenticated', 'and project is public' do
+ let(:project) { create(:project, :public, :repository) }
+
+ it_behaves_like 'repository tag'
+ end
+
+ context 'when unauthenticated', 'and project is private' do
+ it_behaves_like '404 response' do
+ let(:request) { get api(route) }
+ let(:message) { '404 Project Not Found' }
end
end
- context 'when authenticated' do
- it_behaves_like 'repository tag' do
- let(:current_user) { user }
+ context 'when authenticated', 'as a master' do
+ let(:current_user) { user }
+
+ it_behaves_like 'repository tag'
+
+ context 'requesting with the escaped project full path' do
+ let(:project_id) { CGI.escape(project.full_path) }
+
+ it_behaves_like 'repository tag'
+ end
+ end
+
+ context 'when authenticated', 'as a guest' do
+ it_behaves_like '403 response' do
+ let(:request) { get api(route, guest) }
end
end
end
describe 'POST /projects/:id/repository/tags' do
- context 'lightweight tags' do
+ let(:tag_name) { 'new_tag' }
+ let(:route) { "/projects/#{project_id}/repository/tags" }
+
+ shared_examples_for 'repository new tag' do
it 'creates a new tag' do
- post api("/projects/#{project.id}/repository/tags", user),
- tag_name: 'v7.0.1',
- ref: 'master'
+ post api(route, current_user), tag_name: tag_name, ref: 'master'
- expect(response).to have_http_status(201)
- expect(json_response['name']).to eq('v7.0.1')
+ expect(response).to have_gitlab_http_status(201)
+ expect(response).to match_response_schema('public_api/v4/tag')
+ expect(json_response['name']).to eq(tag_name)
end
- end
- context 'lightweight tags with release notes' do
- it 'creates a new tag' do
- post api("/projects/#{project.id}/repository/tags", user),
- tag_name: 'v7.0.1',
- ref: 'master',
- release_description: 'Wow'
+ context 'when repository is disabled' do
+ include_context 'disabled repository'
- expect(response).to have_http_status(201)
- expect(json_response['name']).to eq('v7.0.1')
- expect(json_response['release']['description']).to eq('Wow')
+ it_behaves_like '403 response' do
+ let(:request) { post api(route, current_user) }
+ end
end
end
- describe 'DELETE /projects/:id/repository/tags/:tag_name' do
- let(:tag_name) { project.repository.tag_names.sort.reverse.first }
+ context 'when unauthenticated', 'and project is private' do
+ it_behaves_like '404 response' do
+ let(:request) { post api(route) }
+ let(:message) { '404 Project Not Found' }
+ end
+ end
- before do
- allow_any_instance_of(Repository).to receive(:rm_tag).and_return(true)
+ context 'when authenticated', 'as a guest' do
+ it_behaves_like '403 response' do
+ let(:request) { post api(route, guest) }
end
+ end
+
+ context 'when authenticated', 'as a master' do
+ let(:current_user) { user }
+
+ context "when a protected branch doesn't already exist" do
+ it_behaves_like 'repository new tag'
- context 'delete tag' do
- it 'deletes an existing tag' do
- delete api("/projects/#{project.id}/repository/tags/#{tag_name}", user)
+ context 'when tag contains a dot' do
+ let(:tag_name) { 'v7.0.1' }
- expect(response).to have_http_status(204)
+ it_behaves_like 'repository new tag'
end
- it 'raises 404 if the tag does not exist' do
- delete api("/projects/#{project.id}/repository/tags/foobar", user)
- expect(response).to have_http_status(404)
+ context 'requesting with the escaped project full path' do
+ let(:project_id) { CGI.escape(project.full_path) }
+
+ it_behaves_like 'repository new tag'
+
+ context 'when tag contains a dot' do
+ let(:tag_name) { 'v7.0.1' }
+
+ it_behaves_like 'repository new tag'
+ end
end
end
- end
- context 'annotated tag' do
- it 'creates a new annotated tag' do
- # Identity must be set in .gitconfig to create annotated tag.
- repo_path = project.repository.path_to_repo
- system(*%W(#{Gitlab.config.git.bin_path} --git-dir=#{repo_path} config user.name #{user.name}))
- system(*%W(#{Gitlab.config.git.bin_path} --git-dir=#{repo_path} config user.email #{user.email}))
+ it 'returns 400 if tag name is invalid' do
+ post api(route, current_user), tag_name: 'new design', ref: 'master'
+
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response['message']).to eq('Tag name invalid')
+ end
+
+ it 'returns 400 if tag already exists' do
+ post api(route, current_user), tag_name: 'new_design1', ref: 'master'
- post api("/projects/#{project.id}/repository/tags", user),
- tag_name: 'v7.1.0',
- ref: 'master',
- message: 'Release 7.1.0'
+ expect(response).to have_gitlab_http_status(201)
+ expect(response).to match_response_schema('public_api/v4/tag')
- expect(response).to have_http_status(201)
- expect(json_response['name']).to eq('v7.1.0')
- expect(json_response['message']).to eq('Release 7.1.0')
+ post api(route, current_user), tag_name: 'new_design1', ref: 'master'
+
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response['message']).to eq('Tag new_design1 already exists')
end
- end
- it 'denies for user without push access' do
- post api("/projects/#{project.id}/repository/tags", user2),
- tag_name: 'v1.9.0',
- ref: '621491c677087aa243f165eab467bfdfbee00be1'
- expect(response).to have_http_status(403)
+ it 'returns 400 if ref name is invalid' do
+ post api(route, current_user), tag_name: 'new_design3', ref: 'foo'
+
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response['message']).to eq('Target foo is invalid')
+ end
+
+ context 'lightweight tags with release notes' do
+ it 'creates a new tag' do
+ post api(route, current_user), tag_name: tag_name, ref: 'master', release_description: 'Wow'
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(response).to match_response_schema('public_api/v4/tag')
+ expect(json_response['name']).to eq(tag_name)
+ expect(json_response['release']['description']).to eq('Wow')
+ end
+ end
+
+ context 'annotated tag' do
+ it 'creates a new annotated tag' do
+ # Identity must be set in .gitconfig to create annotated tag.
+ repo_path = project.repository.path_to_repo
+ system(*%W(#{Gitlab.config.git.bin_path} --git-dir=#{repo_path} config user.name #{user.name}))
+ system(*%W(#{Gitlab.config.git.bin_path} --git-dir=#{repo_path} config user.email #{user.email}))
+
+ post api(route, current_user), tag_name: 'v7.1.0', ref: 'master', message: 'Release 7.1.0'
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(response).to match_response_schema('public_api/v4/tag')
+ expect(json_response['name']).to eq('v7.1.0')
+ expect(json_response['message']).to eq('Release 7.1.0')
+ end
+ end
end
+ end
+
+ describe 'DELETE /projects/:id/repository/tags/:tag_name' do
+ let(:route) { "/projects/#{project_id}/repository/tags/#{tag_name}" }
- it 'returns 400 if tag name is invalid' do
- post api("/projects/#{project.id}/repository/tags", user),
- tag_name: 'v 1.0.0',
- ref: 'master'
- expect(response).to have_http_status(400)
- expect(json_response['message']).to eq('Tag name invalid')
+ before do
+ allow_any_instance_of(Repository).to receive(:rm_tag).and_return(true)
end
- it 'returns 400 if tag already exists' do
- post api("/projects/#{project.id}/repository/tags", user),
- tag_name: 'v8.0.0',
- ref: 'master'
- expect(response).to have_http_status(201)
- post api("/projects/#{project.id}/repository/tags", user),
- tag_name: 'v8.0.0',
- ref: 'master'
- expect(response).to have_http_status(400)
- expect(json_response['message']).to eq('Tag v8.0.0 already exists')
+ shared_examples_for 'repository delete tag' do
+ it 'deletes a tag' do
+ delete api(route, current_user)
+
+ expect(response).to have_gitlab_http_status(204)
+ end
+
+ context 'when tag does not exist' do
+ let(:tag_name) { 'unknown' }
+
+ it_behaves_like '404 response' do
+ let(:request) { delete api(route, current_user) }
+ let(:message) { 'No such tag' }
+ end
+ end
+
+ context 'when repository is disabled' do
+ include_context 'disabled repository'
+
+ it_behaves_like '403 response' do
+ let(:request) { delete api(route, current_user) }
+ end
+ end
end
- it 'returns 400 if ref name is invalid' do
- post api("/projects/#{project.id}/repository/tags", user),
- tag_name: 'mytag',
- ref: 'foo'
- expect(response).to have_http_status(400)
- expect(json_response['message']).to eq('Target foo is invalid')
+ context 'when authenticated', 'as a master' do
+ let(:current_user) { user }
+
+ it_behaves_like 'repository delete tag'
+
+ context 'requesting with the escaped project full path' do
+ let(:project_id) { CGI.escape(project.full_path) }
+
+ it_behaves_like 'repository delete tag'
+ end
end
end
describe 'POST /projects/:id/repository/tags/:tag_name/release' do
- let(:tag_name) { project.repository.tag_names.first }
+ let(:route) { "/projects/#{project_id}/repository/tags/#{tag_name}/release" }
let(:description) { 'Awesome release!' }
- it 'creates description for existing git tag' do
- post api("/projects/#{project.id}/repository/tags/#{tag_name}/release", user),
- description: description
+ shared_examples_for 'repository new release' do
+ it 'creates description for existing git tag' do
+ post api(route, user), description: description
- expect(response).to have_http_status(201)
- expect(json_response['tag_name']).to eq(tag_name)
- expect(json_response['description']).to eq(description)
- end
+ expect(response).to have_gitlab_http_status(201)
+ expect(response).to match_response_schema('public_api/v4/release')
+ expect(json_response['tag_name']).to eq(tag_name)
+ expect(json_response['description']).to eq(description)
+ end
+
+ context 'when tag does not exist' do
+ let(:tag_name) { 'unknown' }
+
+ it_behaves_like '404 response' do
+ let(:request) { post api(route, current_user), description: description }
+ let(:message) { 'Tag does not exist' }
+ end
+ end
- it 'returns 404 if the tag does not exist' do
- post api("/projects/#{project.id}/repository/tags/foobar/release", user),
- description: description
+ context 'when repository is disabled' do
+ include_context 'disabled repository'
- expect(response).to have_http_status(404)
- expect(json_response['message']).to eq('Tag does not exist')
+ it_behaves_like '403 response' do
+ let(:request) { post api(route, current_user), description: description }
+ end
+ end
end
- context 'on tag with existing release' do
- before do
- release = project.releases.find_or_initialize_by(tag: tag_name)
- release.update_attributes(description: description)
+ context 'when authenticated', 'as a master' do
+ let(:current_user) { user }
+
+ it_behaves_like 'repository new release'
+
+ context 'requesting with the escaped project full path' do
+ let(:project_id) { CGI.escape(project.full_path) }
+
+ it_behaves_like 'repository new release'
end
- it 'returns 409 if there is already a release' do
- post api("/projects/#{project.id}/repository/tags/#{tag_name}/release", user),
- description: description
+ context 'on tag with existing release' do
+ before do
+ release = project.releases.find_or_initialize_by(tag: tag_name)
+ release.update_attributes(description: description)
+ end
+
+ it 'returns 409 if there is already a release' do
+ post api(route, user), description: description
- expect(response).to have_http_status(409)
- expect(json_response['message']).to eq('Release already exists')
+ expect(response).to have_gitlab_http_status(409)
+ expect(json_response['message']).to eq('Release already exists')
+ end
end
end
end
describe 'PUT id/repository/tags/:tag_name/release' do
- let(:tag_name) { project.repository.tag_names.first }
+ let(:route) { "/projects/#{project_id}/repository/tags/#{tag_name}/release" }
let(:description) { 'Awesome release!' }
let(:new_description) { 'The best release!' }
- context 'on tag with existing release' do
- before do
- release = project.releases.find_or_initialize_by(tag: tag_name)
- release.update_attributes(description: description)
+ shared_examples_for 'repository update release' do
+ context 'on tag with existing release' do
+ before do
+ release = project.releases.find_or_initialize_by(tag: tag_name)
+ release.update_attributes(description: description)
+ end
+
+ it 'updates the release description' do
+ put api(route, current_user), description: new_description
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['tag_name']).to eq(tag_name)
+ expect(json_response['description']).to eq(new_description)
+ end
end
- it 'updates the release description' do
- put api("/projects/#{project.id}/repository/tags/#{tag_name}/release", user),
- description: new_description
+ context 'when tag does not exist' do
+ let(:tag_name) { 'unknown' }
- expect(response).to have_http_status(200)
- expect(json_response['tag_name']).to eq(tag_name)
- expect(json_response['description']).to eq(new_description)
+ it_behaves_like '404 response' do
+ let(:request) { put api(route, current_user), description: new_description }
+ let(:message) { 'Tag does not exist' }
+ end
end
- end
- it 'returns 404 if the tag does not exist' do
- put api("/projects/#{project.id}/repository/tags/foobar/release", user),
- description: new_description
+ context 'when repository is disabled' do
+ include_context 'disabled repository'
- expect(response).to have_http_status(404)
- expect(json_response['message']).to eq('Tag does not exist')
+ it_behaves_like '403 response' do
+ let(:request) { put api(route, current_user), description: new_description }
+ end
+ end
end
- it 'returns 404 if the release does not exist' do
- put api("/projects/#{project.id}/repository/tags/#{tag_name}/release", user),
- description: new_description
+ context 'when authenticated', 'as a master' do
+ let(:current_user) { user }
+
+ it_behaves_like 'repository update release'
- expect(response).to have_http_status(404)
- expect(json_response['message']).to eq('Release does not exist')
+ context 'requesting with the escaped project full path' do
+ let(:project_id) { CGI.escape(project.full_path) }
+
+ it_behaves_like 'repository update release'
+ end
+
+ context 'when release does not exist' do
+ it_behaves_like '404 response' do
+ let(:request) { put api(route, current_user), description: new_description }
+ let(:message) { 'Release does not exist' }
+ end
+ end
end
end
end
diff --git a/spec/requests/api/triggers_spec.rb b/spec/requests/api/triggers_spec.rb
index d5c53b703dd..572e9a0fd07 100644
--- a/spec/requests/api/triggers_spec.rb
+++ b/spec/requests/api/triggers_spec.rb
@@ -185,7 +185,7 @@ describe API::Triggers do
expect do
post api("/projects/#{project.id}/triggers", user),
description: 'trigger'
- end.to change{project.triggers.count}.by(1)
+ end.to change {project.triggers.count}.by(1)
expect(response).to have_http_status(201)
expect(json_response).to include('description' => 'trigger')
@@ -288,7 +288,7 @@ describe API::Triggers do
delete api("/projects/#{project.id}/triggers/#{trigger.id}", user)
expect(response).to have_http_status(204)
- end.to change{project.triggers.count}.by(-1)
+ end.to change {project.triggers.count}.by(-1)
end
it 'responds with 404 Not Found if requesting non-existing trigger' do
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index 2dc7be22f8f..49739a1601a 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -217,9 +217,19 @@ describe API::Users do
it "does not return the user's `is_admin` flag" do
get api("/users/#{user.id}", user)
+ expect(response).to have_http_status(200)
expect(json_response['is_admin']).to be_nil
end
+ context 'when authenticated as admin' do
+ it 'includes the `is_admin` field' do
+ get api("/users/#{user.id}", admin)
+
+ expect(response).to have_http_status(200)
+ expect(json_response['is_admin']).to be(false)
+ end
+ end
+
context 'for an anonymous user' do
it "returns a user by id" do
get api("/users/#{user.id}")
diff --git a/spec/requests/api/v3/deploy_keys_spec.rb b/spec/requests/api/v3/deploy_keys_spec.rb
index 13a62423b1d..2affd0cfa51 100644
--- a/spec/requests/api/v3/deploy_keys_spec.rb
+++ b/spec/requests/api/v3/deploy_keys_spec.rb
@@ -87,7 +87,7 @@ describe API::V3::DeployKeys do
expect do
post v3_api("/projects/#{project.id}/#{path}", admin), key_attrs
- end.to change{ project.deploy_keys.count }.by(1)
+ end.to change { project.deploy_keys.count }.by(1)
end
it 'returns an existing ssh key when attempting to add a duplicate' do
@@ -122,7 +122,7 @@ describe API::V3::DeployKeys do
it 'should delete existing key' do
expect do
delete v3_api("/projects/#{project.id}/#{path}/#{deploy_key.id}", admin)
- end.to change{ project.deploy_keys.count }.by(-1)
+ end.to change { project.deploy_keys.count }.by(-1)
end
it 'should return 404 Not Found with invalid ID' do
diff --git a/spec/requests/api/v3/groups_spec.rb b/spec/requests/api/v3/groups_spec.rb
index 10756e494c3..778fcc73c30 100644
--- a/spec/requests/api/v3/groups_spec.rb
+++ b/spec/requests/api/v3/groups_spec.rb
@@ -504,7 +504,7 @@ describe API::V3::Groups do
let(:project) { create(:project) }
let(:project_path) { CGI.escape(project.full_path) }
- before(:each) do
+ before do
allow_any_instance_of(Projects::TransferService)
.to receive(:execute).and_return(true)
end
diff --git a/spec/requests/api/v3/issues_spec.rb b/spec/requests/api/v3/issues_spec.rb
index b092c863c8a..9eb538c4b09 100644
--- a/spec/requests/api/v3/issues_spec.rb
+++ b/spec/requests/api/v3/issues_spec.rb
@@ -1,8 +1,6 @@
require 'spec_helper'
-describe API::V3::Issues do
- include EmailHelpers
-
+describe API::V3::Issues, :mailer do
let(:user) { create(:user) }
let(:user2) { create(:user) }
let(:non_member) { create(:user) }
diff --git a/spec/requests/api/v3/merge_requests_spec.rb b/spec/requests/api/v3/merge_requests_spec.rb
index ef7516fc28f..86f38dd4ec1 100644
--- a/spec/requests/api/v3/merge_requests_spec.rb
+++ b/spec/requests/api/v3/merge_requests_spec.rb
@@ -90,7 +90,7 @@ describe API::MergeRequests do
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(3)
- response_dates = json_response.map{ |merge_request| merge_request['created_at'] }
+ response_dates = json_response.map { |merge_request| merge_request['created_at'] }
expect(response_dates).to eq(response_dates.sort)
end
@@ -99,7 +99,7 @@ describe API::MergeRequests do
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(3)
- response_dates = json_response.map{ |merge_request| merge_request['created_at'] }
+ response_dates = json_response.map { |merge_request| merge_request['created_at'] }
expect(response_dates).to eq(response_dates.sort.reverse)
end
@@ -108,7 +108,7 @@ describe API::MergeRequests do
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(3)
- response_dates = json_response.map{ |merge_request| merge_request['updated_at'] }
+ response_dates = json_response.map { |merge_request| merge_request['updated_at'] }
expect(response_dates).to eq(response_dates.sort.reverse)
end
@@ -117,7 +117,7 @@ describe API::MergeRequests do
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(3)
- response_dates = json_response.map{ |merge_request| merge_request['created_at'] }
+ response_dates = json_response.map { |merge_request| merge_request['created_at'] }
expect(response_dates).to eq(response_dates.sort)
end
end
@@ -315,15 +315,17 @@ describe API::MergeRequests do
let!(:fork_project) { create(:project, forked_from_project: project, namespace: user2.namespace, creator_id: user2.id) }
let!(:unrelated_project) { create(:project, namespace: create(:user).namespace, creator_id: user2.id) }
- before :each do |each|
- fork_project.team << [user2, :reporter]
+ before do
+ fork_project.add_reporter(user2)
+
+ allow_any_instance_of(MergeRequest).to receive(:write_ref)
end
it "returns merge_request" do
post v3_api("/projects/#{fork_project.id}/merge_requests", user2),
title: 'Test merge_request', source_branch: "feature_conflict", target_branch: "master",
author: user2, target_project_id: project.id, description: 'Test description for Test merge_request'
- expect(response).to have_http_status(201)
+ expect(response).to have_gitlab_http_status(201)
expect(json_response['title']).to eq('Test merge_request')
expect(json_response['description']).to eq('Test description for Test merge_request')
end
@@ -334,7 +336,7 @@ describe API::MergeRequests do
expect(fork_project.forked_from_project).to eq(project)
post v3_api("/projects/#{fork_project.id}/merge_requests", user2),
title: 'Test merge_request', source_branch: "master", target_branch: "master", author: user2, target_project_id: project.id
- expect(response).to have_http_status(201)
+ expect(response).to have_gitlab_http_status(201)
expect(json_response['title']).to eq('Test merge_request')
end
@@ -348,25 +350,25 @@ describe API::MergeRequests do
author: user2,
target_project_id: project.id
- expect(response).to have_http_status(422)
+ expect(response).to have_gitlab_http_status(422)
end
it "returns 400 when source_branch is missing" do
post v3_api("/projects/#{fork_project.id}/merge_requests", user2),
title: 'Test merge_request', target_branch: "master", author: user2, target_project_id: project.id
- expect(response).to have_http_status(400)
+ expect(response).to have_gitlab_http_status(400)
end
it "returns 400 when target_branch is missing" do
post v3_api("/projects/#{fork_project.id}/merge_requests", user2),
title: 'Test merge_request', target_branch: "master", author: user2, target_project_id: project.id
- expect(response).to have_http_status(400)
+ expect(response).to have_gitlab_http_status(400)
end
it "returns 400 when title is missing" do
post v3_api("/projects/#{fork_project.id}/merge_requests", user2),
target_branch: 'master', source_branch: 'markdown', author: user2, target_project_id: project.id
- expect(response).to have_http_status(400)
+ expect(response).to have_gitlab_http_status(400)
end
context 'when target_branch is specified' do
@@ -377,7 +379,7 @@ describe API::MergeRequests do
source_branch: 'markdown',
author: user,
target_project_id: fork_project.id
- expect(response).to have_http_status(422)
+ expect(response).to have_gitlab_http_status(422)
end
it 'returns 422 if targeting a different fork' do
@@ -387,14 +389,14 @@ describe API::MergeRequests do
source_branch: 'markdown',
author: user2,
target_project_id: unrelated_project.id
- expect(response).to have_http_status(422)
+ expect(response).to have_gitlab_http_status(422)
end
end
it "returns 201 when target_branch is specified and for the same project" do
post v3_api("/projects/#{fork_project.id}/merge_requests", user2),
title: 'Test merge_request', target_branch: 'master', source_branch: 'markdown', author: user2, target_project_id: fork_project.id
- expect(response).to have_http_status(201)
+ expect(response).to have_gitlab_http_status(201)
end
end
end
diff --git a/spec/requests/api/v3/project_snippets_spec.rb b/spec/requests/api/v3/project_snippets_spec.rb
index 758fb482374..3963924a066 100644
--- a/spec/requests/api/v3/project_snippets_spec.rb
+++ b/spec/requests/api/v3/project_snippets_spec.rb
@@ -30,7 +30,7 @@ describe API::ProjectSnippets do
expect(response).to have_http_status(200)
expect(json_response.size).to eq(3)
- expect(json_response.map{ |snippet| snippet['id']} ).to include(public_snippet.id, internal_snippet.id, private_snippet.id)
+ expect(json_response.map { |snippet| snippet['id']} ).to include(public_snippet.id, internal_snippet.id, private_snippet.id)
expect(json_response.last).to have_key('web_url')
end
diff --git a/spec/requests/api/v3/projects_spec.rb b/spec/requests/api/v3/projects_spec.rb
index c211cc20e53..fca5b5b5d82 100644
--- a/spec/requests/api/v3/projects_spec.rb
+++ b/spec/requests/api/v3/projects_spec.rb
@@ -82,7 +82,14 @@ describe API::V3::Projects do
context 'GET /projects?simple=true' do
it 'returns a simplified version of all the projects' do
- expected_keys = %w(id http_url_to_repo web_url name name_with_namespace path path_with_namespace)
+ expected_keys = %w(
+ id description default_branch tag_list
+ ssh_url_to_repo http_url_to_repo web_url
+ name name_with_namespace
+ path path_with_namespace
+ star_count forks_count
+ created_at last_activity_at
+ )
get v3_api('/projects?simple=true', user)
@@ -644,6 +651,7 @@ describe API::V3::Projects do
expect(response).to have_http_status(200)
expect(json_response['id']).to eq(public_project.id)
expect(json_response['description']).to eq(public_project.description)
+ expect(json_response['default_branch']).to eq(public_project.default_branch)
expect(json_response.keys).not_to include('permissions')
end
end
diff --git a/spec/requests/api/v3/runners_spec.rb b/spec/requests/api/v3/runners_spec.rb
index 78660afd840..a31eb3f1d43 100644
--- a/spec/requests/api/v3/runners_spec.rb
+++ b/spec/requests/api/v3/runners_spec.rb
@@ -38,7 +38,7 @@ describe API::V3::Runners do
delete v3_api("/runners/#{shared_runner.id}", admin)
expect(response).to have_http_status(200)
- end.to change{ Ci::Runner.shared.count }.by(-1)
+ end.to change { Ci::Runner.shared.count }.by(-1)
end
end
@@ -48,7 +48,7 @@ describe API::V3::Runners do
delete v3_api("/runners/#{unused_specific_runner.id}", admin)
expect(response).to have_http_status(200)
- end.to change{ Ci::Runner.specific.count }.by(-1)
+ end.to change { Ci::Runner.specific.count }.by(-1)
end
it 'deletes used runner' do
@@ -56,7 +56,7 @@ describe API::V3::Runners do
delete v3_api("/runners/#{specific_runner.id}", admin)
expect(response).to have_http_status(200)
- end.to change{ Ci::Runner.specific.count }.by(-1)
+ end.to change { Ci::Runner.specific.count }.by(-1)
end
end
@@ -91,7 +91,7 @@ describe API::V3::Runners do
delete v3_api("/runners/#{specific_runner.id}", user)
expect(response).to have_http_status(200)
- end.to change{ Ci::Runner.specific.count }.by(-1)
+ end.to change { Ci::Runner.specific.count }.by(-1)
end
end
end
@@ -113,7 +113,7 @@ describe API::V3::Runners do
delete v3_api("/projects/#{project.id}/runners/#{two_projects_runner.id}", user)
expect(response).to have_http_status(200)
- end.to change{ project.runners.count }.by(-1)
+ end.to change { project.runners.count }.by(-1)
end
end
@@ -121,7 +121,7 @@ describe API::V3::Runners do
it "does not disable project's runner" do
expect do
delete v3_api("/projects/#{project.id}/runners/#{specific_runner.id}", user)
- end.to change{ project.runners.count }.by(0)
+ end.to change { project.runners.count }.by(0)
expect(response).to have_http_status(403)
end
end
diff --git a/spec/requests/api/v3/snippets_spec.rb b/spec/requests/api/v3/snippets_spec.rb
index 1bc2258ebd3..9ead3cad8bb 100644
--- a/spec/requests/api/v3/snippets_spec.rb
+++ b/spec/requests/api/v3/snippets_spec.rb
@@ -45,10 +45,10 @@ describe API::V3::Snippets do
expect(json_response.map { |snippet| snippet['id']} ).to contain_exactly(
public_snippet.id,
public_snippet_other.id)
- expect(json_response.map{ |snippet| snippet['web_url']} ).to include(
+ expect(json_response.map { |snippet| snippet['web_url']} ).to include(
"http://localhost/snippets/#{public_snippet.id}",
"http://localhost/snippets/#{public_snippet_other.id}")
- expect(json_response.map{ |snippet| snippet['raw_url']} ).to include(
+ expect(json_response.map { |snippet| snippet['raw_url']} ).to include(
"http://localhost/snippets/#{public_snippet.id}/raw",
"http://localhost/snippets/#{public_snippet_other.id}/raw")
end
diff --git a/spec/requests/api/v3/triggers_spec.rb b/spec/requests/api/v3/triggers_spec.rb
index 60212660fb6..075de2c0cba 100644
--- a/spec/requests/api/v3/triggers_spec.rb
+++ b/spec/requests/api/v3/triggers_spec.rb
@@ -171,7 +171,7 @@ describe API::V3::Triggers do
it 'creates trigger' do
expect do
post v3_api("/projects/#{project.id}/triggers", user)
- end.to change{project.triggers.count}.by(1)
+ end.to change {project.triggers.count}.by(1)
expect(response).to have_http_status(201)
expect(json_response).to be_a(Hash)
@@ -202,7 +202,7 @@ describe API::V3::Triggers do
delete v3_api("/projects/#{project.id}/triggers/#{trigger.token}", user)
expect(response).to have_http_status(200)
- end.to change{project.triggers.count}.by(-1)
+ end.to change {project.triggers.count}.by(-1)
end
it 'responds with 404 Not Found if requesting non-existing trigger' do
diff --git a/spec/requests/api/v3/users_spec.rb b/spec/requests/api/v3/users_spec.rb
index bc0a4ab20a3..227b8d1b0c1 100644
--- a/spec/requests/api/v3/users_spec.rb
+++ b/spec/requests/api/v3/users_spec.rb
@@ -252,6 +252,31 @@ describe API::V3::Users do
end
context "as a user than can see the event's project" do
+ context 'when the list of events includes push events' do
+ let(:event) { create(:push_event, author: user, project: project) }
+ let!(:payload) { create(:push_event_payload, event: event) }
+ let(:payload_hash) { json_response[0]['push_data'] }
+
+ before do
+ get api("/users/#{user.id}/events?action=pushed", user)
+ end
+
+ it 'responds with HTTP 200 OK' do
+ expect(response).to have_http_status(200)
+ end
+
+ it 'includes the push payload as a Hash' do
+ expect(payload_hash).to be_an_instance_of(Hash)
+ end
+
+ it 'includes the push payload details' do
+ expect(payload_hash['commit_count']).to eq(payload.commit_count)
+ expect(payload_hash['action']).to eq(payload.action)
+ expect(payload_hash['ref_type']).to eq(payload.ref_type)
+ expect(payload_hash['commit_to']).to eq(payload.commit_to)
+ end
+ end
+
context 'joined event' do
it 'returns the "joined" event' do
get v3_api("/users/#{user.id}/events", user)
diff --git a/spec/requests/api/variables_spec.rb b/spec/requests/api/variables_spec.rb
index 098a0d8ca7d..48592e12822 100644
--- a/spec/requests/api/variables_spec.rb
+++ b/spec/requests/api/variables_spec.rb
@@ -74,7 +74,7 @@ describe API::Variables do
it 'creates variable' do
expect do
post api("/projects/#{project.id}/variables", user), key: 'TEST_VARIABLE_2', value: 'VALUE_2', protected: true
- end.to change{project.variables.count}.by(1)
+ end.to change {project.variables.count}.by(1)
expect(response).to have_http_status(201)
expect(json_response['key']).to eq('TEST_VARIABLE_2')
@@ -85,7 +85,7 @@ describe API::Variables do
it 'creates variable with optional attributes' do
expect do
post api("/projects/#{project.id}/variables", user), key: 'TEST_VARIABLE_2', value: 'VALUE_2'
- end.to change{project.variables.count}.by(1)
+ end.to change {project.variables.count}.by(1)
expect(response).to have_http_status(201)
expect(json_response['key']).to eq('TEST_VARIABLE_2')
@@ -96,7 +96,7 @@ describe API::Variables do
it 'does not allow to duplicate variable key' do
expect do
post api("/projects/#{project.id}/variables", user), key: variable.key, value: 'VALUE_2'
- end.to change{project.variables.count}.by(0)
+ end.to change {project.variables.count}.by(0)
expect(response).to have_http_status(400)
end
@@ -166,7 +166,7 @@ describe API::Variables do
delete api("/projects/#{project.id}/variables/#{variable.key}", user)
expect(response).to have_http_status(204)
- end.to change{project.variables.count}.by(-1)
+ end.to change {project.variables.count}.by(-1)
end
it 'responds with 404 Not Found if requesting non-existing variable' do
diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb
index c077c458163..ebd67eb1e94 100644
--- a/spec/requests/ci/api/builds_spec.rb
+++ b/spec/requests/ci/api/builds_spec.rb
@@ -421,7 +421,7 @@ describe Ci::API::Builds do
end
context 'when build has been updated recently' do
- it { expect{ patch_the_trace }.not_to change { build.updated_at }}
+ it { expect { patch_the_trace }.not_to change { build.updated_at }}
it 'changes the build trace' do
patch_the_trace
@@ -430,7 +430,7 @@ describe Ci::API::Builds do
end
context 'when Runner makes a force-patch' do
- it { expect{ force_patch_the_trace }.not_to change { build.updated_at }}
+ it { expect { force_patch_the_trace }.not_to change { build.updated_at }}
it "doesn't change the build.trace" do
force_patch_the_trace
diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb
index c02409b2e0b..39d44245c3f 100644
--- a/spec/routing/project_routing_spec.rb
+++ b/spec/routing/project_routing_spec.rb
@@ -165,15 +165,19 @@ describe 'project routing' do
# edit_project_repository GET /:project_id/repository/edit(.:format) projects/repositories#edit
describe Projects::RepositoriesController, 'routing' do
it 'to #archive' do
- expect(get('/gitlab/gitlabhq/repository/archive')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq')
+ expect(get('/gitlab/gitlabhq/repository/master/archive')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', ref: 'master')
end
it 'to #archive format:zip' do
- expect(get('/gitlab/gitlabhq/repository/archive.zip')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', format: 'zip')
+ expect(get('/gitlab/gitlabhq/repository/master/archive.zip')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', format: 'zip', ref: 'master')
end
it 'to #archive format:tar.bz2' do
- expect(get('/gitlab/gitlabhq/repository/archive.tar.bz2')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', format: 'tar.bz2')
+ expect(get('/gitlab/gitlabhq/repository/master/archive.tar.bz2')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', format: 'tar.bz2', ref: 'master')
+ end
+
+ it 'to #archive with "/" in route' do
+ expect(get('/gitlab/gitlabhq/repository/improve/awesome/archive')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', ref: 'improve/awesome')
end
end
diff --git a/spec/rubocop/cop/migration/safer_boolean_column_spec.rb b/spec/rubocop/cop/migration/safer_boolean_column_spec.rb
new file mode 100644
index 00000000000..1006594499a
--- /dev/null
+++ b/spec/rubocop/cop/migration/safer_boolean_column_spec.rb
@@ -0,0 +1,85 @@
+require 'spec_helper'
+
+require 'rubocop'
+require 'rubocop/rspec/support'
+
+require_relative '../../../../rubocop/cop/migration/safer_boolean_column'
+
+describe RuboCop::Cop::Migration::SaferBooleanColumn do
+ include CopHelper
+
+ subject(:cop) { described_class.new }
+
+ context 'in migration' do
+ before do
+ allow(cop).to receive(:in_migration?).and_return(true)
+ end
+
+ described_class::SMALL_TABLES.each do |table|
+ context "for the #{table} table" do
+ sources_and_offense = [
+ ["add_column :#{table}, :column, :boolean, default: true", 'should disallow nulls'],
+ ["add_column :#{table}, :column, :boolean, default: false", 'should disallow nulls'],
+ ["add_column :#{table}, :column, :boolean, default: nil", 'should have a default and should disallow nulls'],
+ ["add_column :#{table}, :column, :boolean, null: false", 'should have a default'],
+ ["add_column :#{table}, :column, :boolean, null: true", 'should have a default and should disallow nulls'],
+ ["add_column :#{table}, :column, :boolean", 'should have a default and should disallow nulls'],
+ ["add_column :#{table}, :column, :boolean, default: nil, null: false", 'should have a default'],
+ ["add_column :#{table}, :column, :boolean, default: nil, null: true", 'should have a default and should disallow nulls'],
+ ["add_column :#{table}, :column, :boolean, default: false, null: true", 'should disallow nulls']
+ ]
+
+ sources_and_offense.each do |source, offense|
+ context "given the source \"#{source}\"" do
+ it "registers the offense matching \"#{offense}\"" do
+ inspect_source(cop, source)
+
+ aggregate_failures do
+ expect(cop.offenses.first.message).to match(offense)
+ end
+ end
+ end
+ end
+
+ inoffensive_sources = [
+ "add_column :#{table}, :column, :boolean, default: true, null: false",
+ "add_column :#{table}, :column, :boolean, default: false, null: false"
+ ]
+
+ inoffensive_sources.each do |source|
+ context "given the source \"#{source}\"" do
+ it "registers no offense" do
+ inspect_source(cop, source)
+
+ aggregate_failures do
+ expect(cop.offenses).to be_empty
+ end
+ end
+ end
+ end
+ end
+ end
+
+ it 'registers no offense for tables not listed in SMALL_TABLES' do
+ inspect_source(cop, "add_column :large_table, :column, :boolean")
+
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers no offense for non-boolean columns' do
+ table = described_class::SMALL_TABLES.sample
+ inspect_source(cop, "add_column :#{table}, :column, :string")
+
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ context 'outside of migration' do
+ it 'registers no offense' do
+ table = described_class::SMALL_TABLES.sample
+ inspect_source(cop, "add_column :#{table}, :column, :boolean")
+
+ expect(cop.offenses).to be_empty
+ end
+ end
+end
diff --git a/spec/serializers/analytics_build_entity_spec.rb b/spec/serializers/analytics_build_entity_spec.rb
index 86e703a6448..1ff4908972a 100644
--- a/spec/serializers/analytics_build_entity_spec.rb
+++ b/spec/serializers/analytics_build_entity_spec.rb
@@ -13,12 +13,8 @@ describe AnalyticsBuildEntity do
subject { entity.as_json }
- before do
- Timecop.freeze
- end
-
- after do
- Timecop.return
+ around do |example|
+ Timecop.freeze { example.run }
end
it 'contains the URL' do
@@ -47,7 +43,7 @@ describe AnalyticsBuildEntity do
let(:finished_at) { nil }
it 'does not blow up' do
- expect{ subject[:date] }.not_to raise_error
+ expect { subject[:date] }.not_to raise_error
end
it 'shows the right message' do
@@ -63,7 +59,7 @@ describe AnalyticsBuildEntity do
let(:started_at) { nil }
it 'does not blow up' do
- expect{ subject[:date] }.not_to raise_error
+ expect { subject[:date] }.not_to raise_error
end
it 'shows the right message' do
@@ -79,7 +75,7 @@ describe AnalyticsBuildEntity do
let(:finished_at) { nil }
it 'does not blow up' do
- expect{ subject[:date] }.not_to raise_error
+ expect { subject[:date] }.not_to raise_error
end
it 'shows the right message' do
diff --git a/spec/serializers/merge_request_entity_spec.rb b/spec/serializers/merge_request_entity_spec.rb
index 18cd9e9c006..a2fd5b7daae 100644
--- a/spec/serializers/merge_request_entity_spec.rb
+++ b/spec/serializers/merge_request_entity_spec.rb
@@ -47,7 +47,7 @@ describe MergeRequestEntity do
:cancel_merge_when_pipeline_succeeds_path,
:create_issue_to_resolve_discussions_path,
:source_branch_path, :target_branch_commits_path,
- :target_branch_tree_path, :commits_count)
+ :target_branch_tree_path, :commits_count, :merge_ongoing)
end
it 'has email_patches_path' do
diff --git a/spec/serializers/pipeline_serializer_spec.rb b/spec/serializers/pipeline_serializer_spec.rb
index 362d754bca3..2de8daba6b5 100644
--- a/spec/serializers/pipeline_serializer_spec.rb
+++ b/spec/serializers/pipeline_serializer_spec.rb
@@ -111,7 +111,7 @@ describe PipelineSerializer do
shared_examples 'no N+1 queries' do
it 'verifies number of queries', :request_store do
recorded = ActiveRecord::QueryRecorder.new { subject }
- expect(recorded.count).to be_within(1).of(59)
+ expect(recorded.count).to be_within(1).of(57)
expect(recorded.cached_count).to eq(0)
end
end
diff --git a/spec/services/auth/container_registry_authentication_service_spec.rb b/spec/services/auth/container_registry_authentication_service_spec.rb
index d23c09d6d1d..1c2d0b3e0dc 100644
--- a/spec/services/auth/container_registry_authentication_service_spec.rb
+++ b/spec/services/auth/container_registry_authentication_service_spec.rb
@@ -8,7 +8,7 @@ describe Auth::ContainerRegistryAuthenticationService do
let(:payload) { JWT.decode(subject[:token], rsa_key).first }
let(:authentication_abilities) do
- [:read_container_image, :create_container_image]
+ [:read_container_image, :create_container_image, :admin_container_image]
end
subject do
@@ -59,6 +59,12 @@ describe Auth::ContainerRegistryAuthenticationService do
it { expect(payload).to include('access' => []) }
end
+ shared_examples 'a deletable' do
+ it_behaves_like 'an accessible' do
+ let(:actions) { ['*'] }
+ end
+ end
+
shared_examples 'a pullable' do
it_behaves_like 'an accessible' do
let(:actions) { ['pull'] }
@@ -120,7 +126,7 @@ describe Auth::ContainerRegistryAuthenticationService do
context 'allow developer to push images' do
before do
- project.team << [current_user, :developer]
+ project.add_developer(current_user)
end
let(:current_params) do
@@ -131,9 +137,22 @@ describe Auth::ContainerRegistryAuthenticationService do
it_behaves_like 'container repository factory'
end
+ context 'disallow developer to delete images' do
+ before do
+ project.add_developer(current_user)
+ end
+
+ let(:current_params) do
+ { scope: "repository:#{project.path_with_namespace}:*" }
+ end
+
+ it_behaves_like 'an inaccessible'
+ it_behaves_like 'not a container repository factory'
+ end
+
context 'allow reporter to pull images' do
before do
- project.team << [current_user, :reporter]
+ project.add_reporter(current_user)
end
context 'when pulling from root level repository' do
@@ -146,9 +165,22 @@ describe Auth::ContainerRegistryAuthenticationService do
end
end
+ context 'disallow reporter to delete images' do
+ before do
+ project.add_reporter(current_user)
+ end
+
+ let(:current_params) do
+ { scope: "repository:#{project.path_with_namespace}:*" }
+ end
+
+ it_behaves_like 'an inaccessible'
+ it_behaves_like 'not a container repository factory'
+ end
+
context 'return a least of privileges' do
before do
- project.team << [current_user, :reporter]
+ project.add_reporter(current_user)
end
let(:current_params) do
@@ -161,7 +193,7 @@ describe Auth::ContainerRegistryAuthenticationService do
context 'disallow guest to pull or push images' do
before do
- project.team << [current_user, :guest]
+ project.add_guest(current_user)
end
let(:current_params) do
@@ -171,6 +203,19 @@ describe Auth::ContainerRegistryAuthenticationService do
it_behaves_like 'an inaccessible'
it_behaves_like 'not a container repository factory'
end
+
+ context 'disallow guest to delete images' do
+ before do
+ project.add_guest(current_user)
+ end
+
+ let(:current_params) do
+ { scope: "repository:#{project.path_with_namespace}:*" }
+ end
+
+ it_behaves_like 'an inaccessible'
+ it_behaves_like 'not a container repository factory'
+ end
end
context 'for public project' do
@@ -194,6 +239,15 @@ describe Auth::ContainerRegistryAuthenticationService do
it_behaves_like 'not a container repository factory'
end
+ context 'disallow anyone to delete images' do
+ let(:current_params) do
+ { scope: "repository:#{project.path_with_namespace}:*" }
+ end
+
+ it_behaves_like 'an inaccessible'
+ it_behaves_like 'not a container repository factory'
+ end
+
context 'when repository name is invalid' do
let(:current_params) do
{ scope: 'repository:invalid:push' }
@@ -225,16 +279,62 @@ describe Auth::ContainerRegistryAuthenticationService do
it_behaves_like 'an inaccessible'
it_behaves_like 'not a container repository factory'
end
+
+ context 'disallow anyone to delete images' do
+ let(:current_params) do
+ { scope: "repository:#{project.path_with_namespace}:*" }
+ end
+
+ it_behaves_like 'an inaccessible'
+ it_behaves_like 'not a container repository factory'
+ end
end
context 'for external user' do
- let(:current_user) { create(:user, external: true) }
- let(:current_params) do
- { scope: "repository:#{project.full_path}:pull,push" }
+ context 'disallow anyone to pull or push images' do
+ let(:current_user) { create(:user, external: true) }
+ let(:current_params) do
+ { scope: "repository:#{project.path_with_namespace}:pull,push" }
+ end
+
+ it_behaves_like 'an inaccessible'
+ it_behaves_like 'not a container repository factory'
end
- it_behaves_like 'an inaccessible'
- it_behaves_like 'not a container repository factory'
+ context 'disallow anyone to delete images' do
+ let(:current_user) { create(:user, external: true) }
+ let(:current_params) do
+ { scope: "repository:#{project.path_with_namespace}:*" }
+ end
+
+ it_behaves_like 'an inaccessible'
+ it_behaves_like 'not a container repository factory'
+ end
+ end
+ end
+ end
+
+ context 'delete authorized as master' do
+ let(:current_project) { create(:project) }
+ let(:current_user) { create(:user) }
+
+ let(:authentication_abilities) do
+ [:admin_container_image]
+ end
+
+ before do
+ current_project.add_master(current_user)
+ end
+
+ it_behaves_like 'a valid token'
+
+ context 'allow to delete images' do
+ let(:current_params) do
+ { scope: "repository:#{current_project.path_with_namespace}:*" }
+ end
+
+ it_behaves_like 'a deletable' do
+ let(:project) { current_project }
end
end
end
@@ -248,7 +348,7 @@ describe Auth::ContainerRegistryAuthenticationService do
end
before do
- current_project.team << [current_user, :developer]
+ current_project.add_developer(current_user)
end
it_behaves_like 'a valid token'
@@ -267,6 +367,16 @@ describe Auth::ContainerRegistryAuthenticationService do
end
end
+ context 'disallow to delete images' do
+ let(:current_params) do
+ { scope: "repository:#{current_project.path_with_namespace}:*" }
+ end
+
+ it_behaves_like 'an inaccessible' do
+ let(:project) { current_project }
+ end
+ end
+
context 'for other projects' do
context 'when pulling' do
let(:current_params) do
@@ -288,7 +398,7 @@ describe Auth::ContainerRegistryAuthenticationService do
context 'when you are member' do
before do
- project.team << [current_user, :developer]
+ project.add_developer(current_user)
end
it_behaves_like 'a pullable'
@@ -318,7 +428,7 @@ describe Auth::ContainerRegistryAuthenticationService do
context 'when you are member' do
before do
- project.team << [current_user, :developer]
+ project.add_developer(current_user)
end
it_behaves_like 'a pullable'
@@ -345,7 +455,7 @@ describe Auth::ContainerRegistryAuthenticationService do
let(:project) { create(:project, :public) }
before do
- project.team << [current_user, :developer]
+ project.add_developer(current_user)
end
it_behaves_like 'an inaccessible'
diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb
index 730df4e0336..53d4fcfed18 100644
--- a/spec/services/ci/create_pipeline_service_spec.rb
+++ b/spec/services/ci/create_pipeline_service_spec.rb
@@ -66,7 +66,7 @@ describe Ci::CreatePipelineService do
context 'when there is no pipeline for source branch' do
it "does not update merge request head pipeline" do
- merge_request = create(:merge_request, source_branch: 'other_branch', target_branch: "branch_1", source_project: project)
+ merge_request = create(:merge_request, source_branch: 'feature', target_branch: "branch_1", source_project: project)
head_pipeline = pipeline
diff --git a/spec/services/create_deployment_service_spec.rb b/spec/services/create_deployment_service_spec.rb
index 049b082277a..08267d6e6a0 100644
--- a/spec/services/create_deployment_service_spec.rb
+++ b/spec/services/create_deployment_service_spec.rb
@@ -20,6 +20,10 @@ describe CreateDeploymentService do
let(:service) { described_class.new(job) }
+ before do
+ allow_any_instance_of(Deployment).to receive(:create_ref)
+ end
+
describe '#execute' do
subject { service.execute }
diff --git a/spec/services/event_create_service_spec.rb b/spec/services/event_create_service_spec.rb
index 42adb044190..02d7ddeb86b 100644
--- a/spec/services/event_create_service_spec.rb
+++ b/spec/services/event_create_service_spec.rb
@@ -117,12 +117,52 @@ describe EventCreateService do
let(:project) { create(:project) }
let(:user) { create(:user) }
+ let(:push_data) do
+ {
+ commits: [
+ {
+ id: '1cf19a015df3523caf0a1f9d40c98a267d6a2fc2',
+ message: 'This is a commit'
+ }
+ ],
+ before: '0000000000000000000000000000000000000000',
+ after: '1cf19a015df3523caf0a1f9d40c98a267d6a2fc2',
+ total_commits_count: 1,
+ ref: 'refs/heads/my-branch'
+ }
+ end
+
it 'creates a new event' do
- expect { service.push(project, user, {}) }.to change { Event.count }
+ expect { service.push(project, user, push_data) }.to change { Event.count }
+ end
+
+ it 'creates the push event payload' do
+ expect(PushEventPayloadService).to receive(:new)
+ .with(an_instance_of(PushEvent), push_data)
+ .and_call_original
+
+ service.push(project, user, push_data)
end
it 'updates user last activity' do
- expect { service.push(project, user, {}) }.to change { user_activity(user) }
+ expect { service.push(project, user, push_data) }
+ .to change { user_activity(user) }
+ end
+
+ it 'does not create any event data when an error is raised' do
+ payload_service = double(:service)
+
+ allow(payload_service).to receive(:execute)
+ .and_raise(RuntimeError)
+
+ allow(PushEventPayloadService).to receive(:new)
+ .and_return(payload_service)
+
+ expect { service.push(project, user, push_data) }
+ .to raise_error(RuntimeError)
+
+ expect(Event.count).to eq(0)
+ expect(PushEventPayload.count).to eq(0)
end
end
diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb
index 82724ccd281..8485605b398 100644
--- a/spec/services/git_push_service_spec.rb
+++ b/spec/services/git_push_service_spec.rb
@@ -132,7 +132,7 @@ describe GitPushService, services: true do
end
it "creates a new pipeline" do
- expect{ subject }.to change{ Ci::Pipeline.count }
+ expect { subject }.to change { Ci::Pipeline.count }
expect(Ci::Pipeline.last).to be_push
end
end
@@ -141,10 +141,13 @@ describe GitPushService, services: true do
let!(:push_data) { push_data_from_service(project, user, oldrev, newrev, ref) }
let(:event) { Event.find_by_action(Event::PUSHED) }
- it { expect(event).not_to be_nil }
+ it { expect(event).to be_an_instance_of(PushEvent) }
it { expect(event.project).to eq(project) }
it { expect(event.action).to eq(Event::PUSHED) }
- it { expect(event.data).to eq(push_data) }
+ it { expect(event.push_event_payload).to be_an_instance_of(PushEventPayload) }
+ it { expect(event.push_event_payload.commit_from).to eq(oldrev) }
+ it { expect(event.push_event_payload.commit_to).to eq(newrev) }
+ it { expect(event.push_event_payload.ref).to eq('master') }
context "Updates merge requests" do
it "when pushing a new branch for the first time" do
diff --git a/spec/services/git_tag_push_service_spec.rb b/spec/services/git_tag_push_service_spec.rb
index f877c145390..05695aa8188 100644
--- a/spec/services/git_tag_push_service_spec.rb
+++ b/spec/services/git_tag_push_service_spec.rb
@@ -39,7 +39,7 @@ describe GitTagPushService do
end
it "creates a new pipeline" do
- expect{ subject }.to change{ Ci::Pipeline.count }
+ expect { subject }.to change { Ci::Pipeline.count }
expect(Ci::Pipeline.last).to be_push
end
end
diff --git a/spec/services/issuable/bulk_update_service_spec.rb b/spec/services/issuable/bulk_update_service_spec.rb
index befa65ec0f8..bdaab88d673 100644
--- a/spec/services/issuable/bulk_update_service_spec.rb
+++ b/spec/services/issuable/bulk_update_service_spec.rb
@@ -118,7 +118,7 @@ describe Issuable::BulkUpdateService do
context 'when the new assignee ID is not present' do
it 'does not unassign' do
expect { bulk_update(issue, assignee_ids: []) }
- .not_to change{ issue.reload.assignees }
+ .not_to change { issue.reload.assignees }
end
end
end
diff --git a/spec/services/issues/create_service_spec.rb b/spec/services/issues/create_service_spec.rb
index fcbd69fd58b..9b2d9e79f4f 100644
--- a/spec/services/issues/create_service_spec.rb
+++ b/spec/services/issues/create_service_spec.rb
@@ -351,14 +351,14 @@ describe Issues::CreateService do
end
it 'marks related spam_log as recaptcha_verified' do
- expect { issue }.to change{SpamLog.last.recaptcha_verified}.from(false).to(true)
+ expect { issue }.to change {SpamLog.last.recaptcha_verified}.from(false).to(true)
end
context 'when spam log does not belong to a user' do
let(:log_user) { create(:user) }
it 'does not mark spam_log as recaptcha_verified' do
- expect { issue }.not_to change{SpamLog.last.recaptcha_verified}
+ expect { issue }.not_to change {SpamLog.last.recaptcha_verified}
end
end
end
@@ -378,7 +378,7 @@ describe Issues::CreateService do
end
it 'creates a new spam_log' do
- expect{issue}.to change{SpamLog.count}.from(0).to(1)
+ expect {issue}.to change {SpamLog.count}.from(0).to(1)
end
it 'assigns a spam_log to an issue' do
diff --git a/spec/services/issues/resolve_discussions_spec.rb b/spec/services/issues/resolve_discussions_spec.rb
index fac66791ffb..67a86c50fc1 100644
--- a/spec/services/issues/resolve_discussions_spec.rb
+++ b/spec/services/issues/resolve_discussions_spec.rb
@@ -20,7 +20,7 @@ describe Issues::ResolveDiscussions do
describe "for resolving discussions" do
let(:discussion) { create(:diff_note_on_merge_request, project: project, note: "Almost done").to_discussion }
let(:merge_request) { discussion.noteable }
- let(:other_merge_request) { create(:merge_request, source_project: project, source_branch: "other") }
+ let(:other_merge_request) { create(:merge_request, source_project: project, source_branch: "fix") }
describe "#merge_request_for_resolving_discussion" do
let(:service) { DummyService.new(project, user, merge_request_to_resolve_discussions_of: merge_request.iid) }
diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb
index ff0d876e6da..34fb16edc84 100644
--- a/spec/services/issues/update_service_spec.rb
+++ b/spec/services/issues/update_service_spec.rb
@@ -1,9 +1,7 @@
# coding: utf-8
require 'spec_helper'
-describe Issues::UpdateService do
- include EmailHelpers
-
+describe Issues::UpdateService, :mailer do
let(:user) { create(:user) }
let(:user2) { create(:user) }
let(:user3) { create(:user) }
@@ -480,7 +478,7 @@ describe Issues::UpdateService do
feature_visibility_attr = :"#{issue.model_name.plural}_access_level"
project.project_feature.update_attribute(feature_visibility_attr, ProjectFeature::PRIVATE)
- expect{ update_issue(assignee_ids: [assignee.id]) }.not_to change{ issue.assignees }
+ expect { update_issue(assignee_ids: [assignee.id]) }.not_to change { issue.assignees }
end
end
end
diff --git a/spec/services/labels/update_service_spec.rb b/spec/services/labels/update_service_spec.rb
index bb95fe20fbf..c3fe33045fa 100644
--- a/spec/services/labels/update_service_spec.rb
+++ b/spec/services/labels/update_service_spec.rb
@@ -13,7 +13,7 @@ describe Labels::UpdateService do
let(:expected_saved_color) { hex_color }
- before(:each) do
+ before do
@label = Labels::CreateService.new(title: 'Initial', color: '#000000').execute(project: project)
expect(@label).to be_persisted
end
diff --git a/spec/services/merge_requests/get_urls_service_spec.rb b/spec/services/merge_requests/get_urls_service_spec.rb
index 672d86e4028..25599dea19f 100644
--- a/spec/services/merge_requests/get_urls_service_spec.rb
+++ b/spec/services/merge_requests/get_urls_service_spec.rb
@@ -3,7 +3,7 @@ require "spec_helper"
describe MergeRequests::GetUrlsService do
let(:project) { create(:project, :public, :repository) }
let(:service) { described_class.new(project) }
- let(:source_branch) { "my_branch" }
+ let(:source_branch) { "merge-test" }
let(:new_merge_request_url) { "http://#{Gitlab.config.gitlab.host}/#{project.namespace.name}/#{project.path}/merge_requests/new?merge_request%5Bsource_branch%5D=#{source_branch}" }
let(:show_merge_request_url) { "http://#{Gitlab.config.gitlab.host}/#{project.namespace.name}/#{project.path}/merge_requests/#{merge_request.iid}" }
let(:new_branch_changes) { "#{Gitlab::Git::BLANK_SHA} 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/#{source_branch}" }
@@ -111,9 +111,9 @@ describe MergeRequests::GetUrlsService do
end
context 'pushing new branch and existing branch (with merge request created) at once' do
- let!(:merge_request) { create(:merge_request, source_project: project, source_branch: "existing_branch") }
+ let!(:merge_request) { create(:merge_request, source_project: project, source_branch: "markdown") }
let(:new_branch_changes) { "#{Gitlab::Git::BLANK_SHA} 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/new_branch" }
- let(:existing_branch_changes) { "d14d6c0abdd253381df51a723d58691b2ee1ab08 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/existing_branch" }
+ let(:existing_branch_changes) { "d14d6c0abdd253381df51a723d58691b2ee1ab08 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/markdown" }
let(:changes) { "#{new_branch_changes}\n#{existing_branch_changes}" }
let(:new_merge_request_url) { "http://#{Gitlab.config.gitlab.host}/#{project.namespace.name}/#{project.path}/merge_requests/new?merge_request%5Bsource_branch%5D=new_branch" }
@@ -124,7 +124,7 @@ describe MergeRequests::GetUrlsService do
url: new_merge_request_url,
new_merge_request: true
}, {
- branch_name: "existing_branch",
+ branch_name: "markdown",
url: show_merge_request_url,
new_merge_request: false
}])
diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb
index dd3ac9c4ac6..681feee61d1 100644
--- a/spec/services/merge_requests/update_service_spec.rb
+++ b/spec/services/merge_requests/update_service_spec.rb
@@ -1,8 +1,6 @@
require 'spec_helper'
-describe MergeRequests::UpdateService do
- include EmailHelpers
-
+describe MergeRequests::UpdateService, :mailer do
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
let(:user2) { create(:user) }
@@ -511,7 +509,7 @@ describe MergeRequests::UpdateService do
feature_visibility_attr = :"#{merge_request.model_name.plural}_access_level"
project.project_feature.update_attribute(feature_visibility_attr, ProjectFeature::PRIVATE)
- expect{ update_merge_request(assignee_id: assignee) }.not_to change{ merge_request.assignee }
+ expect { update_merge_request(assignee_id: assignee) }.not_to change { merge_request.assignee }
end
end
end
diff --git a/spec/services/notification_recipient_service_spec.rb b/spec/services/notification_recipient_service_spec.rb
deleted file mode 100644
index 0eb0771fd29..00000000000
--- a/spec/services/notification_recipient_service_spec.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-require 'spec_helper'
-
-describe NotificationRecipientService do
- set(:user) { create(:user) }
- set(:project) { create(:project, :public) }
- set(:issue) { create(:issue, project: project) }
-
- set(:watcher) do
- watcher = create(:user)
- setting = watcher.notification_settings_for(project)
- setting.level = :watch
- setting.save
-
- watcher
- end
-
- subject { described_class.new(project) }
-
- describe '#build_recipients' do
- it 'does not modify the participants of the target' do
- expect { subject.build_recipients(issue, user, action: :new_issue) }
- .not_to change { issue.participants(user) }
- end
- end
-
- describe '#build_new_note_recipients' do
- set(:note) { create(:note_on_issue, noteable: issue, project: project) }
-
- it 'does not modify the participants of the target' do
- expect { subject.build_new_note_recipients(note) }
- .not_to change { note.noteable.participants(note.author) }
- end
- end
-end
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index 882ee7751b5..44b2d28d1d4 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -1,12 +1,10 @@
require 'spec_helper'
-describe NotificationService do
- include EmailHelpers
-
+describe NotificationService, :mailer do
let(:notification) { described_class.new }
let(:assignee) { create(:user) }
- around(:each) do |example|
+ around do |example|
perform_enqueued_jobs do
example.run
end
@@ -14,7 +12,6 @@ describe NotificationService do
shared_examples 'notifications for new mentions' do
def send_notifications(*new_mentions)
- reset_delivered_emails!
notification.send(notification_method, mentionable, new_mentions, @u_disabled)
end
@@ -83,12 +80,16 @@ describe NotificationService do
describe 'Keys' do
describe '#new_key' do
- let!(:key) { create(:personal_key) }
+ let(:key_options) { {} }
+ let!(:key) { create(:personal_key, key_options) }
it { expect(notification.new_key(key)).to be_truthy }
+ it { should_email(key.user) }
- it 'sends email to key owner' do
- expect{ notification.new_key(key) }.to change{ ActionMailer::Base.deliveries.size }.by(1)
+ describe 'never emails the ghost user' do
+ let(:key_options) { { user: User.ghost } }
+
+ it { should_not_email_anyone }
end
end
end
@@ -100,7 +101,7 @@ describe NotificationService do
it { expect(notification.new_gpg_key(key)).to be_truthy }
it 'sends email to key owner' do
- expect{ notification.new_gpg_key(key) }.to change{ ActionMailer::Base.deliveries.size }.by(1)
+ expect { notification.new_gpg_key(key) }.to change { ActionMailer::Base.deliveries.size }.by(1)
end
end
end
@@ -112,7 +113,7 @@ describe NotificationService do
it { expect(notification.new_email(email)).to be_truthy }
it 'sends email to email owner' do
- expect{ notification.new_email(email) }.to change{ ActionMailer::Base.deliveries.size }.by(1)
+ expect { notification.new_email(email) }.to change { ActionMailer::Base.deliveries.size }.by(1)
end
end
end
@@ -137,12 +138,11 @@ describe NotificationService do
describe '#new_note' do
it do
add_users_with_subscription(note.project, issue)
+ reset_delivered_emails!
# Ensure create SentNotification by noteable = issue 6 times, not noteable = note
expect(SentNotification).to receive(:record).with(issue, any_args).exactly(8).times
- reset_delivered_emails!
-
notification.new_note(note)
should_email(@u_watcher)
@@ -165,9 +165,10 @@ describe NotificationService do
it "emails the note author if they've opted into notifications about their activity" do
add_users_with_subscription(note.project, issue)
- note.author.notified_of_own_activity = true
reset_delivered_emails!
+ note.author.notified_of_own_activity = true
+
notification.new_note(note)
should_email(note.author)
@@ -309,6 +310,11 @@ describe NotificationService do
before do
build_team(note.project)
+
+ # make sure these users can read the project snippet!
+ project.add_guest(@u_guest_watcher)
+ project.add_guest(@u_guest_custom)
+
note.project.add_master(note.author)
reset_delivered_emails!
end
@@ -1171,19 +1177,39 @@ describe NotificationService do
end
end
- describe '#project_exported' do
- it do
- notification.project_exported(project, @u_disabled)
+ context 'user with notifications disabled' do
+ describe '#project_exported' do
+ it do
+ notification.project_exported(project, @u_disabled)
- should_only_email(@u_disabled)
+ should_not_email_anyone
+ end
+ end
+
+ describe '#project_not_exported' do
+ it do
+ notification.project_not_exported(project, @u_disabled, ['error'])
+
+ should_not_email_anyone
+ end
end
end
- describe '#project_not_exported' do
- it do
- notification.project_not_exported(project, @u_disabled, ['error'])
+ context 'user with notifications enabled' do
+ describe '#project_exported' do
+ it do
+ notification.project_exported(project, @u_participating)
- should_only_email(@u_disabled)
+ should_only_email(@u_participating)
+ end
+ end
+
+ describe '#project_not_exported' do
+ it do
+ notification.project_not_exported(project, @u_participating, ['error'])
+
+ should_only_email(@u_participating)
+ end
end
end
end
@@ -1194,7 +1220,7 @@ describe NotificationService do
let(:group) { create(:group) }
let(:member) { create(:user) }
- before(:each) do
+ before do
group.add_owner(creator)
group.add_developer(member, creator)
end
@@ -1207,6 +1233,35 @@ describe NotificationService do
end.to change { ActionMailer::Base.deliveries.size }.by(1)
end
end
+
+ describe '#new_group_member' do
+ let(:group) { create(:group) }
+ let(:added_user) { create(:user) }
+
+ def create_member!
+ GroupMember.create(
+ group: group,
+ user: added_user,
+ access_level: Gitlab::Access::GUEST
+ )
+ end
+
+ it 'sends a notification' do
+ create_member!
+ should_only_email(added_user)
+ end
+
+ describe 'when notifications are disabled' do
+ before do
+ create_global_setting_for(added_user, :disabled)
+ end
+
+ it 'does not send a notification' do
+ create_member!
+ should_not_email_anyone
+ end
+ end
+ end
end
describe 'ProjectMember' do
@@ -1214,7 +1269,7 @@ describe NotificationService do
let(:project) { create(:project) }
let(:member) { create(:user) }
- before(:each) do
+ before do
project.add_developer(member, current_user: project.owner)
end
@@ -1226,6 +1281,31 @@ describe NotificationService do
end.to change { ActionMailer::Base.deliveries.size }.by(1)
end
end
+
+ describe '#new_project_member' do
+ let(:project) { create(:project) }
+ let(:added_user) { create(:user) }
+
+ def create_member!
+ create(:project_member, user: added_user, project: project)
+ end
+
+ it do
+ create_member!
+ should_only_email(added_user)
+ end
+
+ describe 'when notifications are disabled' do
+ before do
+ create_global_setting_for(added_user, :disabled)
+ end
+
+ it do
+ create_member!
+ should_not_email_anyone
+ end
+ end
+ end
end
context 'guest user in private project' do
diff --git a/spec/services/projects/autocomplete_service_spec.rb b/spec/services/projects/autocomplete_service_spec.rb
index c1f098530bf..426593be428 100644
--- a/spec/services/projects/autocomplete_service_spec.rb
+++ b/spec/services/projects/autocomplete_service_spec.rb
@@ -88,4 +88,31 @@ describe Projects::AutocompleteService do
end
end
end
+
+ describe '#milestones' do
+ let(:user) { create(:user) }
+ let(:group) { create(:group) }
+ let(:project) { create(:project, group: group) }
+ let!(:group_milestone) { create(:milestone, group: group) }
+ let!(:project_milestone) { create(:milestone, project: project) }
+
+ let(:milestone_titles) { described_class.new(project, user).milestones.map(&:title) }
+
+ it 'includes project and group milestones' do
+ expect(milestone_titles).to eq([group_milestone.title, project_milestone.title])
+ end
+
+ it 'does not include closed milestones' do
+ group_milestone.close
+
+ expect(milestone_titles).to eq([project_milestone.title])
+ end
+
+ it 'does not include milestones from other projects in the group' do
+ other_project = create(:project, group: group)
+ project_milestone.update!(project: other_project)
+
+ expect(milestone_titles).to eq([group_milestone.title])
+ end
+ end
end
diff --git a/spec/services/projects/create_from_template_service_spec.rb b/spec/services/projects/create_from_template_service_spec.rb
new file mode 100644
index 00000000000..9919ec254c6
--- /dev/null
+++ b/spec/services/projects/create_from_template_service_spec.rb
@@ -0,0 +1,26 @@
+require 'spec_helper'
+
+describe Projects::CreateFromTemplateService do
+ let(:user) { create(:user) }
+ let(:project_params) do
+ {
+ path: user.to_param,
+ template_name: 'rails'
+ }
+ end
+
+ subject { described_class.new(user, project_params) }
+
+ it 'calls the importer service' do
+ expect_any_instance_of(Projects::GitlabProjectsImportService).to receive(:execute)
+
+ subject.execute
+ end
+
+ it 'returns the project thats created' do
+ project = subject.execute
+
+ expect(project).to be_saved
+ expect(project.scheduled?).to be(true)
+ end
+end
diff --git a/spec/services/projects/destroy_service_spec.rb b/spec/services/projects/destroy_service_spec.rb
index 85b05ef6d05..c867139d1de 100644
--- a/spec/services/projects/destroy_service_spec.rb
+++ b/spec/services/projects/destroy_service_spec.rb
@@ -142,13 +142,13 @@ describe Projects::DestroyService do
context 'when `execute` raises unexpected error' do
before do
expect_any_instance_of(Project)
- .to receive(:destroy!).and_raise(Exception.new("Other error message"))
+ .to receive(:destroy!).and_raise(Exception.new('Other error message'))
end
it 'allows error to bubble up and rolls back project deletion' do
expect do
Sidekiq::Testing.inline! { destroy_project(project, user, {}) }
- end.to raise_error
+ end.to raise_error(Exception, 'Other error message')
expect(project.reload.pending_delete).to be(false)
expect(project.delete_error).to include("Other error message")
diff --git a/spec/services/projects/housekeeping_service_spec.rb b/spec/services/projects/housekeeping_service_spec.rb
index ebed802708d..385f56e447f 100644
--- a/spec/services/projects/housekeeping_service_spec.rb
+++ b/spec/services/projects/housekeeping_service_spec.rb
@@ -24,7 +24,7 @@ describe Projects::HousekeepingService do
end
context 'when no lease can be obtained' do
- before(:each) do
+ before do
expect(subject).to receive(:try_obtain_lease).and_return(false)
end
diff --git a/spec/services/projects/import_service_spec.rb b/spec/services/projects/import_service_spec.rb
index c0ab1ea704d..034065aab00 100644
--- a/spec/services/projects/import_service_spec.rb
+++ b/spec/services/projects/import_service_spec.rb
@@ -38,8 +38,7 @@ describe Projects::ImportService do
context 'with a Github repository' do
it 'succeeds if repository import is successfully' do
- expect_any_instance_of(Repository).to receive(:fetch_remote).and_return(true)
- expect_any_instance_of(Gitlab::GithubImport::Importer).to receive(:execute).and_return(true)
+ expect_any_instance_of(Github::Import).to receive(:execute).and_return(true)
result = subject.execute
@@ -52,16 +51,7 @@ describe Projects::ImportService do
result = subject.execute
expect(result[:status]).to eq :error
- expect(result[:message]).to eq "Error importing repository #{project.import_url} into #{project.full_path} - Failed to import the repository"
- end
-
- it 'does not remove the GitHub remote' do
- expect_any_instance_of(Repository).to receive(:fetch_remote).and_return(true)
- expect_any_instance_of(Gitlab::GithubImport::Importer).to receive(:execute).and_return(true)
-
- subject.execute
-
- expect(project.repository.raw_repository.remote_names).to include('github')
+ expect(result[:message]).to eq "Error importing repository #{project.import_url} into #{project.path_with_namespace} - The remote data could not be imported."
end
end
@@ -102,8 +92,7 @@ describe Projects::ImportService do
end
it 'succeeds if importer succeeds' do
- allow_any_instance_of(Repository).to receive(:fetch_remote).and_return(true)
- allow_any_instance_of(Gitlab::GithubImport::Importer).to receive(:execute).and_return(true)
+ allow_any_instance_of(Github::Import).to receive(:execute).and_return(true)
result = subject.execute
@@ -111,10 +100,7 @@ describe Projects::ImportService do
end
it 'flushes various caches' do
- allow_any_instance_of(Repository).to receive(:fetch_remote)
- .and_return(true)
-
- allow_any_instance_of(Gitlab::GithubImport::Importer).to receive(:execute)
+ allow_any_instance_of(Github::Import).to receive(:execute)
.and_return(true)
expect_any_instance_of(Repository).to receive(:expire_content_cache)
@@ -123,8 +109,7 @@ describe Projects::ImportService do
end
it 'fails if importer fails' do
- allow_any_instance_of(Repository).to receive(:fetch_remote).and_return(true)
- allow_any_instance_of(Gitlab::GithubImport::Importer).to receive(:execute).and_return(false)
+ allow_any_instance_of(Github::Import).to receive(:execute).and_return(false)
result = subject.execute
@@ -133,8 +118,7 @@ describe Projects::ImportService do
end
it 'fails if importer raise an error' do
- allow_any_instance_of(Gitlab::Shell).to receive(:fetch_remote).and_return(true)
- allow_any_instance_of(Gitlab::GithubImport::Importer).to receive(:execute).and_raise(Projects::ImportService::Error.new('Github: failed to connect API'))
+ allow_any_instance_of(Github::Import).to receive(:execute).and_raise(Projects::ImportService::Error.new('Github: failed to connect API'))
result = subject.execute
@@ -143,9 +127,9 @@ describe Projects::ImportService do
end
it 'expires content cache after error' do
- allow_any_instance_of(Project).to receive(:repository_exists?).and_return(false, true)
+ allow_any_instance_of(Project).to receive(:repository_exists?).and_return(false)
- expect_any_instance_of(Gitlab::Shell).to receive(:fetch_remote).and_raise(Gitlab::Shell::Error.new('Failed to import the repository'))
+ expect_any_instance_of(Repository).to receive(:fetch_remote).and_raise(Gitlab::Shell::Error.new)
expect_any_instance_of(Repository).to receive(:expire_content_cache)
subject.execute
diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb
index d945e0aa1f0..1b282e82187 100644
--- a/spec/services/projects/update_service_spec.rb
+++ b/spec/services/projects/update_service_spec.rb
@@ -101,6 +101,13 @@ describe Projects::UpdateService, '#execute' do
expect(Project.find(project.id).default_branch).to eq 'feature'
end
+
+ it 'does not change a default branch' do
+ # The branch 'unexisted-branch' does not exist.
+ update_project(project, admin, default_branch: 'unexisted-branch')
+
+ expect(Project.find(project.id).default_branch).to eq 'master'
+ end
end
context 'when updating a project that contains container images' do
diff --git a/spec/services/protected_branches/update_service_spec.rb b/spec/services/protected_branches/update_service_spec.rb
index 5698101af54..9fa5983db66 100644
--- a/spec/services/protected_branches/update_service_spec.rb
+++ b/spec/services/protected_branches/update_service_spec.rb
@@ -19,7 +19,7 @@ describe ProtectedBranches::UpdateService do
let(:user) { create(:user) }
it "raises error" do
- expect{ service.execute(protected_branch) }.to raise_error(Gitlab::Access::AccessDeniedError)
+ expect { service.execute(protected_branch) }.to raise_error(Gitlab::Access::AccessDeniedError)
end
end
end
diff --git a/spec/services/protected_tags/update_service_spec.rb b/spec/services/protected_tags/update_service_spec.rb
index d333430088d..2ece4e3b07b 100644
--- a/spec/services/protected_tags/update_service_spec.rb
+++ b/spec/services/protected_tags/update_service_spec.rb
@@ -19,7 +19,7 @@ describe ProtectedTags::UpdateService do
let(:user) { create(:user) }
it "raises error" do
- expect{ service.execute(protected_tag) }.to raise_error(Gitlab::Access::AccessDeniedError)
+ expect { service.execute(protected_tag) }.to raise_error(Gitlab::Access::AccessDeniedError)
end
end
end
diff --git a/spec/services/push_event_payload_service_spec.rb b/spec/services/push_event_payload_service_spec.rb
new file mode 100644
index 00000000000..81956200bff
--- /dev/null
+++ b/spec/services/push_event_payload_service_spec.rb
@@ -0,0 +1,218 @@
+require 'spec_helper'
+
+describe PushEventPayloadService do
+ let(:event) { create(:push_event) }
+
+ describe '#execute' do
+ let(:push_data) do
+ {
+ commits: [
+ {
+ id: '1cf19a015df3523caf0a1f9d40c98a267d6a2fc2',
+ message: 'This is a commit'
+ }
+ ],
+ before: '0000000000000000000000000000000000000000',
+ after: '1cf19a015df3523caf0a1f9d40c98a267d6a2fc2',
+ total_commits_count: 1,
+ ref: 'refs/heads/my-branch'
+ }
+ end
+
+ it 'creates a new PushEventPayload row' do
+ payload = described_class.new(event, push_data).execute
+
+ expect(payload.commit_count).to eq(1)
+ expect(payload.action).to eq('created')
+ expect(payload.ref_type).to eq('branch')
+ expect(payload.commit_from).to be_nil
+ expect(payload.commit_to).to eq(push_data[:after])
+ expect(payload.ref).to eq('my-branch')
+ expect(payload.commit_title).to eq('This is a commit')
+ expect(payload.event_id).to eq(event.id)
+ end
+
+ it 'sets the push_event_payload association of the used event' do
+ payload = described_class.new(event, push_data).execute
+
+ expect(event.push_event_payload).to eq(payload)
+ end
+ end
+
+ describe '#commit_title' do
+ it 'returns nil if no commits were pushed' do
+ service = described_class.new(event, commits: [])
+
+ expect(service.commit_title).to be_nil
+ end
+
+ it 'returns a String limited to 70 characters' do
+ service = described_class.new(event, commits: [{ message: 'a' * 100 }])
+
+ expect(service.commit_title).to eq(('a' * 67) + '...')
+ end
+
+ it 'does not truncate the commit message if it is shorter than 70 characters' do
+ service = described_class.new(event, commits: [{ message: 'Hello' }])
+
+ expect(service.commit_title).to eq('Hello')
+ end
+
+ it 'includes the first line of a commit message if the message spans multiple lines' do
+ service = described_class
+ .new(event, commits: [{ message: "Hello\n\nworld" }])
+
+ expect(service.commit_title).to eq('Hello')
+ end
+ end
+
+ describe '#commit_from_id' do
+ it 'returns nil when creating a new ref' do
+ service = described_class.new(event, before: Gitlab::Git::BLANK_SHA)
+
+ expect(service.commit_from_id).to be_nil
+ end
+
+ it 'returns the ID of the first commit when pushing to an existing ref' do
+ service = described_class.new(event, before: '123')
+
+ expect(service.commit_from_id).to eq('123')
+ end
+ end
+
+ describe '#commit_to_id' do
+ it 'returns nil when removing an existing ref' do
+ service = described_class.new(event, after: Gitlab::Git::BLANK_SHA)
+
+ expect(service.commit_to_id).to be_nil
+ end
+ end
+
+ describe '#commit_count' do
+ it 'returns the number of commits' do
+ service = described_class.new(event, total_commits_count: 1)
+
+ expect(service.commit_count).to eq(1)
+ end
+
+ it 'raises when the push data does not contain the commits count' do
+ service = described_class.new(event, {})
+
+ expect { service.commit_count }.to raise_error(KeyError)
+ end
+ end
+
+ describe '#ref' do
+ it 'returns the name of the ref' do
+ service = described_class.new(event, ref: 'refs/heads/foo')
+
+ expect(service.ref).to eq('refs/heads/foo')
+ end
+
+ it 'raises when the push data does not contain the ref name' do
+ service = described_class.new(event, {})
+
+ expect { service.ref }.to raise_error(KeyError)
+ end
+ end
+
+ describe '#revision_before' do
+ it 'returns the revision from before the push' do
+ service = described_class.new(event, before: 'foo')
+
+ expect(service.revision_before).to eq('foo')
+ end
+
+ it 'raises when the push data does not contain the before revision' do
+ service = described_class.new(event, {})
+
+ expect { service.revision_before }.to raise_error(KeyError)
+ end
+ end
+
+ describe '#revision_after' do
+ it 'returns the revision from after the push' do
+ service = described_class.new(event, after: 'foo')
+
+ expect(service.revision_after).to eq('foo')
+ end
+
+ it 'raises when the push data does not contain the after revision' do
+ service = described_class.new(event, {})
+
+ expect { service.revision_after }.to raise_error(KeyError)
+ end
+ end
+
+ describe '#trimmed_ref' do
+ it 'returns the ref name without its prefix' do
+ service = described_class.new(event, ref: 'refs/heads/foo')
+
+ expect(service.trimmed_ref).to eq('foo')
+ end
+ end
+
+ describe '#create?' do
+ it 'returns true when creating a new ref' do
+ service = described_class.new(event, before: Gitlab::Git::BLANK_SHA)
+
+ expect(service.create?).to eq(true)
+ end
+
+ it 'returns false when pushing to an existing ref' do
+ service = described_class.new(event, before: 'foo')
+
+ expect(service.create?).to eq(false)
+ end
+ end
+
+ describe '#remove?' do
+ it 'returns true when removing an existing ref' do
+ service = described_class.new(event, after: Gitlab::Git::BLANK_SHA)
+
+ expect(service.remove?).to eq(true)
+ end
+
+ it 'returns false pushing to an existing ref' do
+ service = described_class.new(event, after: 'foo')
+
+ expect(service.remove?).to eq(false)
+ end
+ end
+
+ describe '#action' do
+ it 'returns :created when creating a ref' do
+ service = described_class.new(event, before: Gitlab::Git::BLANK_SHA)
+
+ expect(service.action).to eq(:created)
+ end
+
+ it 'returns :removed when removing an existing ref' do
+ service = described_class.new(event,
+ before: '123',
+ after: Gitlab::Git::BLANK_SHA)
+
+ expect(service.action).to eq(:removed)
+ end
+
+ it 'returns :pushed when pushing to an existing ref' do
+ service = described_class.new(event, before: '123', after: '456')
+
+ expect(service.action).to eq(:pushed)
+ end
+ end
+
+ describe '#ref_type' do
+ it 'returns :tag for a tag' do
+ service = described_class.new(event, ref: 'refs/tags/1.2')
+
+ expect(service.ref_type).to eq(:tag)
+ end
+
+ it 'returns :branch for a branch' do
+ service = described_class.new(event, ref: 'refs/heads/master')
+
+ expect(service.ref_type).to eq(:branch)
+ end
+ end
+end
diff --git a/spec/services/quick_actions/interpret_service_spec.rb b/spec/services/quick_actions/interpret_service_spec.rb
index b78ecfb61c4..30fa0ee6873 100644
--- a/spec/services/quick_actions/interpret_service_spec.rb
+++ b/spec/services/quick_actions/interpret_service_spec.rb
@@ -424,6 +424,26 @@ describe QuickActions::InterpretService do
end
end
+ context 'assign command with me alias' do
+ let(:content) { "/assign me" }
+
+ context 'Issue' do
+ it 'fetches assignee and populates assignee_ids if content contains /assign' do
+ _, updates = service.execute(content, issue)
+
+ expect(updates).to eq(assignee_ids: [developer.id])
+ end
+ end
+
+ context 'Merge Request' do
+ it 'fetches assignee and populates assignee_ids if content contains /assign' do
+ _, updates = service.execute(content, merge_request)
+
+ expect(updates).to eq(assignee_ids: [developer.id])
+ end
+ end
+ end
+
it_behaves_like 'empty command' do
let(:content) { '/assign @abcd1234' }
let(:issuable) { issue }
diff --git a/spec/services/submit_usage_ping_service_spec.rb b/spec/services/submit_usage_ping_service_spec.rb
index 817fa4262d5..c8a6fc1a99b 100644
--- a/spec/services/submit_usage_ping_service_spec.rb
+++ b/spec/services/submit_usage_ping_service_spec.rb
@@ -46,6 +46,8 @@ describe SubmitUsagePingService do
.by(1)
expect(ConversationalDevelopmentIndex::Metric.last.leader_issues).to eq 10.2
+ expect(ConversationalDevelopmentIndex::Metric.last.instance_issues).to eq 3.2
+ expect(ConversationalDevelopmentIndex::Metric.last.percentage_issues).to eq 31.37
end
end
@@ -60,6 +62,7 @@ describe SubmitUsagePingService do
conv_index: {
leader_issues: 10.2,
instance_issues: 3.2,
+ percentage_issues: 31.37,
leader_notes: 25.3,
instance_notes: 23.2,
@@ -86,7 +89,9 @@ describe SubmitUsagePingService do
instance_projects_prometheus_active: 0.30,
leader_service_desk_issues: 15.8,
- instance_service_desk_issues: 15.1
+ instance_service_desk_issues: 15.1,
+
+ non_existing_column: 'value'
}
}
end
diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index e3805160b04..6d36affa9dc 100644
--- a/spec/services/system_note_service_spec.rb
+++ b/spec/services/system_note_service_spec.rb
@@ -3,7 +3,8 @@ require 'spec_helper'
describe SystemNoteService do
include Gitlab::Routing
- let(:project) { create(:project) }
+ let(:group) { create(:group) }
+ let(:project) { create(:project, group: group) }
let(:author) { create(:user) }
let(:noteable) { create(:issue, project: project) }
let(:issue) { noteable }
@@ -34,7 +35,7 @@ describe SystemNoteService do
context 'metadata' do
it 'creates a new system note metadata record' do
- expect { subject }.to change{ SystemNoteMetadata.count }.from(0).to(1)
+ expect { subject }.to change { SystemNoteMetadata.count }.from(0).to(1)
end
it 'creates a record correctly' do
@@ -242,25 +243,51 @@ describe SystemNoteService do
end
describe '.change_milestone' do
- subject { described_class.change_milestone(noteable, project, author, milestone) }
+ context 'for a project milestone' do
+ subject { described_class.change_milestone(noteable, project, author, milestone) }
- let(:milestone) { create(:milestone, project: project) }
+ let(:milestone) { create(:milestone, project: project) }
- it_behaves_like 'a system note' do
- let(:action) { 'milestone' }
- end
+ it_behaves_like 'a system note' do
+ let(:action) { 'milestone' }
+ end
- context 'when milestone added' do
- it 'sets the note text' do
- expect(subject.note).to eq "changed milestone to #{milestone.to_reference}"
+ context 'when milestone added' do
+ it 'sets the note text' do
+ expect(subject.note).to eq "changed milestone to #{milestone.to_reference}"
+ end
+ end
+
+ context 'when milestone removed' do
+ let(:milestone) { nil }
+
+ it 'sets the note text' do
+ expect(subject.note).to eq 'removed milestone'
+ end
end
end
- context 'when milestone removed' do
- let(:milestone) { nil }
+ context 'for a group milestone' do
+ subject { described_class.change_milestone(noteable, project, author, milestone) }
- it 'sets the note text' do
- expect(subject.note).to eq 'removed milestone'
+ let(:milestone) { create(:milestone, group: group) }
+
+ it_behaves_like 'a system note' do
+ let(:action) { 'milestone' }
+ end
+
+ context 'when milestone added' do
+ it 'sets the note text to use the milestone name' do
+ expect(subject.note).to eq "changed milestone to #{milestone.to_reference(format: :name)}"
+ end
+ end
+
+ context 'when milestone removed' do
+ let(:milestone) { nil }
+
+ it 'sets the note text' do
+ expect(subject.note).to eq 'removed milestone'
+ end
end
end
end
@@ -450,7 +477,7 @@ describe SystemNoteService do
end
it 'does not create a system note metadata record' do
- expect { subject }.not_to change{ SystemNoteMetadata.count }
+ expect { subject }.not_to change { SystemNoteMetadata.count }
end
end
diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb
index 534d3e65be2..a9b34a5258a 100644
--- a/spec/services/todo_service_spec.rb
+++ b/spec/services/todo_service_spec.rb
@@ -336,7 +336,7 @@ describe TodoService do
describe '#mark_todos_as_done' do
it_behaves_like 'updating todos state', :mark_todos_as_done, :pending, :done do
- let(:collection) { [first_todo, second_todo] }
+ let(:collection) { Todo.all }
end
end
@@ -348,7 +348,7 @@ describe TodoService do
describe '#mark_todos_as_pending' do
it_behaves_like 'updating todos state', :mark_todos_as_pending, :done, :pending do
- let(:collection) { [first_todo, second_todo] }
+ let(:collection) { Todo.all }
end
end
@@ -586,13 +586,13 @@ describe TodoService do
it 'does not create a directly addressed todo if user was already mentioned or addressed and todo is pending' do
create(:todo, :directly_addressed, user: member, project: project, target: addressed_mr_assigned, author: author)
- expect{ service.update_merge_request(addressed_mr_assigned, author) }.not_to change(member.todos, :count)
+ expect { service.update_merge_request(addressed_mr_assigned, author) }.not_to change(member.todos, :count)
end
it 'does not create a directly addressed todo if user was already mentioned or addressed and todo is done' do
create(:todo, :directly_addressed, user: skipped, project: project, target: addressed_mr_assigned, author: author)
- expect{ service.update_merge_request(addressed_mr_assigned, author, skip_users) }.not_to change(skipped.todos, :count)
+ expect { service.update_merge_request(addressed_mr_assigned, author, skip_users) }.not_to change(skipped.todos, :count)
end
context 'with a task list' do
@@ -880,14 +880,16 @@ describe TodoService do
it 'marks an array of todos as done' do
todo = create(:todo, :mentioned, user: john_doe, target: issue, project: project)
- expect { described_class.new.mark_todos_as_done([todo], john_doe) }
+ todos = TodosFinder.new(john_doe, {}).execute
+ expect { described_class.new.mark_todos_as_done(todos, john_doe) }
.to change { todo.reload.state }.from('pending').to('done')
end
it 'returns the ids of updated todos' do # Needed on API
todo = create(:todo, :mentioned, user: john_doe, target: issue, project: project)
- expect(described_class.new.mark_todos_as_done([todo], john_doe)).to eq([todo.id])
+ todos = TodosFinder.new(john_doe, {}).execute
+ expect(described_class.new.mark_todos_as_done(todos, john_doe)).to eq([todo.id])
end
context 'when some of the todos are done already' do
@@ -907,11 +909,32 @@ describe TodoService do
expect(described_class.new.mark_todos_as_done(Todo.all, john_doe)).to eq([])
end
end
+ end
+
+ describe '#mark_todos_as_done_by_ids' do
+ let(:issue) { create(:issue, project: project, author: author, assignees: [john_doe]) }
+ let(:another_issue) { create(:issue, project: project, author: author, assignees: [john_doe]) }
+
+ it 'marks an array of todo ids as done' do
+ todo = create(:todo, :mentioned, user: john_doe, target: issue, project: project)
+ another_todo = create(:todo, :mentioned, user: john_doe, target: another_issue, project: project)
+
+ expect { described_class.new.mark_todos_as_done_by_ids([todo.id, another_todo.id], john_doe) }
+ .to change { john_doe.todos.done.count }.from(0).to(2)
+ end
+
+ it 'marks a single todo id as done' do
+ todo = create(:todo, :mentioned, user: john_doe, target: issue, project: project)
+
+ expect { described_class.new.mark_todos_as_done_by_ids(todo.id, john_doe) }
+ .to change { todo.reload.state }.from('pending').to('done')
+ end
it 'caches the number of todos of a user', :use_clean_rails_memory_store_caching do
create(:todo, :mentioned, user: john_doe, target: issue, project: project)
todo = create(:todo, :mentioned, user: john_doe, target: issue, project: project)
- described_class.new.mark_todos_as_done([todo], john_doe)
+
+ described_class.new.mark_todos_as_done_by_ids(todo, john_doe)
expect_any_instance_of(TodosFinder).not_to receive(:execute)
diff --git a/spec/services/web_hook_service_spec.rb b/spec/services/web_hook_service_spec.rb
index 79d90defd78..365cb6b8f09 100644
--- a/spec/services/web_hook_service_spec.rb
+++ b/spec/services/web_hook_service_spec.rb
@@ -15,7 +15,7 @@ describe WebHookService do
let(:service_instance) { described_class.new(project_hook, data, 'push_hooks') }
describe '#execute' do
- before(:each) do
+ before do
project.hooks << [project_hook]
WebMock.stub_request(:post, project_hook.url)
diff --git a/spec/services/wiki_pages/update_service_spec.rb b/spec/services/wiki_pages/update_service_spec.rb
index a242bf5a5cc..2399db7d3d4 100644
--- a/spec/services/wiki_pages/update_service_spec.rb
+++ b/spec/services/wiki_pages/update_service_spec.rb
@@ -9,7 +9,8 @@ describe WikiPages::UpdateService do
{
content: 'New content for wiki page',
format: 'markdown',
- message: 'New wiki message'
+ message: 'New wiki message',
+ title: 'New Title'
}
end
@@ -27,6 +28,7 @@ describe WikiPages::UpdateService do
expect(updated_page.message).to eq(opts[:message])
expect(updated_page.content).to eq(opts[:content])
expect(updated_page.format).to eq(opts[:format].to_sym)
+ expect(updated_page.title).to eq(opts[:title])
end
it 'executes webhooks' do
diff --git a/spec/simplecov_env.rb b/spec/simplecov_env.rb
index ac2c89b3ff9..25ddf932d42 100644
--- a/spec/simplecov_env.rb
+++ b/spec/simplecov_env.rb
@@ -36,18 +36,25 @@ module SimpleCovEnv
track_files '{app,lib}/**/*.rb'
add_filter '/vendor/ruby/'
+ add_filter 'app/controllers/sherlock/'
add_filter 'config/initializers/'
+ add_filter 'db/fixtures/'
+ add_filter 'lib/gitlab/sidekiq_middleware/'
+ add_filter 'lib/system_check/'
add_group 'Controllers', 'app/controllers'
- add_group 'Models', 'app/models'
- add_group 'Mailers', 'app/mailers'
- add_group 'Helpers', 'app/helpers'
- add_group 'Workers', %w(app/jobs app/workers)
- add_group 'Libraries', 'lib'
- add_group 'Services', 'app/services'
- add_group 'Finders', 'app/finders'
- add_group 'Uploaders', 'app/uploaders'
- add_group 'Validators', 'app/validators'
+ add_group 'Finders', 'app/finders'
+ add_group 'Helpers', 'app/helpers'
+ add_group 'Libraries', 'lib'
+ add_group 'Mailers', 'app/mailers'
+ add_group 'Models', 'app/models'
+ add_group 'Policies', 'app/policies'
+ add_group 'Presenters', 'app/presenters'
+ add_group 'Serializers', 'app/serializers'
+ add_group 'Services', 'app/services'
+ add_group 'Uploaders', 'app/uploaders'
+ add_group 'Validators', 'app/validators'
+ add_group 'Workers', %w(app/jobs app/workers)
merge_timeout 365.days
end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 609998d6e9c..3eea39d4bf4 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -8,6 +8,7 @@ require File.expand_path("../../config/environment", __FILE__)
require 'rspec/rails'
require 'shoulda/matchers'
require 'rspec/retry'
+require 'rspec-parameterized'
rspec_profiling_is_configured =
ENV['RSPEC_PROFILING_POSTGRES_URL'].present? ||
@@ -49,7 +50,7 @@ RSpec.configure do |config|
config.include SearchHelpers, type: :feature
config.include WaitForRequests, :js
config.include StubConfiguration
- config.include EmailHelpers, type: :mailer
+ config.include EmailHelpers, :mailer, type: :mailer
config.include TestEnv
config.include ActiveJob::TestHelper
config.include ActiveSupport::Testing::TimeHelpers
@@ -69,7 +70,14 @@ RSpec.configure do |config|
config.raise_errors_for_deprecations!
+ if ENV['CI']
+ # This includes the first try, i.e. tests will be run 4 times before failing.
+ config.default_retry_count = 4
+ config.reporter.register_listener(RspecFlaky::Listener.new, :example_passed, :dump_summary)
+ end
+
config.before(:suite) do
+ Timecop.safe_mode = true
TestEnv.init
end
@@ -93,10 +101,8 @@ RSpec.configure do |config|
RequestStore.clear!
end
- if ENV['CI']
- config.around(:each) do |ex|
- ex.run_with_retry retry: 2
- end
+ config.before(:example, :mailer) do
+ reset_delivered_emails!
end
config.around(:each, :use_clean_rails_memory_store_caching) do |example|
@@ -130,13 +136,13 @@ RSpec.configure do |config|
ActiveRecord::Migrator
.migrate(migrations_paths, previous_migration.version)
- ActiveRecord::Base.descendants.each(&:reset_column_information)
+ reset_column_in_migration_models
end
config.after(:example, :migration) do
ActiveRecord::Migrator.migrate(migrations_paths)
- ActiveRecord::Base.descendants.each(&:reset_column_information)
+ reset_column_in_migration_models
end
config.around(:each, :nested_groups) do |example|
diff --git a/spec/support/api/milestones_shared_examples.rb b/spec/support/api/milestones_shared_examples.rb
index bf769225012..4bb5113957e 100644
--- a/spec/support/api/milestones_shared_examples.rb
+++ b/spec/support/api/milestones_shared_examples.rb
@@ -50,7 +50,7 @@ shared_examples_for 'group and project milestones' do |route_definition|
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(2)
- expect(json_response.map{ |m| m['id'] }).to match_array([closed_milestone.id, other_milestone.id])
+ expect(json_response.map { |m| m['id'] }).to match_array([closed_milestone.id, other_milestone.id])
end
it 'does not return any milestone if none found' do
diff --git a/spec/support/api/schema_matcher.rb b/spec/support/api/schema_matcher.rb
index 67599f77adb..6591d56e473 100644
--- a/spec/support/api/schema_matcher.rb
+++ b/spec/support/api/schema_matcher.rb
@@ -1,23 +1,25 @@
-def schema_path(schema)
- schema_directory = "#{Dir.pwd}/spec/fixtures/api/schemas"
- "#{schema_directory}/#{schema}.json"
+module SchemaPath
+ def self.expand(schema, dir = '')
+ Rails.root.join('spec', dir, "fixtures/api/schemas/#{schema}.json").to_s
+ end
end
-RSpec::Matchers.define :match_response_schema do |schema, **options|
+RSpec::Matchers.define :match_response_schema do |schema, dir: '', **options|
match do |response|
- @errors = JSON::Validator.fully_validate(schema_path(schema), response.body, options)
+ @errors = JSON::Validator.fully_validate(
+ SchemaPath.expand(schema, dir), response.body, options)
@errors.empty?
end
failure_message do |response|
- "didn't match the schema defined by #{schema_path(schema)}" \
+ "didn't match the schema defined by #{SchemaPath.expand(schema, dir)}" \
" The validation errors were:\n#{@errors.join("\n")}"
end
end
-RSpec::Matchers.define :match_schema do |schema, **options|
+RSpec::Matchers.define :match_schema do |schema, dir: '', **options|
match do |data|
- JSON::Validator.validate!(schema_path(schema), data, options)
+ JSON::Validator.validate!(SchemaPath.expand(schema, dir), data, options)
end
end
diff --git a/spec/support/cycle_analytics_helpers.rb b/spec/support/cycle_analytics_helpers.rb
index c0a5491a430..30911e7fa86 100644
--- a/spec/support/cycle_analytics_helpers.rb
+++ b/spec/support/cycle_analytics_helpers.rb
@@ -41,7 +41,9 @@ module CycleAnalyticsHelpers
target_branch: 'master'
}
- MergeRequests::CreateService.new(project, user, opts).execute
+ mr = MergeRequests::CreateService.new(project, user, opts).execute
+ NewMergeRequestWorker.new.perform(mr, user)
+ mr
end
def merge_merge_requests_closing_issue(issue)
diff --git a/spec/support/features/issuable_slash_commands_shared_examples.rb b/spec/support/features/issuable_slash_commands_shared_examples.rb
index 32835e391a8..68f0ce8afb3 100644
--- a/spec/support/features/issuable_slash_commands_shared_examples.rb
+++ b/spec/support/features/issuable_slash_commands_shared_examples.rb
@@ -279,6 +279,17 @@ shared_examples 'issuable record that supports quick actions in its description
expect(issuable.subscribed?(master, project)).to be_falsy
end
end
+
+ context "with a note assigning the #{issuable_type} to the current user" do
+ it "assigns the #{issuable_type} to the current user" do
+ write_note("/assign me")
+
+ expect(page).not_to have_content '/assign me'
+ expect(page).to have_content 'Commands applied'
+
+ expect(issuable.reload.assignees).to eq [master]
+ end
+ end
end
describe "preview of note on #{issuable_type}" do
diff --git a/spec/support/features/reportable_note_shared_examples.rb b/spec/support/features/reportable_note_shared_examples.rb
index 27e079c01dd..cb483ae9a5a 100644
--- a/spec/support/features/reportable_note_shared_examples.rb
+++ b/spec/support/features/reportable_note_shared_examples.rb
@@ -7,15 +7,18 @@ shared_examples 'reportable note' do
let(:more_actions_selector) { '.more-actions.dropdown' }
let(:abuse_report_path) { new_abuse_report_path(user_id: note.author.id, ref_url: noteable_note_url(note)) }
+ it 'has an edit button' do
+ expect(comment).to have_selector('.js-note-edit')
+ end
+
it 'has a `More actions` dropdown' do
expect(comment).to have_selector(more_actions_selector)
end
- it 'dropdown has Edit, Report and Delete links' do
+ it 'dropdown has Report and Delete links' do
dropdown = comment.find(more_actions_selector)
open_dropdown(dropdown)
- expect(dropdown).to have_button('Edit comment')
expect(dropdown).to have_link('Report as abuse', href: abuse_report_path)
expect(dropdown).to have_link('Delete comment', href: note_url(note, project))
end
diff --git a/spec/support/issuable_shared_examples.rb b/spec/support/issuable_shared_examples.rb
index 970fe10db2b..42f3b4db23c 100644
--- a/spec/support/issuable_shared_examples.rb
+++ b/spec/support/issuable_shared_examples.rb
@@ -21,15 +21,15 @@ shared_examples 'system notes for milestones' do
create(:group_member, group: group, user: user)
end
- it 'does not create system note' do
+ it 'creates a system note' do
expect do
update_issuable(milestone: group_milestone)
- end.not_to change { Note.system.count }
+ end.to change { Note.system.count }.by(1)
end
end
context 'project milestones' do
- it 'creates system note' do
+ it 'creates a system note' do
expect do
update_issuable(milestone: create(:milestone))
end.to change { Note.system.count }.by(1)
diff --git a/spec/support/issuables_list_metadata_shared_examples.rb b/spec/support/issuables_list_metadata_shared_examples.rb
index a60d3b0d22d..75982432ab4 100644
--- a/spec/support/issuables_list_metadata_shared_examples.rb
+++ b/spec/support/issuables_list_metadata_shared_examples.rb
@@ -2,12 +2,12 @@ shared_examples 'issuables list meta-data' do |issuable_type, action = nil|
before do
@issuable_ids = []
- 2.times do |n|
+ %w[fix improve/awesome].each do |source_branch|
issuable =
if issuable_type == :issue
create(issuable_type, project: project)
else
- create(issuable_type, source_project: project, source_branch: "#{n}-feature")
+ create(issuable_type, source_project: project, source_branch: source_branch)
end
@issuable_ids << issuable.id
diff --git a/spec/support/login_helpers.rb b/spec/support/login_helpers.rb
index c714d1b08a6..3e117530151 100644
--- a/spec/support/login_helpers.rb
+++ b/spec/support/login_helpers.rb
@@ -1,3 +1,5 @@
+require_relative 'devise_helpers'
+
module LoginHelpers
include DeviseHelpers
diff --git a/spec/support/markdown_feature.rb b/spec/support/markdown_feature.rb
index 21a054af4e1..c90359d7cfa 100644
--- a/spec/support/markdown_feature.rb
+++ b/spec/support/markdown_feature.rb
@@ -23,7 +23,7 @@ class MarkdownFeature
# Direct references ----------------------------------------------------------
def project
- @project ||= create(:project, :repository).tap do |project|
+ @project ||= create(:project, :repository, group: group).tap do |project|
project.team << [user, :master]
end
end
@@ -75,6 +75,10 @@ class MarkdownFeature
@milestone ||= create(:milestone, name: 'next goal', project: project)
end
+ def group_milestone
+ @group_milestone ||= create(:milestone, name: 'group-milestone', group: group)
+ end
+
# Cross-references -----------------------------------------------------------
def xproject
diff --git a/spec/support/matchers/markdown_matchers.rb b/spec/support/matchers/markdown_matchers.rb
index 7afa57fb76b..d12b2757427 100644
--- a/spec/support/matchers/markdown_matchers.rb
+++ b/spec/support/matchers/markdown_matchers.rb
@@ -155,7 +155,7 @@ module MarkdownMatchers
set_default_markdown_messages
match do |actual|
- expect(actual).to have_selector('a.gfm.gfm-milestone', count: 6)
+ expect(actual).to have_selector('a.gfm.gfm-milestone', count: 8)
end
end
diff --git a/spec/support/matchers/query_matcher.rb b/spec/support/matchers/query_matcher.rb
index ac8c4ab91d9..bb0feca7c43 100644
--- a/spec/support/matchers/query_matcher.rb
+++ b/spec/support/matchers/query_matcher.rb
@@ -28,6 +28,6 @@ RSpec::Matchers.define :make_queries_matching do |matcher, expected_count = nil|
def query_count(regex, &block)
@recorder = ActiveRecord::QueryRecorder.new(&block).log
- @recorder.select{ |q| q.match(regex) }
+ @recorder.select { |q| q.match(regex) }
end
end
diff --git a/spec/support/migrations_helpers.rb b/spec/support/migrations_helpers.rb
index 91fbb4eaf48..aabdad13047 100644
--- a/spec/support/migrations_helpers.rb
+++ b/spec/support/migrations_helpers.rb
@@ -15,6 +15,16 @@ module MigrationsHelpers
ActiveRecord::Migrator.migrations(migrations_paths)
end
+ def reset_column_in_migration_models
+ described_class.constants.sort.each do |name|
+ const = described_class.const_get(name)
+
+ if const.is_a?(Class) && const < ActiveRecord::Base
+ const.reset_column_information
+ end
+ end
+ end
+
def previous_migration
migrations.each_cons(2) do |previous, migration|
break previous if migration.name == described_class.name
diff --git a/spec/support/notify_shared_examples.rb b/spec/support/notify_shared_examples.rb
index d6117d604f2..136f92c6419 100644
--- a/spec/support/notify_shared_examples.rb
+++ b/spec/support/notify_shared_examples.rb
@@ -7,7 +7,6 @@ shared_context 'gitlab email notification' do
let(:new_user_address) { 'newguy@example.com' }
before do
- reset_delivered_emails!
email = recipient.emails.create(email: "notifications@example.com")
recipient.update_attribute(:notification_email, email.email)
stub_incoming_email_setting(enabled: true, address: "reply+%{key}@#{Gitlab.config.gitlab.host}")
diff --git a/spec/support/rake_helpers.rb b/spec/support/rake_helpers.rb
index 5cb415111d2..86bfeed107c 100644
--- a/spec/support/rake_helpers.rb
+++ b/spec/support/rake_helpers.rb
@@ -5,11 +5,15 @@ module RakeHelpers
end
def stub_warn_user_is_not_gitlab
- allow_any_instance_of(Object).to receive(:warn_user_is_not_gitlab)
+ allow(main_object).to receive(:warn_user_is_not_gitlab)
end
def silence_output
- allow($stdout).to receive(:puts)
- allow($stdout).to receive(:print)
+ allow(main_object).to receive(:puts)
+ allow(main_object).to receive(:print)
+ end
+
+ def main_object
+ @main_object ||= TOPLEVEL_BINDING.eval('self')
end
end
diff --git a/spec/support/redis/redis_shared_examples.rb b/spec/support/redis/redis_shared_examples.rb
index f9552e41894..8676f895a83 100644
--- a/spec/support/redis/redis_shared_examples.rb
+++ b/spec/support/redis/redis_shared_examples.rb
@@ -3,12 +3,12 @@ RSpec.shared_examples "redis_shared_examples" do
let(:test_redis_url) { "redis://redishost:#{redis_port}"}
- before(:each) do
+ before do
stub_env(environment_config_file_name, Rails.root.join(config_file_name))
clear_raw_config
end
- after(:each) do
+ after do
clear_raw_config
end
diff --git a/spec/support/stored_repositories.rb b/spec/support/stored_repositories.rb
index df18926d58c..f3deae0f455 100644
--- a/spec/support/stored_repositories.rb
+++ b/spec/support/stored_repositories.rb
@@ -2,4 +2,16 @@ RSpec.configure do |config|
config.before(:each, :repository) do
TestEnv.clean_test_path
end
+
+ config.before(:all, :broken_storage) do
+ FileUtils.rm_rf Gitlab.config.repositories.storages.broken['path']
+ end
+
+ config.before(:each, :broken_storage) do
+ allow(Gitlab::GitalyClient).to receive(:call) do
+ raise GRPC::Unavailable.new('Gitaly broken in this spec')
+ end
+
+ Gitlab::Git::Storage::CircuitBreaker.reset_all!
+ end
end
diff --git a/spec/support/stub_configuration.rb b/spec/support/stub_configuration.rb
index 516f8878679..37c89d37aa0 100644
--- a/spec/support/stub_configuration.rb
+++ b/spec/support/stub_configuration.rb
@@ -38,6 +38,17 @@ module StubConfiguration
allow(Gitlab.config.backup).to receive_messages(to_settings(messages))
end
+ def stub_storage_settings(messages)
+ messages.each do |storage_name, storage_settings|
+ storage_settings['failure_count_threshold'] ||= 10
+ storage_settings['failure_wait_time'] ||= 30
+ storage_settings['failure_reset_time'] ||= 1800
+ storage_settings['storage_timeout'] ||= 5
+ end
+
+ allow(Gitlab.config.repositories).to receive(:storages).and_return(messages)
+ end
+
private
# Modifies stubbed messages to also stub possible predicate versions
diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb
index f0603dfadde..1e39f80699c 100644
--- a/spec/support/test_env.rb
+++ b/spec/support/test_env.rb
@@ -122,25 +122,50 @@ module TestEnv
end
def setup_gitlab_shell
- shell_needs_update = component_needs_update?(Gitlab.config.gitlab_shell.path,
+ puts "\n==> Setting up Gitlab Shell..."
+ start = Time.now
+ gitlab_shell_dir = Gitlab.config.gitlab_shell.path
+ shell_needs_update = component_needs_update?(gitlab_shell_dir,
Gitlab::Shell.version_required)
unless !shell_needs_update || system('rake', 'gitlab:shell:install')
- raise 'Can`t clone gitlab-shell'
+ puts "\nGitLab Shell failed to install, cleaning up #{gitlab_shell_dir}!\n"
+ FileUtils.rm_rf(gitlab_shell_dir)
+ exit 1
end
+
+ puts " GitLab Shell setup in #{Time.now - start} seconds...\n"
end
def setup_gitaly
+ puts "\n==> Setting up Gitaly..."
+ start = Time.now
socket_path = Gitlab::GitalyClient.address('default').sub(/\Aunix:/, '')
gitaly_dir = File.dirname(socket_path)
+
+ if gitaly_dir_stale?(gitaly_dir)
+ puts " Gitaly is outdated, cleaning up #{gitaly_dir}!"
+ FileUtils.rm_rf(gitaly_dir)
+ end
+
gitaly_needs_update = component_needs_update?(gitaly_dir,
Gitlab::GitalyClient.expected_server_version)
unless !gitaly_needs_update || system('rake', "gitlab:gitaly:install[#{gitaly_dir}]")
- raise "Can't clone gitaly"
+ puts "\nGitaly failed to install, cleaning up #{gitaly_dir}!\n"
+ FileUtils.rm_rf(gitaly_dir)
+ exit 1
end
start_gitaly(gitaly_dir)
+ puts " Gitaly setup in #{Time.now - start} seconds...\n"
+ end
+
+ def gitaly_dir_stale?(dir)
+ gitaly_executable = File.join(dir, 'gitaly')
+ return false unless File.exist?(gitaly_executable)
+
+ File.mtime(gitaly_executable) < File.mtime(Rails.root.join('GITALY_SERVER_VERSION'))
end
def start_gitaly(gitaly_dir)
@@ -211,7 +236,6 @@ module TestEnv
# Otherwise they'd be created by the first test, often timing out and
# causing a transient test failure
def eager_load_driver_server
- return unless ENV['CI']
return unless defined?(Capybara)
puts "Starting the Capybara driver server..."
@@ -226,6 +250,14 @@ module TestEnv
"#{forked_repo_path}_bare"
end
+ def with_empty_bare_repository(name = nil)
+ path = Rails.root.join('tmp/tests', name || 'empty-bare-repository').to_s
+
+ yield(Rugged::Repository.init_at(path, :bare))
+ ensure
+ FileUtils.rm_rf(path)
+ end
+
private
def factory_repo_path
diff --git a/spec/support/unique_ip_check_shared_examples.rb b/spec/support/unique_ip_check_shared_examples.rb
index ff0b47899f5..2dfa5fbecea 100644
--- a/spec/support/unique_ip_check_shared_examples.rb
+++ b/spec/support/unique_ip_check_shared_examples.rb
@@ -1,6 +1,6 @@
shared_context 'unique ips sign in limit' do
include StubENV
- before(:each) do
+ before do
Gitlab::Redis::Cache.with(&:flushall)
Gitlab::Redis::Queues.with(&:flushall)
Gitlab::Redis::SharedState.with(&:flushall)
diff --git a/spec/support/updating_mentions_shared_examples.rb b/spec/support/updating_mentions_shared_examples.rb
index eeec3e1d79b..565d3203e4f 100644
--- a/spec/support/updating_mentions_shared_examples.rb
+++ b/spec/support/updating_mentions_shared_examples.rb
@@ -7,8 +7,6 @@ RSpec.shared_examples 'updating mentions' do |service_class|
end
def update_mentionable(opts)
- reset_delivered_emails!
-
perform_enqueued_jobs do
service_class.new(project, user, opts).execute(mentionable)
end
diff --git a/spec/tasks/config_lint_spec.rb b/spec/tasks/config_lint_spec.rb
index ed6c5b09663..83d54259dfa 100644
--- a/spec/tasks/config_lint_spec.rb
+++ b/spec/tasks/config_lint_spec.rb
@@ -2,19 +2,19 @@ require 'rake_helper'
Rake.application.rake_require 'tasks/config_lint'
describe ConfigLint do
- let(:files){ ['lib/support/fake.sh'] }
+ let(:files) { ['lib/support/fake.sh'] }
it 'errors out if any bash scripts have errors' do
- expect { described_class.run(files){ system('exit 1') } }.to raise_error(SystemExit)
+ expect { described_class.run(files) { system('exit 1') } }.to raise_error(SystemExit)
end
it 'passes if all scripts are fine' do
- expect { described_class.run(files){ system('exit 0') } }.not_to raise_error
+ expect { described_class.run(files) { system('exit 0') } }.not_to raise_error
end
end
describe 'config_lint rake task' do
- before(:each) do
+ before do
# Prevent `system` from actually being called
allow(Kernel).to receive(:system).and_return(true)
end
diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb
index fae92451b46..0c8c8a2ab05 100644
--- a/spec/tasks/gitlab/backup_rake_spec.rb
+++ b/spec/tasks/gitlab/backup_rake_spec.rb
@@ -119,7 +119,7 @@ describe 'gitlab:app namespace rake task' do
let(:project) { create(:project, :repository) }
let(:user_backup_path) { "repositories/#{project.disk_path}" }
- before(:each) do
+ before do
@origin_cd = Dir.pwd
path = File.join(project.repository.path_to_repo, filename)
@@ -130,7 +130,7 @@ describe 'gitlab:app namespace rake task' do
create_backup
end
- after(:each) do
+ after do
ENV["SKIP"] = ""
FileUtils.rm(@backup_tar)
Dir.chdir(@origin_cd)
diff --git a/spec/tasks/gitlab/gitaly_rake_spec.rb b/spec/tasks/gitlab/gitaly_rake_spec.rb
index 695231c7d15..b29d63c7d67 100644
--- a/spec/tasks/gitlab/gitaly_rake_spec.rb
+++ b/spec/tasks/gitlab/gitaly_rake_spec.rb
@@ -20,7 +20,7 @@ describe 'gitlab:gitaly namespace rake task' do
context 'when an underlying Git command fail' do
it 'aborts and display a help message' do
- expect_any_instance_of(Object)
+ expect(main_object)
.to receive(:checkout_or_clone_version).and_raise 'Git error'
expect { run_rake_task('gitlab:gitaly:install', clone_path) }.to raise_error 'Git error'
@@ -33,7 +33,7 @@ describe 'gitlab:gitaly namespace rake task' do
end
it 'calls checkout_or_clone_version with the right arguments' do
- expect_any_instance_of(Object)
+ expect(main_object)
.to receive(:checkout_or_clone_version).with(version: version, repo: repo, target_dir: clone_path)
run_rake_task('gitlab:gitaly:install', clone_path)
@@ -41,7 +41,7 @@ describe 'gitlab:gitaly namespace rake task' do
end
describe 'gmake/make' do
- let(:command_preamble) { %w[/usr/bin/env -u BUNDLE_GEMFILE] }
+ let(:command_preamble) { %w[/usr/bin/env -u RUBYOPT -u BUNDLE_GEMFILE] }
before(:all) do
@old_env_ci = ENV.delete('CI')
@@ -54,17 +54,17 @@ describe 'gitlab:gitaly namespace rake task' do
before do
FileUtils.mkdir_p(clone_path)
expect(Dir).to receive(:chdir).with(clone_path).and_call_original
+ allow(Bundler).to receive(:bundle_path).and_return('/fake/bundle_path')
end
context 'gmake is available' do
before do
- expect_any_instance_of(Object).to receive(:checkout_or_clone_version)
- allow_any_instance_of(Object).to receive(:run_command!).with(command_preamble + ['gmake']).and_return(true)
+ expect(main_object).to receive(:checkout_or_clone_version)
end
it 'calls gmake in the gitaly directory' do
expect(Gitlab::Popen).to receive(:popen).with(%w[which gmake]).and_return(['/usr/bin/gmake', 0])
- expect_any_instance_of(Object).to receive(:run_command!).with(command_preamble + ['gmake']).and_return(true)
+ expect(main_object).to receive(:run_command!).with(command_preamble + %w[gmake BUNDLE_PATH=/fake/bundle_path]).and_return(true)
run_rake_task('gitlab:gitaly:install', clone_path)
end
@@ -72,16 +72,27 @@ describe 'gitlab:gitaly namespace rake task' do
context 'gmake is not available' do
before do
- expect_any_instance_of(Object).to receive(:checkout_or_clone_version)
- allow_any_instance_of(Object).to receive(:run_command!).with(command_preamble + ['make']).and_return(true)
+ expect(main_object).to receive(:checkout_or_clone_version)
+ expect(Gitlab::Popen).to receive(:popen).with(%w[which gmake]).and_return(['', 42])
end
it 'calls make in the gitaly directory' do
- expect(Gitlab::Popen).to receive(:popen).with(%w[which gmake]).and_return(['', 42])
- expect_any_instance_of(Object).to receive(:run_command!).with(command_preamble + ['make']).and_return(true)
+ expect(main_object).to receive(:run_command!).with(command_preamble + %w[make BUNDLE_PATH=/fake/bundle_path]).and_return(true)
run_rake_task('gitlab:gitaly:install', clone_path)
end
+
+ context 'when Rails.env is not "test"' do
+ before do
+ allow(Rails.env).to receive(:test?).and_return(false)
+ end
+
+ it 'calls make in the gitaly directory without BUNDLE_PATH' do
+ expect(main_object).to receive(:run_command!).with(command_preamble + ['make']).and_return(true)
+
+ run_rake_task('gitlab:gitaly:install', clone_path)
+ end
+ end
end
end
end
@@ -107,6 +118,8 @@ describe 'gitlab:gitaly namespace rake task' do
# Gitaly storage configuration generated from #{Gitlab.config.source} on #{Time.current.to_s(:long)}
# This is in TOML format suitable for use in Gitaly's config.toml file.
socket_path = "/path/to/my.socket"
+ [gitlab-shell]
+ dir = "#{Gitlab.config.gitlab_shell.path}"
[[storage]]
name = "default"
path = "/path/to/default"
diff --git a/spec/tasks/gitlab/shell_rake_spec.rb b/spec/tasks/gitlab/shell_rake_spec.rb
index ee3614c50f6..65155cb044d 100644
--- a/spec/tasks/gitlab/shell_rake_spec.rb
+++ b/spec/tasks/gitlab/shell_rake_spec.rb
@@ -22,7 +22,8 @@ describe 'gitlab:shell rake tasks' do
describe 'create_hooks task' do
it 'calls gitlab-shell bin/create_hooks' do
expect_any_instance_of(Object).to receive(:system)
- .with("#{Gitlab.config.gitlab_shell.path}/bin/create-hooks", *repository_storage_paths_args)
+ .with("#{Gitlab.config.gitlab_shell.path}/bin/create-hooks",
+ *Gitlab::TaskHelpers.repository_storage_paths_args)
run_rake_task('gitlab:shell:create_hooks')
end
diff --git a/spec/tasks/gitlab/workhorse_rake_spec.rb b/spec/tasks/gitlab/workhorse_rake_spec.rb
index 1b68f3044a4..42516d36c67 100644
--- a/spec/tasks/gitlab/workhorse_rake_spec.rb
+++ b/spec/tasks/gitlab/workhorse_rake_spec.rb
@@ -20,7 +20,7 @@ describe 'gitlab:workhorse namespace rake task' do
context 'when an underlying Git command fail' do
it 'aborts and display a help message' do
- expect_any_instance_of(Object)
+ expect(main_object)
.to receive(:checkout_or_clone_version).and_raise 'Git error'
expect { run_rake_task('gitlab:workhorse:install', clone_path) }.to raise_error 'Git error'
@@ -33,7 +33,7 @@ describe 'gitlab:workhorse namespace rake task' do
end
it 'calls checkout_or_clone_version with the right arguments' do
- expect_any_instance_of(Object)
+ expect(main_object)
.to receive(:checkout_or_clone_version).with(version: version, repo: repo, target_dir: clone_path)
run_rake_task('gitlab:workhorse:install', clone_path)
@@ -48,13 +48,13 @@ describe 'gitlab:workhorse namespace rake task' do
context 'gmake is available' do
before do
- expect_any_instance_of(Object).to receive(:checkout_or_clone_version)
- allow_any_instance_of(Object).to receive(:run_command!).with(['gmake']).and_return(true)
+ expect(main_object).to receive(:checkout_or_clone_version)
+ allow(Object).to receive(:run_command!).with(['gmake']).and_return(true)
end
it 'calls gmake in the gitlab-workhorse directory' do
expect(Gitlab::Popen).to receive(:popen).with(%w[which gmake]).and_return(['/usr/bin/gmake', 0])
- expect_any_instance_of(Object).to receive(:run_command!).with(['gmake']).and_return(true)
+ expect(main_object).to receive(:run_command!).with(['gmake']).and_return(true)
run_rake_task('gitlab:workhorse:install', clone_path)
end
@@ -62,13 +62,13 @@ describe 'gitlab:workhorse namespace rake task' do
context 'gmake is not available' do
before do
- expect_any_instance_of(Object).to receive(:checkout_or_clone_version)
- allow_any_instance_of(Object).to receive(:run_command!).with(['make']).and_return(true)
+ expect(main_object).to receive(:checkout_or_clone_version)
+ allow(main_object).to receive(:run_command!).with(['make']).and_return(true)
end
it 'calls make in the gitlab-workhorse directory' do
expect(Gitlab::Popen).to receive(:popen).with(%w[which gmake]).and_return(['', 42])
- expect_any_instance_of(Object).to receive(:run_command!).with(['make']).and_return(true)
+ expect(main_object).to receive(:run_command!).with(['make']).and_return(true)
run_rake_task('gitlab:workhorse:install', clone_path)
end
diff --git a/spec/uploaders/file_mover_spec.rb b/spec/uploaders/file_mover_spec.rb
index d7c1b390f9a..0cf462e9553 100644
--- a/spec/uploaders/file_mover_spec.rb
+++ b/spec/uploaders/file_mover_spec.rb
@@ -4,11 +4,11 @@ describe FileMover do
let(:filename) { 'banana_sample.gif' }
let(:file) { fixture_file_upload(Rails.root.join('spec', 'fixtures', filename)) }
let(:temp_description) do
- 'test ![banana_sample](/uploads/system/temp/secret55/banana_sample.gif) same ![banana_sample]'\
- '(/uploads/system/temp/secret55/banana_sample.gif)'
+ 'test ![banana_sample](/uploads/-/system/temp/secret55/banana_sample.gif) same ![banana_sample]'\
+ '(/uploads/-/system/temp/secret55/banana_sample.gif)'
end
let(:temp_file_path) { File.join('secret55', filename).to_s }
- let(:file_path) { File.join('uploads', 'system', 'personal_snippet', snippet.id.to_s, 'secret55', filename).to_s }
+ let(:file_path) { File.join('uploads', '-', 'system', 'personal_snippet', snippet.id.to_s, 'secret55', filename).to_s }
let(:snippet) { create(:personal_snippet, description: temp_description) }
@@ -28,8 +28,8 @@ describe FileMover do
expect(snippet.reload.description)
.to eq(
- "test ![banana_sample](/uploads/system/personal_snippet/#{snippet.id}/secret55/banana_sample.gif)"\
- " same ![banana_sample](/uploads/system/personal_snippet/#{snippet.id}/secret55/banana_sample.gif)"
+ "test ![banana_sample](/uploads/-/system/personal_snippet/#{snippet.id}/secret55/banana_sample.gif)"\
+ " same ![banana_sample](/uploads/-/system/personal_snippet/#{snippet.id}/secret55/banana_sample.gif)"
)
end
@@ -50,8 +50,8 @@ describe FileMover do
expect(snippet.reload.description)
.to eq(
- "test ![banana_sample](/uploads/system/temp/secret55/banana_sample.gif)"\
- " same ![banana_sample](/uploads/system/temp/secret55/banana_sample.gif)"
+ "test ![banana_sample](/uploads/-/system/temp/secret55/banana_sample.gif)"\
+ " same ![banana_sample](/uploads/-/system/temp/secret55/banana_sample.gif)"
)
end
diff --git a/spec/uploaders/personal_file_uploader_spec.rb b/spec/uploaders/personal_file_uploader_spec.rb
index e505edc75ce..cbafa9f478d 100644
--- a/spec/uploaders/personal_file_uploader_spec.rb
+++ b/spec/uploaders/personal_file_uploader_spec.rb
@@ -10,7 +10,7 @@ describe PersonalFileUploader do
dynamic_segment = "personal_snippet/#{snippet.id}"
- expect(described_class.absolute_path(upload)).to end_with("/system/#{dynamic_segment}/secret/foo.jpg")
+ expect(described_class.absolute_path(upload)).to end_with("/-/system/#{dynamic_segment}/secret/foo.jpg")
end
end
@@ -19,7 +19,7 @@ describe PersonalFileUploader do
uploader = described_class.new(snippet, 'secret')
allow(uploader).to receive(:file).and_return(double(extension: 'txt', filename: 'file_name'))
- expected_url = "/uploads/system/personal_snippet/#{snippet.id}/secret/file_name"
+ expected_url = "/uploads/-/system/personal_snippet/#{snippet.id}/secret/file_name"
expect(uploader.to_h).to eq(
alt: 'file_name',
diff --git a/spec/views/projects/edit.html.haml_spec.rb b/spec/views/projects/edit.html.haml_spec.rb
index 94899e26292..1af422941d7 100644
--- a/spec/views/projects/edit.html.haml_spec.rb
+++ b/spec/views/projects/edit.html.haml_spec.rb
@@ -11,14 +11,26 @@ describe 'projects/edit' do
allow(controller).to receive(:current_user).and_return(user)
allow(view).to receive_messages(current_user: user, can?: true)
- allow(Gitlab.config.lfs).to receive(:enabled).and_return(true)
end
context 'LFS enabled setting' do
it 'displays the correct elements' do
+ allow(Gitlab.config.lfs).to receive(:enabled).and_return(true)
+
render
+
expect(rendered).to have_select('project_lfs_enabled')
expect(rendered).to have_content('Git Large File Storage')
end
end
+
+ context 'project export disabled' do
+ it 'does not display the project export option' do
+ stub_application_setting(project_export_enabled?: false)
+
+ render
+
+ expect(rendered).not_to have_content('Export project')
+ end
+ end
end
diff --git a/spec/views/projects/notes/_more_actions_dropdown.html.haml_spec.rb b/spec/views/projects/notes/_more_actions_dropdown.html.haml_spec.rb
index aea20d826d0..9c0be249a50 100644
--- a/spec/views/projects/notes/_more_actions_dropdown.html.haml_spec.rb
+++ b/spec/views/projects/notes/_more_actions_dropdown.html.haml_spec.rb
@@ -24,18 +24,16 @@ describe 'projects/notes/_more_actions_dropdown' do
expect(rendered).not_to have_selector('.dropdown.more-actions')
end
- it 'shows Report as abuse, Edit and Delete buttons if editable and not current users comment' do
+ it 'shows Report as abuse and Delete buttons if editable and not current users comment' do
render 'projects/notes/more_actions_dropdown', current_user: not_author_user, note_editable: true, note: note
expect(rendered).to have_link('Report as abuse')
- expect(rendered).to have_button('Edit comment')
expect(rendered).to have_link('Delete comment')
end
- it 'shows Edit and Delete buttons if editable and current users comment' do
+ it 'shows Delete button if editable and current users comment' do
render 'projects/notes/more_actions_dropdown', current_user: author_user, note_editable: true, note: note
- expect(rendered).to have_button('Edit comment')
expect(rendered).to have_link('Delete comment')
end
end
diff --git a/spec/workers/email_receiver_worker_spec.rb b/spec/workers/email_receiver_worker_spec.rb
index fe70501eeac..e4e77c667b3 100644
--- a/spec/workers/email_receiver_worker_spec.rb
+++ b/spec/workers/email_receiver_worker_spec.rb
@@ -1,6 +1,6 @@
require "spec_helper"
-describe EmailReceiverWorker do
+describe EmailReceiverWorker, :mailer do
let(:raw_message) { fixture_file('emails/valid_reply.eml') }
context "when reply by email is enabled" do
@@ -17,12 +17,16 @@ describe EmailReceiverWorker do
context "when an error occurs" do
before do
- allow_any_instance_of(Gitlab::Email::Receiver).to receive(:execute).and_raise(Gitlab::Email::EmptyEmailError)
+ allow_any_instance_of(Gitlab::Email::Receiver).to receive(:execute).and_raise(error)
end
- it "sends out a rejection email" do
- perform_enqueued_jobs do
- described_class.new.perform(raw_message)
+ context 'when the error is Gitlab::Email::EmptyEmailError' do
+ let(:error) { Gitlab::Email::EmptyEmailError }
+
+ it 'sends out a rejection email' do
+ perform_enqueued_jobs do
+ described_class.new.perform(raw_message)
+ end
email = ActionMailer::Base.deliveries.last
expect(email).not_to be_nil
@@ -30,6 +34,18 @@ describe EmailReceiverWorker do
expect(email.subject).to include("Rejected")
end
end
+
+ context 'when the error is Gitlab::Email::AutoGeneratedEmailError' do
+ let(:error) { Gitlab::Email::AutoGeneratedEmailError }
+
+ it 'does not send out any rejection email' do
+ perform_enqueued_jobs do
+ described_class.new.perform(raw_message)
+ end
+
+ should_not_email_anyone
+ end
+ end
end
end
diff --git a/spec/workers/emails_on_push_worker_spec.rb b/spec/workers/emails_on_push_worker_spec.rb
index 5b6b38e0f76..318aad4bc1e 100644
--- a/spec/workers/emails_on_push_worker_spec.rb
+++ b/spec/workers/emails_on_push_worker_spec.rb
@@ -1,8 +1,7 @@
require 'spec_helper'
-describe EmailsOnPushWorker do
+describe EmailsOnPushWorker, :mailer do
include RepoHelpers
- include EmailHelpers
include EmailSpec::Matchers
let(:project) { create(:project, :repository) }
@@ -90,7 +89,6 @@ describe EmailsOnPushWorker do
context "when there is an SMTP error" do
before do
- reset_delivered_emails!
allow(Notify).to receive(:repository_push_email).and_raise(Net::SMTPFatalError)
allow(subject).to receive_message_chain(:logger, :info)
perform
@@ -114,8 +112,6 @@ describe EmailsOnPushWorker do
allow_any_instance_of(Mail::TestMailer).to receive(:deliver!).and_wrap_original do |original, mail|
original.call(Mail.new(mail.encoded))
end
-
- reset_delivered_emails!
end
it "sends the mail to each of the recipients" do
diff --git a/spec/workers/merge_worker_spec.rb b/spec/workers/merge_worker_spec.rb
index 303193bab9b..ee51000161a 100644
--- a/spec/workers/merge_worker_spec.rb
+++ b/spec/workers/merge_worker_spec.rb
@@ -27,4 +27,15 @@ describe MergeWorker do
expect(source_project.repository.branch_names).not_to include('markdown')
end
end
+
+ it 'persists merge_jid' do
+ merge_request = create(:merge_request, merge_jid: nil)
+ user = create(:user)
+ worker = described_class.new
+
+ allow(worker).to receive(:jid) { '999' }
+
+ expect { worker.perform(merge_request.id, user.id, {}) }
+ .to change { merge_request.reload.merge_jid }.from(nil).to('999')
+ end
end
diff --git a/spec/workers/new_issue_worker_spec.rb b/spec/workers/new_issue_worker_spec.rb
new file mode 100644
index 00000000000..4e15ccc534b
--- /dev/null
+++ b/spec/workers/new_issue_worker_spec.rb
@@ -0,0 +1,54 @@
+require 'spec_helper'
+
+describe NewIssueWorker do
+ describe '#perform' do
+ let(:worker) { described_class.new }
+
+ context 'when an issue not found' do
+ it 'does not call Services' do
+ expect(EventCreateService).not_to receive(:new)
+ expect(NotificationService).not_to receive(:new)
+
+ worker.perform(99, create(:user).id)
+ end
+
+ it 'logs an error' do
+ expect(Rails.logger).to receive(:error).with('NewIssueWorker: couldn\'t find Issue with ID=99, skipping job')
+
+ worker.perform(99, create(:user).id)
+ end
+ end
+
+ context 'when a user not found' do
+ it 'does not call Services' do
+ expect(EventCreateService).not_to receive(:new)
+ expect(NotificationService).not_to receive(:new)
+
+ worker.perform(create(:issue).id, 99)
+ end
+
+ it 'logs an error' do
+ expect(Rails.logger).to receive(:error).with('NewIssueWorker: couldn\'t find User with ID=99, skipping job')
+
+ worker.perform(create(:issue).id, 99)
+ end
+ end
+
+ context 'when everything is ok' do
+ let(:project) { create(:project, :public) }
+ let(:mentioned) { create(:user) }
+ let(:user) { create(:user) }
+ let(:issue) { create(:issue, project: project, description: "issue for #{mentioned.to_reference}") }
+
+ it 'creates a new event record' do
+ expect { worker.perform(issue.id, user.id) }.to change { Event.count }.from(0).to(1)
+ end
+
+ it 'creates a notification for the assignee' do
+ expect(Notify).to receive(:new_issue_email).with(mentioned.id, issue.id).and_return(double(deliver_later: true))
+
+ worker.perform(issue.id, user.id)
+ end
+ end
+ end
+end
diff --git a/spec/workers/new_merge_request_worker_spec.rb b/spec/workers/new_merge_request_worker_spec.rb
new file mode 100644
index 00000000000..9e0cbde45b1
--- /dev/null
+++ b/spec/workers/new_merge_request_worker_spec.rb
@@ -0,0 +1,56 @@
+require 'spec_helper'
+
+describe NewMergeRequestWorker do
+ describe '#perform' do
+ let(:worker) { described_class.new }
+
+ context 'when a merge request not found' do
+ it 'does not call Services' do
+ expect(EventCreateService).not_to receive(:new)
+ expect(NotificationService).not_to receive(:new)
+
+ worker.perform(99, create(:user).id)
+ end
+
+ it 'logs an error' do
+ expect(Rails.logger).to receive(:error).with('NewMergeRequestWorker: couldn\'t find MergeRequest with ID=99, skipping job')
+
+ worker.perform(99, create(:user).id)
+ end
+ end
+
+ context 'when a user not found' do
+ it 'does not call Services' do
+ expect(EventCreateService).not_to receive(:new)
+ expect(NotificationService).not_to receive(:new)
+
+ worker.perform(create(:merge_request).id, 99)
+ end
+
+ it 'logs an error' do
+ expect(Rails.logger).to receive(:error).with('NewMergeRequestWorker: couldn\'t find User with ID=99, skipping job')
+
+ worker.perform(create(:merge_request).id, 99)
+ end
+ end
+
+ context 'when everything is ok' do
+ let(:project) { create(:project, :public) }
+ let(:mentioned) { create(:user) }
+ let(:user) { create(:user) }
+ let(:merge_request) do
+ create(:merge_request, source_project: project, description: "mr for #{mentioned.to_reference}")
+ end
+
+ it 'creates a new event record' do
+ expect { worker.perform(merge_request.id, user.id) }.to change { Event.count }.from(0).to(1)
+ end
+
+ it 'creates a notification for the assignee' do
+ expect(Notify).to receive(:new_merge_request_email).with(mentioned.id, merge_request.id).and_return(double(deliver_later: true))
+
+ worker.perform(merge_request.id, user.id)
+ end
+ end
+ end
+end
diff --git a/spec/workers/pipeline_notification_worker_spec.rb b/spec/workers/pipeline_notification_worker_spec.rb
index 139032d77bd..eb539ffd893 100644
--- a/spec/workers/pipeline_notification_worker_spec.rb
+++ b/spec/workers/pipeline_notification_worker_spec.rb
@@ -1,8 +1,6 @@
require 'spec_helper'
-describe PipelineNotificationWorker do
- include EmailHelpers
-
+describe PipelineNotificationWorker, :mailer do
let(:pipeline) { create(:ci_pipeline) }
describe '#execute' do
diff --git a/spec/workers/pipeline_schedule_worker_spec.rb b/spec/workers/pipeline_schedule_worker_spec.rb
index 14ed8b7811e..75197039f5a 100644
--- a/spec/workers/pipeline_schedule_worker_spec.rb
+++ b/spec/workers/pipeline_schedule_worker_spec.rb
@@ -23,7 +23,7 @@ describe PipelineScheduleWorker do
context 'when there is a scheduled pipeline within next_run_at' do
it 'creates a new pipeline' do
- expect{ subject }.to change { project.pipelines.count }.by(1)
+ expect { subject }.to change { project.pipelines.count }.by(1)
expect(Ci::Pipeline.last).to be_schedule
end
diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb
index 74a9f90195c..af6a3c9f6c7 100644
--- a/spec/workers/post_receive_spec.rb
+++ b/spec/workers/post_receive_spec.rb
@@ -78,7 +78,7 @@ describe PostReceive do
stub_ci_pipeline_to_return_yaml_file
end
- it { expect{ subject }.to change{ Ci::Pipeline.count }.by(2) }
+ it { expect { subject }.to change { Ci::Pipeline.count }.by(2) }
end
context "does not create a Ci::Pipeline" do
@@ -86,7 +86,7 @@ describe PostReceive do
stub_ci_pipeline_yaml_file(nil)
end
- it { expect{ subject }.not_to change{ Ci::Pipeline.count } }
+ it { expect { subject }.not_to change { Ci::Pipeline.count } }
end
end
diff --git a/spec/workers/prune_old_events_worker_spec.rb b/spec/workers/prune_old_events_worker_spec.rb
index 35e1518a35e..ea974355050 100644
--- a/spec/workers/prune_old_events_worker_spec.rb
+++ b/spec/workers/prune_old_events_worker_spec.rb
@@ -2,9 +2,11 @@ require 'spec_helper'
describe PruneOldEventsWorker do
describe '#perform' do
- let!(:expired_event) { create(:event, author_id: 0, created_at: 13.months.ago) }
- let!(:not_expired_event) { create(:event, author_id: 0, created_at: 1.day.ago) }
- let!(:exactly_12_months_event) { create(:event, author_id: 0, created_at: 12.months.ago) }
+ let(:user) { create(:user) }
+
+ let!(:expired_event) { create(:event, :closed, author: user, created_at: 13.months.ago) }
+ let!(:not_expired_event) { create(:event, :closed, author: user, created_at: 1.day.ago) }
+ let!(:exactly_12_months_event) { create(:event, :closed, author: user, created_at: 12.months.ago) }
it 'prunes events older than 12 months' do
expect { subject.perform }.to change { Event.count }.by(-1)
diff --git a/spec/workers/stuck_merge_jobs_worker_spec.rb b/spec/workers/stuck_merge_jobs_worker_spec.rb
new file mode 100644
index 00000000000..a5ad78393c9
--- /dev/null
+++ b/spec/workers/stuck_merge_jobs_worker_spec.rb
@@ -0,0 +1,50 @@
+require 'spec_helper'
+
+describe StuckMergeJobsWorker do
+ describe 'perform' do
+ let(:worker) { described_class.new }
+
+ context 'merge job identified as completed' do
+ it 'updates merge request to merged when locked but has merge_commit_sha' do
+ allow(Gitlab::SidekiqStatus).to receive(:completed_jids).and_return(%w(123 456))
+ mr_with_sha = create(:merge_request, :locked, merge_jid: '123', state: :locked, merge_commit_sha: 'foo-bar-baz')
+ mr_without_sha = create(:merge_request, :locked, merge_jid: '123', state: :locked, merge_commit_sha: nil)
+
+ worker.perform
+
+ expect(mr_with_sha.reload).to be_merged
+ expect(mr_without_sha.reload).to be_opened
+ end
+
+ it 'updates merge request to opened when locked but has not been merged' do
+ allow(Gitlab::SidekiqStatus).to receive(:completed_jids).and_return(%w(123))
+ merge_request = create(:merge_request, :locked, merge_jid: '123', state: :locked)
+
+ worker.perform
+
+ expect(merge_request.reload).to be_opened
+ end
+
+ it 'logs updated stuck merge job ids' do
+ allow(Gitlab::SidekiqStatus).to receive(:completed_jids).and_return(%w(123 456))
+
+ create(:merge_request, :locked, merge_jid: '123')
+ create(:merge_request, :locked, merge_jid: '456')
+
+ expect(Rails).to receive_message_chain(:logger, :info).with('Updated state of locked merge jobs. JIDs: 123, 456')
+
+ worker.perform
+ end
+ end
+
+ context 'merge job not identified as completed' do
+ it 'does not change merge request state when job is not completed yet' do
+ allow(Gitlab::SidekiqStatus).to receive(:completed_jids).and_return([])
+
+ merge_request = create(:merge_request, :locked, merge_jid: '123')
+
+ expect { worker.perform }.not_to change { merge_request.reload.state }.from('locked')
+ end
+ end
+ end
+end
diff --git a/vendor/assets/javascripts/jquery.nicescroll.js b/vendor/assets/javascripts/jquery.nicescroll.js
deleted file mode 100644
index 7653f25df4b..00000000000
--- a/vendor/assets/javascripts/jquery.nicescroll.js
+++ /dev/null
@@ -1,3634 +0,0 @@
-/* jquery.nicescroll
--- version 3.6.0
--- copyright 2014-11-21 InuYaksa*2014
--- licensed under the MIT
---
--- http://nicescroll.areaaperta.com/
--- https://github.com/inuyaksa/jquery.nicescroll
---
-*/
-
-(function(factory) {
- if (typeof define === 'function' && define.amd) {
- // AMD. Register as anonymous module.
- define(['jquery'], factory);
- } else {
- // Browser globals.
- factory(jQuery);
- }
-}(function(jQuery) {
- "use strict";
-
- // globals
- var domfocus = false;
- var mousefocus = false;
- var tabindexcounter = 0;
- var ascrailcounter = 2000;
- var globalmaxzindex = 0;
-
- var $ = jQuery; // sandbox
-
- // http://stackoverflow.com/questions/2161159/get-script-path
- function getScriptPath() {
- var scripts = document.getElementsByTagName('script');
- var path = scripts[scripts.length - 1].src.split('?')[0];
- return (path.split('/').length > 0) ? path.split('/').slice(0, -1).join('/') + '/' : '';
- }
-
- var vendors = ['webkit','ms','moz','o'];
-
- var setAnimationFrame = window.requestAnimationFrame || false;
- var clearAnimationFrame = window.cancelAnimationFrame || false;
-
- if (!setAnimationFrame) { // legacy detection
- for (var vx in vendors) {
- var v = vendors[vx];
- if (!setAnimationFrame) setAnimationFrame = window[v + 'RequestAnimationFrame'];
- if (!clearAnimationFrame) clearAnimationFrame = window[v + 'CancelAnimationFrame'] || window[v + 'CancelRequestAnimationFrame'];
- }
- }
-
- var ClsMutationObserver = window.MutationObserver || window.WebKitMutationObserver || false;
-
- var _globaloptions = {
- zindex: "auto",
- cursoropacitymin: 0,
- cursoropacitymax: 1,
- cursorcolor: "#424242",
- cursorwidth: "5px",
- cursorborder: "1px solid #fff",
- cursorborderradius: "5px",
- scrollspeed: 60,
- mousescrollstep: 8 * 3,
- touchbehavior: false,
- hwacceleration: true,
- usetransition: true,
- boxzoom: false,
- dblclickzoom: true,
- gesturezoom: true,
- grabcursorenabled: true,
- autohidemode: true,
- background: "",
- iframeautoresize: true,
- cursorminheight: 32,
- preservenativescrolling: true,
- railoffset: false,
- railhoffset: false,
- bouncescroll: true,
- spacebarenabled: true,
- railpadding: {
- top: 0,
- right: 0,
- left: 0,
- bottom: 0
- },
- disableoutline: true,
- horizrailenabled: true,
- railalign: "right",
- railvalign: "bottom",
- enabletranslate3d: true,
- enablemousewheel: true,
- enablekeyboard: true,
- smoothscroll: true,
- sensitiverail: true,
- enablemouselockapi: true,
- // cursormaxheight:false,
- cursorfixedheight: false,
- directionlockdeadzone: 6,
- hidecursordelay: 400,
- nativeparentscrolling: true,
- enablescrollonselection: true,
- overflowx: true,
- overflowy: true,
- cursordragspeed: 0.3,
- rtlmode: "auto",
- cursordragontouch: false,
- oneaxismousemode: "auto",
- scriptpath: getScriptPath(),
- preventmultitouchscrolling: true
- };
-
- var browserdetected = false;
-
- var getBrowserDetection = function() {
-
- if (browserdetected) return browserdetected;
-
- var _el = document.createElement('DIV'),
- _style = _el.style,
- _agent = navigator.userAgent,
- _platform = navigator.platform,
- d = {};
-
- d.haspointerlock = "pointerLockElement" in document || "webkitPointerLockElement" in document || "mozPointerLockElement" in document;
-
- d.isopera = ("opera" in window); // 12-
- d.isopera12 = (d.isopera && ("getUserMedia" in navigator));
- d.isoperamini = (Object.prototype.toString.call(window.operamini) === "[object OperaMini]");
-
- d.isie = (("all" in document) && ("attachEvent" in _el) && !d.isopera); //IE10-
- d.isieold = (d.isie && !("msInterpolationMode" in _style)); // IE6 and older
- d.isie7 = d.isie && !d.isieold && (!("documentMode" in document) || (document.documentMode == 7));
- d.isie8 = d.isie && ("documentMode" in document) && (document.documentMode == 8);
- d.isie9 = d.isie && ("performance" in window) && (document.documentMode >= 9);
- d.isie10 = d.isie && ("performance" in window) && (document.documentMode == 10);
- d.isie11 = ("msRequestFullscreen" in _el) && (document.documentMode >= 11); // IE11+
-
- d.isie9mobile = /iemobile.9/i.test(_agent); //wp 7.1 mango
- if (d.isie9mobile) d.isie9 = false;
- d.isie7mobile = (!d.isie9mobile && d.isie7) && /iemobile/i.test(_agent); //wp 7.0
-
- d.ismozilla = ("MozAppearance" in _style);
-
- d.iswebkit = ("WebkitAppearance" in _style);
-
- d.ischrome = ("chrome" in window);
- d.ischrome22 = (d.ischrome && d.haspointerlock);
- d.ischrome26 = (d.ischrome && ("transition" in _style)); // issue with transform detection (maintain prefix)
-
- d.cantouch = ("ontouchstart" in document.documentElement) || ("ontouchstart" in window); // detection for Chrome Touch Emulation
- d.hasmstouch = (window.MSPointerEvent || false); // IE10 pointer events
- d.hasw3ctouch = (window.PointerEvent || false); //IE11 pointer events, following W3C Pointer Events spec
-
- d.ismac = /^mac$/i.test(_platform);
-
- d.isios = (d.cantouch && /iphone|ipad|ipod/i.test(_platform));
- d.isios4 = ((d.isios) && !("seal" in Object));
- d.isios7 = ((d.isios)&&("webkitHidden" in document)); //iOS 7+
-
- d.isandroid = (/android/i.test(_agent));
-
- d.haseventlistener = ("addEventListener" in _el);
-
- d.trstyle = false;
- d.hastransform = false;
- d.hastranslate3d = false;
- d.transitionstyle = false;
- d.hastransition = false;
- d.transitionend = false;
-
- var a;
- var check = ['transform', 'msTransform', 'webkitTransform', 'MozTransform', 'OTransform'];
- for (a = 0; a < check.length; a++) {
- if (typeof _style[check[a]] != "undefined") {
- d.trstyle = check[a];
- break;
- }
- }
- d.hastransform = (!!d.trstyle);
- if (d.hastransform) {
- _style[d.trstyle] = "translate3d(1px,2px,3px)";
- d.hastranslate3d = /translate3d/.test(_style[d.trstyle]);
- }
-
- d.transitionstyle = false;
- d.prefixstyle = '';
- d.transitionend = false;
- check = ['transition', 'webkitTransition', 'msTransition', 'MozTransition', 'OTransition', 'OTransition', 'KhtmlTransition'];
- var prefix = ['', '-webkit-', '-ms-', '-moz-', '-o-', '-o', '-khtml-'];
- var evs = ['transitionend', 'webkitTransitionEnd', 'msTransitionEnd', 'transitionend', 'otransitionend', 'oTransitionEnd', 'KhtmlTransitionEnd'];
- for (a = 0; a < check.length; a++) {
- if (check[a] in _style) {
- d.transitionstyle = check[a];
- d.prefixstyle = prefix[a];
- d.transitionend = evs[a];
- break;
- }
- }
- if (d.ischrome26) { // always use prefix
- d.prefixstyle = prefix[1];
- }
-
- d.hastransition = (d.transitionstyle);
-
- function detectCursorGrab() {
- var lst = ['-webkit-grab', '-moz-grab', 'grab'];
- if ((d.ischrome && !d.ischrome22) || d.isie) lst = []; // force setting for IE returns false positive and chrome cursor bug
- for (var a = 0; a < lst.length; a++) {
- var p = lst[a];
- _style.cursor = p;
- if (_style.cursor == p) return p;
- }
- return 'url(//mail.google.com/mail/images/2/openhand.cur),n-resize'; // thank you google for custom cursor!
- }
- d.cursorgrabvalue = detectCursorGrab();
-
- d.hasmousecapture = ("setCapture" in _el);
-
- d.hasMutationObserver = (ClsMutationObserver !== false);
-
- _el = null; //memory released
-
- browserdetected = d;
-
- return d;
- };
-
- var NiceScrollClass = function(myopt, me) {
-
- var self = this;
-
- this.version = '3.6.0';
- this.name = 'nicescroll';
-
- this.me = me;
-
- this.opt = {
- doc: $("body"),
- win: false
- };
-
- $.extend(this.opt, _globaloptions); // clone opts
-
- // Options for internal use
- this.opt.snapbackspeed = 80;
-
- if (myopt || false) {
- for (var a in self.opt) {
- if (typeof myopt[a] != "undefined") self.opt[a] = myopt[a];
- }
- }
-
- this.doc = self.opt.doc;
- this.iddoc = (this.doc && this.doc[0]) ? this.doc[0].id || '' : '';
- this.ispage = /^BODY|HTML/.test((self.opt.win) ? self.opt.win[0].nodeName : this.doc[0].nodeName);
- this.haswrapper = (self.opt.win !== false);
- this.win = self.opt.win || (this.ispage ? $(window) : this.doc);
- this.docscroll = (this.ispage && !this.haswrapper) ? $(window) : this.win;
- this.body = $("body");
- this.viewport = false;
-
- this.isfixed = false;
-
- this.iframe = false;
- this.isiframe = ((this.doc[0].nodeName == 'IFRAME') && (this.win[0].nodeName == 'IFRAME'));
-
- this.istextarea = (this.win[0].nodeName == 'TEXTAREA');
-
- this.forcescreen = false; //force to use screen position on events
-
- this.canshowonmouseevent = (self.opt.autohidemode != "scroll");
-
- // Events jump table
- this.onmousedown = false;
- this.onmouseup = false;
- this.onmousemove = false;
- this.onmousewheel = false;
- this.onkeypress = false;
- this.ongesturezoom = false;
- this.onclick = false;
-
- // Nicescroll custom events
- this.onscrollstart = false;
- this.onscrollend = false;
- this.onscrollcancel = false;
-
- this.onzoomin = false;
- this.onzoomout = false;
-
- // Let's start!
- this.view = false;
- this.page = false;
-
- this.scroll = {
- x: 0,
- y: 0
- };
- this.scrollratio = {
- x: 0,
- y: 0
- };
- this.cursorheight = 20;
- this.scrollvaluemax = 0;
-
- this.isrtlmode = (this.opt.rtlmode == "auto") ? ((this.win[0] == window ? this.body : this.win).css("direction") == "rtl") : (this.opt.rtlmode === true);
- // this.checkrtlmode = false;
-
- this.scrollrunning = false;
-
- this.scrollmom = false;
-
- this.observer = false; // observer div changes
- this.observerremover = false; // observer on parent for remove detection
- this.observerbody = false; // observer on body for position change
-
- do {
- this.id = "ascrail" + (ascrailcounter++);
- } while (document.getElementById(this.id));
-
- this.rail = false;
- this.cursor = false;
- this.cursorfreezed = false;
- this.selectiondrag = false;
-
- this.zoom = false;
- this.zoomactive = false;
-
- this.hasfocus = false;
- this.hasmousefocus = false;
-
- this.visibility = true;
- this.railslocked = false; // locked by resize
- this.locked = false; // prevent lost of locked status sets by user
- this.hidden = false; // rails always hidden
- this.cursoractive = true; // user can interact with cursors
-
- this.wheelprevented = false; //prevent mousewheel event
-
- this.overflowx = self.opt.overflowx;
- this.overflowy = self.opt.overflowy;
-
- this.nativescrollingarea = false;
- this.checkarea = 0;
-
- this.events = []; // event list for unbind
-
- this.saved = {}; // style saved
-
- this.delaylist = {};
- this.synclist = {};
-
- this.lastdeltax = 0;
- this.lastdeltay = 0;
-
- this.detected = getBrowserDetection();
-
- var cap = $.extend({}, this.detected);
-
- this.canhwscroll = (cap.hastransform && self.opt.hwacceleration);
- this.ishwscroll = (this.canhwscroll && self.haswrapper);
-
- this.hasreversehr = (this.isrtlmode&&!cap.iswebkit); //RTL mode with reverse horizontal axis
-
- this.istouchcapable = false; // desktop devices with touch screen support
-
- //## Check WebKit-based desktop with touch support
- //## + Firefox 18 nightly build (desktop) false positive (or desktop with touch support)
- if (cap.cantouch && !cap.isios && !cap.isandroid && (cap.iswebkit || cap.ismozilla)) {
- this.istouchcapable = true;
- cap.cantouch = false; // parse normal desktop events
- }
-
- //## disable MouseLock API on user request
- if (!self.opt.enablemouselockapi) {
- cap.hasmousecapture = false;
- cap.haspointerlock = false;
- }
-
-/* deprecated
- this.delayed = function(name, fn, tm, lazy) {
- };
-*/
-
- this.debounced = function(name, fn, tm) {
- var dd = self.delaylist[name];
- self.delaylist[name] = fn;
- if (!dd) {
- setTimeout(function() {
- var fn = self.delaylist[name];
- self.delaylist[name] = false;
- fn.call(self);
- }, tm);
- }
- };
-
- var _onsync = false;
-
- this.synched = function(name, fn) {
-
- function requestSync() {
- if (_onsync) return;
- setAnimationFrame(function() {
- _onsync = false;
- for (var nn in self.synclist) {
- var fn = self.synclist[nn];
- if (fn) fn.call(self);
- self.synclist[nn] = false;
- }
- });
- _onsync = true;
- }
-
- self.synclist[name] = fn;
- requestSync();
- return name;
- };
-
- this.unsynched = function(name) {
- if (self.synclist[name]) self.synclist[name] = false;
- };
-
- this.css = function(el, pars) { // save & set
- for (var n in pars) {
- self.saved.css.push([el, n, el.css(n)]);
- el.css(n, pars[n]);
- }
- };
-
- this.scrollTop = function(val) {
- return (typeof val == "undefined") ? self.getScrollTop() : self.setScrollTop(val);
- };
-
- this.scrollLeft = function(val) {
- return (typeof val == "undefined") ? self.getScrollLeft() : self.setScrollLeft(val);
- };
-
- // derived by by Dan Pupius www.pupius.net
- var BezierClass = function(st, ed, spd, p1, p2, p3, p4) {
-
- this.st = st;
- this.ed = ed;
- this.spd = spd;
-
- this.p1 = p1 || 0;
- this.p2 = p2 || 1;
- this.p3 = p3 || 0;
- this.p4 = p4 || 1;
-
- this.ts = (new Date()).getTime();
- this.df = this.ed - this.st;
- };
- BezierClass.prototype = {
- B2: function(t) {
- return 3 * t * t * (1 - t);
- },
- B3: function(t) {
- return 3 * t * (1 - t) * (1 - t);
- },
- B4: function(t) {
- return (1 - t) * (1 - t) * (1 - t);
- },
- getNow: function() {
- var nw = (new Date()).getTime();
- var pc = 1 - ((nw - this.ts) / this.spd);
- var bz = this.B2(pc) + this.B3(pc) + this.B4(pc);
- return (pc < 0) ? this.ed : this.st + Math.round(this.df * bz);
- },
- update: function(ed, spd) {
- this.st = this.getNow();
- this.ed = ed;
- this.spd = spd;
- this.ts = (new Date()).getTime();
- this.df = this.ed - this.st;
- return this;
- }
- };
-
- //derived from http://stackoverflow.com/questions/11236090/
- function getMatrixValues() {
- var tr = self.doc.css(cap.trstyle);
- if (tr && (tr.substr(0, 6) == "matrix")) {
- return tr.replace(/^.*\((.*)\)$/g, "$1").replace(/px/g, '').split(/, +/);
- }
- return false;
- }
-
- if (this.ishwscroll) {
- // hw accelerated scroll
- this.doc.translate = {
- x: 0,
- y: 0,
- tx: "0px",
- ty: "0px"
- };
-
- //this one can help to enable hw accel on ios6 http://indiegamr.com/ios6-html-hardware-acceleration-changes-and-how-to-fix-them/
- if (cap.hastranslate3d && cap.isios) this.doc.css("-webkit-backface-visibility", "hidden"); // prevent flickering http://stackoverflow.com/questions/3461441/
-
- this.getScrollTop = function(last) {
- if (!last) {
- var mtx = getMatrixValues();
- if (mtx) return (mtx.length == 16) ? -mtx[13] : -mtx[5]; //matrix3d 16 on IE10
- if (self.timerscroll && self.timerscroll.bz) return self.timerscroll.bz.getNow();
- }
- return self.doc.translate.y;
- };
-
- this.getScrollLeft = function(last) {
- if (!last) {
- var mtx = getMatrixValues();
- if (mtx) return (mtx.length == 16) ? -mtx[12] : -mtx[4]; //matrix3d 16 on IE10
- if (self.timerscroll && self.timerscroll.bh) return self.timerscroll.bh.getNow();
- }
- return self.doc.translate.x;
- };
-
- this.notifyScrollEvent = function(el) {
- var e = document.createEvent("UIEvents");
- e.initUIEvent("scroll", false, true, window, 1);
- e.niceevent = true;
- el.dispatchEvent(e);
- };
-
- var cxscrollleft = (this.isrtlmode) ? 1 : -1;
-
- if (cap.hastranslate3d && self.opt.enabletranslate3d) {
- this.setScrollTop = function(val, silent) {
- self.doc.translate.y = val;
- self.doc.translate.ty = (val * -1) + "px";
- self.doc.css(cap.trstyle, "translate3d(" + self.doc.translate.tx + "," + self.doc.translate.ty + ",0px)");
- if (!silent) self.notifyScrollEvent(self.win[0]);
- };
- this.setScrollLeft = function(val, silent) {
- self.doc.translate.x = val;
- self.doc.translate.tx = (val * cxscrollleft) + "px";
- self.doc.css(cap.trstyle, "translate3d(" + self.doc.translate.tx + "," + self.doc.translate.ty + ",0px)");
- if (!silent) self.notifyScrollEvent(self.win[0]);
- };
- } else {
- this.setScrollTop = function(val, silent) {
- self.doc.translate.y = val;
- self.doc.translate.ty = (val * -1) + "px";
- self.doc.css(cap.trstyle, "translate(" + self.doc.translate.tx + "," + self.doc.translate.ty + ")");
- if (!silent) self.notifyScrollEvent(self.win[0]);
- };
- this.setScrollLeft = function(val, silent) {
- self.doc.translate.x = val;
- self.doc.translate.tx = (val * cxscrollleft) + "px";
- self.doc.css(cap.trstyle, "translate(" + self.doc.translate.tx + "," + self.doc.translate.ty + ")");
- if (!silent) self.notifyScrollEvent(self.win[0]);
- };
- }
- } else {
- // native scroll
- this.getScrollTop = function() {
- return self.docscroll.scrollTop();
- };
- this.setScrollTop = function(val) {
- return self.docscroll.scrollTop(val);
- };
- this.getScrollLeft = function() {
- if (self.detected.ismozilla && self.isrtlmode)
- return Math.abs(self.docscroll.scrollLeft());
- return self.docscroll.scrollLeft();
- };
- this.setScrollLeft = function(val) {
- return self.docscroll.scrollLeft((self.detected.ismozilla && self.isrtlmode) ? -val : val);
- };
- }
-
- this.getTarget = function(e) {
- if (!e) return false;
- if (e.target) return e.target;
- if (e.srcElement) return e.srcElement;
- return false;
- };
-
- this.hasParent = function(e, id) {
- if (!e) return false;
- var el = e.target || e.srcElement || e || false;
- while (el && el.id != id) {
- el = el.parentNode || false;
- }
- return (el !== false);
- };
-
- function getZIndex() {
- var dom = self.win;
- if ("zIndex" in dom) return dom.zIndex(); // use jQuery UI method when available
- while (dom.length > 0) {
- if (dom[0].nodeType == 9) return false;
- var zi = dom.css('zIndex');
- if (!isNaN(zi) && zi != 0) return parseInt(zi);
- dom = dom.parent();
- }
- return false;
- }
-
- //inspired by http://forum.jquery.com/topic/width-includes-border-width-when-set-to-thin-medium-thick-in-ie
- var _convertBorderWidth = {
- "thin": 1,
- "medium": 3,
- "thick": 5
- };
-
- function getWidthToPixel(dom, prop, chkheight) {
- var wd = dom.css(prop);
- var px = parseFloat(wd);
- if (isNaN(px)) {
- px = _convertBorderWidth[wd] || 0;
- var brd = (px == 3) ? ((chkheight) ? (self.win.outerHeight() - self.win.innerHeight()) : (self.win.outerWidth() - self.win.innerWidth())) : 1; //DON'T TRUST CSS
- if (self.isie8 && px) px += 1;
- return (brd) ? px : 0;
- }
- return px;
- }
-
- this.getDocumentScrollOffset = function() {
- return {top:window.pageYOffset||document.documentElement.scrollTop,
- left:window.pageXOffset||document.documentElement.scrollLeft};
- }
-
- this.getOffset = function() {
- if (self.isfixed) {
- var ofs = self.win.offset(); // fix Chrome auto issue (when right/bottom props only)
- var scrl = self.getDocumentScrollOffset();
- ofs.top-=scrl.top;
- ofs.left-=scrl.left;
- return ofs;
- }
- var ww = self.win.offset();
- if (!self.viewport) return ww;
- var vp = self.viewport.offset();
- return {
- top: ww.top - vp.top,// + self.viewport.scrollTop(),
- left: ww.left - vp.left // + self.viewport.scrollLeft()
- };
- };
-
- this.updateScrollBar = function(len) {
- if (self.ishwscroll) {
- self.rail.css({ //**
- height: self.win.innerHeight() - (self.opt.railpadding.top + self.opt.railpadding.bottom)
- });
- if (self.railh) self.railh.css({ //**
- width: self.win.innerWidth() - (self.opt.railpadding.left + self.opt.railpadding.right)
- });
-
- } else {
- var wpos = self.getOffset();
- var pos = {
- top: wpos.top,
- left: wpos.left - (self.opt.railpadding.left + self.opt.railpadding.right)
- };
- pos.top += getWidthToPixel(self.win, 'border-top-width', true);
- pos.left += (self.rail.align) ? self.win.outerWidth() - getWidthToPixel(self.win, 'border-right-width') - self.rail.width : getWidthToPixel(self.win, 'border-left-width');
-
- var off = self.opt.railoffset;
- if (off) {
- if (off.top) pos.top += off.top;
- if (self.rail.align && off.left) pos.left += off.left;
- }
-
- if (!self.railslocked) self.rail.css({
- top: pos.top,
- left: pos.left,
- height: ((len) ? len.h : self.win.innerHeight()) - (self.opt.railpadding.top + self.opt.railpadding.bottom)
- });
-
- if (self.zoom) {
- self.zoom.css({
- top: pos.top + 1,
- left: (self.rail.align == 1) ? pos.left - 20 : pos.left + self.rail.width + 4
- });
- }
-
- if (self.railh && !self.railslocked) {
- var pos = {
- top: wpos.top,
- left: wpos.left
- };
- var off = self.opt.railhoffset;
- if (!!off) {
- if (!!off.top) pos.top += off.top;
- if (!!off.left) pos.left += off.left;
- }
- var y = (self.railh.align) ? pos.top + getWidthToPixel(self.win, 'border-top-width', true) + self.win.innerHeight() - self.railh.height : pos.top + getWidthToPixel(self.win, 'border-top-width', true);
- var x = pos.left + getWidthToPixel(self.win, 'border-left-width');
- self.railh.css({
- top: y - (self.opt.railpadding.top + self.opt.railpadding.bottom),
- left: x,
- width: self.railh.width
- });
- }
-
-
- }
- };
-
- this.doRailClick = function(e, dbl, hr) {
- var fn, pg, cur, pos;
-
- if (self.railslocked) return;
- self.cancelEvent(e);
-
- if (dbl) {
- fn = (hr) ? self.doScrollLeft : self.doScrollTop;
- cur = (hr) ? ((e.pageX - self.railh.offset().left - (self.cursorwidth / 2)) * self.scrollratio.x) : ((e.pageY - self.rail.offset().top - (self.cursorheight / 2)) * self.scrollratio.y);
- fn(cur);
- } else {
- fn = (hr) ? self.doScrollLeftBy : self.doScrollBy;
- cur = (hr) ? self.scroll.x : self.scroll.y;
- pos = (hr) ? e.pageX - self.railh.offset().left : e.pageY - self.rail.offset().top;
- pg = (hr) ? self.view.w : self.view.h;
- fn((cur >= pos) ? pg: -pg);// (cur >= pos) ? fn(pg): fn(-pg);
- }
-
- };
-
- self.hasanimationframe = (setAnimationFrame);
- self.hascancelanimationframe = (clearAnimationFrame);
-
- if (!self.hasanimationframe) {
- setAnimationFrame = function(fn) {
- return setTimeout(fn, 15 - Math.floor((+new Date()) / 1000) % 16);
- }; // 1000/60)};
- clearAnimationFrame = clearInterval;
- } else if (!self.hascancelanimationframe) clearAnimationFrame = function() {
- self.cancelAnimationFrame = true;
- };
-
- this.init = function() {
-
- self.saved.css = [];
-
- if (cap.isie7mobile) return true; // SORRY, DO NOT WORK!
- if (cap.isoperamini) return true; // SORRY, DO NOT WORK!
-
- if (cap.hasmstouch) self.css((self.ispage) ? $("html") : self.win, {
- '-ms-touch-action': 'none'
- });
-
- self.zindex = "auto";
- if (!self.ispage && self.opt.zindex == "auto") {
- self.zindex = getZIndex() || "auto";
- } else {
- self.zindex = self.opt.zindex;
- }
-
- if (!self.ispage && self.zindex != "auto") {
- if (self.zindex > globalmaxzindex) globalmaxzindex = self.zindex;
- }
-
- if (self.isie && self.zindex == 0 && self.opt.zindex == "auto") { // fix IE auto == 0
- self.zindex = "auto";
- }
-
- if (!self.ispage || (!cap.cantouch && !cap.isieold && !cap.isie9mobile)) {
-
- var cont = self.docscroll;
- if (self.ispage) cont = (self.haswrapper) ? self.win : self.doc;
-
- if (!cap.isie9mobile) self.css(cont, {
- 'overflow-y': 'hidden'
- });
-
- if (self.ispage && cap.isie7) {
- if (self.doc[0].nodeName == 'BODY') self.css($("html"), {
- 'overflow-y': 'hidden'
- }); //IE7 double scrollbar issue
- else if (self.doc[0].nodeName == 'HTML') self.css($("body"), {
- 'overflow-y': 'hidden'
- }); //IE7 double scrollbar issue
- }
-
- if (cap.isios && !self.ispage && !self.haswrapper) self.css($("body"), {
- "-webkit-overflow-scrolling": "touch"
- }); //force hw acceleration
-
- var cursor = $(document.createElement('div'));
- cursor.css({
- position: "relative",
- top: 0,
- "float": "right",
- width: self.opt.cursorwidth,
- height: "0px",
- 'background-color': self.opt.cursorcolor,
- border: self.opt.cursorborder,
- 'background-clip': 'padding-box',
- '-webkit-border-radius': self.opt.cursorborderradius,
- '-moz-border-radius': self.opt.cursorborderradius,
- 'border-radius': self.opt.cursorborderradius
- });
-
- cursor.hborder = parseFloat(cursor.outerHeight() - cursor.innerHeight());
-
- cursor.addClass('nicescroll-cursors');
-
- self.cursor = cursor;
-
- var rail = $(document.createElement('div'));
- rail.attr('id', self.id);
- rail.addClass('nicescroll-rails nicescroll-rails-vr');
-
- var v, a, kp = ["left","right","top","bottom"]; //**
- for (var n in kp) {
- a = kp[n];
- v = self.opt.railpadding[a];
- (v) ? rail.css("padding-"+a,v+"px") : self.opt.railpadding[a] = 0;
- }
-
- rail.append(cursor);
-
- rail.width = Math.max(parseFloat(self.opt.cursorwidth), cursor.outerWidth());
- rail.css({
- width: rail.width + "px",
- 'zIndex': self.zindex,
- "background": self.opt.background,
- cursor: "default"
- });
-
- rail.visibility = true;
- rail.scrollable = true;
-
- rail.align = (self.opt.railalign == "left") ? 0 : 1;
-
- self.rail = rail;
-
- self.rail.drag = false;
-
- var zoom = false;
- if (self.opt.boxzoom && !self.ispage && !cap.isieold) {
- zoom = document.createElement('div');
-
- self.bind(zoom, "click", self.doZoom);
- self.bind(zoom, "mouseenter", function() {
- self.zoom.css('opacity', self.opt.cursoropacitymax);
- });
- self.bind(zoom, "mouseleave", function() {
- self.zoom.css('opacity', self.opt.cursoropacitymin);
- });
-
- self.zoom = $(zoom);
- self.zoom.css({
- "cursor": "pointer",
- 'z-index': self.zindex,
- 'backgroundImage': 'url(' + self.opt.scriptpath + 'zoomico.png)',
- 'height': 18,
- 'width': 18,
- 'backgroundPosition': '0px 0px'
- });
- if (self.opt.dblclickzoom) self.bind(self.win, "dblclick", self.doZoom);
- if (cap.cantouch && self.opt.gesturezoom) {
- self.ongesturezoom = function(e) {
- if (e.scale > 1.5) self.doZoomIn(e);
- if (e.scale < 0.8) self.doZoomOut(e);
- return self.cancelEvent(e);
- };
- self.bind(self.win, "gestureend", self.ongesturezoom);
- }
- }
-
- // init HORIZ
-
- self.railh = false;
- var railh;
-
- if (self.opt.horizrailenabled) {
-
- self.css(cont, {
- 'overflow-x': 'hidden'
- });
-
- var cursor = $(document.createElement('div'));
- cursor.css({
- position: "absolute",
- top: 0,
- height: self.opt.cursorwidth,
- width: "0px",
- 'background-color': self.opt.cursorcolor,
- border: self.opt.cursorborder,
- 'background-clip': 'padding-box',
- '-webkit-border-radius': self.opt.cursorborderradius,
- '-moz-border-radius': self.opt.cursorborderradius,
- 'border-radius': self.opt.cursorborderradius
- });
-
- if (cap.isieold) cursor.css({'overflow':'hidden'}); //IE6 horiz scrollbar issue
-
- cursor.wborder = parseFloat(cursor.outerWidth() - cursor.innerWidth());
-
- cursor.addClass('nicescroll-cursors');
-
- self.cursorh = cursor;
-
- railh = $(document.createElement('div'));
- railh.attr('id', self.id + '-hr');
- railh.addClass('nicescroll-rails nicescroll-rails-hr');
- railh.height = Math.max(parseFloat(self.opt.cursorwidth), cursor.outerHeight());
- railh.css({
- height: railh.height + "px",
- 'zIndex': self.zindex,
- "background": self.opt.background
- });
-
- railh.append(cursor);
-
- railh.visibility = true;
- railh.scrollable = true;
-
- railh.align = (self.opt.railvalign == "top") ? 0 : 1;
-
- self.railh = railh;
-
- self.railh.drag = false;
-
- }
-
- //
-
- if (self.ispage) {
- rail.css({
- position: "fixed",
- top: "0px",
- height: "100%"
- });
- (rail.align) ? rail.css({
- right: "0px"
- }): rail.css({
- left: "0px"
- });
- self.body.append(rail);
- if (self.railh) {
- railh.css({
- position: "fixed",
- left: "0px",
- width: "100%"
- });
- (railh.align) ? railh.css({
- bottom: "0px"
- }): railh.css({
- top: "0px"
- });
- self.body.append(railh);
- }
- } else {
- if (self.ishwscroll) {
- if (self.win.css('position') == 'static') self.css(self.win, {
- 'position': 'relative'
- });
- var bd = (self.win[0].nodeName == 'HTML') ? self.body : self.win;
- $(bd).scrollTop(0).scrollLeft(0); // fix rail position if content already scrolled
- if (self.zoom) {
- self.zoom.css({
- position: "absolute",
- top: 1,
- right: 0,
- "margin-right": rail.width + 4
- });
- bd.append(self.zoom);
- }
- rail.css({
- position: "absolute",
- top: 0
- });
- (rail.align) ? rail.css({
- right: 0
- }): rail.css({
- left: 0
- });
- bd.append(rail);
- if (railh) {
- railh.css({
- position: "absolute",
- left: 0,
- bottom: 0
- });
- (railh.align) ? railh.css({
- bottom: 0
- }): railh.css({
- top: 0
- });
- bd.append(railh);
- }
- } else {
- self.isfixed = (self.win.css("position") == "fixed");
- var rlpos = (self.isfixed) ? "fixed" : "absolute";
-
- if (!self.isfixed) self.viewport = self.getViewport(self.win[0]);
- if (self.viewport) {
- self.body = self.viewport;
- if ((/fixed|absolute/.test(self.viewport.css("position"))) == false) self.css(self.viewport, {
- "position": "relative"
- });
- }
-
- rail.css({
- position: rlpos
- });
- if (self.zoom) self.zoom.css({
- position: rlpos
- });
- self.updateScrollBar();
- self.body.append(rail);
- if (self.zoom) self.body.append(self.zoom);
- if (self.railh) {
- railh.css({
- position: rlpos
- });
- self.body.append(railh);
- }
- }
-
- if (cap.isios) self.css(self.win, {
- '-webkit-tap-highlight-color': 'rgba(0,0,0,0)',
- '-webkit-touch-callout': 'none'
- }); // prevent grey layer on click
-
- if (cap.isie && self.opt.disableoutline) self.win.attr("hideFocus", "true"); // IE, prevent dotted rectangle on focused div
- if (cap.iswebkit && self.opt.disableoutline) self.win.css({"outline": "none"}); // Webkit outline
- //if (cap.isopera&&self.opt.disableoutline) self.win.css({"outline":"0"}); // Opera 12- to test [TODO]
-
- }
-
- if (self.opt.autohidemode === false) {
- self.autohidedom = false;
- self.rail.css({
- opacity: self.opt.cursoropacitymax
- });
- if (self.railh) self.railh.css({
- opacity: self.opt.cursoropacitymax
- });
- } else if ((self.opt.autohidemode === true) || (self.opt.autohidemode === "leave")) {
- self.autohidedom = $().add(self.rail);
- if (cap.isie8) self.autohidedom = self.autohidedom.add(self.cursor);
- if (self.railh) self.autohidedom = self.autohidedom.add(self.railh);
- if (self.railh && cap.isie8) self.autohidedom = self.autohidedom.add(self.cursorh);
- } else if (self.opt.autohidemode == "scroll") {
- self.autohidedom = $().add(self.rail);
- if (self.railh) self.autohidedom = self.autohidedom.add(self.railh);
- } else if (self.opt.autohidemode == "cursor") {
- self.autohidedom = $().add(self.cursor);
- if (self.railh) self.autohidedom = self.autohidedom.add(self.cursorh);
- } else if (self.opt.autohidemode == "hidden") {
- self.autohidedom = false;
- self.hide();
- self.railslocked = false;
- }
-
- if (cap.isie9mobile) {
-
- self.scrollmom = new ScrollMomentumClass2D(self);
-
- self.onmangotouch = function() {
- var py = self.getScrollTop();
- var px = self.getScrollLeft();
-
- if ((py == self.scrollmom.lastscrolly) && (px == self.scrollmom.lastscrollx)) return true;
-
- var dfy = py - self.mangotouch.sy;
- var dfx = px - self.mangotouch.sx;
- var df = Math.round(Math.sqrt(Math.pow(dfx, 2) + Math.pow(dfy, 2)));
- if (df == 0) return;
-
- var dry = (dfy < 0) ? -1 : 1;
- var drx = (dfx < 0) ? -1 : 1;
-
- var tm = +new Date();
- if (self.mangotouch.lazy) clearTimeout(self.mangotouch.lazy);
-
- if (((tm - self.mangotouch.tm) > 80) || (self.mangotouch.dry != dry) || (self.mangotouch.drx != drx)) {
- self.scrollmom.stop();
- self.scrollmom.reset(px, py);
- self.mangotouch.sy = py;
- self.mangotouch.ly = py;
- self.mangotouch.sx = px;
- self.mangotouch.lx = px;
- self.mangotouch.dry = dry;
- self.mangotouch.drx = drx;
- self.mangotouch.tm = tm;
- } else {
-
- self.scrollmom.stop();
- self.scrollmom.update(self.mangotouch.sx - dfx, self.mangotouch.sy - dfy);
- self.mangotouch.tm = tm;
-
- var ds = Math.max(Math.abs(self.mangotouch.ly - py), Math.abs(self.mangotouch.lx - px));
- self.mangotouch.ly = py;
- self.mangotouch.lx = px;
-
- if (ds > 2) {
- self.mangotouch.lazy = setTimeout(function() {
- self.mangotouch.lazy = false;
- self.mangotouch.dry = 0;
- self.mangotouch.drx = 0;
- self.mangotouch.tm = 0;
- self.scrollmom.doMomentum(30);
- }, 100);
- }
- }
- };
-
- var top = self.getScrollTop();
- var lef = self.getScrollLeft();
- self.mangotouch = {
- sy: top,
- ly: top,
- dry: 0,
- sx: lef,
- lx: lef,
- drx: 0,
- lazy: false,
- tm: 0
- };
-
- self.bind(self.docscroll, "scroll", self.onmangotouch);
-
- } else {
-
- if (cap.cantouch || self.istouchcapable || self.opt.touchbehavior || cap.hasmstouch) {
-
- self.scrollmom = new ScrollMomentumClass2D(self);
-
- self.ontouchstart = function(e) {
- if (e.pointerType && e.pointerType != 2 && e.pointerType != "touch") return false;
-
- self.hasmoving = false;
-
- if (!self.railslocked) {
-
- var tg;
- if (cap.hasmstouch) {
- tg = (e.target) ? e.target : false;
- while (tg) {
- var nc = $(tg).getNiceScroll();
- if ((nc.length > 0) && (nc[0].me == self.me)) break;
- if (nc.length > 0) return false;
- if ((tg.nodeName == 'DIV') && (tg.id == self.id)) break;
- tg = (tg.parentNode) ? tg.parentNode : false;
- }
- }
-
- self.cancelScroll();
-
- tg = self.getTarget(e);
-
- if (tg) {
- var skp = (/INPUT/i.test(tg.nodeName)) && (/range/i.test(tg.type));
- if (skp) return self.stopPropagation(e);
- }
-
- if (!("clientX" in e) && ("changedTouches" in e)) {
- e.clientX = e.changedTouches[0].clientX;
- e.clientY = e.changedTouches[0].clientY;
- }
-
- if (self.forcescreen) {
- var le = e;
- e = {
- "original": (e.original) ? e.original : e
- };
- e.clientX = le.screenX;
- e.clientY = le.screenY;
- }
-
- self.rail.drag = {
- x: e.clientX,
- y: e.clientY,
- sx: self.scroll.x,
- sy: self.scroll.y,
- st: self.getScrollTop(),
- sl: self.getScrollLeft(),
- pt: 2,
- dl: false
- };
-
- if (self.ispage || !self.opt.directionlockdeadzone) {
- self.rail.drag.dl = "f";
- } else {
-
- var view = {
- w: $(window).width(),
- h: $(window).height()
- };
-
- var page = {
- w: Math.max(document.body.scrollWidth, document.documentElement.scrollWidth),
- h: Math.max(document.body.scrollHeight, document.documentElement.scrollHeight)
- };
-
- var maxh = Math.max(0, page.h - view.h);
- var maxw = Math.max(0, page.w - view.w);
-
- if (!self.rail.scrollable && self.railh.scrollable) self.rail.drag.ck = (maxh > 0) ? "v" : false;
- else if (self.rail.scrollable && !self.railh.scrollable) self.rail.drag.ck = (maxw > 0) ? "h" : false;
- else self.rail.drag.ck = false;
- if (!self.rail.drag.ck) self.rail.drag.dl = "f";
- }
-
- if (self.opt.touchbehavior && self.isiframe && cap.isie) {
- var wp = self.win.position();
- self.rail.drag.x += wp.left;
- self.rail.drag.y += wp.top;
- }
-
- self.hasmoving = false;
- self.lastmouseup = false;
- self.scrollmom.reset(e.clientX, e.clientY);
-
- if (!cap.cantouch && !this.istouchcapable && !e.pointerType) {
-
- var ip = (tg) ? /INPUT|SELECT|TEXTAREA/i.test(tg.nodeName) : false;
- if (!ip) {
- if (!self.ispage && cap.hasmousecapture) tg.setCapture();
- if (self.opt.touchbehavior) {
- if (tg.onclick && !(tg._onclick || false)) { // intercept DOM0 onclick event
- tg._onclick = tg.onclick;
- tg.onclick = function(e) {
- if (self.hasmoving) return false;
- tg._onclick.call(this, e);
- };
- }
- return self.cancelEvent(e);
- }
- return self.stopPropagation(e);
- }
-
- if (/SUBMIT|CANCEL|BUTTON/i.test($(tg).attr('type'))) {
- pc = {
- "tg": tg,
- "click": false
- };
- self.preventclick = pc;
- }
-
- }
- }
-
- };
-
- self.ontouchend = function(e) {
- if (!self.rail.drag) return true;
- if (self.rail.drag.pt == 2) {
- if (e.pointerType && e.pointerType != 2 && e.pointerType != "touch") return false;
- self.scrollmom.doMomentum();
- self.rail.drag = false;
- if (self.hasmoving) {
- self.lastmouseup = true;
- self.hideCursor();
- if (cap.hasmousecapture) document.releaseCapture();
- if (!cap.cantouch) return self.cancelEvent(e);
- }
- }
- else if (self.rail.drag.pt == 1) {
- return self.onmouseup(e);
- }
-
- };
-
- var moveneedoffset = (self.opt.touchbehavior && self.isiframe && !cap.hasmousecapture);
-
- self.ontouchmove = function(e, byiframe) {
-
- if (!self.rail.drag) return false;
-
- if (e.targetTouches && self.opt.preventmultitouchscrolling) {
- if (e.targetTouches.length > 1) return false; // multitouch
- }
-
- if (e.pointerType && e.pointerType != 2 && e.pointerType != "touch") return false;
-
- if (self.rail.drag.pt == 2) {
- if (cap.cantouch && (cap.isios) && (typeof e.original == "undefined")) return true; // prevent ios "ghost" events by clickable elements
-
- self.hasmoving = true;
-
- if (self.preventclick && !self.preventclick.click) {
- self.preventclick.click = self.preventclick.tg.onclick || false;
- self.preventclick.tg.onclick = self.onpreventclick;
- }
-
- var ev = $.extend({
- "original": e
- }, e);
- e = ev;
-
- if (("changedTouches" in e)) {
- e.clientX = e.changedTouches[0].clientX;
- e.clientY = e.changedTouches[0].clientY;
- }
-
- if (self.forcescreen) {
- var le = e;
- e = {
- "original": (e.original) ? e.original : e
- };
- e.clientX = le.screenX;
- e.clientY = le.screenY;
- }
-
- var ofy,ofx;
- ofx = ofy = 0;
-
- if (moveneedoffset && !byiframe) {
- var wp = self.win.position();
- ofx = -wp.left;
- ofy = -wp.top;
- }
-
- var fy = e.clientY + ofy;
- var my = (fy - self.rail.drag.y);
- var fx = e.clientX + ofx;
- var mx = (fx - self.rail.drag.x);
-
- var ny = self.rail.drag.st - my;
-
- if (self.ishwscroll && self.opt.bouncescroll) {
- if (ny < 0) {
- ny = Math.round(ny / 2);
- // fy = 0;
- } else if (ny > self.page.maxh) {
- ny = self.page.maxh + Math.round((ny - self.page.maxh) / 2);
- // fy = 0;
- }
- } else {
- if (ny < 0) {
- ny = 0;
- fy = 0;
- }
- if (ny > self.page.maxh) {
- ny = self.page.maxh;
- fy = 0;
- }
- }
-
- var nx;
- if (self.railh && self.railh.scrollable) {
- nx = (self.isrtlmode) ? mx - self.rail.drag.sl : self.rail.drag.sl - mx;
-
- if (self.ishwscroll && self.opt.bouncescroll) {
- if (nx < 0) {
- nx = Math.round(nx / 2);
- // fx = 0;
- } else if (nx > self.page.maxw) {
- nx = self.page.maxw + Math.round((nx - self.page.maxw) / 2);
- // fx = 0;
- }
- } else {
- if (nx < 0) {
- nx = 0;
- fx = 0;
- }
- if (nx > self.page.maxw) {
- nx = self.page.maxw;
- fx = 0;
- }
- }
-
- }
-
- var grabbed = false;
- if (self.rail.drag.dl) {
- grabbed = true;
- if (self.rail.drag.dl == "v") nx = self.rail.drag.sl;
- else if (self.rail.drag.dl == "h") ny = self.rail.drag.st;
- } else {
- var ay = Math.abs(my);
- var ax = Math.abs(mx);
- var dz = self.opt.directionlockdeadzone;
- if (self.rail.drag.ck == "v") {
- if (ay > dz && (ax <= (ay * 0.3))) {
- self.rail.drag = false;
- return true;
- } else if (ax > dz) {
- self.rail.drag.dl = "f";
- $("body").scrollTop($("body").scrollTop()); // stop iOS native scrolling (when active javascript has blocked)
- }
- } else if (self.rail.drag.ck == "h") {
- if (ax > dz && (ay <= (ax * 0.3))) {
- self.rail.drag = false;
- return true;
- } else if (ay > dz) {
- self.rail.drag.dl = "f";
- $("body").scrollLeft($("body").scrollLeft()); // stop iOS native scrolling (when active javascript has blocked)
- }
- }
- }
-
- self.synched("touchmove", function() {
- if (self.rail.drag && (self.rail.drag.pt == 2)) {
- if (self.prepareTransition) self.prepareTransition(0);
- if (self.rail.scrollable) self.setScrollTop(ny);
- self.scrollmom.update(fx, fy);
- if (self.railh && self.railh.scrollable) {
- self.setScrollLeft(nx);
- self.showCursor(ny, nx);
- } else {
- self.showCursor(ny);
- }
- if (cap.isie10) document.selection.clear();
- }
- });
-
- if (cap.ischrome && self.istouchcapable) grabbed = false; //chrome touch emulation doesn't like!
- if (grabbed) return self.cancelEvent(e);
- }
- else if (self.rail.drag.pt == 1) { // drag on cursor
- return self.onmousemove(e);
- }
-
- };
-
- }
-
- self.onmousedown = function(e, hronly) {
- if (self.rail.drag && self.rail.drag.pt != 1) return;
- if (self.railslocked) return self.cancelEvent(e);
- self.cancelScroll();
- self.rail.drag = {
- x: e.clientX,
- y: e.clientY,
- sx: self.scroll.x,
- sy: self.scroll.y,
- pt: 1,
- hr: (!!hronly)
- };
- var tg = self.getTarget(e);
- if (!self.ispage && cap.hasmousecapture) tg.setCapture();
- if (self.isiframe && !cap.hasmousecapture) {
- self.saved.csspointerevents = self.doc.css("pointer-events");
- self.css(self.doc, {
- "pointer-events": "none"
- });
- }
- self.hasmoving = false;
- return self.cancelEvent(e);
- };
-
- self.onmouseup = function(e) {
- if (self.rail.drag) {
- if (self.rail.drag.pt != 1) return true;
- if (cap.hasmousecapture) document.releaseCapture();
- if (self.isiframe && !cap.hasmousecapture) self.doc.css("pointer-events", self.saved.csspointerevents);
- self.rail.drag = false;
- //if (!self.rail.active) self.hideCursor();
- if (self.hasmoving) self.triggerScrollEnd(); // TODO - check &&!self.scrollrunning
- return self.cancelEvent(e);
- }
- };
-
- self.onmousemove = function(e) {
- if (self.rail.drag) {
- if (self.rail.drag.pt != 1) return;
-
- if (cap.ischrome && e.which == 0) return self.onmouseup(e);
-
- self.cursorfreezed = true;
- self.hasmoving = true;
-
- if (self.rail.drag.hr) {
- self.scroll.x = self.rail.drag.sx + (e.clientX - self.rail.drag.x);
- if (self.scroll.x < 0) self.scroll.x = 0;
- var mw = self.scrollvaluemaxw;
- if (self.scroll.x > mw) self.scroll.x = mw;
- } else {
- self.scroll.y = self.rail.drag.sy + (e.clientY - self.rail.drag.y);
- if (self.scroll.y < 0) self.scroll.y = 0;
- var my = self.scrollvaluemax;
- if (self.scroll.y > my) self.scroll.y = my;
- }
-
- self.synched('mousemove', function() {
- if (self.rail.drag && (self.rail.drag.pt == 1)) {
- self.showCursor();
- if (self.rail.drag.hr) {
- if (self.hasreversehr) {
- self.doScrollLeft(self.scrollvaluemaxw-Math.round(self.scroll.x * self.scrollratio.x), self.opt.cursordragspeed);
- } else {
- self.doScrollLeft(Math.round(self.scroll.x * self.scrollratio.x), self.opt.cursordragspeed);
- }
- }
- else self.doScrollTop(Math.round(self.scroll.y * self.scrollratio.y), self.opt.cursordragspeed);
- }
- });
-
- return self.cancelEvent(e);
- }
- /*
- else {
- self.checkarea = true;
- }
-*/
- };
-
- if (cap.cantouch || self.opt.touchbehavior) {
-
- self.onpreventclick = function(e) {
- if (self.preventclick) {
- self.preventclick.tg.onclick = self.preventclick.click;
- self.preventclick = false;
- return self.cancelEvent(e);
- }
- }
-
- self.bind(self.win, "mousedown", self.ontouchstart); // control content dragging
-
- self.onclick = (cap.isios) ? false : function(e) {
- if (self.lastmouseup) {
- self.lastmouseup = false;
- return self.cancelEvent(e);
- } else {
- return true;
- }
- };
-
- if (self.opt.grabcursorenabled && cap.cursorgrabvalue) {
- self.css((self.ispage) ? self.doc : self.win, {
- 'cursor': cap.cursorgrabvalue
- });
- self.css(self.rail, {
- 'cursor': cap.cursorgrabvalue
- });
- }
-
- } else {
-
- var checkSelectionScroll = function(e) {
- if (!self.selectiondrag) return;
-
- if (e) {
- var ww = self.win.outerHeight();
- var df = (e.pageY - self.selectiondrag.top);
- if (df > 0 && df < ww) df = 0;
- if (df >= ww) df -= ww;
- self.selectiondrag.df = df;
- }
- if (self.selectiondrag.df == 0) return;
-
- var rt = -Math.floor(self.selectiondrag.df / 6) * 2;
- self.doScrollBy(rt);
-
- self.debounced("doselectionscroll", function() {
- checkSelectionScroll()
- }, 50);
- };
-
- if ("getSelection" in document) { // A grade - Major browsers
- self.hasTextSelected = function() {
- return (document.getSelection().rangeCount > 0);
- };
- } else if ("selection" in document) { //IE9-
- self.hasTextSelected = function() {
- return (document.selection.type != "None");
- };
- } else {
- self.hasTextSelected = function() { // no support
- return false;
- };
- }
-
- self.onselectionstart = function(e) {
-/* More testing - severe chrome issues
- if (!self.haswrapper&&(e.which&&e.which==2)) { // fool browser to manage middle button scrolling
- self.win.css({'overflow':'auto'});
- setTimeout(function(){
- self.win.css({'overflow':''});
- },10);
- return true;
- }
-*/
- if (self.ispage) return;
- self.selectiondrag = self.win.offset();
- };
-
- self.onselectionend = function(e) {
- self.selectiondrag = false;
- };
- self.onselectiondrag = function(e) {
- if (!self.selectiondrag) return;
- if (self.hasTextSelected()) self.debounced("selectionscroll", function() {
- checkSelectionScroll(e)
- }, 250);
- };
-
-
- }
-
- if (cap.hasw3ctouch) { //IE11+
- self.css(self.rail, {
- 'touch-action': 'none'
- });
- self.css(self.cursor, {
- 'touch-action': 'none'
- });
- self.bind(self.win, "pointerdown", self.ontouchstart);
- self.bind(document, "pointerup", self.ontouchend);
- self.bind(document, "pointermove", self.ontouchmove);
- } else if (cap.hasmstouch) { //IE10
- self.css(self.rail, {
- '-ms-touch-action': 'none'
- });
- self.css(self.cursor, {
- '-ms-touch-action': 'none'
- });
- self.bind(self.win, "MSPointerDown", self.ontouchstart);
- self.bind(document, "MSPointerUp", self.ontouchend);
- self.bind(document, "MSPointerMove", self.ontouchmove);
- self.bind(self.cursor, "MSGestureHold", function(e) {
- e.preventDefault()
- });
- self.bind(self.cursor, "contextmenu", function(e) {
- e.preventDefault()
- });
- } else if (this.istouchcapable) { //desktop with screen touch enabled
- self.bind(self.win, "touchstart", self.ontouchstart);
- self.bind(document, "touchend", self.ontouchend);
- self.bind(document, "touchcancel", self.ontouchend);
- self.bind(document, "touchmove", self.ontouchmove);
- }
-
-
- if (self.opt.cursordragontouch || (!cap.cantouch && !self.opt.touchbehavior)) {
-
- self.rail.css({
- "cursor": "default"
- });
- self.railh && self.railh.css({
- "cursor": "default"
- });
-
- self.jqbind(self.rail, "mouseenter", function() {
- if (!self.ispage && !self.win.is(":visible")) return false;
- if (self.canshowonmouseevent) self.showCursor();
- self.rail.active = true;
- });
- self.jqbind(self.rail, "mouseleave", function() {
- self.rail.active = false;
- if (!self.rail.drag) self.hideCursor();
- });
-
- if (self.opt.sensitiverail) {
- self.bind(self.rail, "click", function(e) {
- self.doRailClick(e, false, false)
- });
- self.bind(self.rail, "dblclick", function(e) {
- self.doRailClick(e, true, false)
- });
- self.bind(self.cursor, "click", function(e) {
- self.cancelEvent(e)
- });
- self.bind(self.cursor, "dblclick", function(e) {
- self.cancelEvent(e)
- });
- }
-
- if (self.railh) {
- self.jqbind(self.railh, "mouseenter", function() {
- if (!self.ispage && !self.win.is(":visible")) return false;
- if (self.canshowonmouseevent) self.showCursor();
- self.rail.active = true;
- });
- self.jqbind(self.railh, "mouseleave", function() {
- self.rail.active = false;
- if (!self.rail.drag) self.hideCursor();
- });
-
- if (self.opt.sensitiverail) {
- self.bind(self.railh, "click", function(e) {
- self.doRailClick(e, false, true)
- });
- self.bind(self.railh, "dblclick", function(e) {
- self.doRailClick(e, true, true)
- });
- self.bind(self.cursorh, "click", function(e) {
- self.cancelEvent(e)
- });
- self.bind(self.cursorh, "dblclick", function(e) {
- self.cancelEvent(e)
- });
- }
-
- }
-
- }
-
- if (!cap.cantouch && !self.opt.touchbehavior) {
-
- self.bind((cap.hasmousecapture) ? self.win : document, "mouseup", self.onmouseup);
- self.bind(document, "mousemove", self.onmousemove);
- if (self.onclick) self.bind(document, "click", self.onclick);
-
- self.bind(self.cursor, "mousedown", self.onmousedown);
- self.bind(self.cursor, "mouseup", self.onmouseup);
-
- if (self.railh) {
- self.bind(self.cursorh, "mousedown", function(e) {
- self.onmousedown(e, true)
- });
- self.bind(self.cursorh, "mouseup", self.onmouseup);
- }
-
- if (!self.ispage && self.opt.enablescrollonselection) {
- self.bind(self.win[0], "mousedown", self.onselectionstart);
- self.bind(document, "mouseup", self.onselectionend);
- self.bind(self.cursor, "mouseup", self.onselectionend);
- if (self.cursorh) self.bind(self.cursorh, "mouseup", self.onselectionend);
- self.bind(document, "mousemove", self.onselectiondrag);
- }
-
- if (self.zoom) {
- self.jqbind(self.zoom, "mouseenter", function() {
- if (self.canshowonmouseevent) self.showCursor();
- self.rail.active = true;
- });
- self.jqbind(self.zoom, "mouseleave", function() {
- self.rail.active = false;
- if (!self.rail.drag) self.hideCursor();
- });
- }
-
- } else {
-
- self.bind((cap.hasmousecapture) ? self.win : document, "mouseup", self.ontouchend);
- self.bind(document, "mousemove", self.ontouchmove);
- if (self.onclick) self.bind(document, "click", self.onclick);
-
- if (self.opt.cursordragontouch) {
- self.bind(self.cursor, "mousedown", self.onmousedown);
- self.bind(self.cursor, "mouseup", self.onmouseup);
- //self.bind(self.cursor, "mousemove", self.onmousemove);
- self.cursorh && self.bind(self.cursorh, "mousedown", function(e) {
- self.onmousedown(e, true)
- });
- //self.cursorh && self.bind(self.cursorh, "mousemove", self.onmousemove);
- self.cursorh && self.bind(self.cursorh, "mouseup", self.onmouseup);
- }
-
- }
-
- if (self.opt.enablemousewheel) {
- if (!self.isiframe) self.bind((cap.isie && self.ispage) ? document : self.win /*self.docscroll*/ , "mousewheel", self.onmousewheel);
- self.bind(self.rail, "mousewheel", self.onmousewheel);
- if (self.railh) self.bind(self.railh, "mousewheel", self.onmousewheelhr);
- }
-
- if (!self.ispage && !cap.cantouch && !(/HTML|^BODY/.test(self.win[0].nodeName))) {
- if (!self.win.attr("tabindex")) self.win.attr({
- "tabindex": tabindexcounter++
- });
-
- self.jqbind(self.win, "focus", function(e) {
- domfocus = (self.getTarget(e)).id || true;
- self.hasfocus = true;
- if (self.canshowonmouseevent) self.noticeCursor();
- });
- self.jqbind(self.win, "blur", function(e) {
- domfocus = false;
- self.hasfocus = false;
- });
-
- self.jqbind(self.win, "mouseenter", function(e) {
- mousefocus = (self.getTarget(e)).id || true;
- self.hasmousefocus = true;
- if (self.canshowonmouseevent) self.noticeCursor();
- });
- self.jqbind(self.win, "mouseleave", function() {
- mousefocus = false;
- self.hasmousefocus = false;
- if (!self.rail.drag) self.hideCursor();
- });
-
- }
-
- } // !ie9mobile
-
- //Thanks to http://www.quirksmode.org !!
- self.onkeypress = function(e) {
- if (self.railslocked && self.page.maxh == 0) return true;
-
- e = (e) ? e : window.e;
- var tg = self.getTarget(e);
- if (tg && /INPUT|TEXTAREA|SELECT|OPTION/.test(tg.nodeName)) {
- var tp = tg.getAttribute('type') || tg.type || false;
- if ((!tp) || !(/submit|button|cancel/i.tp)) return true;
- }
-
- if ($(tg).attr('contenteditable')) return true;
-
- if (self.hasfocus || (self.hasmousefocus && !domfocus) || (self.ispage && !domfocus && !mousefocus)) {
- var key = e.keyCode;
-
- if (self.railslocked && key != 27) return self.cancelEvent(e);
-
- var ctrl = e.ctrlKey || false;
- var shift = e.shiftKey || false;
-
- var ret = false;
- switch (key) {
- case 38:
- case 63233: //safari
- self.doScrollBy(24 * 3);
- ret = true;
- break;
- case 40:
- case 63235: //safari
- self.doScrollBy(-24 * 3);
- ret = true;
- break;
- case 37:
- case 63232: //safari
- if (self.railh) {
- (ctrl) ? self.doScrollLeft(0): self.doScrollLeftBy(24 * 3);
- ret = true;
- }
- break;
- case 39:
- case 63234: //safari
- if (self.railh) {
- (ctrl) ? self.doScrollLeft(self.page.maxw): self.doScrollLeftBy(-24 * 3);
- ret = true;
- }
- break;
- case 33:
- case 63276: // safari
- self.doScrollBy(self.view.h);
- ret = true;
- break;
- case 34:
- case 63277: // safari
- self.doScrollBy(-self.view.h);
- ret = true;
- break;
- case 36:
- case 63273: // safari
- (self.railh && ctrl) ? self.doScrollPos(0, 0): self.doScrollTo(0);
- ret = true;
- break;
- case 35:
- case 63275: // safari
- (self.railh && ctrl) ? self.doScrollPos(self.page.maxw, self.page.maxh): self.doScrollTo(self.page.maxh);
- ret = true;
- break;
- case 32:
- if (self.opt.spacebarenabled) {
- (shift) ? self.doScrollBy(self.view.h): self.doScrollBy(-self.view.h);
- ret = true;
- }
- break;
- case 27: // ESC
- if (self.zoomactive) {
- self.doZoom();
- ret = true;
- }
- break;
- }
- if (ret) return self.cancelEvent(e);
- }
- };
-
- if (self.opt.enablekeyboard) self.bind(document, (cap.isopera && !cap.isopera12) ? "keypress" : "keydown", self.onkeypress);
-
- self.bind(document, "keydown", function(e) {
- var ctrl = e.ctrlKey || false;
- if (ctrl) self.wheelprevented = true;
- });
- self.bind(document, "keyup", function(e) {
- var ctrl = e.ctrlKey || false;
- if (!ctrl) self.wheelprevented = false;
- });
- self.bind(window,"blur",function(e){
- self.wheelprevented = false;
- });
-
- self.bind(window, 'resize', self.lazyResize);
- self.bind(window, 'orientationchange', self.lazyResize);
-
- self.bind(window, "load", self.lazyResize);
-
- if (cap.ischrome && !self.ispage && !self.haswrapper) { //chrome void scrollbar bug - it persists in version 26
- var tmp = self.win.attr("style");
- var ww = parseFloat(self.win.css("width")) + 1;
- self.win.css('width', ww);
- self.synched("chromefix", function() {
- self.win.attr("style", tmp)
- });
- }
-
-
- // Trying a cross-browser implementation - good luck!
-
- self.onAttributeChange = function(e) {
- self.lazyResize(self.isieold ? 250 : 30);
- };
-
- if (ClsMutationObserver !== false) {
- self.observerbody = new ClsMutationObserver(function(mutations) {
- mutations.forEach(function(mut){
- if (mut.type=="attributes") {
- return ($("body").hasClass("modal-open")) ? self.hide() : self.show(); // Support for Bootstrap modal
- }
- });
- if (document.body.scrollHeight!=self.page.maxh) return self.lazyResize(30);
- });
- self.observerbody.observe(document.body, {
- childList: true,
- subtree: true,
- characterData: false,
- attributes: true,
- attributeFilter: ['class']
- });
- }
-
- if (!self.ispage && !self.haswrapper) {
- // redesigned MutationObserver for Chrome18+/Firefox14+/iOS6+ with support for: remove div, add/remove content
- if (ClsMutationObserver !== false) {
- self.observer = new ClsMutationObserver(function(mutations) {
- mutations.forEach(self.onAttributeChange);
- });
- self.observer.observe(self.win[0], {
- childList: true,
- characterData: false,
- attributes: true,
- subtree: false
- });
- self.observerremover = new ClsMutationObserver(function(mutations) {
- mutations.forEach(function(mo) {
- if (mo.removedNodes.length > 0) {
- for (var dd in mo.removedNodes) {
- if (!!self && (mo.removedNodes[dd] == self.win[0])) return self.remove();
- }
- }
- });
- });
- self.observerremover.observe(self.win[0].parentNode, {
- childList: true,
- characterData: false,
- attributes: false,
- subtree: false
- });
- } else {
- self.bind(self.win, (cap.isie && !cap.isie9) ? "propertychange" : "DOMAttrModified", self.onAttributeChange);
- if (cap.isie9) self.win[0].attachEvent("onpropertychange", self.onAttributeChange); //IE9 DOMAttrModified bug
- self.bind(self.win, "DOMNodeRemoved", function(e) {
- if (e.target == self.win[0]) self.remove();
- });
- }
- }
-
- //
-
- if (!self.ispage && self.opt.boxzoom) self.bind(window, "resize", self.resizeZoom);
- if (self.istextarea) self.bind(self.win, "mouseup", self.lazyResize);
-
- // self.checkrtlmode = true;
- self.lazyResize(30);
-
- }
-
- if (this.doc[0].nodeName == 'IFRAME') {
- var oniframeload = function() {
- self.iframexd = false;
- var doc;
- try {
- doc = 'contentDocument' in this ? this.contentDocument : this.contentWindow.document;
- var a = doc.domain;
- } catch (e) {
- self.iframexd = true;
- doc = false
- }
-
- if (self.iframexd) {
- if ("console" in window) console.log('NiceScroll error: policy restriced iframe');
- return true; //cross-domain - I can't manage this
- }
-
- self.forcescreen = true;
-
- if (self.isiframe) {
- self.iframe = {
- "doc": $(doc),
- "html": self.doc.contents().find('html')[0],
- "body": self.doc.contents().find('body')[0]
- };
- self.getContentSize = function() {
- return {
- w: Math.max(self.iframe.html.scrollWidth, self.iframe.body.scrollWidth),
- h: Math.max(self.iframe.html.scrollHeight, self.iframe.body.scrollHeight)
- };
- };
- self.docscroll = $(self.iframe.body); //$(this.contentWindow);
- }
-
- if (!cap.isios && self.opt.iframeautoresize && !self.isiframe) {
- self.win.scrollTop(0); // reset position
- self.doc.height(""); //reset height to fix browser bug
- var hh = Math.max(doc.getElementsByTagName('html')[0].scrollHeight, doc.body.scrollHeight);
- self.doc.height(hh);
- }
- self.lazyResize(30);
-
- if (cap.isie7) self.css($(self.iframe.html), {
- 'overflow-y': 'hidden'
- });
- self.css($(self.iframe.body), {
- 'overflow-y': 'hidden'
- });
-
- if (cap.isios && self.haswrapper) {
- self.css($(doc.body), {
- '-webkit-transform': 'translate3d(0,0,0)'
- }); // avoid iFrame content clipping - thanks to http://blog.derraab.com/2012/04/02/avoid-iframe-content-clipping-with-css-transform-on-ios/
- }
-
- if ('contentWindow' in this) {
- self.bind(this.contentWindow, "scroll", self.onscroll); //IE8 & minor
- } else {
- self.bind(doc, "scroll", self.onscroll);
- }
-
- if (self.opt.enablemousewheel) {
- self.bind(doc, "mousewheel", self.onmousewheel);
- }
-
- if (self.opt.enablekeyboard) self.bind(doc, (cap.isopera) ? "keypress" : "keydown", self.onkeypress);
-
- if (cap.cantouch || self.opt.touchbehavior) {
- self.bind(doc, "mousedown", self.ontouchstart);
- self.bind(doc, "mousemove", function(e) {
- return self.ontouchmove(e, true)
- });
- if (self.opt.grabcursorenabled && cap.cursorgrabvalue) self.css($(doc.body), {
- 'cursor': cap.cursorgrabvalue
- });
- }
-
- self.bind(doc, "mouseup", self.ontouchend);
-
- if (self.zoom) {
- if (self.opt.dblclickzoom) self.bind(doc, 'dblclick', self.doZoom);
- if (self.ongesturezoom) self.bind(doc, "gestureend", self.ongesturezoom);
- }
- };
-
- if (this.doc[0].readyState && this.doc[0].readyState == "complete") {
- setTimeout(function() {
- oniframeload.call(self.doc[0], false)
- }, 500);
- }
- self.bind(this.doc, "load", oniframeload);
-
- }
-
- };
-
- this.showCursor = function(py, px) {
- if (self.cursortimeout) {
- clearTimeout(self.cursortimeout);
- self.cursortimeout = 0;
- }
- if (!self.rail) return;
- if (self.autohidedom) {
- self.autohidedom.stop().css({
- opacity: self.opt.cursoropacitymax
- });
- self.cursoractive = true;
- }
-
- if (!self.rail.drag || self.rail.drag.pt != 1) {
- if ((typeof py != "undefined") && (py !== false)) {
- self.scroll.y = Math.round(py * 1 / self.scrollratio.y);
- }
- if (typeof px != "undefined") {
- self.scroll.x = Math.round(px * 1 / self.scrollratio.x);
- }
- }
-
- self.cursor.css({
- height: self.cursorheight,
- top: self.scroll.y
- });
- if (self.cursorh) {
- var lx = (self.hasreversehr) ? self.scrollvaluemaxw-self.scroll.x : self.scroll.x;
- (!self.rail.align && self.rail.visibility) ? self.cursorh.css({
- width: self.cursorwidth,
- left: lx + self.rail.width
- }): self.cursorh.css({
- width: self.cursorwidth,
- left: lx
- });
- self.cursoractive = true;
- }
-
- if (self.zoom) self.zoom.stop().css({
- opacity: self.opt.cursoropacitymax
- });
- };
-
- this.hideCursor = function(tm) {
- if (self.cursortimeout) return;
- if (!self.rail) return;
- if (!self.autohidedom) return;
- if (self.hasmousefocus && self.opt.autohidemode == "leave") return;
- self.cursortimeout = setTimeout(function() {
- if (!self.rail.active || !self.showonmouseevent) {
- self.autohidedom.stop().animate({
- opacity: self.opt.cursoropacitymin
- });
- if (self.zoom) self.zoom.stop().animate({
- opacity: self.opt.cursoropacitymin
- });
- self.cursoractive = false;
- }
- self.cursortimeout = 0;
- }, tm || self.opt.hidecursordelay);
- };
-
- this.noticeCursor = function(tm, py, px) {
- self.showCursor(py, px);
- if (!self.rail.active) self.hideCursor(tm);
- };
-
- this.getContentSize =
- (self.ispage) ?
- function() {
- return {
- w: Math.max(document.body.scrollWidth, document.documentElement.scrollWidth),
- h: Math.max(document.body.scrollHeight, document.documentElement.scrollHeight)
- }
- } : (self.haswrapper) ?
- function() {
- return {
- w: self.doc.outerWidth() + parseInt(self.win.css('paddingLeft')) + parseInt(self.win.css('paddingRight')),
- h: self.doc.outerHeight() + parseInt(self.win.css('paddingTop')) + parseInt(self.win.css('paddingBottom'))
- }
- } : function() {
- return {
- w: self.docscroll[0].scrollWidth,
- h: self.docscroll[0].scrollHeight
- }
- };
-
- this.onResize = function(e, page) {
-
- if (!self || !self.win) return false;
-
- if (!self.haswrapper && !self.ispage) {
- if (self.win.css('display') == 'none') {
- if (self.visibility) self.hideRail().hideRailHr();
- return false;
- } else {
- if (!self.hidden && !self.visibility) self.showRail().showRailHr();
- }
- }
-
- var premaxh = self.page.maxh;
- var premaxw = self.page.maxw;
-
- var preview = {
- h: self.view.h,
- w: self.view.w
- };
-
- self.view = {
- w: (self.ispage) ? self.win.width() : parseInt(self.win[0].clientWidth),
- h: (self.ispage) ? self.win.height() : parseInt(self.win[0].clientHeight)
- };
-
- self.page = (page) ? page : self.getContentSize();
-
- self.page.maxh = Math.max(0, self.page.h - self.view.h);
- self.page.maxw = Math.max(0, self.page.w - self.view.w);
-
- if ((self.page.maxh == premaxh) && (self.page.maxw == premaxw) && (self.view.w == preview.w) && (self.view.h == preview.h)) {
- // test position
- if (!self.ispage) {
- var pos = self.win.offset();
- if (self.lastposition) {
- var lst = self.lastposition;
- if ((lst.top == pos.top) && (lst.left == pos.left)) return self; //nothing to do
- }
- self.lastposition = pos;
- } else {
- return self; //nothing to do
- }
- }
-
- if (self.page.maxh == 0) {
- self.hideRail();
- self.scrollvaluemax = 0;
- self.scroll.y = 0;
- self.scrollratio.y = 0;
- self.cursorheight = 0;
- self.setScrollTop(0);
- self.rail.scrollable = false;
- } else {
- self.page.maxh -= (self.opt.railpadding.top + self.opt.railpadding.bottom); //**
- self.rail.scrollable = true;
- }
-
- if (self.page.maxw == 0) {
- self.hideRailHr();
- self.scrollvaluemaxw = 0;
- self.scroll.x = 0;
- self.scrollratio.x = 0;
- self.cursorwidth = 0;
- self.setScrollLeft(0);
- self.railh.scrollable = false;
- } else {
- self.page.maxw -= (self.opt.railpadding.left + self.opt.railpadding.right); //**
- self.railh.scrollable = true;
- }
-
- self.railslocked = (self.locked) || ((self.page.maxh == 0) && (self.page.maxw == 0));
- if (self.railslocked) {
- if (!self.ispage) self.updateScrollBar(self.view);
- return false;
- }
-
- if (!self.hidden && !self.visibility) {
- self.showRail().showRailHr();
- }
- else if (!self.hidden && !self.railh.visibility) self.showRailHr();
-
- if (self.istextarea && self.win.css('resize') && self.win.css('resize') != 'none') self.view.h -= 20;
-
- self.cursorheight = Math.min(self.view.h, Math.round(self.view.h * (self.view.h / self.page.h)));
- self.cursorheight = (self.opt.cursorfixedheight) ? self.opt.cursorfixedheight : Math.max(self.opt.cursorminheight, self.cursorheight);
-
- self.cursorwidth = Math.min(self.view.w, Math.round(self.view.w * (self.view.w / self.page.w)));
- self.cursorwidth = (self.opt.cursorfixedheight) ? self.opt.cursorfixedheight : Math.max(self.opt.cursorminheight, self.cursorwidth);
-
- self.scrollvaluemax = self.view.h - self.cursorheight - self.cursor.hborder - (self.opt.railpadding.top + self.opt.railpadding.bottom); //**
-
- if (self.railh) {
- self.railh.width = (self.page.maxh > 0) ? (self.view.w - self.rail.width) : self.view.w;
- self.scrollvaluemaxw = self.railh.width - self.cursorwidth - self.cursorh.wborder - (self.opt.railpadding.left + self.opt.railpadding.right); //**
- }
-
- /*
- if (self.checkrtlmode&&self.railh) {
- self.checkrtlmode = false;
- if (self.opt.rtlmode&&self.scroll.x==0) self.setScrollLeft(self.page.maxw);
- }
-*/
-
- if (!self.ispage) self.updateScrollBar(self.view);
-
- self.scrollratio = {
- x: (self.page.maxw / self.scrollvaluemaxw),
- y: (self.page.maxh / self.scrollvaluemax)
- };
-
- var sy = self.getScrollTop();
- if (sy > self.page.maxh) {
- self.doScrollTop(self.page.maxh);
- } else {
- self.scroll.y = Math.round(self.getScrollTop() * (1 / self.scrollratio.y));
- self.scroll.x = Math.round(self.getScrollLeft() * (1 / self.scrollratio.x));
- if (self.cursoractive) self.noticeCursor();
- }
-
- if (self.scroll.y && (self.getScrollTop() == 0)) self.doScrollTo(Math.floor(self.scroll.y * self.scrollratio.y));
-
- return self;
- };
-
- this.resize = self.onResize;
-
- this.lazyResize = function(tm) { // event debounce
- tm = (isNaN(tm)) ? 30 : tm;
- self.debounced('resize', self.resize, tm);
- return self;
- };
-
- // modified by MDN https://developer.mozilla.org/en-US/docs/DOM/Mozilla_event_reference/wheel
- function _modernWheelEvent(dom, name, fn, bubble) {
- self._bind(dom, name, function(e) {
- var e = (e) ? e : window.event;
- var event = {
- original: e,
- target: e.target || e.srcElement,
- type: "wheel",
- deltaMode: e.type == "MozMousePixelScroll" ? 0 : 1,
- deltaX: 0,
- deltaZ: 0,
- preventDefault: function() {
- e.preventDefault ? e.preventDefault() : e.returnValue = false;
- return false;
- },
- stopImmediatePropagation: function() {
- (e.stopImmediatePropagation) ? e.stopImmediatePropagation(): e.cancelBubble = true;
- }
- };
-
- if (name == "mousewheel") {
- event.deltaY = -1 / 40 * e.wheelDelta;
- e.wheelDeltaX && (event.deltaX = -1 / 40 * e.wheelDeltaX);
- } else {
- event.deltaY = e.detail;
- }
-
- return fn.call(dom, event);
- }, bubble);
- };
-
-
-
- this.jqbind = function(dom, name, fn) { // use jquery bind for non-native events (mouseenter/mouseleave)
- self.events.push({
- e: dom,
- n: name,
- f: fn,
- q: true
- });
- $(dom).bind(name, fn);
- };
-
- this.bind = function(dom, name, fn, bubble) { // touch-oriented & fixing jquery bind
- var el = ("jquery" in dom) ? dom[0] : dom;
-
- if (name == 'mousewheel') {
- if (window.addEventListener||'onwheel' in document) { // modern brosers & IE9 detection fix
- self._bind(el, "wheel", fn, bubble || false);
- } else {
- var wname = (typeof document.onmousewheel != "undefined") ? "mousewheel" : "DOMMouseScroll"; // older IE/Firefox
- _modernWheelEvent(el, wname, fn, bubble || false);
- if (wname == "DOMMouseScroll") _modernWheelEvent(el, "MozMousePixelScroll", fn, bubble || false); // Firefox legacy
- }
- } else if (el.addEventListener) {
- if (cap.cantouch && /mouseup|mousedown|mousemove/.test(name)) { // touch device support
- var tt = (name == 'mousedown') ? 'touchstart' : (name == 'mouseup') ? 'touchend' : 'touchmove';
- self._bind(el, tt, function(e) {
- if (e.touches) {
- if (e.touches.length < 2) {
- var ev = (e.touches.length) ? e.touches[0] : e;
- ev.original = e;
- fn.call(this, ev);
- }
- } else if (e.changedTouches) {
- var ev = e.changedTouches[0];
- ev.original = e;
- fn.call(this, ev);
- } //blackberry
- }, bubble || false);
- }
- self._bind(el, name, fn, bubble || false);
- if (cap.cantouch && name == "mouseup") self._bind(el, "touchcancel", fn, bubble || false);
- } else {
- self._bind(el, name, function(e) {
- e = e || window.event || false;
- if (e) {
- if (e.srcElement) e.target = e.srcElement;
- }
- if (!("pageY" in e)) {
- e.pageX = e.clientX + document.documentElement.scrollLeft;
- e.pageY = e.clientY + document.documentElement.scrollTop;
- }
- return ((fn.call(el, e) === false) || bubble === false) ? self.cancelEvent(e) : true;
- });
- }
- };
-
- if (cap.haseventlistener) { // W3C standard model
- this._bind = function(el, name, fn, bubble) { // primitive bind
- self.events.push({
- e: el,
- n: name,
- f: fn,
- b: bubble,
- q: false
- });
- el.addEventListener(name, fn, bubble || false);
- };
- this.cancelEvent = function(e) {
- if (!e) return false;
- var e = (e.original) ? e.original : e;
- e.preventDefault();
- e.stopPropagation();
- if (e.preventManipulation) e.preventManipulation(); //IE10
- return false;
- };
- this.stopPropagation = function(e) {
- if (!e) return false;
- var e = (e.original) ? e.original : e;
- e.stopPropagation();
- return false;
- };
- this._unbind = function(el, name, fn, bub) { // primitive unbind
- el.removeEventListener(name, fn, bub);
- };
- } else { // old IE model
- this._bind = function(el, name, fn, bubble) { // primitive bind
- self.events.push({
- e: el,
- n: name,
- f: fn,
- b: bubble,
- q: false
- });
- if (el.attachEvent) {
- el.attachEvent("on" + name, fn);
- } else {
- el["on" + name] = fn;
- }
- };
- // Thanks to http://www.switchonthecode.com !!
- this.cancelEvent = function(e) {
- var e = window.event || false;
- if (!e) return false;
- e.cancelBubble = true;
- e.cancel = true;
- e.returnValue = false;
- return false;
- };
- this.stopPropagation = function(e) {
- var e = window.event || false;
- if (!e) return false;
- e.cancelBubble = true;
- return false;
- };
- this._unbind = function(el, name, fn, bub) { // primitive unbind IE old
- if (el.detachEvent) {
- el.detachEvent('on' + name, fn);
- } else {
- el['on' + name] = false;
- }
- };
- }
-
- this.unbindAll = function() {
- for (var a = 0; a < self.events.length; a++) {
- var r = self.events[a];
- (r.q) ? r.e.unbind(r.n, r.f): self._unbind(r.e, r.n, r.f, r.b);
- }
- };
-
- this.showRail = function() {
- if ((self.page.maxh != 0) && (self.ispage || self.win.css('display') != 'none')) {
- self.visibility = true;
- self.rail.visibility = true;
- self.rail.css('display', 'block');
- }
- return self;
- };
-
- this.showRailHr = function() {
- if (!self.railh) return self;
- if ((self.page.maxw != 0) && (self.ispage || self.win.css('display') != 'none')) {
- self.railh.visibility = true;
- self.railh.css('display', 'block');
- }
- return self;
- };
-
- this.hideRail = function() {
- self.visibility = false;
- self.rail.visibility = false;
- self.rail.css('display', 'none');
- return self;
- };
-
- this.hideRailHr = function() {
- if (!self.railh) return self;
- self.railh.visibility = false;
- self.railh.css('display', 'none');
- return self;
- };
-
- this.show = function() {
- self.hidden = false;
- self.railslocked = false;
- return self.showRail().showRailHr();
- };
-
- this.hide = function() {
- self.hidden = true;
- self.railslocked = true;
- return self.hideRail().hideRailHr();
- };
-
- this.toggle = function() {
- return (self.hidden) ? self.show() : self.hide();
- };
-
- this.remove = function() {
- self.stop();
- if (self.cursortimeout) clearTimeout(self.cursortimeout);
- self.doZoomOut();
- self.unbindAll();
-
- if (cap.isie9) self.win[0].detachEvent("onpropertychange", self.onAttributeChange); //IE9 DOMAttrModified bug
-
- if (self.observer !== false) self.observer.disconnect();
- if (self.observerremover !== false) self.observerremover.disconnect();
- if (self.observerbody !== false) self.observerbody.disconnect();
-
- self.events = null;
-
- if (self.cursor) {
- self.cursor.remove();
- }
- if (self.cursorh) {
- self.cursorh.remove();
- }
- if (self.rail) {
- self.rail.remove();
- }
- if (self.railh) {
- self.railh.remove();
- }
- if (self.zoom) {
- self.zoom.remove();
- }
- for (var a = 0; a < self.saved.css.length; a++) {
- var d = self.saved.css[a];
- d[0].css(d[1], (typeof d[2] == "undefined") ? '' : d[2]);
- }
- self.saved = false;
- self.me.data('__nicescroll', ''); //erase all traces
-
- // memory leak fixed by GianlucaGuarini - thanks a lot!
- // remove the current nicescroll from the $.nicescroll array & normalize array
- var lst = $.nicescroll;
- lst.each(function(i) {
- if (!this) return;
- if (this.id === self.id) {
- delete lst[i];
- for (var b = ++i; b < lst.length; b++, i++) lst[i] = lst[b];
- lst.length--;
- if (lst.length) delete lst[lst.length];
- }
- });
-
- for (var i in self) {
- self[i] = null;
- delete self[i];
- }
-
- self = null;
-
- };
-
- this.scrollstart = function(fn) {
- this.onscrollstart = fn;
- return self;
- };
- this.scrollend = function(fn) {
- this.onscrollend = fn;
- return self;
- };
- this.scrollcancel = function(fn) {
- this.onscrollcancel = fn;
- return self;
- };
-
- this.zoomin = function(fn) {
- this.onzoomin = fn;
- return self;
- };
- this.zoomout = function(fn) {
- this.onzoomout = fn;
- return self;
- };
-
- this.isScrollable = function(e) {
- var dom = (e.target) ? e.target : e;
- if (dom.nodeName == 'OPTION') return true;
- while (dom && (dom.nodeType == 1) && !(/^BODY|HTML/.test(dom.nodeName))) {
- var dd = $(dom);
- var ov = dd.css('overflowY') || dd.css('overflowX') || dd.css('overflow') || '';
- if (/scroll|auto/.test(ov)) return (dom.clientHeight != dom.scrollHeight);
- dom = (dom.parentNode) ? dom.parentNode : false;
- }
- return false;
- };
-
- this.getViewport = function(me) {
- var dom = (me && me.parentNode) ? me.parentNode : false;
- while (dom && (dom.nodeType == 1) && !(/^BODY|HTML/.test(dom.nodeName))) {
- var dd = $(dom);
- if (/fixed|absolute/.test(dd.css("position"))) return dd;
- var ov = dd.css('overflowY') || dd.css('overflowX') || dd.css('overflow') || '';
- if ((/scroll|auto/.test(ov)) && (dom.clientHeight != dom.scrollHeight)) return dd;
- if (dd.getNiceScroll().length > 0) return dd;
- dom = (dom.parentNode) ? dom.parentNode : false;
- }
- return false; //(dom) ? $(dom) : false;
- };
-
- this.triggerScrollEnd = function() {
- if (!self.onscrollend) return;
-
- var px = self.getScrollLeft();
- var py = self.getScrollTop();
-
- var info = {
- "type": "scrollend",
- "current": {
- "x": px,
- "y": py
- },
- "end": {
- "x": px,
- "y": py
- }
- };
- self.onscrollend.call(self, info);
- }
-
- function execScrollWheel(e, hr, chkscroll) {
- var px, py;
-
- if (e.deltaMode == 0) { // PIXEL
- px = -Math.floor(e.deltaX * (self.opt.mousescrollstep / (18 * 3)));
- py = -Math.floor(e.deltaY * (self.opt.mousescrollstep / (18 * 3)));
- } else if (e.deltaMode == 1) { // LINE
- px = -Math.floor(e.deltaX * self.opt.mousescrollstep);
- py = -Math.floor(e.deltaY * self.opt.mousescrollstep);
- }
-
- if (hr && self.opt.oneaxismousemode && (px == 0) && py) { // classic vertical-only mousewheel + browser with x/y support
- px = py;
- py = 0;
-
- if (chkscroll) {
- var hrend = (px < 0) ? (self.getScrollLeft() >= self.page.maxw) : (self.getScrollLeft() <= 0);
- if (hrend) { // preserve vertical scrolling
- py = px;
- px = 0;
- }
- }
-
- }
-
- if (px) {
- if (self.scrollmom) {
- self.scrollmom.stop()
- }
- self.lastdeltax += px;
- self.debounced("mousewheelx", function() {
- var dt = self.lastdeltax;
- self.lastdeltax = 0;
- if (!self.rail.drag) {
- self.doScrollLeftBy(dt)
- }
- }, 15);
- }
- if (py) {
- if (self.opt.nativeparentscrolling && chkscroll && !self.ispage && !self.zoomactive) {
- if (py < 0) {
- if (self.getScrollTop() >= self.page.maxh) return true;
- } else {
- if (self.getScrollTop() <= 0) return true;
- }
- }
- if (self.scrollmom) {
- self.scrollmom.stop()
- }
- self.lastdeltay += py;
- self.debounced("mousewheely", function() {
- var dt = self.lastdeltay;
- self.lastdeltay = 0;
- if (!self.rail.drag) {
- self.doScrollBy(dt)
- }
- }, 15);
- }
-
- e.stopImmediatePropagation();
- return e.preventDefault();
- };
-
- this.onmousewheel = function(e) {
- if (self.wheelprevented) return;
- if (self.railslocked) {
- self.debounced("checkunlock", self.resize, 250);
- return true;
- }
- if (self.rail.drag) return self.cancelEvent(e);
-
- if (self.opt.oneaxismousemode == "auto" && e.deltaX != 0) self.opt.oneaxismousemode = false; // check two-axis mouse support (not very elegant)
-
- if (self.opt.oneaxismousemode && e.deltaX == 0) {
- if (!self.rail.scrollable) {
- if (self.railh && self.railh.scrollable) {
- return self.onmousewheelhr(e);
- } else {
- return true;
- }
- }
- }
-
- var nw = +(new Date());
- var chk = false;
- if (self.opt.preservenativescrolling && ((self.checkarea + 600) < nw)) {
- self.nativescrollingarea = self.isScrollable(e);
- chk = true;
- }
- self.checkarea = nw;
- if (self.nativescrollingarea) return true; // this isn't my business
- var ret = execScrollWheel(e, false, chk);
- if (ret) self.checkarea = 0;
- return ret;
- };
-
- this.onmousewheelhr = function(e) {
- if (self.wheelprevented) return;
- if (self.railslocked || !self.railh.scrollable) return true;
- if (self.rail.drag) return self.cancelEvent(e);
-
- var nw = +(new Date());
- var chk = false;
- if (self.opt.preservenativescrolling && ((self.checkarea + 600) < nw)) {
- self.nativescrollingarea = self.isScrollable(e);
- chk = true;
- }
- self.checkarea = nw;
- if (self.nativescrollingarea) return true; // this isn't my business
- if (self.railslocked) return self.cancelEvent(e);
-
- return execScrollWheel(e, true, chk);
- };
-
- this.stop = function() {
- self.cancelScroll();
- if (self.scrollmon) self.scrollmon.stop();
- self.cursorfreezed = false;
- self.scroll.y = Math.round(self.getScrollTop() * (1 / self.scrollratio.y));
- self.noticeCursor();
- return self;
- };
-
- this.getTransitionSpeed = function(dif) {
- var sp = Math.round(self.opt.scrollspeed * 10);
- var ex = Math.min(sp, Math.round((dif / 20) * self.opt.scrollspeed));
- return (ex > 20) ? ex : 0;
- };
-
- if (!self.opt.smoothscroll) {
- this.doScrollLeft = function(x, spd) { //direct
- var y = self.getScrollTop();
- self.doScrollPos(x, y, spd);
- };
- this.doScrollTop = function(y, spd) { //direct
- var x = self.getScrollLeft();
- self.doScrollPos(x, y, spd);
- };
- this.doScrollPos = function(x, y, spd) { //direct
- var nx = (x > self.page.maxw) ? self.page.maxw : x;
- if (nx < 0) nx = 0;
- var ny = (y > self.page.maxh) ? self.page.maxh : y;
- if (ny < 0) ny = 0;
- self.synched('scroll', function() {
- self.setScrollTop(ny);
- self.setScrollLeft(nx);
- });
- };
- this.cancelScroll = function() {}; // direct
- } else if (self.ishwscroll && cap.hastransition && self.opt.usetransition && !!self.opt.smoothscroll) {
- this.prepareTransition = function(dif, istime) {
- var ex = (istime) ? ((dif > 20) ? dif : 0) : self.getTransitionSpeed(dif);
- var trans = (ex) ? cap.prefixstyle + 'transform ' + ex + 'ms ease-out' : '';
- if (!self.lasttransitionstyle || self.lasttransitionstyle != trans) {
- self.lasttransitionstyle = trans;
- self.doc.css(cap.transitionstyle, trans);
- }
- return ex;
- };
-
- this.doScrollLeft = function(x, spd) { //trans
- var y = (self.scrollrunning) ? self.newscrolly : self.getScrollTop();
- self.doScrollPos(x, y, spd);
- };
-
- this.doScrollTop = function(y, spd) { //trans
- var x = (self.scrollrunning) ? self.newscrollx : self.getScrollLeft();
- self.doScrollPos(x, y, spd);
- };
-
- this.doScrollPos = function(x, y, spd) { //trans
-
- var py = self.getScrollTop();
- var px = self.getScrollLeft();
-
- if (((self.newscrolly - py) * (y - py) < 0) || ((self.newscrollx - px) * (x - px) < 0)) self.cancelScroll(); //inverted movement detection
-
- if (self.opt.bouncescroll == false) {
- if (y < 0) y = 0;
- else if (y > self.page.maxh) y = self.page.maxh;
- if (x < 0) x = 0;
- else if (x > self.page.maxw) x = self.page.maxw;
- }
-
- if (self.scrollrunning && x == self.newscrollx && y == self.newscrolly) return false;
-
- self.newscrolly = y;
- self.newscrollx = x;
-
- self.newscrollspeed = spd || false;
-
- if (self.timer) return false;
-
- self.timer = setTimeout(function() {
-
- var top = self.getScrollTop();
- var lft = self.getScrollLeft();
-
- var dst = {};
- dst.x = x - lft;
- dst.y = y - top;
- dst.px = lft;
- dst.py = top;
-
- var dd = Math.round(Math.sqrt(Math.pow(dst.x, 2) + Math.pow(dst.y, 2)));
- var ms = (self.newscrollspeed && self.newscrollspeed > 1) ? self.newscrollspeed : self.getTransitionSpeed(dd);
- if (self.newscrollspeed && self.newscrollspeed <= 1) ms *= self.newscrollspeed;
-
- self.prepareTransition(ms, true);
-
- if (self.timerscroll && self.timerscroll.tm) clearInterval(self.timerscroll.tm);
-
- if (ms > 0) {
-
- if (!self.scrollrunning && self.onscrollstart) {
- var info = {
- "type": "scrollstart",
- "current": {
- "x": lft,
- "y": top
- },
- "request": {
- "x": x,
- "y": y
- },
- "end": {
- "x": self.newscrollx,
- "y": self.newscrolly
- },
- "speed": ms
- };
- self.onscrollstart.call(self, info);
- }
-
- if (cap.transitionend) {
- if (!self.scrollendtrapped) {
- self.scrollendtrapped = true;
- self.bind(self.doc, cap.transitionend, self.onScrollTransitionEnd, false); //I have got to do something usefull!!
- }
- } else {
- if (self.scrollendtrapped) clearTimeout(self.scrollendtrapped);
- self.scrollendtrapped = setTimeout(self.onScrollTransitionEnd, ms); // simulate transitionend event
- }
-
- var py = top;
- var px = lft;
- self.timerscroll = {
- bz: new BezierClass(py, self.newscrolly, ms, 0, 0, 0.58, 1),
- bh: new BezierClass(px, self.newscrollx, ms, 0, 0, 0.58, 1)
- };
- if (!self.cursorfreezed) self.timerscroll.tm = setInterval(function() {
- self.showCursor(self.getScrollTop(), self.getScrollLeft())
- }, 60);
-
- }
-
- self.synched("doScroll-set", function() {
- self.timer = 0;
- if (self.scrollendtrapped) self.scrollrunning = true;
- self.setScrollTop(self.newscrolly);
- self.setScrollLeft(self.newscrollx);
- if (!self.scrollendtrapped) self.onScrollTransitionEnd();
- });
-
-
- }, 50);
-
- };
-
- this.cancelScroll = function() {
- if (!self.scrollendtrapped) return true;
- var py = self.getScrollTop();
- var px = self.getScrollLeft();
- self.scrollrunning = false;
- if (!cap.transitionend) clearTimeout(cap.transitionend);
- self.scrollendtrapped = false;
- self._unbind(self.doc[0], cap.transitionend, self.onScrollTransitionEnd);
- self.prepareTransition(0);
- self.setScrollTop(py); // fire event onscroll
- if (self.railh) self.setScrollLeft(px);
- if (self.timerscroll && self.timerscroll.tm) clearInterval(self.timerscroll.tm);
- self.timerscroll = false;
-
- self.cursorfreezed = false;
-
- self.showCursor(py, px);
- return self;
- };
- this.onScrollTransitionEnd = function() {
- if (self.scrollendtrapped) self._unbind(self.doc[0], cap.transitionend, self.onScrollTransitionEnd);
- self.scrollendtrapped = false;
- self.prepareTransition(0);
- if (self.timerscroll && self.timerscroll.tm) clearInterval(self.timerscroll.tm);
- self.timerscroll = false;
- var py = self.getScrollTop();
- var px = self.getScrollLeft();
- self.setScrollTop(py); // fire event onscroll
- if (self.railh) self.setScrollLeft(px); // fire event onscroll left
-
- self.noticeCursor(false, py, px);
-
- self.cursorfreezed = false;
-
- if (py < 0) py = 0
- else if (py > self.page.maxh) py = self.page.maxh;
- if (px < 0) px = 0
- else if (px > self.page.maxw) px = self.page.maxw;
- if ((py != self.newscrolly) || (px != self.newscrollx)) return self.doScrollPos(px, py, self.opt.snapbackspeed);
-
- if (self.onscrollend && self.scrollrunning) {
- self.triggerScrollEnd();
- }
- self.scrollrunning = false;
-
- };
-
- } else {
-
- this.doScrollLeft = function(x, spd) { //no-trans
- var y = (self.scrollrunning) ? self.newscrolly : self.getScrollTop();
- self.doScrollPos(x, y, spd);
- };
-
- this.doScrollTop = function(y, spd) { //no-trans
- var x = (self.scrollrunning) ? self.newscrollx : self.getScrollLeft();
- self.doScrollPos(x, y, spd);
- };
-
- this.doScrollPos = function(x, y, spd) { //no-trans
- var y = ((typeof y == "undefined") || (y === false)) ? self.getScrollTop(true) : y;
-
- if ((self.timer) && (self.newscrolly == y) && (self.newscrollx == x)) return true;
-
- if (self.timer) clearAnimationFrame(self.timer);
- self.timer = 0;
-
- var py = self.getScrollTop();
- var px = self.getScrollLeft();
-
- if (((self.newscrolly - py) * (y - py) < 0) || ((self.newscrollx - px) * (x - px) < 0)) self.cancelScroll(); //inverted movement detection
-
- self.newscrolly = y;
- self.newscrollx = x;
-
- if (!self.bouncescroll || !self.rail.visibility) {
- if (self.newscrolly < 0) {
- self.newscrolly = 0;
- } else if (self.newscrolly > self.page.maxh) {
- self.newscrolly = self.page.maxh;
- }
- }
- if (!self.bouncescroll || !self.railh.visibility) {
- if (self.newscrollx < 0) {
- self.newscrollx = 0;
- } else if (self.newscrollx > self.page.maxw) {
- self.newscrollx = self.page.maxw;
- }
- }
-
- self.dst = {};
- self.dst.x = x - px;
- self.dst.y = y - py;
- self.dst.px = px;
- self.dst.py = py;
-
- var dst = Math.round(Math.sqrt(Math.pow(self.dst.x, 2) + Math.pow(self.dst.y, 2)));
-
- self.dst.ax = self.dst.x / dst;
- self.dst.ay = self.dst.y / dst;
-
- var pa = 0;
- var pe = dst;
-
- if (self.dst.x == 0) {
- pa = py;
- pe = y;
- self.dst.ay = 1;
- self.dst.py = 0;
- } else if (self.dst.y == 0) {
- pa = px;
- pe = x;
- self.dst.ax = 1;
- self.dst.px = 0;
- }
-
- var ms = self.getTransitionSpeed(dst);
- if (spd && spd <= 1) ms *= spd;
- if (ms > 0) {
- self.bzscroll = (self.bzscroll) ? self.bzscroll.update(pe, ms) : new BezierClass(pa, pe, ms, 0, 1, 0, 1);
- } else {
- self.bzscroll = false;
- }
-
- if (self.timer) return;
-
- if ((py == self.page.maxh && y >= self.page.maxh) || (px == self.page.maxw && x >= self.page.maxw)) self.checkContentSize();
-
- var sync = 1;
-
- function scrolling() {
- if (self.cancelAnimationFrame) return true;
-
- self.scrollrunning = true;
-
- sync = 1 - sync;
- if (sync) return (self.timer = setAnimationFrame(scrolling) || 1);
-
- var done = 0;
- var sx, sy;
-
- var sc = sy = self.getScrollTop();
- if (self.dst.ay) {
- sc = (self.bzscroll) ? self.dst.py + (self.bzscroll.getNow() * self.dst.ay) : self.newscrolly;
- var dr = sc - sy;
- if ((dr < 0 && sc < self.newscrolly) || (dr > 0 && sc > self.newscrolly)) sc = self.newscrolly;
- self.setScrollTop(sc);
- if (sc == self.newscrolly) done = 1;
- } else {
- done = 1;
- }
-
- var scx = sx = self.getScrollLeft();
- if (self.dst.ax) {
- scx = (self.bzscroll) ? self.dst.px + (self.bzscroll.getNow() * self.dst.ax) : self.newscrollx;
- var dr = scx - sx;
- if ((dr < 0 && scx < self.newscrollx) || (dr > 0 && scx > self.newscrollx)) scx = self.newscrollx;
- self.setScrollLeft(scx);
- if (scx == self.newscrollx) done += 1;
- } else {
- done += 1;
- }
-
- if (done == 2) {
- self.timer = 0;
- self.cursorfreezed = false;
- self.bzscroll = false;
- self.scrollrunning = false;
- if (sc < 0) sc = 0;
- else if (sc > self.page.maxh) sc = self.page.maxh;
- if (scx < 0) scx = 0;
- else if (scx > self.page.maxw) scx = self.page.maxw;
- if ((scx != self.newscrollx) || (sc != self.newscrolly)) self.doScrollPos(scx, sc);
- else {
- if (self.onscrollend) {
- self.triggerScrollEnd();
- }
- }
- } else {
- self.timer = setAnimationFrame(scrolling) || 1;
- }
- };
- self.cancelAnimationFrame = false;
- self.timer = 1;
-
- if (self.onscrollstart && !self.scrollrunning) {
- var info = {
- "type": "scrollstart",
- "current": {
- "x": px,
- "y": py
- },
- "request": {
- "x": x,
- "y": y
- },
- "end": {
- "x": self.newscrollx,
- "y": self.newscrolly
- },
- "speed": ms
- };
- self.onscrollstart.call(self, info);
- }
-
- scrolling();
-
- if ((py == self.page.maxh && y >= py) || (px == self.page.maxw && x >= px)) self.checkContentSize();
-
- self.noticeCursor();
- };
-
- this.cancelScroll = function() {
- if (self.timer) clearAnimationFrame(self.timer);
- self.timer = 0;
- self.bzscroll = false;
- self.scrollrunning = false;
- return self;
- };
-
- }
-
- this.doScrollBy = function(stp, relative) {
- var ny = 0;
- if (relative) {
- ny = Math.floor((self.scroll.y - stp) * self.scrollratio.y)
- } else {
- var sy = (self.timer) ? self.newscrolly : self.getScrollTop(true);
- ny = sy - stp;
- }
- if (self.bouncescroll) {
- var haf = Math.round(self.view.h / 2);
- if (ny < -haf) ny = -haf
- else if (ny > (self.page.maxh + haf)) ny = (self.page.maxh + haf);
- }
- self.cursorfreezed = false;
-
- var py = self.getScrollTop(true);
- if (ny < 0 && py <= 0) return self.noticeCursor();
- else if (ny > self.page.maxh && py >= self.page.maxh) {
- self.checkContentSize();
- return self.noticeCursor();
- }
-
- self.doScrollTop(ny);
- };
-
- this.doScrollLeftBy = function(stp, relative) {
- var nx = 0;
- if (relative) {
- nx = Math.floor((self.scroll.x - stp) * self.scrollratio.x)
- } else {
- var sx = (self.timer) ? self.newscrollx : self.getScrollLeft(true);
- nx = sx - stp;
- }
- if (self.bouncescroll) {
- var haf = Math.round(self.view.w / 2);
- if (nx < -haf) nx = -haf;
- else if (nx > (self.page.maxw + haf)) nx = (self.page.maxw + haf);
- }
- self.cursorfreezed = false;
-
- var px = self.getScrollLeft(true);
- if (nx < 0 && px <= 0) return self.noticeCursor();
- else if (nx > self.page.maxw && px >= self.page.maxw) return self.noticeCursor();
-
- self.doScrollLeft(nx);
- };
-
- this.doScrollTo = function(pos, relative) {
- var ny = (relative) ? Math.round(pos * self.scrollratio.y) : pos;
- if (ny < 0) ny = 0;
- else if (ny > self.page.maxh) ny = self.page.maxh;
- self.cursorfreezed = false;
- self.doScrollTop(pos);
- };
-
- this.checkContentSize = function() {
- var pg = self.getContentSize();
- if ((pg.h != self.page.h) || (pg.w != self.page.w)) self.resize(false, pg);
- };
-
- self.onscroll = function(e) {
- if (self.rail.drag) return;
- if (!self.cursorfreezed) {
- self.synched('scroll', function() {
- self.scroll.y = Math.round(self.getScrollTop() * (1 / self.scrollratio.y));
- if (self.railh) self.scroll.x = Math.round(self.getScrollLeft() * (1 / self.scrollratio.x));
- self.noticeCursor();
- });
- }
- };
- self.bind(self.docscroll, "scroll", self.onscroll);
-
- this.doZoomIn = function(e) {
- if (self.zoomactive) return;
- self.zoomactive = true;
-
- self.zoomrestore = {
- style: {}
- };
- var lst = ['position', 'top', 'left', 'zIndex', 'backgroundColor', 'marginTop', 'marginBottom', 'marginLeft', 'marginRight'];
- var win = self.win[0].style;
- for (var a in lst) {
- var pp = lst[a];
- self.zoomrestore.style[pp] = (typeof win[pp] != "undefined") ? win[pp] : '';
- }
-
- self.zoomrestore.style.width = self.win.css('width');
- self.zoomrestore.style.height = self.win.css('height');
-
- self.zoomrestore.padding = {
- w: self.win.outerWidth() - self.win.width(),
- h: self.win.outerHeight() - self.win.height()
- };
-
- if (cap.isios4) {
- self.zoomrestore.scrollTop = $(window).scrollTop();
- $(window).scrollTop(0);
- }
-
- self.win.css({
- "position": (cap.isios4) ? "absolute" : "fixed",
- "top": 0,
- "left": 0,
- "z-index": globalmaxzindex + 100,
- "margin": "0px"
- });
- var bkg = self.win.css("backgroundColor");
- if (bkg == "" || /transparent|rgba\(0, 0, 0, 0\)|rgba\(0,0,0,0\)/.test(bkg)) self.win.css("backgroundColor", "#fff");
- self.rail.css({
- "z-index": globalmaxzindex + 101
- });
- self.zoom.css({
- "z-index": globalmaxzindex + 102
- });
- self.zoom.css('backgroundPosition', '0px -18px');
- self.resizeZoom();
-
- if (self.onzoomin) self.onzoomin.call(self);
-
- return self.cancelEvent(e);
- };
-
- this.doZoomOut = function(e) {
- if (!self.zoomactive) return;
- self.zoomactive = false;
-
- self.win.css("margin", "");
- self.win.css(self.zoomrestore.style);
-
- if (cap.isios4) {
- $(window).scrollTop(self.zoomrestore.scrollTop);
- }
-
- self.rail.css({
- "z-index": self.zindex
- });
- self.zoom.css({
- "z-index": self.zindex
- });
- self.zoomrestore = false;
- self.zoom.css('backgroundPosition', '0px 0px');
- self.onResize();
-
- if (self.onzoomout) self.onzoomout.call(self);
-
- return self.cancelEvent(e);
- };
-
- this.doZoom = function(e) {
- return (self.zoomactive) ? self.doZoomOut(e) : self.doZoomIn(e);
- };
-
- this.resizeZoom = function() {
- if (!self.zoomactive) return;
-
- var py = self.getScrollTop(); //preserve scrolling position
- self.win.css({
- width: $(window).width() - self.zoomrestore.padding.w + "px",
- height: $(window).height() - self.zoomrestore.padding.h + "px"
- });
- self.onResize();
-
- self.setScrollTop(Math.min(self.page.maxh, py));
- };
-
- this.init();
-
- $.nicescroll.push(this);
-
- };
-
- // Inspired by the work of Kin Blas
- // http://webpro.host.adobe.com/people/jblas/momentum/includes/jquery.momentum.0.7.js
-
-
- var ScrollMomentumClass2D = function(nc) {
- var self = this;
- this.nc = nc;
-
- this.lastx = 0;
- this.lasty = 0;
- this.speedx = 0;
- this.speedy = 0;
- this.lasttime = 0;
- this.steptime = 0;
- this.snapx = false;
- this.snapy = false;
- this.demulx = 0;
- this.demuly = 0;
-
- this.lastscrollx = -1;
- this.lastscrolly = -1;
-
- this.chkx = 0;
- this.chky = 0;
-
- this.timer = 0;
-
- this.time = function() {
- return +new Date(); //beautifull hack
- };
-
- this.reset = function(px, py) {
- self.stop();
- var now = self.time();
- self.steptime = 0;
- self.lasttime = now;
- self.speedx = 0;
- self.speedy = 0;
- self.lastx = px;
- self.lasty = py;
- self.lastscrollx = -1;
- self.lastscrolly = -1;
- };
-
- this.update = function(px, py) {
- var now = self.time();
- self.steptime = now - self.lasttime;
- self.lasttime = now;
- var dy = py - self.lasty;
- var dx = px - self.lastx;
- var sy = self.nc.getScrollTop();
- var sx = self.nc.getScrollLeft();
- var newy = sy + dy;
- var newx = sx + dx;
- self.snapx = (newx < 0) || (newx > self.nc.page.maxw);
- self.snapy = (newy < 0) || (newy > self.nc.page.maxh);
- self.speedx = dx;
- self.speedy = dy;
- self.lastx = px;
- self.lasty = py;
- };
-
- this.stop = function() {
- self.nc.unsynched("domomentum2d");
- if (self.timer) clearTimeout(self.timer);
- self.timer = 0;
- self.lastscrollx = -1;
- self.lastscrolly = -1;
- };
-
- this.doSnapy = function(nx, ny) {
- var snap = false;
-
- if (ny < 0) {
- ny = 0;
- snap = true;
- } else if (ny > self.nc.page.maxh) {
- ny = self.nc.page.maxh;
- snap = true;
- }
-
- if (nx < 0) {
- nx = 0;
- snap = true;
- } else if (nx > self.nc.page.maxw) {
- nx = self.nc.page.maxw;
- snap = true;
- }
-
- (snap) ? self.nc.doScrollPos(nx, ny, self.nc.opt.snapbackspeed): self.nc.triggerScrollEnd();
- };
-
- this.doMomentum = function(gp) {
- var t = self.time();
- var l = (gp) ? t + gp : self.lasttime;
-
- var sl = self.nc.getScrollLeft();
- var st = self.nc.getScrollTop();
-
- var pageh = self.nc.page.maxh;
- var pagew = self.nc.page.maxw;
-
- self.speedx = (pagew > 0) ? Math.min(60, self.speedx) : 0;
- self.speedy = (pageh > 0) ? Math.min(60, self.speedy) : 0;
-
- var chk = l && (t - l) <= 60;
-
- if ((st < 0) || (st > pageh) || (sl < 0) || (sl > pagew)) chk = false;
-
- var sy = (self.speedy && chk) ? self.speedy : false;
- var sx = (self.speedx && chk) ? self.speedx : false;
-
- if (sy || sx) {
- var tm = Math.max(16, self.steptime); //timeout granularity
-
- if (tm > 50) { // do smooth
- var xm = tm / 50;
- self.speedx *= xm;
- self.speedy *= xm;
- tm = 50;
- }
-
- self.demulxy = 0;
-
- self.lastscrollx = self.nc.getScrollLeft();
- self.chkx = self.lastscrollx;
- self.lastscrolly = self.nc.getScrollTop();
- self.chky = self.lastscrolly;
-
- var nx = self.lastscrollx;
- var ny = self.lastscrolly;
-
- var onscroll = function() {
- var df = ((self.time() - t) > 600) ? 0.04 : 0.02;
-
- if (self.speedx) {
- nx = Math.floor(self.lastscrollx - (self.speedx * (1 - self.demulxy)));
- self.lastscrollx = nx;
- if ((nx < 0) || (nx > pagew)) df = 0.10;
- }
-
- if (self.speedy) {
- ny = Math.floor(self.lastscrolly - (self.speedy * (1 - self.demulxy)));
- self.lastscrolly = ny;
- if ((ny < 0) || (ny > pageh)) df = 0.10;
- }
-
- self.demulxy = Math.min(1, self.demulxy + df);
-
- self.nc.synched("domomentum2d", function() {
-
- if (self.speedx) {
- var scx = self.nc.getScrollLeft();
- if (scx != self.chkx) self.stop();
- self.chkx = nx;
- self.nc.setScrollLeft(nx);
- }
-
- if (self.speedy) {
- var scy = self.nc.getScrollTop();
- if (scy != self.chky) self.stop();
- self.chky = ny;
- self.nc.setScrollTop(ny);
- }
-
- if (!self.timer) {
- self.nc.hideCursor();
- self.doSnapy(nx, ny);
- }
-
- });
-
- if (self.demulxy < 1) {
- self.timer = setTimeout(onscroll, tm);
- } else {
- self.stop();
- self.nc.hideCursor();
- self.doSnapy(nx, ny);
- }
- };
-
- onscroll();
-
- } else {
- self.doSnapy(self.nc.getScrollLeft(), self.nc.getScrollTop());
- }
-
- }
-
- };
-
-
- // override jQuery scrollTop
-
- var _scrollTop = jQuery.fn.scrollTop; // preserve original function
-
- jQuery.cssHooks["pageYOffset"] = {
- get: function(elem, computed, extra) {
- var nice = $.data(elem, '__nicescroll') || false;
- return (nice && nice.ishwscroll) ? nice.getScrollTop() : _scrollTop.call(elem);
- },
- set: function(elem, value) {
- var nice = $.data(elem, '__nicescroll') || false;
- (nice && nice.ishwscroll) ? nice.setScrollTop(parseInt(value)): _scrollTop.call(elem, value);
- return this;
- }
- };
-
- /*
- $.fx.step["scrollTop"] = function(fx){
- $.cssHooks["scrollTop"].set( fx.elem, fx.now + fx.unit );
- };
-*/
-
- jQuery.fn.scrollTop = function(value) {
- if (typeof value == "undefined") {
- var nice = (this[0]) ? $.data(this[0], '__nicescroll') || false : false;
- return (nice && nice.ishwscroll) ? nice.getScrollTop() : _scrollTop.call(this);
- } else {
- return this.each(function() {
- var nice = $.data(this, '__nicescroll') || false;
- (nice && nice.ishwscroll) ? nice.setScrollTop(parseInt(value)): _scrollTop.call($(this), value);
- });
- }
- };
-
- // override jQuery scrollLeft
-
- var _scrollLeft = jQuery.fn.scrollLeft; // preserve original function
-
- $.cssHooks.pageXOffset = {
- get: function(elem, computed, extra) {
- var nice = $.data(elem, '__nicescroll') || false;
- return (nice && nice.ishwscroll) ? nice.getScrollLeft() : _scrollLeft.call(elem);
- },
- set: function(elem, value) {
- var nice = $.data(elem, '__nicescroll') || false;
- (nice && nice.ishwscroll) ? nice.setScrollLeft(parseInt(value)): _scrollLeft.call(elem, value);
- return this;
- }
- };
-
- /*
- $.fx.step["scrollLeft"] = function(fx){
- $.cssHooks["scrollLeft"].set( fx.elem, fx.now + fx.unit );
- };
-*/
-
- jQuery.fn.scrollLeft = function(value) {
- if (typeof value == "undefined") {
- var nice = (this[0]) ? $.data(this[0], '__nicescroll') || false : false;
- return (nice && nice.ishwscroll) ? nice.getScrollLeft() : _scrollLeft.call(this);
- } else {
- return this.each(function() {
- var nice = $.data(this, '__nicescroll') || false;
- (nice && nice.ishwscroll) ? nice.setScrollLeft(parseInt(value)): _scrollLeft.call($(this), value);
- });
- }
- };
-
- var NiceScrollArray = function(doms) {
- var self = this;
- this.length = 0;
- this.name = "nicescrollarray";
-
- this.each = function(fn) {
- for (var a = 0, i = 0; a < self.length; a++) fn.call(self[a], i++);
- return self;
- };
-
- this.push = function(nice) {
- self[self.length] = nice;
- self.length++;
- };
-
- this.eq = function(idx) {
- return self[idx];
- };
-
- if (doms) {
- for (var a = 0; a < doms.length; a++) {
- var nice = $.data(doms[a], '__nicescroll') || false;
- if (nice) {
- this[this.length] = nice;
- this.length++;
- }
- };
- }
-
- return this;
- };
-
- function mplex(el, lst, fn) {
- for (var a = 0; a < lst.length; a++) fn(el, lst[a]);
- };
- mplex(
- NiceScrollArray.prototype, ['show', 'hide', 'toggle', 'onResize', 'resize', 'remove', 'stop', 'doScrollPos'],
- function(e, n) {
- e[n] = function() {
- var args = arguments;
- return this.each(function() {
- this[n].apply(this, args);
- });
- };
- }
- );
-
- jQuery.fn.getNiceScroll = function(index) {
- if (typeof index == "undefined") {
- return new NiceScrollArray(this);
- } else {
- var nice = this[index] && $.data(this[index], '__nicescroll') || false;
- return nice;
- }
- };
-
- jQuery.extend(jQuery.expr[':'], {
- nicescroll: function(a) {
- return ($.data(a, '__nicescroll')) ? true : false;
- }
- });
-
- $.fn.niceScroll = function(wrapper, opt) {
- if (typeof opt == "undefined") {
- if ((typeof wrapper == "object") && !("jquery" in wrapper)) {
- opt = wrapper;
- wrapper = false;
- }
- }
- opt = $.extend({},opt); // cloning
- var ret = new NiceScrollArray();
- if (typeof opt == "undefined") opt = {};
-
- if (wrapper || false) {
- opt.doc = $(wrapper);
- opt.win = $(this);
- }
- var docundef = !("doc" in opt);
- if (!docundef && !("win" in opt)) opt.win = $(this);
-
- this.each(function() {
- var nice = $(this).data('__nicescroll') || false;
- if (!nice) {
- opt.doc = (docundef) ? $(this) : opt.doc;
- nice = new NiceScrollClass(opt, $(this));
- $(this).data('__nicescroll', nice);
- }
- ret.push(nice);
- });
- return (ret.length == 1) ? ret[0] : ret;
- };
-
- window.NiceScroll = {
- getjQuery: function() {
- return jQuery
- }
- };
-
- if (!$.nicescroll) {
- $.nicescroll = new NiceScrollArray();
- $.nicescroll.options = _globaloptions;
- }
-
-})); \ No newline at end of file
diff --git a/vendor/assets/javascripts/pdflab.js b/vendor/assets/javascripts/pdf.js
index 5d9c348ce35..91c43b12716 100644..100755
--- a/vendor/assets/javascripts/pdflab.js
+++ b/vendor/assets/javascripts/pdf.js
@@ -1,348 +1,4 @@
-(function webpackUniversalModuleDefinition(root, factory) {
- if(typeof exports === 'object' && typeof module === 'object')
- module.exports = factory();
- else if(typeof define === 'function' && define.amd)
- define("PDFLab", [], factory);
- else if(typeof exports === 'object')
- exports["PDFLab"] = factory();
- else
- root["PDFLab"] = factory();
-})(this, function() {
-return /******/ (function(modules) { // webpackBootstrap
-/******/ // install a JSONP callback for chunk loading
-/******/ var parentJsonpFunction = window["webpackJsonpPDFLab"];
-/******/ window["webpackJsonpPDFLab"] = function webpackJsonpCallback(chunkIds, moreModules, executeModules) {
-/******/ // add "moreModules" to the modules object,
-/******/ // then flag all "chunkIds" as loaded and fire callback
-/******/ var moduleId, chunkId, i = 0, resolves = [], result;
-/******/ for(;i < chunkIds.length; i++) {
-/******/ chunkId = chunkIds[i];
-/******/ if(installedChunks[chunkId])
-/******/ resolves.push(installedChunks[chunkId][0]);
-/******/ installedChunks[chunkId] = 0;
-/******/ }
-/******/ for(moduleId in moreModules) {
-/******/ if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
-/******/ modules[moduleId] = moreModules[moduleId];
-/******/ }
-/******/ }
-/******/ if(parentJsonpFunction) parentJsonpFunction(chunkIds, moreModules, executeModules);
-/******/ while(resolves.length)
-/******/ resolves.shift()();
-/******/
-/******/ };
-/******/
-/******/ // The module cache
-/******/ var installedModules = {};
-/******/
-/******/ // objects to store loaded and loading chunks
-/******/ var installedChunks = {
-/******/ 1: 0,
-/******/ 2: 0
-/******/ };
-/******/
-/******/ // The require function
-/******/ function __webpack_require__(moduleId) {
-/******/
-/******/ // Check if module is in cache
-/******/ if(installedModules[moduleId])
-/******/ return installedModules[moduleId].exports;
-/******/
-/******/ // Create a new module (and put it into the cache)
-/******/ var module = installedModules[moduleId] = {
-/******/ i: moduleId,
-/******/ l: false,
-/******/ exports: {}
-/******/ };
-/******/
-/******/ // Execute the module function
-/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
-/******/
-/******/ // Flag the module as loaded
-/******/ module.l = true;
-/******/
-/******/ // Return the exports of the module
-/******/ return module.exports;
-/******/ }
-/******/
-/******/ // This file contains only the entry chunk.
-/******/ // The chunk loading function for additional chunks
-/******/ __webpack_require__.e = function requireEnsure(chunkId) {
-/******/ if(installedChunks[chunkId] === 0)
-/******/ return Promise.resolve();
-/******/
-/******/ // an Promise means "currently loading".
-/******/ if(installedChunks[chunkId]) {
-/******/ return installedChunks[chunkId][2];
-/******/ }
-/******/ // start chunk loading
-/******/ var head = document.getElementsByTagName('head')[0];
-/******/ var script = document.createElement('script');
-/******/ script.type = 'text/javascript';
-/******/ script.charset = 'utf-8';
-/******/ script.async = true;
-/******/ script.timeout = 120000;
-/******/
-/******/ if (__webpack_require__.nc) {
-/******/ script.setAttribute("nonce", __webpack_require__.nc);
-/******/ }
-/******/ script.src = __webpack_require__.p + "" + chunkId + ".js";
-/******/ var timeout = setTimeout(onScriptComplete, 120000);
-/******/ script.onerror = script.onload = onScriptComplete;
-/******/ function onScriptComplete() {
-/******/ // avoid mem leaks in IE.
-/******/ script.onerror = script.onload = null;
-/******/ clearTimeout(timeout);
-/******/ var chunk = installedChunks[chunkId];
-/******/ if(chunk !== 0) {
-/******/ if(chunk) chunk[1](new Error('Loading chunk ' + chunkId + ' failed.'));
-/******/ installedChunks[chunkId] = undefined;
-/******/ }
-/******/ };
-/******/
-/******/ var promise = new Promise(function(resolve, reject) {
-/******/ installedChunks[chunkId] = [resolve, reject];
-/******/ });
-/******/ installedChunks[chunkId][2] = promise;
-/******/
-/******/ head.appendChild(script);
-/******/ return promise;
-/******/ };
-/******/
-/******/ // expose the modules object (__webpack_modules__)
-/******/ __webpack_require__.m = modules;
-/******/
-/******/ // expose the module cache
-/******/ __webpack_require__.c = installedModules;
-/******/
-/******/ // identity function for calling harmony imports with the correct context
-/******/ __webpack_require__.i = function(value) { return value; };
-/******/
-/******/ // define getter function for harmony exports
-/******/ __webpack_require__.d = function(exports, name, getter) {
-/******/ if(!__webpack_require__.o(exports, name)) {
-/******/ Object.defineProperty(exports, name, {
-/******/ configurable: false,
-/******/ enumerable: true,
-/******/ get: getter
-/******/ });
-/******/ }
-/******/ };
-/******/
-/******/ // getDefaultExport function for compatibility with non-harmony modules
-/******/ __webpack_require__.n = function(module) {
-/******/ var getter = module && module.__esModule ?
-/******/ function getDefault() { return module['default']; } :
-/******/ function getModuleExports() { return module; };
-/******/ __webpack_require__.d(getter, 'a', getter);
-/******/ return getter;
-/******/ };
-/******/
-/******/ // Object.prototype.hasOwnProperty.call
-/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
-/******/
-/******/ // __webpack_public_path__
-/******/ __webpack_require__.p = "";
-/******/
-/******/ // on error function for async loading
-/******/ __webpack_require__.oe = function(err) { console.error(err); throw err; };
-/******/
-/******/ // Load entry module and return exports
-/******/ return __webpack_require__(__webpack_require__.s = 23);
-/******/ })
-/************************************************************************/
-/******/ ([
-/* 0 */
-/***/ (function(module, exports) {
-
-// shim for using process in browser
-var process = module.exports = {};
-
-// cached from whatever global is present so that test runners that stub it
-// don't break things. But we need to wrap it in a try catch in case it is
-// wrapped in strict mode code which doesn't define any globals. It's inside a
-// function because try/catches deoptimize in certain engines.
-
-var cachedSetTimeout;
-var cachedClearTimeout;
-
-function defaultSetTimout() {
- throw new Error('setTimeout has not been defined');
-}
-function defaultClearTimeout () {
- throw new Error('clearTimeout has not been defined');
-}
-(function () {
- try {
- if (typeof setTimeout === 'function') {
- cachedSetTimeout = setTimeout;
- } else {
- cachedSetTimeout = defaultSetTimout;
- }
- } catch (e) {
- cachedSetTimeout = defaultSetTimout;
- }
- try {
- if (typeof clearTimeout === 'function') {
- cachedClearTimeout = clearTimeout;
- } else {
- cachedClearTimeout = defaultClearTimeout;
- }
- } catch (e) {
- cachedClearTimeout = defaultClearTimeout;
- }
-} ())
-function runTimeout(fun) {
- if (cachedSetTimeout === setTimeout) {
- //normal enviroments in sane situations
- return setTimeout(fun, 0);
- }
- // if setTimeout wasn't available but was latter defined
- if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) {
- cachedSetTimeout = setTimeout;
- return setTimeout(fun, 0);
- }
- try {
- // when when somebody has screwed with setTimeout but no I.E. maddness
- return cachedSetTimeout(fun, 0);
- } catch(e){
- try {
- // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally
- return cachedSetTimeout.call(null, fun, 0);
- } catch(e){
- // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error
- return cachedSetTimeout.call(this, fun, 0);
- }
- }
-
-
-}
-function runClearTimeout(marker) {
- if (cachedClearTimeout === clearTimeout) {
- //normal enviroments in sane situations
- return clearTimeout(marker);
- }
- // if clearTimeout wasn't available but was latter defined
- if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) {
- cachedClearTimeout = clearTimeout;
- return clearTimeout(marker);
- }
- try {
- // when when somebody has screwed with setTimeout but no I.E. maddness
- return cachedClearTimeout(marker);
- } catch (e){
- try {
- // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally
- return cachedClearTimeout.call(null, marker);
- } catch (e){
- // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error.
- // Some versions of I.E. have different rules for clearTimeout vs setTimeout
- return cachedClearTimeout.call(this, marker);
- }
- }
-
-
-
-}
-var queue = [];
-var draining = false;
-var currentQueue;
-var queueIndex = -1;
-
-function cleanUpNextTick() {
- if (!draining || !currentQueue) {
- return;
- }
- draining = false;
- if (currentQueue.length) {
- queue = currentQueue.concat(queue);
- } else {
- queueIndex = -1;
- }
- if (queue.length) {
- drainQueue();
- }
-}
-
-function drainQueue() {
- if (draining) {
- return;
- }
- var timeout = runTimeout(cleanUpNextTick);
- draining = true;
-
- var len = queue.length;
- while(len) {
- currentQueue = queue;
- queue = [];
- while (++queueIndex < len) {
- if (currentQueue) {
- currentQueue[queueIndex].run();
- }
- }
- queueIndex = -1;
- len = queue.length;
- }
- currentQueue = null;
- draining = false;
- runClearTimeout(timeout);
-}
-
-process.nextTick = function (fun) {
- var args = new Array(arguments.length - 1);
- if (arguments.length > 1) {
- for (var i = 1; i < arguments.length; i++) {
- args[i - 1] = arguments[i];
- }
- }
- queue.push(new Item(fun, args));
- if (queue.length === 1 && !draining) {
- runTimeout(drainQueue);
- }
-};
-
-// v8 likes predictible objects
-function Item(fun, array) {
- this.fun = fun;
- this.array = array;
-}
-Item.prototype.run = function () {
- this.fun.apply(null, this.array);
-};
-process.title = 'browser';
-process.browser = true;
-process.env = {};
-process.argv = [];
-process.version = ''; // empty string to avoid regexp issues
-process.versions = {};
-
-function noop() {}
-
-process.on = noop;
-process.addListener = noop;
-process.once = noop;
-process.off = noop;
-process.removeListener = noop;
-process.removeAllListeners = noop;
-process.emit = noop;
-
-process.binding = function (name) {
- throw new Error('process.binding is not supported');
-};
-
-process.cwd = function () { return '/' };
-process.chdir = function (dir) {
- throw new Error('process.chdir is not supported');
-};
-process.umask = function() { return 0; };
-
-
-/***/ }),
-/* 1 */,
-/* 2 */
-/***/ (function(module, exports, __webpack_require__) {
-
-/* WEBPACK VAR INJECTION */(function(process) {/* Copyright 2017 Mozilla Foundation
+/* Copyright 2017 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -358,7 +14,7 @@ process.umask = function() { return 0; };
*/
(function webpackUniversalModuleDefinition(root, factory) {
- if(true)
+ if(typeof exports === 'object' && typeof module === 'object')
module.exports = factory();
else if(typeof define === 'function' && define.amd)
define("pdfjs-dist/build/pdf", [], factory);
@@ -2478,11 +2134,11 @@ var useRequireEnsure = false;
if (typeof __pdfjsdev_webpack__ === 'undefined') {
if (typeof window === 'undefined') {
isWorkerDisabled = true;
- if (false) {
+ if (typeof require.ensure === 'undefined') {
require.ensure = require('node-ensure');
}
useRequireEnsure = true;
- } else if (true) {
+ } else if (typeof require !== 'undefined' && typeof require.ensure === 'function') {
useRequireEnsure = true;
}
if (typeof requirejs !== 'undefined' && requirejs.toUrl) {
@@ -2490,10 +2146,10 @@ if (typeof __pdfjsdev_webpack__ === 'undefined') {
}
var dynamicLoaderSupported = typeof requirejs !== 'undefined' && requirejs.load;
fakeWorkerFilesLoader = useRequireEnsure ? function (callback) {
- __webpack_require__.e/* require.ensure */(0).then((function () {
- var worker = __webpack_require__(1);
+ require.ensure([], function () {
+ var worker = require('./pdf.worker.js');
callback(worker.WorkerMessageHandler);
- }).bind(null, __webpack_require__)).catch(__webpack_require__.oe);
+ });
} : dynamicLoaderSupported ? function (callback) {
requirejs(['pdfjs-dist/build/pdf.worker'], function (worker) {
callback(worker.WorkerMessageHandler);
@@ -9706,2779 +9362,4 @@ if (typeof PDFJS === 'undefined' || !PDFJS.compatibilityChecked) {
/***/ })
/******/ ]);
-});
-/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(0)))
-
-/***/ }),
-/* 3 */
-/***/ (function(module, exports, __webpack_require__) {
-
-/* WEBPACK VAR INJECTION */(function(Buffer) {/*
- MIT License http://www.opensource.org/licenses/mit-license.php
- Author Tobias Koppers @sokra
-*/
-// css base code, injected by the css-loader
-module.exports = function(useSourceMap) {
- var list = [];
-
- // return the list of modules as css string
- list.toString = function toString() {
- return this.map(function (item) {
- var content = cssWithMappingToString(item, useSourceMap);
- if(item[2]) {
- return "@media " + item[2] + "{" + content + "}";
- } else {
- return content;
- }
- }).join("");
- };
-
- // import a list of modules into the list
- list.i = function(modules, mediaQuery) {
- if(typeof modules === "string")
- modules = [[null, modules, ""]];
- var alreadyImportedModules = {};
- for(var i = 0; i < this.length; i++) {
- var id = this[i][0];
- if(typeof id === "number")
- alreadyImportedModules[id] = true;
- }
- for(i = 0; i < modules.length; i++) {
- var item = modules[i];
- // skip already imported module
- // this implementation is not 100% perfect for weird media query combinations
- // when a module is imported multiple times with different media queries.
- // I hope this will never occur (Hey this way we have smaller bundles)
- if(typeof item[0] !== "number" || !alreadyImportedModules[item[0]]) {
- if(mediaQuery && !item[2]) {
- item[2] = mediaQuery;
- } else if(mediaQuery) {
- item[2] = "(" + item[2] + ") and (" + mediaQuery + ")";
- }
- list.push(item);
- }
- }
- };
- return list;
-};
-
-function cssWithMappingToString(item, useSourceMap) {
- var content = item[1] || '';
- var cssMapping = item[3];
- if (!cssMapping) {
- return content;
- }
-
- if (useSourceMap) {
- var sourceMapping = toComment(cssMapping);
- var sourceURLs = cssMapping.sources.map(function (source) {
- return '/*# sourceURL=' + cssMapping.sourceRoot + source + ' */'
- });
-
- return [content].concat(sourceURLs).concat([sourceMapping]).join('\n');
- }
-
- return [content].join('\n');
-}
-
-// Adapted from convert-source-map (MIT)
-function toComment(sourceMap) {
- var base64 = new Buffer(JSON.stringify(sourceMap)).toString('base64');
- var data = 'sourceMappingURL=data:application/json;charset=utf-8;base64,' + base64;
-
- return '/*# ' + data + ' */';
-}
-
-/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(10).Buffer))
-
-/***/ }),
-/* 4 */
-/***/ (function(module, exports) {
-
-// this module is a runtime utility for cleaner component module output and will
-// be included in the final webpack user bundle
-
-module.exports = function normalizeComponent (
- rawScriptExports,
- compiledTemplate,
- scopeId,
- cssModules
-) {
- var esModule
- var scriptExports = rawScriptExports = rawScriptExports || {}
-
- // ES6 modules interop
- var type = typeof rawScriptExports.default
- if (type === 'object' || type === 'function') {
- esModule = rawScriptExports
- scriptExports = rawScriptExports.default
- }
-
- // Vue.extend constructor export interop
- var options = typeof scriptExports === 'function'
- ? scriptExports.options
- : scriptExports
-
- // render functions
- if (compiledTemplate) {
- options.render = compiledTemplate.render
- options.staticRenderFns = compiledTemplate.staticRenderFns
- }
-
- // scopedId
- if (scopeId) {
- options._scopeId = scopeId
- }
-
- // inject cssModules
- if (cssModules) {
- var computed = Object.create(options.computed || null)
- Object.keys(cssModules).forEach(function (key) {
- var module = cssModules[key]
- computed[key] = function () { return module }
- })
- options.computed = computed
- }
-
- return {
- esModule: esModule,
- exports: scriptExports,
- options: options
- }
-}
-
-
-/***/ }),
-/* 5 */
-/***/ (function(module, exports, __webpack_require__) {
-
-/*
- MIT License http://www.opensource.org/licenses/mit-license.php
- Author Tobias Koppers @sokra
- Modified by Evan You @yyx990803
-*/
-
-var hasDocument = typeof document !== 'undefined'
-
-if (typeof DEBUG !== 'undefined' && DEBUG) {
- if (!hasDocument) {
- throw new Error(
- 'vue-style-loader cannot be used in a non-browser environment. ' +
- "Use { target: 'node' } in your Webpack config to indicate a server-rendering environment."
- ) }
-}
-
-var listToStyles = __webpack_require__(21)
-
-/*
-type StyleObject = {
- id: number;
- parts: Array<StyleObjectPart>
-}
-
-type StyleObjectPart = {
- css: string;
- media: string;
- sourceMap: ?string
-}
-*/
-
-var stylesInDom = {/*
- [id: number]: {
- id: number,
- refs: number,
- parts: Array<(obj?: StyleObjectPart) => void>
- }
-*/}
-
-var head = hasDocument && (document.head || document.getElementsByTagName('head')[0])
-var singletonElement = null
-var singletonCounter = 0
-var isProduction = false
-var noop = function () {}
-
-// Force single-tag solution on IE6-9, which has a hard limit on the # of <style>
-// tags it will allow on a page
-var isOldIE = typeof navigator !== 'undefined' && /msie [6-9]\b/.test(navigator.userAgent.toLowerCase())
-
-module.exports = function (parentId, list, _isProduction) {
- isProduction = _isProduction
-
- var styles = listToStyles(parentId, list)
- addStylesToDom(styles)
-
- return function update (newList) {
- var mayRemove = []
- for (var i = 0; i < styles.length; i++) {
- var item = styles[i]
- var domStyle = stylesInDom[item.id]
- domStyle.refs--
- mayRemove.push(domStyle)
- }
- if (newList) {
- styles = listToStyles(parentId, newList)
- addStylesToDom(styles)
- } else {
- styles = []
- }
- for (var i = 0; i < mayRemove.length; i++) {
- var domStyle = mayRemove[i]
- if (domStyle.refs === 0) {
- for (var j = 0; j < domStyle.parts.length; j++) {
- domStyle.parts[j]()
- }
- delete stylesInDom[domStyle.id]
- }
- }
- }
-}
-
-function addStylesToDom (styles /* Array<StyleObject> */) {
- for (var i = 0; i < styles.length; i++) {
- var item = styles[i]
- var domStyle = stylesInDom[item.id]
- if (domStyle) {
- domStyle.refs++
- for (var j = 0; j < domStyle.parts.length; j++) {
- domStyle.parts[j](item.parts[j])
- }
- for (; j < item.parts.length; j++) {
- domStyle.parts.push(addStyle(item.parts[j]))
- }
- if (domStyle.parts.length > item.parts.length) {
- domStyle.parts.length = item.parts.length
- }
- } else {
- var parts = []
- for (var j = 0; j < item.parts.length; j++) {
- parts.push(addStyle(item.parts[j]))
- }
- stylesInDom[item.id] = { id: item.id, refs: 1, parts: parts }
- }
- }
-}
-
-function createStyleElement () {
- var styleElement = document.createElement('style')
- styleElement.type = 'text/css'
- head.appendChild(styleElement)
- return styleElement
-}
-
-function addStyle (obj /* StyleObjectPart */) {
- var update, remove
- var styleElement = document.querySelector('style[data-vue-ssr-id~="' + obj.id + '"]')
-
- if (styleElement) {
- if (isProduction) {
- // has SSR styles and in production mode.
- // simply do nothing.
- return noop
- } else {
- // has SSR styles but in dev mode.
- // for some reason Chrome can't handle source map in server-rendered
- // style tags - source maps in <style> only works if the style tag is
- // created and inserted dynamically. So we remove the server rendered
- // styles and inject new ones.
- styleElement.parentNode.removeChild(styleElement)
- }
- }
-
- if (isOldIE) {
- // use singleton mode for IE9.
- var styleIndex = singletonCounter++
- styleElement = singletonElement || (singletonElement = createStyleElement())
- update = applyToSingletonTag.bind(null, styleElement, styleIndex, false)
- remove = applyToSingletonTag.bind(null, styleElement, styleIndex, true)
- } else {
- // use multi-style-tag mode in all other cases
- styleElement = createStyleElement()
- update = applyToTag.bind(null, styleElement)
- remove = function () {
- styleElement.parentNode.removeChild(styleElement)
- }
- }
-
- update(obj)
-
- return function updateStyle (newObj /* StyleObjectPart */) {
- if (newObj) {
- if (newObj.css === obj.css &&
- newObj.media === obj.media &&
- newObj.sourceMap === obj.sourceMap) {
- return
- }
- update(obj = newObj)
- } else {
- remove()
- }
- }
-}
-
-var replaceText = (function () {
- var textStore = []
-
- return function (index, replacement) {
- textStore[index] = replacement
- return textStore.filter(Boolean).join('\n')
- }
-})()
-
-function applyToSingletonTag (styleElement, index, remove, obj) {
- var css = remove ? '' : obj.css
-
- if (styleElement.styleSheet) {
- styleElement.styleSheet.cssText = replaceText(index, css)
- } else {
- var cssNode = document.createTextNode(css)
- var childNodes = styleElement.childNodes
- if (childNodes[index]) styleElement.removeChild(childNodes[index])
- if (childNodes.length) {
- styleElement.insertBefore(cssNode, childNodes[index])
- } else {
- styleElement.appendChild(cssNode)
- }
- }
-}
-
-function applyToTag (styleElement, obj) {
- var css = obj.css
- var media = obj.media
- var sourceMap = obj.sourceMap
-
- if (media) {
- styleElement.setAttribute('media', media)
- }
-
- if (sourceMap) {
- // https://developer.chrome.com/devtools/docs/javascript-debugging
- // this makes source maps inside style tags work properly in Chrome
- css += '\n/*# sourceURL=' + sourceMap.sources[0] + ' */'
- // http://stackoverflow.com/a/26603875
- css += '\n/*# sourceMappingURL=data:application/json;base64,' + btoa(unescape(encodeURIComponent(JSON.stringify(sourceMap)))) + ' */'
- }
-
- if (styleElement.styleSheet) {
- styleElement.styleSheet.cssText = css
- } else {
- while (styleElement.firstChild) {
- styleElement.removeChild(styleElement.firstChild)
- }
- styleElement.appendChild(document.createTextNode(css))
- }
-}
-
-
-/***/ }),
-/* 6 */
-/***/ (function(module, exports, __webpack_require__) {
-
-
-/* styles */
-__webpack_require__(19)
-
-var Component = __webpack_require__(4)(
- /* script */
- __webpack_require__(7),
- /* template */
- __webpack_require__(17),
- /* scopeId */
- null,
- /* cssModules */
- null
-)
-
-module.exports = Component.exports
-
-
-/***/ }),
-/* 7 */
-/***/ (function(module, exports, __webpack_require__) {
-
-"use strict";
-
-
-Object.defineProperty(exports, "__esModule", {
- value: true
-});
-
-var _pdfjsDist = __webpack_require__(2);
-
-var _pdfjsDist2 = _interopRequireDefault(_pdfjsDist);
-
-var _index = __webpack_require__(16);
-
-var _index2 = _interopRequireDefault(_index);
-
-function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
-
-//
-//
-//
-//
-//
-//
-//
-//
-//
-//
-
-exports.default = {
- props: {
- pdf: {
- type: [String, Uint8Array],
- required: true
- }
- },
- data: function data() {
- return {
- loading: false,
- pages: []
- };
- },
-
- components: { page: _index2.default },
- watch: { pdf: 'load' },
- computed: {
- document: function document() {
- return typeof this.pdf === 'string' ? this.pdf : { data: this.pdf };
- },
- hasPDF: function hasPDF() {
- return this.pdf && this.pdf.length > 0;
- }
- },
- methods: {
- load: function load() {
- var _this = this;
-
- this.pages = [];
- return _pdfjsDist2.default.getDocument(this.document).then(this.renderPages).then(function () {
- return _this.$emit('pdflabload');
- }).catch(function (error) {
- return _this.$emit('pdflaberror', error);
- }).then(function () {
- _this.loading = false;
- });
- },
- renderPages: function renderPages(pdf) {
- var _this2 = this;
-
- var pagePromises = [];
- this.loading = true;
- for (var num = 1; num <= pdf.numPages; num += 1) {
- pagePromises.push(pdf.getPage(num).then(function (p) {
- return _this2.pages.push(p);
- }));
- }
- return Promise.all(pagePromises);
- }
- },
- mounted: function mounted() {
- if (this.hasPDF) this.load();
- }
-};
-
-/***/ }),
-/* 8 */
-/***/ (function(module, exports, __webpack_require__) {
-
-"use strict";
-
-
-Object.defineProperty(exports, "__esModule", {
- value: true
-});
-//
-//
-//
-//
-//
-//
-//
-
-exports.default = {
- props: {
- page: {
- type: Object,
- required: true
- },
- number: {
- type: Number,
- required: true
- }
- },
- data: function data() {
- return {
- scale: 4,
- rendering: false
- };
- },
-
- computed: {
- viewport: function viewport() {
- return this.page.getViewport(this.scale);
- },
- context: function context() {
- return this.$refs.canvas.getContext('2d');
- },
- renderContext: function renderContext() {
- return {
- canvasContext: this.context,
- viewport: this.viewport
- };
- }
- },
- mounted: function mounted() {
- var _this = this;
-
- this.$refs.canvas.height = this.viewport.height;
- this.$refs.canvas.width = this.viewport.width;
- this.rendering = true;
- this.page.render(this.renderContext).then(function () {
- _this.rendering = false;
- });
- }
-};
-
-/***/ }),
-/* 9 */
-/***/ (function(module, exports, __webpack_require__) {
-
-"use strict";
-
-
-exports.byteLength = byteLength
-exports.toByteArray = toByteArray
-exports.fromByteArray = fromByteArray
-
-var lookup = []
-var revLookup = []
-var Arr = typeof Uint8Array !== 'undefined' ? Uint8Array : Array
-
-var code = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
-for (var i = 0, len = code.length; i < len; ++i) {
- lookup[i] = code[i]
- revLookup[code.charCodeAt(i)] = i
-}
-
-revLookup['-'.charCodeAt(0)] = 62
-revLookup['_'.charCodeAt(0)] = 63
-
-function placeHoldersCount (b64) {
- var len = b64.length
- if (len % 4 > 0) {
- throw new Error('Invalid string. Length must be a multiple of 4')
- }
-
- // the number of equal signs (place holders)
- // if there are two placeholders, than the two characters before it
- // represent one byte
- // if there is only one, then the three characters before it represent 2 bytes
- // this is just a cheap hack to not do indexOf twice
- return b64[len - 2] === '=' ? 2 : b64[len - 1] === '=' ? 1 : 0
-}
-
-function byteLength (b64) {
- // base64 is 4/3 + up to two characters of the original data
- return b64.length * 3 / 4 - placeHoldersCount(b64)
-}
-
-function toByteArray (b64) {
- var i, j, l, tmp, placeHolders, arr
- var len = b64.length
- placeHolders = placeHoldersCount(b64)
-
- arr = new Arr(len * 3 / 4 - placeHolders)
-
- // if there are placeholders, only get up to the last complete 4 chars
- l = placeHolders > 0 ? len - 4 : len
-
- var L = 0
-
- for (i = 0, j = 0; i < l; i += 4, j += 3) {
- tmp = (revLookup[b64.charCodeAt(i)] << 18) | (revLookup[b64.charCodeAt(i + 1)] << 12) | (revLookup[b64.charCodeAt(i + 2)] << 6) | revLookup[b64.charCodeAt(i + 3)]
- arr[L++] = (tmp >> 16) & 0xFF
- arr[L++] = (tmp >> 8) & 0xFF
- arr[L++] = tmp & 0xFF
- }
-
- if (placeHolders === 2) {
- tmp = (revLookup[b64.charCodeAt(i)] << 2) | (revLookup[b64.charCodeAt(i + 1)] >> 4)
- arr[L++] = tmp & 0xFF
- } else if (placeHolders === 1) {
- tmp = (revLookup[b64.charCodeAt(i)] << 10) | (revLookup[b64.charCodeAt(i + 1)] << 4) | (revLookup[b64.charCodeAt(i + 2)] >> 2)
- arr[L++] = (tmp >> 8) & 0xFF
- arr[L++] = tmp & 0xFF
- }
-
- return arr
-}
-
-function tripletToBase64 (num) {
- return lookup[num >> 18 & 0x3F] + lookup[num >> 12 & 0x3F] + lookup[num >> 6 & 0x3F] + lookup[num & 0x3F]
-}
-
-function encodeChunk (uint8, start, end) {
- var tmp
- var output = []
- for (var i = start; i < end; i += 3) {
- tmp = (uint8[i] << 16) + (uint8[i + 1] << 8) + (uint8[i + 2])
- output.push(tripletToBase64(tmp))
- }
- return output.join('')
-}
-
-function fromByteArray (uint8) {
- var tmp
- var len = uint8.length
- var extraBytes = len % 3 // if we have 1 byte left, pad 2 bytes
- var output = ''
- var parts = []
- var maxChunkLength = 16383 // must be multiple of 3
-
- // go through the array every three bytes, we'll deal with trailing stuff later
- for (var i = 0, len2 = len - extraBytes; i < len2; i += maxChunkLength) {
- parts.push(encodeChunk(uint8, i, (i + maxChunkLength) > len2 ? len2 : (i + maxChunkLength)))
- }
-
- // pad the end with zeros, but make sure to not forget the extra bytes
- if (extraBytes === 1) {
- tmp = uint8[len - 1]
- output += lookup[tmp >> 2]
- output += lookup[(tmp << 4) & 0x3F]
- output += '=='
- } else if (extraBytes === 2) {
- tmp = (uint8[len - 2] << 8) + (uint8[len - 1])
- output += lookup[tmp >> 10]
- output += lookup[(tmp >> 4) & 0x3F]
- output += lookup[(tmp << 2) & 0x3F]
- output += '='
- }
-
- parts.push(output)
-
- return parts.join('')
-}
-
-
-/***/ }),
-/* 10 */
-/***/ (function(module, exports, __webpack_require__) {
-
-"use strict";
-/* WEBPACK VAR INJECTION */(function(global) {/*!
- * The buffer module from node.js, for the browser.
- *
- * @author Feross Aboukhadijeh <feross@feross.org> <http://feross.org>
- * @license MIT
- */
-/* eslint-disable no-proto */
-
-
-
-var base64 = __webpack_require__(9)
-var ieee754 = __webpack_require__(13)
-var isArray = __webpack_require__(14)
-
-exports.Buffer = Buffer
-exports.SlowBuffer = SlowBuffer
-exports.INSPECT_MAX_BYTES = 50
-
-/**
- * If `Buffer.TYPED_ARRAY_SUPPORT`:
- * === true Use Uint8Array implementation (fastest)
- * === false Use Object implementation (most compatible, even IE6)
- *
- * Browsers that support typed arrays are IE 10+, Firefox 4+, Chrome 7+, Safari 5.1+,
- * Opera 11.6+, iOS 4.2+.
- *
- * Due to various browser bugs, sometimes the Object implementation will be used even
- * when the browser supports typed arrays.
- *
- * Note:
- *
- * - Firefox 4-29 lacks support for adding new properties to `Uint8Array` instances,
- * See: https://bugzilla.mozilla.org/show_bug.cgi?id=695438.
- *
- * - Chrome 9-10 is missing the `TypedArray.prototype.subarray` function.
- *
- * - IE10 has a broken `TypedArray.prototype.subarray` function which returns arrays of
- * incorrect length in some situations.
-
- * We detect these buggy browsers and set `Buffer.TYPED_ARRAY_SUPPORT` to `false` so they
- * get the Object implementation, which is slower but behaves correctly.
- */
-Buffer.TYPED_ARRAY_SUPPORT = global.TYPED_ARRAY_SUPPORT !== undefined
- ? global.TYPED_ARRAY_SUPPORT
- : typedArraySupport()
-
-/*
- * Export kMaxLength after typed array support is determined.
- */
-exports.kMaxLength = kMaxLength()
-
-function typedArraySupport () {
- try {
- var arr = new Uint8Array(1)
- arr.__proto__ = {__proto__: Uint8Array.prototype, foo: function () { return 42 }}
- return arr.foo() === 42 && // typed array instances can be augmented
- typeof arr.subarray === 'function' && // chrome 9-10 lack `subarray`
- arr.subarray(1, 1).byteLength === 0 // ie10 has broken `subarray`
- } catch (e) {
- return false
- }
-}
-
-function kMaxLength () {
- return Buffer.TYPED_ARRAY_SUPPORT
- ? 0x7fffffff
- : 0x3fffffff
-}
-
-function createBuffer (that, length) {
- if (kMaxLength() < length) {
- throw new RangeError('Invalid typed array length')
- }
- if (Buffer.TYPED_ARRAY_SUPPORT) {
- // Return an augmented `Uint8Array` instance, for best performance
- that = new Uint8Array(length)
- that.__proto__ = Buffer.prototype
- } else {
- // Fallback: Return an object instance of the Buffer class
- if (that === null) {
- that = new Buffer(length)
- }
- that.length = length
- }
-
- return that
-}
-
-/**
- * The Buffer constructor returns instances of `Uint8Array` that have their
- * prototype changed to `Buffer.prototype`. Furthermore, `Buffer` is a subclass of
- * `Uint8Array`, so the returned instances will have all the node `Buffer` methods
- * and the `Uint8Array` methods. Square bracket notation works as expected -- it
- * returns a single octet.
- *
- * The `Uint8Array` prototype remains unmodified.
- */
-
-function Buffer (arg, encodingOrOffset, length) {
- if (!Buffer.TYPED_ARRAY_SUPPORT && !(this instanceof Buffer)) {
- return new Buffer(arg, encodingOrOffset, length)
- }
-
- // Common case.
- if (typeof arg === 'number') {
- if (typeof encodingOrOffset === 'string') {
- throw new Error(
- 'If encoding is specified then the first argument must be a string'
- )
- }
- return allocUnsafe(this, arg)
- }
- return from(this, arg, encodingOrOffset, length)
-}
-
-Buffer.poolSize = 8192 // not used by this implementation
-
-// TODO: Legacy, not needed anymore. Remove in next major version.
-Buffer._augment = function (arr) {
- arr.__proto__ = Buffer.prototype
- return arr
-}
-
-function from (that, value, encodingOrOffset, length) {
- if (typeof value === 'number') {
- throw new TypeError('"value" argument must not be a number')
- }
-
- if (typeof ArrayBuffer !== 'undefined' && value instanceof ArrayBuffer) {
- return fromArrayBuffer(that, value, encodingOrOffset, length)
- }
-
- if (typeof value === 'string') {
- return fromString(that, value, encodingOrOffset)
- }
-
- return fromObject(that, value)
-}
-
-/**
- * Functionally equivalent to Buffer(arg, encoding) but throws a TypeError
- * if value is a number.
- * Buffer.from(str[, encoding])
- * Buffer.from(array)
- * Buffer.from(buffer)
- * Buffer.from(arrayBuffer[, byteOffset[, length]])
- **/
-Buffer.from = function (value, encodingOrOffset, length) {
- return from(null, value, encodingOrOffset, length)
-}
-
-if (Buffer.TYPED_ARRAY_SUPPORT) {
- Buffer.prototype.__proto__ = Uint8Array.prototype
- Buffer.__proto__ = Uint8Array
- if (typeof Symbol !== 'undefined' && Symbol.species &&
- Buffer[Symbol.species] === Buffer) {
- // Fix subarray() in ES2016. See: https://github.com/feross/buffer/pull/97
- Object.defineProperty(Buffer, Symbol.species, {
- value: null,
- configurable: true
- })
- }
-}
-
-function assertSize (size) {
- if (typeof size !== 'number') {
- throw new TypeError('"size" argument must be a number')
- } else if (size < 0) {
- throw new RangeError('"size" argument must not be negative')
- }
-}
-
-function alloc (that, size, fill, encoding) {
- assertSize(size)
- if (size <= 0) {
- return createBuffer(that, size)
- }
- if (fill !== undefined) {
- // Only pay attention to encoding if it's a string. This
- // prevents accidentally sending in a number that would
- // be interpretted as a start offset.
- return typeof encoding === 'string'
- ? createBuffer(that, size).fill(fill, encoding)
- : createBuffer(that, size).fill(fill)
- }
- return createBuffer(that, size)
-}
-
-/**
- * Creates a new filled Buffer instance.
- * alloc(size[, fill[, encoding]])
- **/
-Buffer.alloc = function (size, fill, encoding) {
- return alloc(null, size, fill, encoding)
-}
-
-function allocUnsafe (that, size) {
- assertSize(size)
- that = createBuffer(that, size < 0 ? 0 : checked(size) | 0)
- if (!Buffer.TYPED_ARRAY_SUPPORT) {
- for (var i = 0; i < size; ++i) {
- that[i] = 0
- }
- }
- return that
-}
-
-/**
- * Equivalent to Buffer(num), by default creates a non-zero-filled Buffer instance.
- * */
-Buffer.allocUnsafe = function (size) {
- return allocUnsafe(null, size)
-}
-/**
- * Equivalent to SlowBuffer(num), by default creates a non-zero-filled Buffer instance.
- */
-Buffer.allocUnsafeSlow = function (size) {
- return allocUnsafe(null, size)
-}
-
-function fromString (that, string, encoding) {
- if (typeof encoding !== 'string' || encoding === '') {
- encoding = 'utf8'
- }
-
- if (!Buffer.isEncoding(encoding)) {
- throw new TypeError('"encoding" must be a valid string encoding')
- }
-
- var length = byteLength(string, encoding) | 0
- that = createBuffer(that, length)
-
- var actual = that.write(string, encoding)
-
- if (actual !== length) {
- // Writing a hex string, for example, that contains invalid characters will
- // cause everything after the first invalid character to be ignored. (e.g.
- // 'abxxcd' will be treated as 'ab')
- that = that.slice(0, actual)
- }
-
- return that
-}
-
-function fromArrayLike (that, array) {
- var length = array.length < 0 ? 0 : checked(array.length) | 0
- that = createBuffer(that, length)
- for (var i = 0; i < length; i += 1) {
- that[i] = array[i] & 255
- }
- return that
-}
-
-function fromArrayBuffer (that, array, byteOffset, length) {
- array.byteLength // this throws if `array` is not a valid ArrayBuffer
-
- if (byteOffset < 0 || array.byteLength < byteOffset) {
- throw new RangeError('\'offset\' is out of bounds')
- }
-
- if (array.byteLength < byteOffset + (length || 0)) {
- throw new RangeError('\'length\' is out of bounds')
- }
-
- if (byteOffset === undefined && length === undefined) {
- array = new Uint8Array(array)
- } else if (length === undefined) {
- array = new Uint8Array(array, byteOffset)
- } else {
- array = new Uint8Array(array, byteOffset, length)
- }
-
- if (Buffer.TYPED_ARRAY_SUPPORT) {
- // Return an augmented `Uint8Array` instance, for best performance
- that = array
- that.__proto__ = Buffer.prototype
- } else {
- // Fallback: Return an object instance of the Buffer class
- that = fromArrayLike(that, array)
- }
- return that
-}
-
-function fromObject (that, obj) {
- if (Buffer.isBuffer(obj)) {
- var len = checked(obj.length) | 0
- that = createBuffer(that, len)
-
- if (that.length === 0) {
- return that
- }
-
- obj.copy(that, 0, 0, len)
- return that
- }
-
- if (obj) {
- if ((typeof ArrayBuffer !== 'undefined' &&
- obj.buffer instanceof ArrayBuffer) || 'length' in obj) {
- if (typeof obj.length !== 'number' || isnan(obj.length)) {
- return createBuffer(that, 0)
- }
- return fromArrayLike(that, obj)
- }
-
- if (obj.type === 'Buffer' && isArray(obj.data)) {
- return fromArrayLike(that, obj.data)
- }
- }
-
- throw new TypeError('First argument must be a string, Buffer, ArrayBuffer, Array, or array-like object.')
-}
-
-function checked (length) {
- // Note: cannot use `length < kMaxLength()` here because that fails when
- // length is NaN (which is otherwise coerced to zero.)
- if (length >= kMaxLength()) {
- throw new RangeError('Attempt to allocate Buffer larger than maximum ' +
- 'size: 0x' + kMaxLength().toString(16) + ' bytes')
- }
- return length | 0
-}
-
-function SlowBuffer (length) {
- if (+length != length) { // eslint-disable-line eqeqeq
- length = 0
- }
- return Buffer.alloc(+length)
-}
-
-Buffer.isBuffer = function isBuffer (b) {
- return !!(b != null && b._isBuffer)
-}
-
-Buffer.compare = function compare (a, b) {
- if (!Buffer.isBuffer(a) || !Buffer.isBuffer(b)) {
- throw new TypeError('Arguments must be Buffers')
- }
-
- if (a === b) return 0
-
- var x = a.length
- var y = b.length
-
- for (var i = 0, len = Math.min(x, y); i < len; ++i) {
- if (a[i] !== b[i]) {
- x = a[i]
- y = b[i]
- break
- }
- }
-
- if (x < y) return -1
- if (y < x) return 1
- return 0
-}
-
-Buffer.isEncoding = function isEncoding (encoding) {
- switch (String(encoding).toLowerCase()) {
- case 'hex':
- case 'utf8':
- case 'utf-8':
- case 'ascii':
- case 'latin1':
- case 'binary':
- case 'base64':
- case 'ucs2':
- case 'ucs-2':
- case 'utf16le':
- case 'utf-16le':
- return true
- default:
- return false
- }
-}
-
-Buffer.concat = function concat (list, length) {
- if (!isArray(list)) {
- throw new TypeError('"list" argument must be an Array of Buffers')
- }
-
- if (list.length === 0) {
- return Buffer.alloc(0)
- }
-
- var i
- if (length === undefined) {
- length = 0
- for (i = 0; i < list.length; ++i) {
- length += list[i].length
- }
- }
-
- var buffer = Buffer.allocUnsafe(length)
- var pos = 0
- for (i = 0; i < list.length; ++i) {
- var buf = list[i]
- if (!Buffer.isBuffer(buf)) {
- throw new TypeError('"list" argument must be an Array of Buffers')
- }
- buf.copy(buffer, pos)
- pos += buf.length
- }
- return buffer
-}
-
-function byteLength (string, encoding) {
- if (Buffer.isBuffer(string)) {
- return string.length
- }
- if (typeof ArrayBuffer !== 'undefined' && typeof ArrayBuffer.isView === 'function' &&
- (ArrayBuffer.isView(string) || string instanceof ArrayBuffer)) {
- return string.byteLength
- }
- if (typeof string !== 'string') {
- string = '' + string
- }
-
- var len = string.length
- if (len === 0) return 0
-
- // Use a for loop to avoid recursion
- var loweredCase = false
- for (;;) {
- switch (encoding) {
- case 'ascii':
- case 'latin1':
- case 'binary':
- return len
- case 'utf8':
- case 'utf-8':
- case undefined:
- return utf8ToBytes(string).length
- case 'ucs2':
- case 'ucs-2':
- case 'utf16le':
- case 'utf-16le':
- return len * 2
- case 'hex':
- return len >>> 1
- case 'base64':
- return base64ToBytes(string).length
- default:
- if (loweredCase) return utf8ToBytes(string).length // assume utf8
- encoding = ('' + encoding).toLowerCase()
- loweredCase = true
- }
- }
-}
-Buffer.byteLength = byteLength
-
-function slowToString (encoding, start, end) {
- var loweredCase = false
-
- // No need to verify that "this.length <= MAX_UINT32" since it's a read-only
- // property of a typed array.
-
- // This behaves neither like String nor Uint8Array in that we set start/end
- // to their upper/lower bounds if the value passed is out of range.
- // undefined is handled specially as per ECMA-262 6th Edition,
- // Section 13.3.3.7 Runtime Semantics: KeyedBindingInitialization.
- if (start === undefined || start < 0) {
- start = 0
- }
- // Return early if start > this.length. Done here to prevent potential uint32
- // coercion fail below.
- if (start > this.length) {
- return ''
- }
-
- if (end === undefined || end > this.length) {
- end = this.length
- }
-
- if (end <= 0) {
- return ''
- }
-
- // Force coersion to uint32. This will also coerce falsey/NaN values to 0.
- end >>>= 0
- start >>>= 0
-
- if (end <= start) {
- return ''
- }
-
- if (!encoding) encoding = 'utf8'
-
- while (true) {
- switch (encoding) {
- case 'hex':
- return hexSlice(this, start, end)
-
- case 'utf8':
- case 'utf-8':
- return utf8Slice(this, start, end)
-
- case 'ascii':
- return asciiSlice(this, start, end)
-
- case 'latin1':
- case 'binary':
- return latin1Slice(this, start, end)
-
- case 'base64':
- return base64Slice(this, start, end)
-
- case 'ucs2':
- case 'ucs-2':
- case 'utf16le':
- case 'utf-16le':
- return utf16leSlice(this, start, end)
-
- default:
- if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding)
- encoding = (encoding + '').toLowerCase()
- loweredCase = true
- }
- }
-}
-
-// The property is used by `Buffer.isBuffer` and `is-buffer` (in Safari 5-7) to detect
-// Buffer instances.
-Buffer.prototype._isBuffer = true
-
-function swap (b, n, m) {
- var i = b[n]
- b[n] = b[m]
- b[m] = i
-}
-
-Buffer.prototype.swap16 = function swap16 () {
- var len = this.length
- if (len % 2 !== 0) {
- throw new RangeError('Buffer size must be a multiple of 16-bits')
- }
- for (var i = 0; i < len; i += 2) {
- swap(this, i, i + 1)
- }
- return this
-}
-
-Buffer.prototype.swap32 = function swap32 () {
- var len = this.length
- if (len % 4 !== 0) {
- throw new RangeError('Buffer size must be a multiple of 32-bits')
- }
- for (var i = 0; i < len; i += 4) {
- swap(this, i, i + 3)
- swap(this, i + 1, i + 2)
- }
- return this
-}
-
-Buffer.prototype.swap64 = function swap64 () {
- var len = this.length
- if (len % 8 !== 0) {
- throw new RangeError('Buffer size must be a multiple of 64-bits')
- }
- for (var i = 0; i < len; i += 8) {
- swap(this, i, i + 7)
- swap(this, i + 1, i + 6)
- swap(this, i + 2, i + 5)
- swap(this, i + 3, i + 4)
- }
- return this
-}
-
-Buffer.prototype.toString = function toString () {
- var length = this.length | 0
- if (length === 0) return ''
- if (arguments.length === 0) return utf8Slice(this, 0, length)
- return slowToString.apply(this, arguments)
-}
-
-Buffer.prototype.equals = function equals (b) {
- if (!Buffer.isBuffer(b)) throw new TypeError('Argument must be a Buffer')
- if (this === b) return true
- return Buffer.compare(this, b) === 0
-}
-
-Buffer.prototype.inspect = function inspect () {
- var str = ''
- var max = exports.INSPECT_MAX_BYTES
- if (this.length > 0) {
- str = this.toString('hex', 0, max).match(/.{2}/g).join(' ')
- if (this.length > max) str += ' ... '
- }
- return '<Buffer ' + str + '>'
-}
-
-Buffer.prototype.compare = function compare (target, start, end, thisStart, thisEnd) {
- if (!Buffer.isBuffer(target)) {
- throw new TypeError('Argument must be a Buffer')
- }
-
- if (start === undefined) {
- start = 0
- }
- if (end === undefined) {
- end = target ? target.length : 0
- }
- if (thisStart === undefined) {
- thisStart = 0
- }
- if (thisEnd === undefined) {
- thisEnd = this.length
- }
-
- if (start < 0 || end > target.length || thisStart < 0 || thisEnd > this.length) {
- throw new RangeError('out of range index')
- }
-
- if (thisStart >= thisEnd && start >= end) {
- return 0
- }
- if (thisStart >= thisEnd) {
- return -1
- }
- if (start >= end) {
- return 1
- }
-
- start >>>= 0
- end >>>= 0
- thisStart >>>= 0
- thisEnd >>>= 0
-
- if (this === target) return 0
-
- var x = thisEnd - thisStart
- var y = end - start
- var len = Math.min(x, y)
-
- var thisCopy = this.slice(thisStart, thisEnd)
- var targetCopy = target.slice(start, end)
-
- for (var i = 0; i < len; ++i) {
- if (thisCopy[i] !== targetCopy[i]) {
- x = thisCopy[i]
- y = targetCopy[i]
- break
- }
- }
-
- if (x < y) return -1
- if (y < x) return 1
- return 0
-}
-
-// Finds either the first index of `val` in `buffer` at offset >= `byteOffset`,
-// OR the last index of `val` in `buffer` at offset <= `byteOffset`.
-//
-// Arguments:
-// - buffer - a Buffer to search
-// - val - a string, Buffer, or number
-// - byteOffset - an index into `buffer`; will be clamped to an int32
-// - encoding - an optional encoding, relevant is val is a string
-// - dir - true for indexOf, false for lastIndexOf
-function bidirectionalIndexOf (buffer, val, byteOffset, encoding, dir) {
- // Empty buffer means no match
- if (buffer.length === 0) return -1
-
- // Normalize byteOffset
- if (typeof byteOffset === 'string') {
- encoding = byteOffset
- byteOffset = 0
- } else if (byteOffset > 0x7fffffff) {
- byteOffset = 0x7fffffff
- } else if (byteOffset < -0x80000000) {
- byteOffset = -0x80000000
- }
- byteOffset = +byteOffset // Coerce to Number.
- if (isNaN(byteOffset)) {
- // byteOffset: it it's undefined, null, NaN, "foo", etc, search whole buffer
- byteOffset = dir ? 0 : (buffer.length - 1)
- }
-
- // Normalize byteOffset: negative offsets start from the end of the buffer
- if (byteOffset < 0) byteOffset = buffer.length + byteOffset
- if (byteOffset >= buffer.length) {
- if (dir) return -1
- else byteOffset = buffer.length - 1
- } else if (byteOffset < 0) {
- if (dir) byteOffset = 0
- else return -1
- }
-
- // Normalize val
- if (typeof val === 'string') {
- val = Buffer.from(val, encoding)
- }
-
- // Finally, search either indexOf (if dir is true) or lastIndexOf
- if (Buffer.isBuffer(val)) {
- // Special case: looking for empty string/buffer always fails
- if (val.length === 0) {
- return -1
- }
- return arrayIndexOf(buffer, val, byteOffset, encoding, dir)
- } else if (typeof val === 'number') {
- val = val & 0xFF // Search for a byte value [0-255]
- if (Buffer.TYPED_ARRAY_SUPPORT &&
- typeof Uint8Array.prototype.indexOf === 'function') {
- if (dir) {
- return Uint8Array.prototype.indexOf.call(buffer, val, byteOffset)
- } else {
- return Uint8Array.prototype.lastIndexOf.call(buffer, val, byteOffset)
- }
- }
- return arrayIndexOf(buffer, [ val ], byteOffset, encoding, dir)
- }
-
- throw new TypeError('val must be string, number or Buffer')
-}
-
-function arrayIndexOf (arr, val, byteOffset, encoding, dir) {
- var indexSize = 1
- var arrLength = arr.length
- var valLength = val.length
-
- if (encoding !== undefined) {
- encoding = String(encoding).toLowerCase()
- if (encoding === 'ucs2' || encoding === 'ucs-2' ||
- encoding === 'utf16le' || encoding === 'utf-16le') {
- if (arr.length < 2 || val.length < 2) {
- return -1
- }
- indexSize = 2
- arrLength /= 2
- valLength /= 2
- byteOffset /= 2
- }
- }
-
- function read (buf, i) {
- if (indexSize === 1) {
- return buf[i]
- } else {
- return buf.readUInt16BE(i * indexSize)
- }
- }
-
- var i
- if (dir) {
- var foundIndex = -1
- for (i = byteOffset; i < arrLength; i++) {
- if (read(arr, i) === read(val, foundIndex === -1 ? 0 : i - foundIndex)) {
- if (foundIndex === -1) foundIndex = i
- if (i - foundIndex + 1 === valLength) return foundIndex * indexSize
- } else {
- if (foundIndex !== -1) i -= i - foundIndex
- foundIndex = -1
- }
- }
- } else {
- if (byteOffset + valLength > arrLength) byteOffset = arrLength - valLength
- for (i = byteOffset; i >= 0; i--) {
- var found = true
- for (var j = 0; j < valLength; j++) {
- if (read(arr, i + j) !== read(val, j)) {
- found = false
- break
- }
- }
- if (found) return i
- }
- }
-
- return -1
-}
-
-Buffer.prototype.includes = function includes (val, byteOffset, encoding) {
- return this.indexOf(val, byteOffset, encoding) !== -1
-}
-
-Buffer.prototype.indexOf = function indexOf (val, byteOffset, encoding) {
- return bidirectionalIndexOf(this, val, byteOffset, encoding, true)
-}
-
-Buffer.prototype.lastIndexOf = function lastIndexOf (val, byteOffset, encoding) {
- return bidirectionalIndexOf(this, val, byteOffset, encoding, false)
-}
-
-function hexWrite (buf, string, offset, length) {
- offset = Number(offset) || 0
- var remaining = buf.length - offset
- if (!length) {
- length = remaining
- } else {
- length = Number(length)
- if (length > remaining) {
- length = remaining
- }
- }
-
- // must be an even number of digits
- var strLen = string.length
- if (strLen % 2 !== 0) throw new TypeError('Invalid hex string')
-
- if (length > strLen / 2) {
- length = strLen / 2
- }
- for (var i = 0; i < length; ++i) {
- var parsed = parseInt(string.substr(i * 2, 2), 16)
- if (isNaN(parsed)) return i
- buf[offset + i] = parsed
- }
- return i
-}
-
-function utf8Write (buf, string, offset, length) {
- return blitBuffer(utf8ToBytes(string, buf.length - offset), buf, offset, length)
-}
-
-function asciiWrite (buf, string, offset, length) {
- return blitBuffer(asciiToBytes(string), buf, offset, length)
-}
-
-function latin1Write (buf, string, offset, length) {
- return asciiWrite(buf, string, offset, length)
-}
-
-function base64Write (buf, string, offset, length) {
- return blitBuffer(base64ToBytes(string), buf, offset, length)
-}
-
-function ucs2Write (buf, string, offset, length) {
- return blitBuffer(utf16leToBytes(string, buf.length - offset), buf, offset, length)
-}
-
-Buffer.prototype.write = function write (string, offset, length, encoding) {
- // Buffer#write(string)
- if (offset === undefined) {
- encoding = 'utf8'
- length = this.length
- offset = 0
- // Buffer#write(string, encoding)
- } else if (length === undefined && typeof offset === 'string') {
- encoding = offset
- length = this.length
- offset = 0
- // Buffer#write(string, offset[, length][, encoding])
- } else if (isFinite(offset)) {
- offset = offset | 0
- if (isFinite(length)) {
- length = length | 0
- if (encoding === undefined) encoding = 'utf8'
- } else {
- encoding = length
- length = undefined
- }
- // legacy write(string, encoding, offset, length) - remove in v0.13
- } else {
- throw new Error(
- 'Buffer.write(string, encoding, offset[, length]) is no longer supported'
- )
- }
-
- var remaining = this.length - offset
- if (length === undefined || length > remaining) length = remaining
-
- if ((string.length > 0 && (length < 0 || offset < 0)) || offset > this.length) {
- throw new RangeError('Attempt to write outside buffer bounds')
- }
-
- if (!encoding) encoding = 'utf8'
-
- var loweredCase = false
- for (;;) {
- switch (encoding) {
- case 'hex':
- return hexWrite(this, string, offset, length)
-
- case 'utf8':
- case 'utf-8':
- return utf8Write(this, string, offset, length)
-
- case 'ascii':
- return asciiWrite(this, string, offset, length)
-
- case 'latin1':
- case 'binary':
- return latin1Write(this, string, offset, length)
-
- case 'base64':
- // Warning: maxLength not taken into account in base64Write
- return base64Write(this, string, offset, length)
-
- case 'ucs2':
- case 'ucs-2':
- case 'utf16le':
- case 'utf-16le':
- return ucs2Write(this, string, offset, length)
-
- default:
- if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding)
- encoding = ('' + encoding).toLowerCase()
- loweredCase = true
- }
- }
-}
-
-Buffer.prototype.toJSON = function toJSON () {
- return {
- type: 'Buffer',
- data: Array.prototype.slice.call(this._arr || this, 0)
- }
-}
-
-function base64Slice (buf, start, end) {
- if (start === 0 && end === buf.length) {
- return base64.fromByteArray(buf)
- } else {
- return base64.fromByteArray(buf.slice(start, end))
- }
-}
-
-function utf8Slice (buf, start, end) {
- end = Math.min(buf.length, end)
- var res = []
-
- var i = start
- while (i < end) {
- var firstByte = buf[i]
- var codePoint = null
- var bytesPerSequence = (firstByte > 0xEF) ? 4
- : (firstByte > 0xDF) ? 3
- : (firstByte > 0xBF) ? 2
- : 1
-
- if (i + bytesPerSequence <= end) {
- var secondByte, thirdByte, fourthByte, tempCodePoint
-
- switch (bytesPerSequence) {
- case 1:
- if (firstByte < 0x80) {
- codePoint = firstByte
- }
- break
- case 2:
- secondByte = buf[i + 1]
- if ((secondByte & 0xC0) === 0x80) {
- tempCodePoint = (firstByte & 0x1F) << 0x6 | (secondByte & 0x3F)
- if (tempCodePoint > 0x7F) {
- codePoint = tempCodePoint
- }
- }
- break
- case 3:
- secondByte = buf[i + 1]
- thirdByte = buf[i + 2]
- if ((secondByte & 0xC0) === 0x80 && (thirdByte & 0xC0) === 0x80) {
- tempCodePoint = (firstByte & 0xF) << 0xC | (secondByte & 0x3F) << 0x6 | (thirdByte & 0x3F)
- if (tempCodePoint > 0x7FF && (tempCodePoint < 0xD800 || tempCodePoint > 0xDFFF)) {
- codePoint = tempCodePoint
- }
- }
- break
- case 4:
- secondByte = buf[i + 1]
- thirdByte = buf[i + 2]
- fourthByte = buf[i + 3]
- if ((secondByte & 0xC0) === 0x80 && (thirdByte & 0xC0) === 0x80 && (fourthByte & 0xC0) === 0x80) {
- tempCodePoint = (firstByte & 0xF) << 0x12 | (secondByte & 0x3F) << 0xC | (thirdByte & 0x3F) << 0x6 | (fourthByte & 0x3F)
- if (tempCodePoint > 0xFFFF && tempCodePoint < 0x110000) {
- codePoint = tempCodePoint
- }
- }
- }
- }
-
- if (codePoint === null) {
- // we did not generate a valid codePoint so insert a
- // replacement char (U+FFFD) and advance only 1 byte
- codePoint = 0xFFFD
- bytesPerSequence = 1
- } else if (codePoint > 0xFFFF) {
- // encode to utf16 (surrogate pair dance)
- codePoint -= 0x10000
- res.push(codePoint >>> 10 & 0x3FF | 0xD800)
- codePoint = 0xDC00 | codePoint & 0x3FF
- }
-
- res.push(codePoint)
- i += bytesPerSequence
- }
-
- return decodeCodePointsArray(res)
-}
-
-// Based on http://stackoverflow.com/a/22747272/680742, the browser with
-// the lowest limit is Chrome, with 0x10000 args.
-// We go 1 magnitude less, for safety
-var MAX_ARGUMENTS_LENGTH = 0x1000
-
-function decodeCodePointsArray (codePoints) {
- var len = codePoints.length
- if (len <= MAX_ARGUMENTS_LENGTH) {
- return String.fromCharCode.apply(String, codePoints) // avoid extra slice()
- }
-
- // Decode in chunks to avoid "call stack size exceeded".
- var res = ''
- var i = 0
- while (i < len) {
- res += String.fromCharCode.apply(
- String,
- codePoints.slice(i, i += MAX_ARGUMENTS_LENGTH)
- )
- }
- return res
-}
-
-function asciiSlice (buf, start, end) {
- var ret = ''
- end = Math.min(buf.length, end)
-
- for (var i = start; i < end; ++i) {
- ret += String.fromCharCode(buf[i] & 0x7F)
- }
- return ret
-}
-
-function latin1Slice (buf, start, end) {
- var ret = ''
- end = Math.min(buf.length, end)
-
- for (var i = start; i < end; ++i) {
- ret += String.fromCharCode(buf[i])
- }
- return ret
-}
-
-function hexSlice (buf, start, end) {
- var len = buf.length
-
- if (!start || start < 0) start = 0
- if (!end || end < 0 || end > len) end = len
-
- var out = ''
- for (var i = start; i < end; ++i) {
- out += toHex(buf[i])
- }
- return out
-}
-
-function utf16leSlice (buf, start, end) {
- var bytes = buf.slice(start, end)
- var res = ''
- for (var i = 0; i < bytes.length; i += 2) {
- res += String.fromCharCode(bytes[i] + bytes[i + 1] * 256)
- }
- return res
-}
-
-Buffer.prototype.slice = function slice (start, end) {
- var len = this.length
- start = ~~start
- end = end === undefined ? len : ~~end
-
- if (start < 0) {
- start += len
- if (start < 0) start = 0
- } else if (start > len) {
- start = len
- }
-
- if (end < 0) {
- end += len
- if (end < 0) end = 0
- } else if (end > len) {
- end = len
- }
-
- if (end < start) end = start
-
- var newBuf
- if (Buffer.TYPED_ARRAY_SUPPORT) {
- newBuf = this.subarray(start, end)
- newBuf.__proto__ = Buffer.prototype
- } else {
- var sliceLen = end - start
- newBuf = new Buffer(sliceLen, undefined)
- for (var i = 0; i < sliceLen; ++i) {
- newBuf[i] = this[i + start]
- }
- }
-
- return newBuf
-}
-
-/*
- * Need to make sure that buffer isn't trying to write out of bounds.
- */
-function checkOffset (offset, ext, length) {
- if ((offset % 1) !== 0 || offset < 0) throw new RangeError('offset is not uint')
- if (offset + ext > length) throw new RangeError('Trying to access beyond buffer length')
-}
-
-Buffer.prototype.readUIntLE = function readUIntLE (offset, byteLength, noAssert) {
- offset = offset | 0
- byteLength = byteLength | 0
- if (!noAssert) checkOffset(offset, byteLength, this.length)
-
- var val = this[offset]
- var mul = 1
- var i = 0
- while (++i < byteLength && (mul *= 0x100)) {
- val += this[offset + i] * mul
- }
-
- return val
-}
-
-Buffer.prototype.readUIntBE = function readUIntBE (offset, byteLength, noAssert) {
- offset = offset | 0
- byteLength = byteLength | 0
- if (!noAssert) {
- checkOffset(offset, byteLength, this.length)
- }
-
- var val = this[offset + --byteLength]
- var mul = 1
- while (byteLength > 0 && (mul *= 0x100)) {
- val += this[offset + --byteLength] * mul
- }
-
- return val
-}
-
-Buffer.prototype.readUInt8 = function readUInt8 (offset, noAssert) {
- if (!noAssert) checkOffset(offset, 1, this.length)
- return this[offset]
-}
-
-Buffer.prototype.readUInt16LE = function readUInt16LE (offset, noAssert) {
- if (!noAssert) checkOffset(offset, 2, this.length)
- return this[offset] | (this[offset + 1] << 8)
-}
-
-Buffer.prototype.readUInt16BE = function readUInt16BE (offset, noAssert) {
- if (!noAssert) checkOffset(offset, 2, this.length)
- return (this[offset] << 8) | this[offset + 1]
-}
-
-Buffer.prototype.readUInt32LE = function readUInt32LE (offset, noAssert) {
- if (!noAssert) checkOffset(offset, 4, this.length)
-
- return ((this[offset]) |
- (this[offset + 1] << 8) |
- (this[offset + 2] << 16)) +
- (this[offset + 3] * 0x1000000)
-}
-
-Buffer.prototype.readUInt32BE = function readUInt32BE (offset, noAssert) {
- if (!noAssert) checkOffset(offset, 4, this.length)
-
- return (this[offset] * 0x1000000) +
- ((this[offset + 1] << 16) |
- (this[offset + 2] << 8) |
- this[offset + 3])
-}
-
-Buffer.prototype.readIntLE = function readIntLE (offset, byteLength, noAssert) {
- offset = offset | 0
- byteLength = byteLength | 0
- if (!noAssert) checkOffset(offset, byteLength, this.length)
-
- var val = this[offset]
- var mul = 1
- var i = 0
- while (++i < byteLength && (mul *= 0x100)) {
- val += this[offset + i] * mul
- }
- mul *= 0x80
-
- if (val >= mul) val -= Math.pow(2, 8 * byteLength)
-
- return val
-}
-
-Buffer.prototype.readIntBE = function readIntBE (offset, byteLength, noAssert) {
- offset = offset | 0
- byteLength = byteLength | 0
- if (!noAssert) checkOffset(offset, byteLength, this.length)
-
- var i = byteLength
- var mul = 1
- var val = this[offset + --i]
- while (i > 0 && (mul *= 0x100)) {
- val += this[offset + --i] * mul
- }
- mul *= 0x80
-
- if (val >= mul) val -= Math.pow(2, 8 * byteLength)
-
- return val
-}
-
-Buffer.prototype.readInt8 = function readInt8 (offset, noAssert) {
- if (!noAssert) checkOffset(offset, 1, this.length)
- if (!(this[offset] & 0x80)) return (this[offset])
- return ((0xff - this[offset] + 1) * -1)
-}
-
-Buffer.prototype.readInt16LE = function readInt16LE (offset, noAssert) {
- if (!noAssert) checkOffset(offset, 2, this.length)
- var val = this[offset] | (this[offset + 1] << 8)
- return (val & 0x8000) ? val | 0xFFFF0000 : val
-}
-
-Buffer.prototype.readInt16BE = function readInt16BE (offset, noAssert) {
- if (!noAssert) checkOffset(offset, 2, this.length)
- var val = this[offset + 1] | (this[offset] << 8)
- return (val & 0x8000) ? val | 0xFFFF0000 : val
-}
-
-Buffer.prototype.readInt32LE = function readInt32LE (offset, noAssert) {
- if (!noAssert) checkOffset(offset, 4, this.length)
-
- return (this[offset]) |
- (this[offset + 1] << 8) |
- (this[offset + 2] << 16) |
- (this[offset + 3] << 24)
-}
-
-Buffer.prototype.readInt32BE = function readInt32BE (offset, noAssert) {
- if (!noAssert) checkOffset(offset, 4, this.length)
-
- return (this[offset] << 24) |
- (this[offset + 1] << 16) |
- (this[offset + 2] << 8) |
- (this[offset + 3])
-}
-
-Buffer.prototype.readFloatLE = function readFloatLE (offset, noAssert) {
- if (!noAssert) checkOffset(offset, 4, this.length)
- return ieee754.read(this, offset, true, 23, 4)
-}
-
-Buffer.prototype.readFloatBE = function readFloatBE (offset, noAssert) {
- if (!noAssert) checkOffset(offset, 4, this.length)
- return ieee754.read(this, offset, false, 23, 4)
-}
-
-Buffer.prototype.readDoubleLE = function readDoubleLE (offset, noAssert) {
- if (!noAssert) checkOffset(offset, 8, this.length)
- return ieee754.read(this, offset, true, 52, 8)
-}
-
-Buffer.prototype.readDoubleBE = function readDoubleBE (offset, noAssert) {
- if (!noAssert) checkOffset(offset, 8, this.length)
- return ieee754.read(this, offset, false, 52, 8)
-}
-
-function checkInt (buf, value, offset, ext, max, min) {
- if (!Buffer.isBuffer(buf)) throw new TypeError('"buffer" argument must be a Buffer instance')
- if (value > max || value < min) throw new RangeError('"value" argument is out of bounds')
- if (offset + ext > buf.length) throw new RangeError('Index out of range')
-}
-
-Buffer.prototype.writeUIntLE = function writeUIntLE (value, offset, byteLength, noAssert) {
- value = +value
- offset = offset | 0
- byteLength = byteLength | 0
- if (!noAssert) {
- var maxBytes = Math.pow(2, 8 * byteLength) - 1
- checkInt(this, value, offset, byteLength, maxBytes, 0)
- }
-
- var mul = 1
- var i = 0
- this[offset] = value & 0xFF
- while (++i < byteLength && (mul *= 0x100)) {
- this[offset + i] = (value / mul) & 0xFF
- }
-
- return offset + byteLength
-}
-
-Buffer.prototype.writeUIntBE = function writeUIntBE (value, offset, byteLength, noAssert) {
- value = +value
- offset = offset | 0
- byteLength = byteLength | 0
- if (!noAssert) {
- var maxBytes = Math.pow(2, 8 * byteLength) - 1
- checkInt(this, value, offset, byteLength, maxBytes, 0)
- }
-
- var i = byteLength - 1
- var mul = 1
- this[offset + i] = value & 0xFF
- while (--i >= 0 && (mul *= 0x100)) {
- this[offset + i] = (value / mul) & 0xFF
- }
-
- return offset + byteLength
-}
-
-Buffer.prototype.writeUInt8 = function writeUInt8 (value, offset, noAssert) {
- value = +value
- offset = offset | 0
- if (!noAssert) checkInt(this, value, offset, 1, 0xff, 0)
- if (!Buffer.TYPED_ARRAY_SUPPORT) value = Math.floor(value)
- this[offset] = (value & 0xff)
- return offset + 1
-}
-
-function objectWriteUInt16 (buf, value, offset, littleEndian) {
- if (value < 0) value = 0xffff + value + 1
- for (var i = 0, j = Math.min(buf.length - offset, 2); i < j; ++i) {
- buf[offset + i] = (value & (0xff << (8 * (littleEndian ? i : 1 - i)))) >>>
- (littleEndian ? i : 1 - i) * 8
- }
-}
-
-Buffer.prototype.writeUInt16LE = function writeUInt16LE (value, offset, noAssert) {
- value = +value
- offset = offset | 0
- if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0)
- if (Buffer.TYPED_ARRAY_SUPPORT) {
- this[offset] = (value & 0xff)
- this[offset + 1] = (value >>> 8)
- } else {
- objectWriteUInt16(this, value, offset, true)
- }
- return offset + 2
-}
-
-Buffer.prototype.writeUInt16BE = function writeUInt16BE (value, offset, noAssert) {
- value = +value
- offset = offset | 0
- if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0)
- if (Buffer.TYPED_ARRAY_SUPPORT) {
- this[offset] = (value >>> 8)
- this[offset + 1] = (value & 0xff)
- } else {
- objectWriteUInt16(this, value, offset, false)
- }
- return offset + 2
-}
-
-function objectWriteUInt32 (buf, value, offset, littleEndian) {
- if (value < 0) value = 0xffffffff + value + 1
- for (var i = 0, j = Math.min(buf.length - offset, 4); i < j; ++i) {
- buf[offset + i] = (value >>> (littleEndian ? i : 3 - i) * 8) & 0xff
- }
-}
-
-Buffer.prototype.writeUInt32LE = function writeUInt32LE (value, offset, noAssert) {
- value = +value
- offset = offset | 0
- if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0)
- if (Buffer.TYPED_ARRAY_SUPPORT) {
- this[offset + 3] = (value >>> 24)
- this[offset + 2] = (value >>> 16)
- this[offset + 1] = (value >>> 8)
- this[offset] = (value & 0xff)
- } else {
- objectWriteUInt32(this, value, offset, true)
- }
- return offset + 4
-}
-
-Buffer.prototype.writeUInt32BE = function writeUInt32BE (value, offset, noAssert) {
- value = +value
- offset = offset | 0
- if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0)
- if (Buffer.TYPED_ARRAY_SUPPORT) {
- this[offset] = (value >>> 24)
- this[offset + 1] = (value >>> 16)
- this[offset + 2] = (value >>> 8)
- this[offset + 3] = (value & 0xff)
- } else {
- objectWriteUInt32(this, value, offset, false)
- }
- return offset + 4
-}
-
-Buffer.prototype.writeIntLE = function writeIntLE (value, offset, byteLength, noAssert) {
- value = +value
- offset = offset | 0
- if (!noAssert) {
- var limit = Math.pow(2, 8 * byteLength - 1)
-
- checkInt(this, value, offset, byteLength, limit - 1, -limit)
- }
-
- var i = 0
- var mul = 1
- var sub = 0
- this[offset] = value & 0xFF
- while (++i < byteLength && (mul *= 0x100)) {
- if (value < 0 && sub === 0 && this[offset + i - 1] !== 0) {
- sub = 1
- }
- this[offset + i] = ((value / mul) >> 0) - sub & 0xFF
- }
-
- return offset + byteLength
-}
-
-Buffer.prototype.writeIntBE = function writeIntBE (value, offset, byteLength, noAssert) {
- value = +value
- offset = offset | 0
- if (!noAssert) {
- var limit = Math.pow(2, 8 * byteLength - 1)
-
- checkInt(this, value, offset, byteLength, limit - 1, -limit)
- }
-
- var i = byteLength - 1
- var mul = 1
- var sub = 0
- this[offset + i] = value & 0xFF
- while (--i >= 0 && (mul *= 0x100)) {
- if (value < 0 && sub === 0 && this[offset + i + 1] !== 0) {
- sub = 1
- }
- this[offset + i] = ((value / mul) >> 0) - sub & 0xFF
- }
-
- return offset + byteLength
-}
-
-Buffer.prototype.writeInt8 = function writeInt8 (value, offset, noAssert) {
- value = +value
- offset = offset | 0
- if (!noAssert) checkInt(this, value, offset, 1, 0x7f, -0x80)
- if (!Buffer.TYPED_ARRAY_SUPPORT) value = Math.floor(value)
- if (value < 0) value = 0xff + value + 1
- this[offset] = (value & 0xff)
- return offset + 1
-}
-
-Buffer.prototype.writeInt16LE = function writeInt16LE (value, offset, noAssert) {
- value = +value
- offset = offset | 0
- if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000)
- if (Buffer.TYPED_ARRAY_SUPPORT) {
- this[offset] = (value & 0xff)
- this[offset + 1] = (value >>> 8)
- } else {
- objectWriteUInt16(this, value, offset, true)
- }
- return offset + 2
-}
-
-Buffer.prototype.writeInt16BE = function writeInt16BE (value, offset, noAssert) {
- value = +value
- offset = offset | 0
- if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000)
- if (Buffer.TYPED_ARRAY_SUPPORT) {
- this[offset] = (value >>> 8)
- this[offset + 1] = (value & 0xff)
- } else {
- objectWriteUInt16(this, value, offset, false)
- }
- return offset + 2
-}
-
-Buffer.prototype.writeInt32LE = function writeInt32LE (value, offset, noAssert) {
- value = +value
- offset = offset | 0
- if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000)
- if (Buffer.TYPED_ARRAY_SUPPORT) {
- this[offset] = (value & 0xff)
- this[offset + 1] = (value >>> 8)
- this[offset + 2] = (value >>> 16)
- this[offset + 3] = (value >>> 24)
- } else {
- objectWriteUInt32(this, value, offset, true)
- }
- return offset + 4
-}
-
-Buffer.prototype.writeInt32BE = function writeInt32BE (value, offset, noAssert) {
- value = +value
- offset = offset | 0
- if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000)
- if (value < 0) value = 0xffffffff + value + 1
- if (Buffer.TYPED_ARRAY_SUPPORT) {
- this[offset] = (value >>> 24)
- this[offset + 1] = (value >>> 16)
- this[offset + 2] = (value >>> 8)
- this[offset + 3] = (value & 0xff)
- } else {
- objectWriteUInt32(this, value, offset, false)
- }
- return offset + 4
-}
-
-function checkIEEE754 (buf, value, offset, ext, max, min) {
- if (offset + ext > buf.length) throw new RangeError('Index out of range')
- if (offset < 0) throw new RangeError('Index out of range')
-}
-
-function writeFloat (buf, value, offset, littleEndian, noAssert) {
- if (!noAssert) {
- checkIEEE754(buf, value, offset, 4, 3.4028234663852886e+38, -3.4028234663852886e+38)
- }
- ieee754.write(buf, value, offset, littleEndian, 23, 4)
- return offset + 4
-}
-
-Buffer.prototype.writeFloatLE = function writeFloatLE (value, offset, noAssert) {
- return writeFloat(this, value, offset, true, noAssert)
-}
-
-Buffer.prototype.writeFloatBE = function writeFloatBE (value, offset, noAssert) {
- return writeFloat(this, value, offset, false, noAssert)
-}
-
-function writeDouble (buf, value, offset, littleEndian, noAssert) {
- if (!noAssert) {
- checkIEEE754(buf, value, offset, 8, 1.7976931348623157E+308, -1.7976931348623157E+308)
- }
- ieee754.write(buf, value, offset, littleEndian, 52, 8)
- return offset + 8
-}
-
-Buffer.prototype.writeDoubleLE = function writeDoubleLE (value, offset, noAssert) {
- return writeDouble(this, value, offset, true, noAssert)
-}
-
-Buffer.prototype.writeDoubleBE = function writeDoubleBE (value, offset, noAssert) {
- return writeDouble(this, value, offset, false, noAssert)
-}
-
-// copy(targetBuffer, targetStart=0, sourceStart=0, sourceEnd=buffer.length)
-Buffer.prototype.copy = function copy (target, targetStart, start, end) {
- if (!start) start = 0
- if (!end && end !== 0) end = this.length
- if (targetStart >= target.length) targetStart = target.length
- if (!targetStart) targetStart = 0
- if (end > 0 && end < start) end = start
-
- // Copy 0 bytes; we're done
- if (end === start) return 0
- if (target.length === 0 || this.length === 0) return 0
-
- // Fatal error conditions
- if (targetStart < 0) {
- throw new RangeError('targetStart out of bounds')
- }
- if (start < 0 || start >= this.length) throw new RangeError('sourceStart out of bounds')
- if (end < 0) throw new RangeError('sourceEnd out of bounds')
-
- // Are we oob?
- if (end > this.length) end = this.length
- if (target.length - targetStart < end - start) {
- end = target.length - targetStart + start
- }
-
- var len = end - start
- var i
-
- if (this === target && start < targetStart && targetStart < end) {
- // descending copy from end
- for (i = len - 1; i >= 0; --i) {
- target[i + targetStart] = this[i + start]
- }
- } else if (len < 1000 || !Buffer.TYPED_ARRAY_SUPPORT) {
- // ascending copy from start
- for (i = 0; i < len; ++i) {
- target[i + targetStart] = this[i + start]
- }
- } else {
- Uint8Array.prototype.set.call(
- target,
- this.subarray(start, start + len),
- targetStart
- )
- }
-
- return len
-}
-
-// Usage:
-// buffer.fill(number[, offset[, end]])
-// buffer.fill(buffer[, offset[, end]])
-// buffer.fill(string[, offset[, end]][, encoding])
-Buffer.prototype.fill = function fill (val, start, end, encoding) {
- // Handle string cases:
- if (typeof val === 'string') {
- if (typeof start === 'string') {
- encoding = start
- start = 0
- end = this.length
- } else if (typeof end === 'string') {
- encoding = end
- end = this.length
- }
- if (val.length === 1) {
- var code = val.charCodeAt(0)
- if (code < 256) {
- val = code
- }
- }
- if (encoding !== undefined && typeof encoding !== 'string') {
- throw new TypeError('encoding must be a string')
- }
- if (typeof encoding === 'string' && !Buffer.isEncoding(encoding)) {
- throw new TypeError('Unknown encoding: ' + encoding)
- }
- } else if (typeof val === 'number') {
- val = val & 255
- }
-
- // Invalid ranges are not set to a default, so can range check early.
- if (start < 0 || this.length < start || this.length < end) {
- throw new RangeError('Out of range index')
- }
-
- if (end <= start) {
- return this
- }
-
- start = start >>> 0
- end = end === undefined ? this.length : end >>> 0
-
- if (!val) val = 0
-
- var i
- if (typeof val === 'number') {
- for (i = start; i < end; ++i) {
- this[i] = val
- }
- } else {
- var bytes = Buffer.isBuffer(val)
- ? val
- : utf8ToBytes(new Buffer(val, encoding).toString())
- var len = bytes.length
- for (i = 0; i < end - start; ++i) {
- this[i + start] = bytes[i % len]
- }
- }
-
- return this
-}
-
-// HELPER FUNCTIONS
-// ================
-
-var INVALID_BASE64_RE = /[^+\/0-9A-Za-z-_]/g
-
-function base64clean (str) {
- // Node strips out invalid characters like \n and \t from the string, base64-js does not
- str = stringtrim(str).replace(INVALID_BASE64_RE, '')
- // Node converts strings with length < 2 to ''
- if (str.length < 2) return ''
- // Node allows for non-padded base64 strings (missing trailing ===), base64-js does not
- while (str.length % 4 !== 0) {
- str = str + '='
- }
- return str
-}
-
-function stringtrim (str) {
- if (str.trim) return str.trim()
- return str.replace(/^\s+|\s+$/g, '')
-}
-
-function toHex (n) {
- if (n < 16) return '0' + n.toString(16)
- return n.toString(16)
-}
-
-function utf8ToBytes (string, units) {
- units = units || Infinity
- var codePoint
- var length = string.length
- var leadSurrogate = null
- var bytes = []
-
- for (var i = 0; i < length; ++i) {
- codePoint = string.charCodeAt(i)
-
- // is surrogate component
- if (codePoint > 0xD7FF && codePoint < 0xE000) {
- // last char was a lead
- if (!leadSurrogate) {
- // no lead yet
- if (codePoint > 0xDBFF) {
- // unexpected trail
- if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)
- continue
- } else if (i + 1 === length) {
- // unpaired lead
- if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)
- continue
- }
-
- // valid lead
- leadSurrogate = codePoint
-
- continue
- }
-
- // 2 leads in a row
- if (codePoint < 0xDC00) {
- if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)
- leadSurrogate = codePoint
- continue
- }
-
- // valid surrogate pair
- codePoint = (leadSurrogate - 0xD800 << 10 | codePoint - 0xDC00) + 0x10000
- } else if (leadSurrogate) {
- // valid bmp char, but last char was a lead
- if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)
- }
-
- leadSurrogate = null
-
- // encode utf8
- if (codePoint < 0x80) {
- if ((units -= 1) < 0) break
- bytes.push(codePoint)
- } else if (codePoint < 0x800) {
- if ((units -= 2) < 0) break
- bytes.push(
- codePoint >> 0x6 | 0xC0,
- codePoint & 0x3F | 0x80
- )
- } else if (codePoint < 0x10000) {
- if ((units -= 3) < 0) break
- bytes.push(
- codePoint >> 0xC | 0xE0,
- codePoint >> 0x6 & 0x3F | 0x80,
- codePoint & 0x3F | 0x80
- )
- } else if (codePoint < 0x110000) {
- if ((units -= 4) < 0) break
- bytes.push(
- codePoint >> 0x12 | 0xF0,
- codePoint >> 0xC & 0x3F | 0x80,
- codePoint >> 0x6 & 0x3F | 0x80,
- codePoint & 0x3F | 0x80
- )
- } else {
- throw new Error('Invalid code point')
- }
- }
-
- return bytes
-}
-
-function asciiToBytes (str) {
- var byteArray = []
- for (var i = 0; i < str.length; ++i) {
- // Node's code seems to be doing this and not & 0x7F..
- byteArray.push(str.charCodeAt(i) & 0xFF)
- }
- return byteArray
-}
-
-function utf16leToBytes (str, units) {
- var c, hi, lo
- var byteArray = []
- for (var i = 0; i < str.length; ++i) {
- if ((units -= 2) < 0) break
-
- c = str.charCodeAt(i)
- hi = c >> 8
- lo = c % 256
- byteArray.push(lo)
- byteArray.push(hi)
- }
-
- return byteArray
-}
-
-function base64ToBytes (str) {
- return base64.toByteArray(base64clean(str))
-}
-
-function blitBuffer (src, dst, offset, length) {
- for (var i = 0; i < length; ++i) {
- if ((i + offset >= dst.length) || (i >= src.length)) break
- dst[i + offset] = src[i]
- }
- return i
-}
-
-function isnan (val) {
- return val !== val // eslint-disable-line no-self-compare
-}
-
-/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(22)))
-
-/***/ }),
-/* 11 */
-/***/ (function(module, exports, __webpack_require__) {
-
-exports = module.exports = __webpack_require__(3)(undefined);
-// imports
-
-
-// module
-exports.push([module.i, ".pdf-viewer{background:url(" + __webpack_require__(15) + ");display:flex;flex-flow:column nowrap}", ""]);
-
-// exports
-
-
-/***/ }),
-/* 12 */
-/***/ (function(module, exports, __webpack_require__) {
-
-exports = module.exports = __webpack_require__(3)(undefined);
-// imports
-
-
-// module
-exports.push([module.i, ".pdf-page{margin:8px auto 0;border-top:1px solid #ddd;border-bottom:1px solid #ddd;width:100%}.pdf-page:first-child{margin-top:0;border-top:0}.pdf-page:last-child{margin-bottom:0;border-bottom:0}", ""]);
-
-// exports
-
-
-/***/ }),
-/* 13 */
-/***/ (function(module, exports) {
-
-exports.read = function (buffer, offset, isLE, mLen, nBytes) {
- var e, m
- var eLen = nBytes * 8 - mLen - 1
- var eMax = (1 << eLen) - 1
- var eBias = eMax >> 1
- var nBits = -7
- var i = isLE ? (nBytes - 1) : 0
- var d = isLE ? -1 : 1
- var s = buffer[offset + i]
-
- i += d
-
- e = s & ((1 << (-nBits)) - 1)
- s >>= (-nBits)
- nBits += eLen
- for (; nBits > 0; e = e * 256 + buffer[offset + i], i += d, nBits -= 8) {}
-
- m = e & ((1 << (-nBits)) - 1)
- e >>= (-nBits)
- nBits += mLen
- for (; nBits > 0; m = m * 256 + buffer[offset + i], i += d, nBits -= 8) {}
-
- if (e === 0) {
- e = 1 - eBias
- } else if (e === eMax) {
- return m ? NaN : ((s ? -1 : 1) * Infinity)
- } else {
- m = m + Math.pow(2, mLen)
- e = e - eBias
- }
- return (s ? -1 : 1) * m * Math.pow(2, e - mLen)
-}
-
-exports.write = function (buffer, value, offset, isLE, mLen, nBytes) {
- var e, m, c
- var eLen = nBytes * 8 - mLen - 1
- var eMax = (1 << eLen) - 1
- var eBias = eMax >> 1
- var rt = (mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0)
- var i = isLE ? 0 : (nBytes - 1)
- var d = isLE ? 1 : -1
- var s = value < 0 || (value === 0 && 1 / value < 0) ? 1 : 0
-
- value = Math.abs(value)
-
- if (isNaN(value) || value === Infinity) {
- m = isNaN(value) ? 1 : 0
- e = eMax
- } else {
- e = Math.floor(Math.log(value) / Math.LN2)
- if (value * (c = Math.pow(2, -e)) < 1) {
- e--
- c *= 2
- }
- if (e + eBias >= 1) {
- value += rt / c
- } else {
- value += rt * Math.pow(2, 1 - eBias)
- }
- if (value * c >= 2) {
- e++
- c /= 2
- }
-
- if (e + eBias >= eMax) {
- m = 0
- e = eMax
- } else if (e + eBias >= 1) {
- m = (value * c - 1) * Math.pow(2, mLen)
- e = e + eBias
- } else {
- m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen)
- e = 0
- }
- }
-
- for (; mLen >= 8; buffer[offset + i] = m & 0xff, i += d, m /= 256, mLen -= 8) {}
-
- e = (e << mLen) | m
- eLen += mLen
- for (; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8) {}
-
- buffer[offset + i - d] |= s * 128
-}
-
-
-/***/ }),
-/* 14 */
-/***/ (function(module, exports) {
-
-var toString = {}.toString;
-
-module.exports = Array.isArray || function (arr) {
- return toString.call(arr) == '[object Array]';
-};
-
-
-/***/ }),
-/* 15 */
-/***/ (function(module, exports) {
-
-module.exports = "data:image/gif;base64,R0lGODlhCgAKAIAAAOXl5f///yH5BAAAAAAALAAAAAAKAAoAAAIRhB2ZhxoM3GMSykqd1VltzxQAOw=="
-
-/***/ }),
-/* 16 */
-/***/ (function(module, exports, __webpack_require__) {
-
-
-/* styles */
-__webpack_require__(20)
-
-var Component = __webpack_require__(4)(
- /* script */
- __webpack_require__(8),
- /* template */
- __webpack_require__(18),
- /* scopeId */
- null,
- /* cssModules */
- null
-)
-
-module.exports = Component.exports
-
-
-/***/ }),
-/* 17 */
-/***/ (function(module, exports) {
-
-module.exports={render:function (){var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;
- return (_vm.hasPDF) ? _c('div', {
- staticClass: "pdf-viewer"
- }, _vm._l((_vm.pages), function(page, index) {
- return _c('page', {
- key: index,
- attrs: {
- "v-if": !_vm.loading,
- "page": page,
- "number": index + 1
- }
- })
- })) : _vm._e()
-},staticRenderFns: []}
-
-/***/ }),
-/* 18 */
-/***/ (function(module, exports) {
-
-module.exports={render:function (){var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;
- return _c('canvas', {
- ref: "canvas",
- staticClass: "pdf-page",
- attrs: {
- "data-page": _vm.number
- }
- })
-},staticRenderFns: []}
-
-/***/ }),
-/* 19 */
-/***/ (function(module, exports, __webpack_require__) {
-
-// style-loader: Adds some css to the DOM by adding a <style> tag
-
-// load the styles
-var content = __webpack_require__(11);
-if(typeof content === 'string') content = [[module.i, content, '']];
-if(content.locals) module.exports = content.locals;
-// add the styles to the DOM
-var update = __webpack_require__(5)("59cf066f", content, true);
-// Hot Module Replacement
-if(false) {
- // When the styles change, update the <style> tags
- if(!content.locals) {
- module.hot.accept("!!../node_modules/css-loader/index.js?minimize!../node_modules/vue-loader/lib/style-compiler/index.js?{\"id\":\"data-v-7c7bed7e\",\"scoped\":false,\"hasInlineConfig\":false}!../node_modules/vue-loader/lib/selector.js?type=styles&index=0!./index.vue", function() {
- var newContent = require("!!../node_modules/css-loader/index.js?minimize!../node_modules/vue-loader/lib/style-compiler/index.js?{\"id\":\"data-v-7c7bed7e\",\"scoped\":false,\"hasInlineConfig\":false}!../node_modules/vue-loader/lib/selector.js?type=styles&index=0!./index.vue");
- if(typeof newContent === 'string') newContent = [[module.id, newContent, '']];
- update(newContent);
- });
- }
- // When the module is disposed, remove the <style> tags
- module.hot.dispose(function() { update(); });
-}
-
-/***/ }),
-/* 20 */
-/***/ (function(module, exports, __webpack_require__) {
-
-// style-loader: Adds some css to the DOM by adding a <style> tag
-
-// load the styles
-var content = __webpack_require__(12);
-if(typeof content === 'string') content = [[module.i, content, '']];
-if(content.locals) module.exports = content.locals;
-// add the styles to the DOM
-var update = __webpack_require__(5)("09f1e2d8", content, true);
-// Hot Module Replacement
-if(false) {
- // When the styles change, update the <style> tags
- if(!content.locals) {
- module.hot.accept("!!../../node_modules/css-loader/index.js?minimize!../../node_modules/vue-loader/lib/style-compiler/index.js?{\"id\":\"data-v-7e912b1a\",\"scoped\":false,\"hasInlineConfig\":false}!../../node_modules/vue-loader/lib/selector.js?type=styles&index=0!./index.vue", function() {
- var newContent = require("!!../../node_modules/css-loader/index.js?minimize!../../node_modules/vue-loader/lib/style-compiler/index.js?{\"id\":\"data-v-7e912b1a\",\"scoped\":false,\"hasInlineConfig\":false}!../../node_modules/vue-loader/lib/selector.js?type=styles&index=0!./index.vue");
- if(typeof newContent === 'string') newContent = [[module.id, newContent, '']];
- update(newContent);
- });
- }
- // When the module is disposed, remove the <style> tags
- module.hot.dispose(function() { update(); });
-}
-
-/***/ }),
-/* 21 */
-/***/ (function(module, exports) {
-
-/**
- * Translates the list format produced by css-loader into something
- * easier to manipulate.
- */
-module.exports = function listToStyles (parentId, list) {
- var styles = []
- var newStyles = {}
- for (var i = 0; i < list.length; i++) {
- var item = list[i]
- var id = item[0]
- var css = item[1]
- var media = item[2]
- var sourceMap = item[3]
- var part = {
- id: parentId + ':' + i,
- css: css,
- media: media,
- sourceMap: sourceMap
- }
- if (!newStyles[id]) {
- styles.push(newStyles[id] = { id: id, parts: [part] })
- } else {
- newStyles[id].parts.push(part)
- }
- }
- return styles
-}
-
-
-/***/ }),
-/* 22 */
-/***/ (function(module, exports) {
-
-var g;
-
-// This works in non-strict mode
-g = (function() {
- return this;
-})();
-
-try {
- // This works if eval is allowed (see CSP)
- g = g || Function("return this")() || (1,eval)("this");
-} catch(e) {
- // This works if the window reference is available
- if(typeof window === "object")
- g = window;
-}
-
-// g can still be undefined, but nothing to do about it...
-// We return undefined, instead of nothing here, so it's
-// easier to handle this case. if(!global) { ...}
-
-module.exports = g;
-
-
-/***/ }),
-/* 23 */
-/***/ (function(module, exports, __webpack_require__) {
-
-"use strict";
-
-
-var PDF = __webpack_require__(6);
-var pdfjsLib = __webpack_require__(2);
-
-module.exports = {
- install: function install(_vue) {
- var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
-
- pdfjsLib.PDFJS.workerSrc = options.workerSrc || '';
- _vue.component('pdf-lab', PDF);
- }
-};
-
-/***/ })
-/******/ ]);
}); \ No newline at end of file
diff --git a/vendor/assets/javascripts/pdf.min.js b/vendor/assets/javascripts/pdf.min.js
new file mode 100755
index 00000000000..271a471fd49
--- /dev/null
+++ b/vendor/assets/javascripts/pdf.min.js
@@ -0,0 +1,6 @@
+!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define("pdfjs-dist/build/pdf",[],e):"object"==typeof exports?exports["pdfjs-dist/build/pdf"]=e():t["pdfjs-dist/build/pdf"]=t.pdfjsDistBuildPdf=e()}(this,function(){return function(t){function e(r){if(n[r])return n[r].exports;var i=n[r]={i:r,l:!1,exports:{}};return t[r].call(i.exports,i,i.exports,e),i.l=!0,i.exports}var n={};return e.m=t,e.c=n,e.i=function(t){return t},e.d=function(t,n,r){e.o(t,n)||Object.defineProperty(t,n,{configurable:!1,enumerable:!0,get:r})},e.n=function(t){var n=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(n,"a",n),n},e.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},e.p="",e(e.s=13)}([function(t,e,n){"use strict";(function(t){function r(t){nt=t}function i(){return nt}function a(t){nt>=$.infos&&console.log("Info: "+t)}function s(t){nt>=$.warnings&&console.log("Warning: "+t)}function o(t){console.log("Deprecated API usage: "+t)}function c(t){throw nt>=$.errors&&(console.log("Error: "+t),console.log(l())),new Error(t)}function l(){try{throw new Error}catch(t){return t.stack?t.stack.split("\n").slice(2).join("\n"):""}}function h(t,e){t||c(e)}function u(t,e){try{var n=new URL(t);if(!n.origin||"null"===n.origin)return!1}catch(t){return!1}var r=new URL(e,n);return n.origin===r.origin}function d(t){if(!t)return!1;switch(t.protocol){case"http:":case"https:":case"ftp:":case"mailto:":case"tel:":return!0;default:return!1}}function f(t,e){if(!t)return null;try{var n=e?new URL(t,e):new URL(t);if(d(n))return n}catch(t){}return null}function p(t,e,n){return Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!1}),n}function g(t){var e;return function(){return t&&(e=Object.create(null),t(e),t=null),e}}function m(t){return"string"!=typeof t?(s("The argument for removeNullCharacters must be a string."),t):t.replace(ft,"")}function A(t){h(null!==t&&"object"==typeof t&&void 0!==t.length,"Invalid argument for bytesToString");var e=t.length;if(e<8192)return String.fromCharCode.apply(null,t);for(var n=[],r=0;r<e;r+=8192){var i=Math.min(r+8192,e),a=t.subarray(r,i);n.push(String.fromCharCode.apply(null,a))}return n.join("")}function v(t){h("string"==typeof t,"Invalid argument for stringToBytes");for(var e=t.length,n=new Uint8Array(e),r=0;r<e;++r)n[r]=255&t.charCodeAt(r);return n}function b(t){return void 0!==t.length?t.length:(h(void 0!==t.byteLength),t.byteLength)}function y(t){if(1===t.length&&t[0]instanceof Uint8Array)return t[0];var e,n,r,i=0,a=t.length;for(e=0;e<a;e++)n=t[e],r=b(n),i+=r;var s=0,o=new Uint8Array(i);for(e=0;e<a;e++)n=t[e],n instanceof Uint8Array||(n="string"==typeof n?v(n):new Uint8Array(n)),r=n.byteLength,o.set(n,s),s+=r;return o}function x(t){return String.fromCharCode(t>>24&255,t>>16&255,t>>8&255,255&t)}function S(t){for(var e=1,n=0;t>e;)e<<=1,n++;return n}function w(t,e){return t[e]<<24>>24}function k(t,e){return t[e]<<8|t[e+1]}function C(t,e){return(t[e]<<24|t[e+1]<<16|t[e+2]<<8|t[e+3])>>>0}function _(){var t=new Uint8Array(2);return t[0]=1,1===new Uint16Array(t.buffer)[0]}function T(){try{return new Function(""),!0}catch(t){return!1}}function P(t){var e,n=t.length,r=[];if("þ"===t[0]&&"ÿ"===t[1])for(e=2;e<n;e+=2)r.push(String.fromCharCode(t.charCodeAt(e)<<8|t.charCodeAt(e+1)));else for(e=0;e<n;++e){var i=vt[t.charCodeAt(e)];r.push(i?String.fromCharCode(i):t.charAt(e))}return r.join("")}function L(t){return decodeURIComponent(escape(t))}function E(t){return unescape(encodeURIComponent(t))}function R(t){for(var e in t)return!1;return!0}function I(t){return"boolean"==typeof t}function F(t){return"number"==typeof t&&(0|t)===t}function O(t){return"number"==typeof t}function M(t){return"string"==typeof t}function D(t){return t instanceof Array}function N(t){return"object"==typeof t&&null!==t&&void 0!==t.byteLength}function j(t){return 32===t||9===t||13===t||10===t}function U(){return"undefined"==typeof __pdfjsdev_webpack__&&("object"==typeof process&&process+""=="[object process]")}function B(){var t={};return t.promise=new Promise(function(e,n){t.resolve=e,t.reject=n}),t}function W(t,e,n){this.sourceName=t,this.targetName=e,this.comObj=n,this.callbackIndex=1,this.postMessageTransfers=!0;var r=this.callbacksCapabilities=Object.create(null),i=this.actionHandler=Object.create(null);this._onComObjOnMessage=function(t){var e=t.data;if(e.targetName===this.sourceName)if(e.isReply){var a=e.callbackId;if(e.callbackId in r){var s=r[a];delete r[a],"error"in e?s.reject(e.error):s.resolve(e.data)}else c("Cannot resolve callback "+a)}else if(e.action in i){var o=i[e.action];if(e.callbackId){var l=this.sourceName,h=e.sourceName;Promise.resolve().then(function(){return o[0].call(o[1],e.data)}).then(function(t){n.postMessage({sourceName:l,targetName:h,isReply:!0,callbackId:e.callbackId,data:t})},function(t){t instanceof Error&&(t+=""),n.postMessage({sourceName:l,targetName:h,isReply:!0,callbackId:e.callbackId,error:t})})}else o[0].call(o[1],e.data)}else c("Unknown action from worker: "+e.action)}.bind(this),n.addEventListener("message",this._onComObjOnMessage)}function G(t,e,n){var r=new Image;r.onload=function(){n.resolve(t,r)},r.onerror=function(){n.resolve(t,null),s("Error during JPEG image loading")},r.src=e}var X=(n(14),"undefined"!=typeof window?window:void 0!==t?t:"undefined"!=typeof self?self:void 0),H=[.001,0,0,.001,0,0],z={FILL:0,STROKE:1,FILL_STROKE:2,INVISIBLE:3,FILL_ADD_TO_PATH:4,STROKE_ADD_TO_PATH:5,FILL_STROKE_ADD_TO_PATH:6,ADD_TO_PATH:7,FILL_STROKE_MASK:3,ADD_TO_PATH_FLAG:4},Y={GRAYSCALE_1BPP:1,RGB_24BPP:2,RGBA_32BPP:3},q={TEXT:1,LINK:2,FREETEXT:3,LINE:4,SQUARE:5,CIRCLE:6,POLYGON:7,POLYLINE:8,HIGHLIGHT:9,UNDERLINE:10,SQUIGGLY:11,STRIKEOUT:12,STAMP:13,CARET:14,INK:15,POPUP:16,FILEATTACHMENT:17,SOUND:18,MOVIE:19,WIDGET:20,SCREEN:21,PRINTERMARK:22,TRAPNET:23,WATERMARK:24,THREED:25,REDACT:26},V={INVISIBLE:1,HIDDEN:2,PRINT:4,NOZOOM:8,NOROTATE:16,NOVIEW:32,READONLY:64,LOCKED:128,TOGGLENOVIEW:256,LOCKEDCONTENTS:512},J={READONLY:1,REQUIRED:2,NOEXPORT:4,MULTILINE:4096,PASSWORD:8192,NOTOGGLETOOFF:16384,RADIO:32768,PUSHBUTTON:65536,COMBO:131072,EDIT:262144,SORT:524288,FILESELECT:1048576,MULTISELECT:2097152,DONOTSPELLCHECK:4194304,DONOTSCROLL:8388608,COMB:16777216,RICHTEXT:33554432,RADIOSINUNISON:33554432,COMMITONSELCHANGE:67108864},Q={SOLID:1,DASHED:2,BEVELED:3,INSET:4,UNDERLINE:5},K={UNKNOWN:0,FLATE:1,LZW:2,DCT:3,JPX:4,JBIG:5,A85:6,AHX:7,CCF:8,RL:9},Z={UNKNOWN:0,TYPE1:1,TYPE1C:2,CIDFONTTYPE0:3,CIDFONTTYPE0C:4,TRUETYPE:5,CIDFONTTYPE2:6,TYPE3:7,OPENTYPE:8,TYPE0:9,MMTYPE1:10},$={errors:0,warnings:1,infos:5},tt={NONE:0,BINARY:1,STREAM:2},et={dependency:1,setLineWidth:2,setLineCap:3,setLineJoin:4,setMiterLimit:5,setDash:6,setRenderingIntent:7,setFlatness:8,setGState:9,save:10,restore:11,transform:12,moveTo:13,lineTo:14,curveTo:15,curveTo2:16,curveTo3:17,closePath:18,rectangle:19,stroke:20,closeStroke:21,fill:22,eoFill:23,fillStroke:24,eoFillStroke:25,closeFillStroke:26,closeEOFillStroke:27,endPath:28,clip:29,eoClip:30,beginText:31,endText:32,setCharSpacing:33,setWordSpacing:34,setHScale:35,setLeading:36,setFont:37,setTextRenderingMode:38,setTextRise:39,moveText:40,setLeadingMoveText:41,setTextMatrix:42,nextLine:43,showText:44,showSpacedText:45,nextLineShowText:46,nextLineSetSpacingShowText:47,setCharWidth:48,setCharWidthAndBounds:49,setStrokeColorSpace:50,setFillColorSpace:51,setStrokeColor:52,setStrokeColorN:53,setFillColor:54,setFillColorN:55,setStrokeGray:56,setFillGray:57,setStrokeRGBColor:58,setFillRGBColor:59,setStrokeCMYKColor:60,setFillCMYKColor:61,shadingFill:62,beginInlineImage:63,beginImageData:64,endInlineImage:65,paintXObject:66,markPoint:67,markPointProps:68,beginMarkedContent:69,beginMarkedContentProps:70,endMarkedContent:71,beginCompat:72,endCompat:73,paintFormXObjectBegin:74,paintFormXObjectEnd:75,beginGroup:76,endGroup:77,beginAnnotations:78,endAnnotations:79,beginAnnotation:80,endAnnotation:81,paintJpegXObject:82,paintImageMaskXObject:83,paintImageMaskXObjectGroup:84,paintImageXObject:85,paintInlineImageXObject:86,paintInlineImageXObjectGroup:87,paintImageXObjectRepeat:88,paintImageMaskXObjectRepeat:89,paintSolidColorImageMask:90,constructPath:91},nt=$.warnings,rt={unknown:"unknown",forms:"forms",javaScript:"javaScript",smask:"smask",shadingPattern:"shadingPattern",font:"font"},it={NEED_PASSWORD:1,INCORRECT_PASSWORD:2},at=function(){function t(t,e){this.name="PasswordException",this.message=t,this.code=e}return t.prototype=new Error,t.constructor=t,t}(),st=function(){function t(t,e){this.name="UnknownErrorException",this.message=t,this.details=e}return t.prototype=new Error,t.constructor=t,t}(),ot=function(){function t(t){this.name="InvalidPDFException",this.message=t}return t.prototype=new Error,t.constructor=t,t}(),ct=function(){function t(t){this.name="MissingPDFException",this.message=t}return t.prototype=new Error,t.constructor=t,t}(),lt=function(){function t(t,e){this.name="UnexpectedResponseException",this.message=t,this.status=e}return t.prototype=new Error,t.constructor=t,t}(),ht=function(){function t(t){this.message=t}return t.prototype=new Error,t.prototype.name="NotImplementedException",t.constructor=t,t}(),ut=function(){function t(t,e){this.begin=t,this.end=e,this.message="Missing data ["+t+", "+e+")"}return t.prototype=new Error,t.prototype.name="MissingDataException",t.constructor=t,t}(),dt=function(){function t(t){this.message=t}return t.prototype=new Error,t.prototype.name="XRefParseException",t.constructor=t,t}(),ft=/\x00/g,pt=function(){function t(t,e){this.buffer=t,this.byteLength=t.length,this.length=void 0===e?this.byteLength>>2:e,n(this.length)}function e(t){return{get:function(){var e=this.buffer,n=t<<2;return(e[n]|e[n+1]<<8|e[n+2]<<16|e[n+3]<<24)>>>0},set:function(e){var n=this.buffer,r=t<<2;n[r]=255&e,n[r+1]=e>>8&255,n[r+2]=e>>16&255,n[r+3]=e>>>24&255}}}function n(n){for(;r<n;)Object.defineProperty(t.prototype,r,e(r)),r++}t.prototype=Object.create(null);var r=0;return t}();e.Uint32ArrayView=pt;var gt=[1,0,0,1,0,0],mt=function(){function t(){}var e=["rgb(",0,",",0,",",0,")"];t.makeCssRgb=function(t,n,r){return e[1]=t,e[3]=n,e[5]=r,e.join("")},t.transform=function(t,e){return[t[0]*e[0]+t[2]*e[1],t[1]*e[0]+t[3]*e[1],t[0]*e[2]+t[2]*e[3],t[1]*e[2]+t[3]*e[3],t[0]*e[4]+t[2]*e[5]+t[4],t[1]*e[4]+t[3]*e[5]+t[5]]},t.applyTransform=function(t,e){return[t[0]*e[0]+t[1]*e[2]+e[4],t[0]*e[1]+t[1]*e[3]+e[5]]},t.applyInverseTransform=function(t,e){var n=e[0]*e[3]-e[1]*e[2];return[(t[0]*e[3]-t[1]*e[2]+e[2]*e[5]-e[4]*e[3])/n,(-t[0]*e[1]+t[1]*e[0]+e[4]*e[1]-e[5]*e[0])/n]},t.getAxialAlignedBoundingBox=function(e,n){var r=t.applyTransform(e,n),i=t.applyTransform(e.slice(2,4),n),a=t.applyTransform([e[0],e[3]],n),s=t.applyTransform([e[2],e[1]],n);return[Math.min(r[0],i[0],a[0],s[0]),Math.min(r[1],i[1],a[1],s[1]),Math.max(r[0],i[0],a[0],s[0]),Math.max(r[1],i[1],a[1],s[1])]},t.inverseTransform=function(t){var e=t[0]*t[3]-t[1]*t[2];return[t[3]/e,-t[1]/e,-t[2]/e,t[0]/e,(t[2]*t[5]-t[4]*t[3])/e,(t[4]*t[1]-t[5]*t[0])/e]},t.apply3dTransform=function(t,e){return[t[0]*e[0]+t[1]*e[1]+t[2]*e[2],t[3]*e[0]+t[4]*e[1]+t[5]*e[2],t[6]*e[0]+t[7]*e[1]+t[8]*e[2]]},t.singularValueDecompose2dScale=function(t){var e=[t[0],t[2],t[1],t[3]],n=t[0]*e[0]+t[1]*e[2],r=t[0]*e[1]+t[1]*e[3],i=t[2]*e[0]+t[3]*e[2],a=t[2]*e[1]+t[3]*e[3],s=(n+a)/2,o=Math.sqrt((n+a)*(n+a)-4*(n*a-i*r))/2,c=s+o||1,l=s-o||1;return[Math.sqrt(c),Math.sqrt(l)]},t.normalizeRect=function(t){var e=t.slice(0);return t[0]>t[2]&&(e[0]=t[2],e[2]=t[0]),t[1]>t[3]&&(e[1]=t[3],e[3]=t[1]),e},t.intersect=function(e,n){function r(t,e){return t-e}var i=[e[0],e[2],n[0],n[2]].sort(r),a=[e[1],e[3],n[1],n[3]].sort(r),s=[];return e=t.normalizeRect(e),n=t.normalizeRect(n),(i[0]===e[0]&&i[1]===n[0]||i[0]===n[0]&&i[1]===e[0])&&(s[0]=i[1],s[2]=i[2],(a[0]===e[1]&&a[1]===n[1]||a[0]===n[1]&&a[1]===e[1])&&(s[1]=a[1],s[3]=a[2],s))},t.sign=function(t){return t<0?-1:1};var n=["","C","CC","CCC","CD","D","DC","DCC","DCCC","CM","","X","XX","XXX","XL","L","LX","LXX","LXXX","XC","","I","II","III","IV","V","VI","VII","VIII","IX"];return t.toRoman=function(t,e){h(F(t)&&t>0,"The number should be a positive integer.");for(var r,i=[];t>=1e3;)t-=1e3,i.push("M");r=t/100|0,t%=100,i.push(n[r]),r=t/10|0,t%=10,i.push(n[10+r]),i.push(n[20+t]);var a=i.join("");return e?a.toLowerCase():a},t.appendToArray=function(t,e){Array.prototype.push.apply(t,e)},t.prependToArray=function(t,e){Array.prototype.unshift.apply(t,e)},t.extendObj=function(t,e){for(var n in e)t[n]=e[n]},t.getInheritableProperty=function(t,e,n){for(;t&&!t.has(e);)t=t.get("Parent");return t?n?t.getArray(e):t.get(e):null},t.inherit=function(t,e,n){t.prototype=Object.create(e.prototype),t.prototype.constructor=t;for(var r in n)t.prototype[r]=n[r]},t.loadScript=function(t,e){var n=document.createElement("script"),r=!1;n.setAttribute("src",t),e&&(n.onload=function(){r||e(),r=!0}),document.getElementsByTagName("head")[0].appendChild(n)},t}(),At=function(){function t(t,e,n,r,i,a){this.viewBox=t,this.scale=e,this.rotation=n,this.offsetX=r,this.offsetY=i;var s,o,c,l,h=(t[2]+t[0])/2,u=(t[3]+t[1])/2;switch(n%=360,n=n<0?n+360:n){case 180:s=-1,o=0,c=0,l=1;break;case 90:s=0,o=1,c=1,l=0;break;case 270:s=0,o=-1,c=-1,l=0;break;default:s=1,o=0,c=0,l=-1}a&&(c=-c,l=-l);var d,f,p,g;0===s?(d=Math.abs(u-t[1])*e+r,f=Math.abs(h-t[0])*e+i,p=Math.abs(t[3]-t[1])*e,g=Math.abs(t[2]-t[0])*e):(d=Math.abs(h-t[0])*e+r,f=Math.abs(u-t[1])*e+i,p=Math.abs(t[2]-t[0])*e,g=Math.abs(t[3]-t[1])*e),this.transform=[s*e,o*e,c*e,l*e,d-s*e*h-c*e*u,f-o*e*h-l*e*u],this.width=p,this.height=g,this.fontScale=e}return t.prototype={clone:function(e){e=e||{};var n="scale"in e?e.scale:this.scale,r="rotation"in e?e.rotation:this.rotation;return new t(this.viewBox.slice(),n,r,this.offsetX,this.offsetY,e.dontFlip)},convertToViewportPoint:function(t,e){return mt.applyTransform([t,e],this.transform)},convertToViewportRectangle:function(t){var e=mt.applyTransform([t[0],t[1]],this.transform),n=mt.applyTransform([t[2],t[3]],this.transform);return[e[0],e[1],n[0],n[1]]},convertToPdfPoint:function(t,e){return mt.applyInverseTransform([t,e],this.transform)}},t}(),vt=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,728,711,710,729,733,731,730,732,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8226,8224,8225,8230,8212,8211,402,8260,8249,8250,8722,8240,8222,8220,8221,8216,8217,8218,8482,64257,64258,321,338,352,376,381,305,322,339,353,382,0,8364],bt=function(){function t(t,e,n){for(;t.length<n;)t+=e;return t}function e(){this.started=Object.create(null),this.times=[],this.enabled=!0}return e.prototype={time:function(t){this.enabled&&(t in this.started&&s("Timer is already running for "+t),this.started[t]=Date.now())},timeEnd:function(t){this.enabled&&(t in this.started||s("Timer has not been started for "+t),this.times.push({name:t,start:this.started[t],end:Date.now()}),delete this.started[t])},toString:function(){var e,n,r=this.times,i="",a=0;for(e=0,n=r.length;e<n;++e){var s=r[e].name;s.length>a&&(a=s.length)}for(e=0,n=r.length;e<n;++e){var o=r[e],c=o.end-o.start;i+=t(o.name," ",a)+" "+c+"ms\n"}return i}},e}(),yt=function(t,e){if("undefined"!=typeof Blob)return new Blob([t],{type:e});s('The "Blob" constructor is not supported.')},xt=function(){var t="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";return function(e,n,r){if(!r&&"undefined"!=typeof URL&&URL.createObjectURL){var i=yt(e,n);return URL.createObjectURL(i)}for(var a="data:"+n+";base64,",s=0,o=e.length;s<o;s+=3){var c=255&e[s],l=255&e[s+1],h=255&e[s+2],u=c>>2,d=(3&c)<<4|l>>4,f=s+1<o?(15&l)<<2|h>>6:64,p=s+2<o?63&h:64;a+=t[u]+t[d]+t[f]+t[p]}return a}}();W.prototype={on:function(t,e,n){var r=this.actionHandler;r[t]&&c('There is already an actionName called "'+t+'"'),r[t]=[e,n]},send:function(t,e,n){var r={sourceName:this.sourceName,targetName:this.targetName,action:t,data:e};this.postMessage(r,n)},sendWithPromise:function(t,e,n){var r=this.callbackIndex++,i={sourceName:this.sourceName,targetName:this.targetName,action:t,data:e,callbackId:r},a=B();this.callbacksCapabilities[r]=a;try{this.postMessage(i,n)}catch(t){a.reject(t)}return a.promise},postMessage:function(t,e){e&&this.postMessageTransfers?this.comObj.postMessage(t,e):this.comObj.postMessage(t)},destroy:function(){this.comObj.removeEventListener("message",this._onComObjOnMessage)}},e.FONT_IDENTITY_MATRIX=H,e.IDENTITY_MATRIX=gt,e.OPS=et,e.VERBOSITY_LEVELS=$,e.UNSUPPORTED_FEATURES=rt,e.AnnotationBorderStyleType=Q,e.AnnotationFieldFlag=J,e.AnnotationFlag=V,e.AnnotationType=q,e.FontType=Z,e.ImageKind=Y,e.CMapCompressionType=tt,e.InvalidPDFException=ot,e.MessageHandler=W,e.MissingDataException=ut,e.MissingPDFException=ct,e.NotImplementedException=ht,e.PageViewport=At,e.PasswordException=at,e.PasswordResponses=it,e.StatTimer=bt,e.StreamType=K,e.TextRenderingMode=z,e.UnexpectedResponseException=lt,e.UnknownErrorException=st,e.Util=mt,e.XRefParseException=dt,e.arrayByteLength=b,e.arraysToBytes=y,e.assert=h,e.bytesToString=A,e.createBlob=yt,e.createPromiseCapability=B,e.createObjectURL=xt,e.deprecated=o,e.error=c,e.getLookupTableFactory=g,e.getVerbosityLevel=i,e.globalScope=X,e.info=a,e.isArray=D,e.isArrayBuffer=N,e.isBool=I,e.isEmptyObj=R,e.isInt=F,e.isNum=O,e.isString=M,e.isSpace=j,e.isNodeJS=U,e.isSameOrigin=u,e.createValidAbsoluteUrl=f,e.isLittleEndian=_,e.isEvalSupported=T,e.loadJpegStream=G,e.log2=S,e.readInt8=w,e.readUint16=k,e.readUint32=C,e.removeNullCharacters=m,e.setVerbosityLevel=r,e.shadow=p,e.string32=x,e.stringToBytes=v,e.stringToPDFString=P,e.stringToUTF8String=L,e.utf8StringToString=E,e.warn=s}).call(e,n(6))},function(t,e,n){"use strict";function r(){}function i(t,e){var n=e&&e.url;if(t.href=t.title=n?u(n):"",n){var r=e.target;void 0===r&&(r=s("externalLinkTarget")),t.target=w[r];var i=e.rel;void 0===i&&(i=s("externalLinkRel")),t.rel=i}}function a(t){var e=t.indexOf("#"),n=t.indexOf("?"),r=Math.min(e>0?e:t.length,n>0?n:t.length);return t.substring(t.lastIndexOf("/",r)+1,r)}function s(t){var e=l.globalScope.PDFJS;switch(t){case"pdfBug":return!!e&&e.pdfBug;case"disableAutoFetch":return!!e&&e.disableAutoFetch;case"disableStream":return!!e&&e.disableStream;case"disableRange":return!!e&&e.disableRange;case"disableFontFace":return!!e&&e.disableFontFace;case"disableCreateObjectURL":return!!e&&e.disableCreateObjectURL;case"disableWebGL":return!e||e.disableWebGL;case"cMapUrl":return e?e.cMapUrl:null;case"cMapPacked":return!!e&&e.cMapPacked;case"postMessageTransfers":return!e||e.postMessageTransfers;case"workerPort":return e?e.workerPort:null;case"workerSrc":return e?e.workerSrc:null;case"disableWorker":return!!e&&e.disableWorker;case"maxImageSize":return e?e.maxImageSize:-1;case"imageResourcesPath":return e?e.imageResourcesPath:"";case"isEvalSupported":return!e||e.isEvalSupported;case"externalLinkTarget":if(!e)return S.NONE;switch(e.externalLinkTarget){case S.NONE:case S.SELF:case S.BLANK:case S.PARENT:case S.TOP:return e.externalLinkTarget}return d("PDFJS.externalLinkTarget is invalid: "+e.externalLinkTarget),e.externalLinkTarget=S.NONE,S.NONE;case"externalLinkRel":return e?e.externalLinkRel:A;case"enableStats":return!(!e||!e.enableStats);case"pdfjsNext":return!(!e||!e.pdfjsNext);default:throw new Error("Unknown default setting: "+t)}}function o(){switch(s("externalLinkTarget")){case S.NONE:return!1;case S.SELF:case S.BLANK:case S.PARENT:case S.TOP:return!0}}function c(t,e){return f("isValidUrl(), please use createValidAbsoluteUrl() instead."),null!==p(t,e?"http://example.com":null)}var l=n(0),h=l.assert,u=l.removeNullCharacters,d=l.warn,f=l.deprecated,p=l.createValidAbsoluteUrl,g=l.stringToBytes,m=l.CMapCompressionType,A="noopener noreferrer nofollow";r.prototype={create:function(t,e){h(t>0&&e>0,"invalid canvas size");var n=document.createElement("canvas"),r=n.getContext("2d");return n.width=t,n.height=e,{canvas:n,context:r}},reset:function(t,e,n){h(t.canvas,"canvas is not specified"),h(e>0&&n>0,"invalid canvas size"),t.canvas.width=e,t.canvas.height=n},destroy:function(t){h(t.canvas,"canvas is not specified"),t.canvas.width=0,t.canvas.height=0,t.canvas=null,t.context=null}};var v,b=function(){function t(t){this.baseUrl=t.baseUrl||null,this.isCompressed=t.isCompressed||!1}return t.prototype={fetch:function(t){var e=t.name;return e?new Promise(function(t,n){var r=this.baseUrl+e+(this.isCompressed?".bcmap":""),i=new XMLHttpRequest;i.open("GET",r,!0),this.isCompressed&&(i.responseType="arraybuffer"),i.onreadystatechange=function(){if(i.readyState===XMLHttpRequest.DONE){if(200===i.status||0===i.status){var e;if(this.isCompressed&&i.response?e=new Uint8Array(i.response):!this.isCompressed&&i.responseText&&(e=g(i.responseText)),e)return void t({cMapData:e,compressionType:this.isCompressed?m.BINARY:m.NONE})}n(new Error("Unable to load "+(this.isCompressed?"binary ":"")+"CMap at: "+r))}}.bind(this),i.send(null)}.bind(this)):Promise.reject(new Error("CMap name must be specified."))}},t}(),y=function(){function t(){}var e=["ms","Moz","Webkit","O"],n=Object.create(null);return t.getProp=function(t,r){if(1===arguments.length&&"string"==typeof n[t])return n[t];r=r||document.documentElement;var i,a,s=r.style;if("string"==typeof s[t])return n[t]=t;a=t.charAt(0).toUpperCase()+t.slice(1);for(var o=0,c=e.length;o<c;o++)if(i=e[o]+a,"string"==typeof s[i])return n[t]=i;return n[t]="undefined"},t.setProp=function(t,e,n){var r=this.getProp(t);"undefined"!==r&&(e.style[r]=n)},t}(),x=function(){function t(t,e){this.message=t,this.type=e}return t.prototype=new Error,t.prototype.name="RenderingCancelledException",t.constructor=t,t}();v=function(){var t=document.createElement("canvas");return t.width=t.height=1,void 0!==t.getContext("2d").createImageData(1,1).data.buffer};var S={NONE:0,SELF:1,BLANK:2,PARENT:3,TOP:4},w=["","_self","_blank","_parent","_top"];e.CustomStyle=y,e.addLinkAttributes=i,e.isExternalLinkTargetSet=o,e.isValidUrl=c,e.getFilenameFromUrl=a,e.LinkTarget=S,e.RenderingCancelledException=x,e.hasCanvasTypedArrays=v,e.getDefaultSetting=s,e.DEFAULT_LINK_REL=A,e.DOMCanvasFactory=r,e.DOMCMapReaderFactory=b},function(t,e,n){"use strict";function r(){}var i=n(0),a=n(1),s=i.AnnotationBorderStyleType,o=i.AnnotationType,c=i.stringToPDFString,l=i.Util,h=a.addLinkAttributes,u=a.LinkTarget,d=a.getFilenameFromUrl,f=i.warn,p=a.CustomStyle,g=a.getDefaultSetting;r.prototype={create:function(t){switch(t.data.annotationType){case o.LINK:return new A(t);case o.TEXT:return new v(t);case o.WIDGET:switch(t.data.fieldType){case"Tx":return new y(t);case"Btn":if(t.data.radioButton)return new S(t);if(t.data.checkBox)return new x(t);f("Unimplemented button widget annotation: pushbutton");break;case"Ch":return new w(t)}return new b(t);case o.POPUP:return new k(t);case o.HIGHLIGHT:return new _(t);case o.UNDERLINE:return new T(t);case o.SQUIGGLY:return new P(t);case o.STRIKEOUT:return new L(t);case o.FILEATTACHMENT:return new E(t);default:return new m(t)}}};var m=function(){function t(t,e){this.isRenderable=e||!1,this.data=t.data,this.layer=t.layer,this.page=t.page,this.viewport=t.viewport,this.linkService=t.linkService,this.downloadManager=t.downloadManager,this.imageResourcesPath=t.imageResourcesPath,this.renderInteractiveForms=t.renderInteractiveForms,e&&(this.container=this._createContainer())}return t.prototype={_createContainer:function(){var t=this.data,e=this.page,n=this.viewport,r=document.createElement("section"),i=t.rect[2]-t.rect[0],a=t.rect[3]-t.rect[1];r.setAttribute("data-annotation-id",t.id);var o=l.normalizeRect([t.rect[0],e.view[3]-t.rect[1]+e.view[1],t.rect[2],e.view[3]-t.rect[3]+e.view[1]]);if(p.setProp("transform",r,"matrix("+n.transform.join(",")+")"),p.setProp("transformOrigin",r,-o[0]+"px "+-o[1]+"px"),t.borderStyle.width>0){r.style.borderWidth=t.borderStyle.width+"px",t.borderStyle.style!==s.UNDERLINE&&(i-=2*t.borderStyle.width,a-=2*t.borderStyle.width);var c=t.borderStyle.horizontalCornerRadius,h=t.borderStyle.verticalCornerRadius;if(c>0||h>0){var u=c+"px / "+h+"px";p.setProp("borderRadius",r,u)}switch(t.borderStyle.style){case s.SOLID:r.style.borderStyle="solid";break;case s.DASHED:r.style.borderStyle="dashed";break;case s.BEVELED:f("Unimplemented border style: beveled");break;case s.INSET:f("Unimplemented border style: inset");break;case s.UNDERLINE:r.style.borderBottomStyle="solid"}t.color?r.style.borderColor=l.makeCssRgb(0|t.color[0],0|t.color[1],0|t.color[2]):r.style.borderWidth=0}return r.style.left=o[0]+"px",r.style.top=o[1]+"px",r.style.width=i+"px",r.style.height=a+"px",r},_createPopup:function(t,e,n){e||(e=document.createElement("div"),e.style.height=t.style.height,e.style.width=t.style.width,t.appendChild(e));var r=new C({container:t,trigger:e,color:n.color,title:n.title,contents:n.contents,hideWrapper:!0}),i=r.render();i.style.left=t.style.width,t.appendChild(i)},render:function(){throw new Error("Abstract method AnnotationElement.render called")}},t}(),A=function(){function t(t){m.call(this,t,!0)}return l.inherit(t,m,{render:function(){this.container.className="linkAnnotation";var t=document.createElement("a");return h(t,{url:this.data.url,target:this.data.newWindow?u.BLANK:void 0}),this.data.url||(this.data.action?this._bindNamedAction(t,this.data.action):this._bindLink(t,this.data.dest)),this.container.appendChild(t),this.container},_bindLink:function(t,e){var n=this;t.href=this.linkService.getDestinationHash(e),t.onclick=function(){return e&&n.linkService.navigateTo(e),!1},e&&(t.className="internalLink")},_bindNamedAction:function(t,e){var n=this;t.href=this.linkService.getAnchorUrl(""),t.onclick=function(){return n.linkService.executeNamedAction(e),!1},t.className="internalLink"}}),t}(),v=function(){function t(t){var e=!!(t.data.hasPopup||t.data.title||t.data.contents);m.call(this,t,e)}return l.inherit(t,m,{render:function(){this.container.className="textAnnotation";var t=document.createElement("img");return t.style.height=this.container.style.height,t.style.width=this.container.style.width,t.src=this.imageResourcesPath+"annotation-"+this.data.name.toLowerCase()+".svg",t.alt="[{{type}} Annotation]",t.dataset.l10nId="text_annotation_type",t.dataset.l10nArgs=JSON.stringify({type:this.data.name}),this.data.hasPopup||this._createPopup(this.container,t,this.data),this.container.appendChild(t),this.container}}),t}(),b=function(){function t(t,e){m.call(this,t,e)}return l.inherit(t,m,{render:function(){return this.container}}),t}(),y=function(){function t(t){var e=t.renderInteractiveForms||!t.data.hasAppearance&&!!t.data.fieldValue;b.call(this,t,e)}var e=["left","center","right"];return l.inherit(t,b,{render:function(){this.container.className="textWidgetAnnotation";var t=null;if(this.renderInteractiveForms){if(this.data.multiLine?(t=document.createElement("textarea"),t.textContent=this.data.fieldValue):(t=document.createElement("input"),t.type="text",t.setAttribute("value",this.data.fieldValue)),t.disabled=this.data.readOnly,null!==this.data.maxLen&&(t.maxLength=this.data.maxLen),this.data.comb){var n=this.data.rect[2]-this.data.rect[0],r=n/this.data.maxLen;t.classList.add("comb"),t.style.letterSpacing="calc("+r+"px - 1ch)"}}else{t=document.createElement("div"),t.textContent=this.data.fieldValue,t.style.verticalAlign="middle",t.style.display="table-cell";var i=null;this.data.fontRefName&&(i=this.page.commonObjs.getData(this.data.fontRefName)),this._setTextStyle(t,i)}return null!==this.data.textAlignment&&(t.style.textAlign=e[this.data.textAlignment]),this.container.appendChild(t),this.container},_setTextStyle:function(t,e){var n=t.style;if(n.fontSize=this.data.fontSize+"px",n.direction=this.data.fontDirection<0?"rtl":"ltr",e){n.fontWeight=e.black?e.bold?"900":"bold":e.bold?"bold":"normal",n.fontStyle=e.italic?"italic":"normal";var r=e.loadedName?'"'+e.loadedName+'", ':"",i=e.fallbackName||"Helvetica, sans-serif";n.fontFamily=r+i}}}),t}(),x=function(){function t(t){b.call(this,t,t.renderInteractiveForms)}return l.inherit(t,b,{render:function(){this.container.className="buttonWidgetAnnotation checkBox";var t=document.createElement("input");return t.disabled=this.data.readOnly,t.type="checkbox",this.data.fieldValue&&"Off"!==this.data.fieldValue&&t.setAttribute("checked",!0),this.container.appendChild(t),this.container}}),t}(),S=function(){function t(t){b.call(this,t,t.renderInteractiveForms)}return l.inherit(t,b,{render:function(){this.container.className="buttonWidgetAnnotation radioButton";var t=document.createElement("input");return t.disabled=this.data.readOnly,t.type="radio",t.name=this.data.fieldName,this.data.fieldValue===this.data.buttonValue&&t.setAttribute("checked",!0),this.container.appendChild(t),this.container}}),t}(),w=function(){function t(t){b.call(this,t,t.renderInteractiveForms)}return l.inherit(t,b,{render:function(){this.container.className="choiceWidgetAnnotation";var t=document.createElement("select");t.disabled=this.data.readOnly,this.data.combo||(t.size=this.data.options.length,this.data.multiSelect&&(t.multiple=!0));for(var e=0,n=this.data.options.length;e<n;e++){var r=this.data.options[e],i=document.createElement("option");i.textContent=r.displayValue,i.value=r.exportValue,this.data.fieldValue.indexOf(r.displayValue)>=0&&i.setAttribute("selected",!0),t.appendChild(i)}return this.container.appendChild(t),this.container}}),t}(),k=function(){function t(t){var e=!(!t.data.title&&!t.data.contents);m.call(this,t,e)}return l.inherit(t,m,{render:function(){this.container.className="popupAnnotation";var t='[data-annotation-id="'+this.data.parentId+'"]',e=this.layer.querySelector(t);if(!e)return this.container;var n=new C({container:this.container,trigger:e,color:this.data.color,title:this.data.title,contents:this.data.contents}),r=parseFloat(e.style.left),i=parseFloat(e.style.width);return p.setProp("transformOrigin",this.container,-(r+i)+"px -"+e.style.top),this.container.style.left=r+i+"px",this.container.appendChild(n.render()),this.container}}),t}(),C=function(){function t(t){this.container=t.container,this.trigger=t.trigger,this.color=t.color,this.title=t.title,this.contents=t.contents,this.hideWrapper=t.hideWrapper||!1,this.pinned=!1}return t.prototype={render:function(){var t=document.createElement("div");t.className="popupWrapper",this.hideElement=this.hideWrapper?t:this.container,this.hideElement.setAttribute("hidden",!0);var e=document.createElement("div");e.className="popup";var n=this.color;if(n){var r=.7*(255-n[0])+n[0],i=.7*(255-n[1])+n[1],a=.7*(255-n[2])+n[2];e.style.backgroundColor=l.makeCssRgb(0|r,0|i,0|a)}var s=this._formatContents(this.contents),o=document.createElement("h1");return o.textContent=this.title,this.trigger.addEventListener("click",this._toggle.bind(this)),this.trigger.addEventListener("mouseover",this._show.bind(this,!1)),this.trigger.addEventListener("mouseout",this._hide.bind(this,!1)),e.addEventListener("click",this._hide.bind(this,!0)),e.appendChild(o),e.appendChild(s),t.appendChild(e),t},_formatContents:function(t){for(var e=document.createElement("p"),n=t.split(/(?:\r\n?|\n)/),r=0,i=n.length;r<i;++r){var a=n[r];e.appendChild(document.createTextNode(a)),r<i-1&&e.appendChild(document.createElement("br"))}return e},_toggle:function(){this.pinned?this._hide(!0):this._show(!0)},_show:function(t){t&&(this.pinned=!0),this.hideElement.hasAttribute("hidden")&&(this.hideElement.removeAttribute("hidden"),this.container.style.zIndex+=1)},_hide:function(t){t&&(this.pinned=!1),this.hideElement.hasAttribute("hidden")||this.pinned||(this.hideElement.setAttribute("hidden",!0),this.container.style.zIndex-=1)}},t}(),_=function(){function t(t){var e=!!(t.data.hasPopup||t.data.title||t.data.contents);m.call(this,t,e)}return l.inherit(t,m,{render:function(){return this.container.className="highlightAnnotation",this.data.hasPopup||this._createPopup(this.container,null,this.data),this.container}}),t}(),T=function(){function t(t){var e=!!(t.data.hasPopup||t.data.title||t.data.contents)
+;m.call(this,t,e)}return l.inherit(t,m,{render:function(){return this.container.className="underlineAnnotation",this.data.hasPopup||this._createPopup(this.container,null,this.data),this.container}}),t}(),P=function(){function t(t){var e=!!(t.data.hasPopup||t.data.title||t.data.contents);m.call(this,t,e)}return l.inherit(t,m,{render:function(){return this.container.className="squigglyAnnotation",this.data.hasPopup||this._createPopup(this.container,null,this.data),this.container}}),t}(),L=function(){function t(t){var e=!!(t.data.hasPopup||t.data.title||t.data.contents);m.call(this,t,e)}return l.inherit(t,m,{render:function(){return this.container.className="strikeoutAnnotation",this.data.hasPopup||this._createPopup(this.container,null,this.data),this.container}}),t}(),E=function(){function t(t){m.call(this,t,!0);var e=this.data.file;this.filename=d(e.filename),this.content=e.content,this.linkService.onFileAttachmentAnnotation({id:c(e.filename),filename:e.filename,content:e.content})}return l.inherit(t,m,{render:function(){this.container.className="fileAttachmentAnnotation";var t=document.createElement("div");return t.style.height=this.container.style.height,t.style.width=this.container.style.width,t.addEventListener("dblclick",this._download.bind(this)),this.data.hasPopup||!this.data.title&&!this.data.contents||this._createPopup(this.container,t,this.data),this.container.appendChild(t),this.container},_download:function(){if(!this.downloadManager)return void f("Download cannot be started due to unavailable download manager");this.downloadManager.downloadData(this.content,this.filename,"")}}),t}(),R=function(){return{render:function(t){for(var e=new r,n=0,i=t.annotations.length;n<i;n++){var a=t.annotations[n];if(a){var s=e.create({data:a,layer:t.div,page:t.page,viewport:t.viewport,linkService:t.linkService,downloadManager:t.downloadManager,imageResourcesPath:t.imageResourcesPath||g("imageResourcesPath"),renderInteractiveForms:t.renderInteractiveForms||!1});s.isRenderable&&t.div.appendChild(s.render())}}},update:function(t){for(var e=0,n=t.annotations.length;e<n;e++){var r=t.annotations[e],i=t.div.querySelector('[data-annotation-id="'+r.id+'"]');i&&p.setProp("transform",i,"matrix("+t.viewport.transform.join(",")+")")}t.div.removeAttribute("hidden")}}}();e.AnnotationLayer=R},function(t,e,n){"use strict";function r(t,e,n,r){var a=new V;arguments.length>1&&S("getDocument is called with pdfDataRangeTransport, passwordCallback or progressCallback argument"),e&&(e instanceof J||(e=Object.create(e),e.length=t.length,e.initialData=t.initialData,e.abort||(e.abort=function(){})),t=Object.create(t),t.range=e),a.onPassword=n||null,a.onProgress=r||null;var s;"string"==typeof t?s={url:t}:T(t)?s={data:t}:t instanceof J?s={range:t}:("object"!=typeof t&&x("Invalid parameter in getDocument, need either Uint8Array, string or a parameter object"),t.url||t.data||t.range||x("Invalid parameter object: need either .data, .range or .url"),s=t);var o={},c=null,l=null;for(var h in s)if("url"!==h||"undefined"==typeof window)if("range"!==h)if("worker"!==h)if("data"!==h||s[h]instanceof Uint8Array)o[h]=s[h];else{var u=s[h];"string"==typeof u?o[h]=E(u):"object"!=typeof u||null===u||isNaN(u.length)?T(u)?o[h]=new Uint8Array(u):x("Invalid PDF binary data: either typed array, string or array-like object is expected in the data property."):o[h]=new Uint8Array(u)}else l=s[h];else c=s[h];else o[h]=new URL(s[h],window.location).href;o.rangeChunkSize=o.rangeChunkSize||W,o.disableNativeImageDecoder=!0===o.disableNativeImageDecoder;var f=o.CMapReaderFactory||B;if(!l){var p=j("workerPort");l=p?new Z(null,p):new Z,a._worker=l}var g=a.docId;return l.promise.then(function(){if(a.destroyed)throw new Error("Loading aborted");return i(l,o,c,g).then(function(t){if(a.destroyed)throw new Error("Loading aborted");var e=new d(g,t,l.port),n=new $(e,a,c,f);a._transport=n,e.send("Ready",null)})}).catch(a._capability.reject),a}function i(t,e,n,r){return t.destroyed?Promise.reject(new Error("Worker was destroyed")):(e.disableAutoFetch=j("disableAutoFetch"),e.disableStream=j("disableStream"),e.chunkedViewerLoading=!!n,n&&(e.length=n.length,e.initialData=n.initialData),t.messageHandler.sendWithPromise("GetDocRequest",{docId:r,source:e,disableRange:j("disableRange"),maxImageSize:j("maxImageSize"),disableFontFace:j("disableFontFace"),disableCreateObjectURL:j("disableCreateObjectURL"),postMessageTransfers:j("postMessageTransfers")&&!X,docBaseUrl:e.docBaseUrl,disableNativeImageDecoder:e.disableNativeImageDecoder}).then(function(e){if(t.destroyed)throw new Error("Worker was destroyed");return e}))}var a,s=n(0),o=n(11),c=n(10),l=n(7),h=n(1),u=s.InvalidPDFException,d=s.MessageHandler,f=s.MissingPDFException,p=s.PageViewport,g=s.PasswordException,m=s.StatTimer,A=s.UnexpectedResponseException,v=s.UnknownErrorException,b=s.Util,y=s.createPromiseCapability,x=s.error,S=s.deprecated,w=s.getVerbosityLevel,k=s.info,C=s.isInt,_=s.isArray,T=s.isArrayBuffer,P=s.isSameOrigin,L=s.loadJpegStream,E=s.stringToBytes,R=s.globalScope,I=s.warn,F=o.FontFaceObject,O=o.FontLoader,M=c.CanvasGraphics,D=l.Metadata,N=h.RenderingCancelledException,j=h.getDefaultSetting,U=h.DOMCanvasFactory,B=h.DOMCMapReaderFactory,W=65536,G=!1,X=!1,H="undefined"!=typeof document&&document.currentScript?document.currentScript.src:null,z=null,Y=!1;if("undefined"==typeof __pdfjsdev_webpack__){"undefined"==typeof window?(G=!0,void 0===require.ensure&&(require.ensure=require("node-ensure")),Y=!0):"undefined"!=typeof require&&"function"==typeof require.ensure&&(Y=!0),"undefined"!=typeof requirejs&&requirejs.toUrl&&(a=requirejs.toUrl("pdfjs-dist/build/pdf.worker.js"));var q="undefined"!=typeof requirejs&&requirejs.load;z=Y?function(t){require.ensure([],function(){var e=require("./pdf.worker.js");t(e.WorkerMessageHandler)})}:q?function(t){requirejs(["pdfjs-dist/build/pdf.worker"],function(e){t(e.WorkerMessageHandler)})}:null}var V=function(){function t(){this._capability=y(),this._transport=null,this._worker=null,this.docId="d"+e++,this.destroyed=!1,this.onPassword=null,this.onProgress=null,this.onUnsupportedFeature=null}var e=0;return t.prototype={get promise(){return this._capability.promise},destroy:function(){return this.destroyed=!0,(this._transport?this._transport.destroy():Promise.resolve()).then(function(){this._transport=null,this._worker&&(this._worker.destroy(),this._worker=null)}.bind(this))},then:function(t,e){return this.promise.then.apply(this.promise,arguments)}},t}(),J=function(){function t(t,e){this.length=t,this.initialData=e,this._rangeListeners=[],this._progressListeners=[],this._progressiveReadListeners=[],this._readyCapability=y()}return t.prototype={addRangeListener:function(t){this._rangeListeners.push(t)},addProgressListener:function(t){this._progressListeners.push(t)},addProgressiveReadListener:function(t){this._progressiveReadListeners.push(t)},onDataRange:function(t,e){for(var n=this._rangeListeners,r=0,i=n.length;r<i;++r)n[r](t,e)},onDataProgress:function(t){this._readyCapability.promise.then(function(){for(var e=this._progressListeners,n=0,r=e.length;n<r;++n)e[n](t)}.bind(this))},onDataProgressiveRead:function(t){this._readyCapability.promise.then(function(){for(var e=this._progressiveReadListeners,n=0,r=e.length;n<r;++n)e[n](t)}.bind(this))},transportReady:function(){this._readyCapability.resolve()},requestDataRange:function(t,e){throw new Error("Abstract method PDFDataRangeTransport.requestDataRange")},abort:function(){}},t}(),Q=function(){function t(t,e,n){this.pdfInfo=t,this.transport=e,this.loadingTask=n}return t.prototype={get numPages(){return this.pdfInfo.numPages},get fingerprint(){return this.pdfInfo.fingerprint},getPage:function(t){return this.transport.getPage(t)},getPageIndex:function(t){return this.transport.getPageIndex(t)},getDestinations:function(){return this.transport.getDestinations()},getDestination:function(t){return this.transport.getDestination(t)},getPageLabels:function(){return this.transport.getPageLabels()},getAttachments:function(){return this.transport.getAttachments()},getJavaScript:function(){return this.transport.getJavaScript()},getOutline:function(){return this.transport.getOutline()},getMetadata:function(){return this.transport.getMetadata()},getData:function(){return this.transport.getData()},getDownloadInfo:function(){return this.transport.downloadInfoCapability.promise},getStats:function(){return this.transport.getStats()},cleanup:function(){this.transport.startCleanup()},destroy:function(){return this.loadingTask.destroy()}},t}(),K=function(){function t(t,e,n){this.pageIndex=t,this.pageInfo=e,this.transport=n,this.stats=new m,this.stats.enabled=j("enableStats"),this.commonObjs=n.commonObjs,this.objs=new tt,this.cleanupAfterRender=!1,this.pendingCleanup=!1,this.intentStates=Object.create(null),this.destroyed=!1}return t.prototype={get pageNumber(){return this.pageIndex+1},get rotate(){return this.pageInfo.rotate},get ref(){return this.pageInfo.ref},get userUnit(){return this.pageInfo.userUnit},get view(){return this.pageInfo.view},getViewport:function(t,e){return arguments.length<2&&(e=this.rotate),new p(this.view,t,e,0,0)},getAnnotations:function(t){var e=t&&t.intent||null;return this.annotationsPromise&&this.annotationsIntent===e||(this.annotationsPromise=this.transport.getAnnotations(this.pageIndex,e),this.annotationsIntent=e),this.annotationsPromise},render:function(t){function e(t){var e=s.renderTasks.indexOf(o);e>=0&&s.renderTasks.splice(e,1),l.cleanupAfterRender&&(l.pendingCleanup=!0),l._tryCleanup(),t?o.capability.reject(t):o.capability.resolve(),n.timeEnd("Rendering"),n.timeEnd("Overall")}var n=this.stats;n.time("Overall"),this.pendingCleanup=!1;var r="print"===t.intent?"print":"display",i=!0===t.renderInteractiveForms,a=t.canvasFactory||new U;this.intentStates[r]||(this.intentStates[r]=Object.create(null));var s=this.intentStates[r];s.displayReadyCapability||(s.receivingOperatorList=!0,s.displayReadyCapability=y(),s.operatorList={fnArray:[],argsArray:[],lastChunk:!1},this.stats.time("Page Request"),this.transport.messageHandler.send("RenderPageRequest",{pageIndex:this.pageNumber-1,intent:r,renderInteractiveForms:i}));var o=new nt(e,t,this.objs,this.commonObjs,s.operatorList,this.pageNumber,a);o.useRequestAnimationFrame="print"!==r,s.renderTasks||(s.renderTasks=[]),s.renderTasks.push(o);var c=o.task;t.continueCallback&&(S("render is used with continueCallback parameter"),c.onContinue=t.continueCallback);var l=this;return s.displayReadyCapability.promise.then(function(t){if(l.pendingCleanup)return void e();n.time("Rendering"),o.initializeGraphics(t),o.operatorListChanged()},function(t){e(t)}),c},getOperatorList:function(){function t(){if(n.operatorList.lastChunk){n.opListReadCapability.resolve(n.operatorList);var t=n.renderTasks.indexOf(e);t>=0&&n.renderTasks.splice(t,1)}}this.intentStates.oplist||(this.intentStates.oplist=Object.create(null));var e,n=this.intentStates.oplist;return n.opListReadCapability||(e={},e.operatorListChanged=t,n.receivingOperatorList=!0,n.opListReadCapability=y(),n.renderTasks=[],n.renderTasks.push(e),n.operatorList={fnArray:[],argsArray:[],lastChunk:!1},this.transport.messageHandler.send("RenderPageRequest",{pageIndex:this.pageIndex,intent:"oplist"})),n.opListReadCapability.promise},getTextContent:function(t){return this.transport.messageHandler.sendWithPromise("GetTextContent",{pageIndex:this.pageNumber-1,normalizeWhitespace:!(!t||!0!==t.normalizeWhitespace),combineTextItems:!t||!0!==t.disableCombineTextItems})},_destroy:function(){this.destroyed=!0,this.transport.pageCache[this.pageIndex]=null;var t=[];return Object.keys(this.intentStates).forEach(function(e){if("oplist"!==e){this.intentStates[e].renderTasks.forEach(function(e){var n=e.capability.promise.catch(function(){});t.push(n),e.cancel()})}},this),this.objs.clear(),this.annotationsPromise=null,this.pendingCleanup=!1,Promise.all(t)},destroy:function(){S("page destroy method, use cleanup() instead"),this.cleanup()},cleanup:function(){this.pendingCleanup=!0,this._tryCleanup()},_tryCleanup:function(){this.pendingCleanup&&!Object.keys(this.intentStates).some(function(t){var e=this.intentStates[t];return 0!==e.renderTasks.length||e.receivingOperatorList},this)&&(Object.keys(this.intentStates).forEach(function(t){delete this.intentStates[t]},this),this.objs.clear(),this.annotationsPromise=null,this.pendingCleanup=!1)},_startRenderPage:function(t,e){var n=this.intentStates[e];n.displayReadyCapability&&n.displayReadyCapability.resolve(t)},_renderPageChunk:function(t,e){var n,r,i=this.intentStates[e];for(n=0,r=t.length;n<r;n++)i.operatorList.fnArray.push(t.fnArray[n]),i.operatorList.argsArray.push(t.argsArray[n]);for(i.operatorList.lastChunk=t.lastChunk,n=0;n<i.renderTasks.length;n++)i.renderTasks[n].operatorListChanged();t.lastChunk&&(i.receivingOperatorList=!1,this._tryCleanup())}},t}(),Z=function(){function t(){return void 0!==a?a:j("workerSrc")?j("workerSrc"):H?H.replace(/\.js$/i,".worker.js"):void x("No PDFJS.workerSrc specified")}function e(){return s?s.promise:(s=y(),(z||function(e){b.loadScript(t(),function(){e(window.pdfjsDistBuildPdfWorker.WorkerMessageHandler)})})(s.resolve),s.promise)}function n(t){this._listeners=[],this._defer=t,this._deferred=Promise.resolve(void 0)}function r(t){var e="importScripts('"+t+"');";return URL.createObjectURL(new Blob([e]))}function i(t,e){if(this.name=t,this.destroyed=!1,this._readyCapability=y(),this._port=null,this._webWorker=null,this._messageHandler=null,e)return void this._initializeFromPort(e);this._initialize()}var s,o=0;return n.prototype={postMessage:function(t,e){function n(t){if("object"!=typeof t||null===t)return t;if(r.has(t))return r.get(t);var i,a;if((a=t.buffer)&&T(a)){var s=e&&e.indexOf(a)>=0;return i=t===a?t:s?new t.constructor(a,t.byteOffset,t.byteLength):new t.constructor(t),r.set(t,i),i}i=_(t)?[]:{},r.set(t,i);for(var o in t){for(var c,l=t;!(c=Object.getOwnPropertyDescriptor(l,o));)l=Object.getPrototypeOf(l);void 0!==c.value&&"function"!=typeof c.value&&(i[o]=n(c.value))}return i}if(!this._defer)return void this._listeners.forEach(function(e){e.call(this,{data:t})},this);var r=new WeakMap,i={data:n(t)};this._deferred.then(function(){this._listeners.forEach(function(t){t.call(this,i)},this)}.bind(this))},addEventListener:function(t,e){this._listeners.push(e)},removeEventListener:function(t,e){var n=this._listeners.indexOf(e);this._listeners.splice(n,1)},terminate:function(){this._listeners=[]}},i.prototype={get promise(){return this._readyCapability.promise},get port(){return this._port},get messageHandler(){return this._messageHandler},_initializeFromPort:function(t){this._port=t,this._messageHandler=new d("main","worker",t),this._messageHandler.on("ready",function(){}),this._readyCapability.resolve()},_initialize:function(){if(!G&&!j("disableWorker")&&"undefined"!=typeof Worker){var e=t();try{P(window.location.href,e)||(e=r(new URL(e,window.location).href));var n=new Worker(e),i=new d("main","worker",n),a=function(){n.removeEventListener("error",s),i.destroy(),n.terminate(),this.destroyed?this._readyCapability.reject(new Error("Worker was destroyed")):this._setupFakeWorker()}.bind(this),s=function(t){this._webWorker||a()}.bind(this);n.addEventListener("error",s),i.on("test",function(t){if(n.removeEventListener("error",s),this.destroyed)return void a();t&&t.supportTypedArray?(this._messageHandler=i,this._port=n,this._webWorker=n,t.supportTransfers||(X=!0),this._readyCapability.resolve(),i.send("configure",{verbosity:w()})):(this._setupFakeWorker(),i.destroy(),n.terminate())}.bind(this)),i.on("console_log",function(t){console.log.apply(console,t)}),i.on("console_error",function(t){console.error.apply(console,t)}),i.on("ready",function(t){if(n.removeEventListener("error",s),this.destroyed)return void a();try{o()}catch(t){this._setupFakeWorker()}}.bind(this));var o=function(){var t=j("postMessageTransfers")&&!X,e=new Uint8Array([t?255:0]);try{i.send("test",e,[e.buffer])}catch(t){k("Cannot use postMessage transfers"),e[0]=0,i.send("test",e)}};return void o()}catch(t){k("The worker has been disabled.")}}this._setupFakeWorker()},_setupFakeWorker:function(){G||j("disableWorker")||(I("Setting up fake worker."),G=!0),e().then(function(t){if(this.destroyed)return void this._readyCapability.reject(new Error("Worker was destroyed"));var e=Uint8Array!==Float32Array,r=new n(e);this._port=r;var i="fake"+o++,a=new d(i+"_worker",i,r);t.setup(a,r);var s=new d(i,i+"_worker",r);this._messageHandler=s,this._readyCapability.resolve()}.bind(this))},destroy:function(){this.destroyed=!0,this._webWorker&&(this._webWorker.terminate(),this._webWorker=null),this._port=null,this._messageHandler&&(this._messageHandler.destroy(),this._messageHandler=null)}},i}(),$=function(){function t(t,e,n,r){this.messageHandler=t,this.loadingTask=e,this.pdfDataRangeTransport=n,this.commonObjs=new tt,this.fontLoader=new O(e.docId),this.CMapReaderFactory=new r({baseUrl:j("cMapUrl"),isCompressed:j("cMapPacked")}),this.destroyed=!1,this.destroyCapability=null,this._passwordCapability=null,this.pageCache=[],this.pagePromises=[],this.downloadInfoCapability=y(),this.setupMessageHandler()}return t.prototype={destroy:function(){if(this.destroyCapability)return this.destroyCapability.promise;this.destroyed=!0,this.destroyCapability=y(),this._passwordCapability&&this._passwordCapability.reject(new Error("Worker was destroyed during onPassword callback"));var t=[];this.pageCache.forEach(function(e){e&&t.push(e._destroy())}),this.pageCache=[],this.pagePromises=[];var e=this,n=this.messageHandler.sendWithPromise("Terminate",null);return t.push(n),Promise.all(t).then(function(){e.fontLoader.clear(),e.pdfDataRangeTransport&&(e.pdfDataRangeTransport.abort(),e.pdfDataRangeTransport=null),e.messageHandler&&(e.messageHandler.destroy(),e.messageHandler=null),e.destroyCapability.resolve()},this.destroyCapability.reject),this.destroyCapability.promise},setupMessageHandler:function(){var t=this.messageHandler,e=this.loadingTask,n=this.pdfDataRangeTransport;n&&(n.addRangeListener(function(e,n){t.send("OnDataRange",{begin:e,chunk:n})}),n.addProgressListener(function(e){t.send("OnDataProgress",{loaded:e})}),n.addProgressiveReadListener(function(e){t.send("OnDataRange",{chunk:e})}),t.on("RequestDataRange",function(t){n.requestDataRange(t.begin,t.end)},this)),t.on("GetDoc",function(t){var e=t.pdfInfo;this.numPages=t.pdfInfo.numPages;var n=this.loadingTask,r=new Q(e,this,n);this.pdfDocument=r,n._capability.resolve(r)},this),t.on("PasswordRequest",function(t){if(this._passwordCapability=y(),e.onPassword){var n=function(t){this._passwordCapability.resolve({password:t})}.bind(this);e.onPassword(n,t.code)}else this._passwordCapability.reject(new g(t.message,t.code));return this._passwordCapability.promise},this),t.on("PasswordException",function(t){e._capability.reject(new g(t.message,t.code))},this),t.on("InvalidPDF",function(t){this.loadingTask._capability.reject(new u(t.message))},this),t.on("MissingPDF",function(t){this.loadingTask._capability.reject(new f(t.message))},this),t.on("UnexpectedResponse",function(t){this.loadingTask._capability.reject(new A(t.message,t.status))},this),t.on("UnknownError",function(t){this.loadingTask._capability.reject(new v(t.message,t.details))},this),t.on("DataLoaded",function(t){this.downloadInfoCapability.resolve(t)},this),t.on("PDFManagerReady",function(t){this.pdfDataRangeTransport&&this.pdfDataRangeTransport.transportReady()},this),t.on("StartRenderPage",function(t){if(!this.destroyed){var e=this.pageCache[t.pageIndex];e.stats.timeEnd("Page Request"),e._startRenderPage(t.transparency,t.intent)}},this),t.on("RenderPageChunk",function(t){if(!this.destroyed){this.pageCache[t.pageIndex]._renderPageChunk(t.operatorList,t.intent)}},this),t.on("commonobj",function(t){if(!this.destroyed){var e=t[0],n=t[1];if(!this.commonObjs.hasData(e))switch(n){case"Font":var r=t[2];if("error"in r){var i=r.error;I("Error during font loading: "+i),this.commonObjs.resolve(e,i);break}var a=null;j("pdfBug")&&R.FontInspector&&R.FontInspector.enabled&&(a={registerFont:function(t,e){R.FontInspector.fontAdded(t,e)}});var s=new F(r,{isEvalSuported:j("isEvalSupported"),disableFontFace:j("disableFontFace"),fontRegistry:a});this.fontLoader.bind([s],function(t){this.commonObjs.resolve(e,s)}.bind(this));break;case"FontPath":this.commonObjs.resolve(e,t[2]);break;default:x("Got unknown common object type "+n)}}},this),t.on("obj",function(t){if(!this.destroyed){var e,n=t[0],r=t[1],i=t[2],a=this.pageCache[r];if(!a.objs.hasData(n))switch(i){case"JpegStream":e=t[3],L(n,e,a.objs);break;case"Image":e=t[3],a.objs.resolve(n,e);e&&"data"in e&&e.data.length>8e6&&(a.cleanupAfterRender=!0);break;default:x("Got unknown object type "+i)}}},this),t.on("DocProgress",function(t){if(!this.destroyed){var e=this.loadingTask;e.onProgress&&e.onProgress({loaded:t.loaded,total:t.total})}},this),t.on("PageError",function(t){if(!this.destroyed){var e=this.pageCache[t.pageNum-1],n=e.intentStates[t.intent];if(n.displayReadyCapability?n.displayReadyCapability.reject(t.error):x(t.error),n.operatorList){n.operatorList.lastChunk=!0;for(var r=0;r<n.renderTasks.length;r++)n.renderTasks[r].operatorListChanged()}}},this),t.on("UnsupportedFeature",function(t){if(!this.destroyed){var e=t.featureId,n=this.loadingTask;n.onUnsupportedFeature&&n.onUnsupportedFeature(e),rt.notify(e)}},this),t.on("JpegDecode",function(t){if(this.destroyed)return Promise.reject(new Error("Worker was destroyed"));if("undefined"==typeof document)return Promise.reject(new Error('"document" is not defined.'));var e=t[0],n=t[1];return 3!==n&&1!==n?Promise.reject(new Error("Only 3 components or 1 component can be returned")):new Promise(function(t,r){var i=new Image;i.onload=function(){var e=i.width,r=i.height,a=e*r,s=4*a,o=new Uint8Array(a*n),c=document.createElement("canvas");c.width=e,c.height=r;var l=c.getContext("2d");l.drawImage(i,0,0);var h,u,d=l.getImageData(0,0,e,r).data;if(3===n)for(h=0,u=0;h<s;h+=4,u+=3)o[u]=d[h],o[u+1]=d[h+1],o[u+2]=d[h+2];else if(1===n)for(h=0,u=0;h<s;h+=4,u++)o[u]=d[h];t({data:o,width:e,height:r})},i.onerror=function(){r(new Error("JpegDecode failed to load image"))},i.src=e})},this),t.on("FetchBuiltInCMap",function(t){return this.destroyed?Promise.reject(new Error("Worker was destroyed")):this.CMapReaderFactory.fetch({name:t.name})},this)},getData:function(){return this.messageHandler.sendWithPromise("GetData",null)},getPage:function(t,e){if(!C(t)||t<=0||t>this.numPages)return Promise.reject(new Error("Invalid page request"));var n=t-1;if(n in this.pagePromises)return this.pagePromises[n];var r=this.messageHandler.sendWithPromise("GetPage",{pageIndex:n}).then(function(t){if(this.destroyed)throw new Error("Transport destroyed");var e=new K(n,t,this);return this.pageCache[n]=e,e}.bind(this));return this.pagePromises[n]=r,r},getPageIndex:function(t){return this.messageHandler.sendWithPromise("GetPageIndex",{ref:t}).catch(function(t){return Promise.reject(new Error(t))})},getAnnotations:function(t,e){return this.messageHandler.sendWithPromise("GetAnnotations",{pageIndex:t,intent:e})},getDestinations:function(){return this.messageHandler.sendWithPromise("GetDestinations",null)},getDestination:function(t){return this.messageHandler.sendWithPromise("GetDestination",{id:t})},getPageLabels:function(){return this.messageHandler.sendWithPromise("GetPageLabels",null)},getAttachments:function(){return this.messageHandler.sendWithPromise("GetAttachments",null)},getJavaScript:function(){return this.messageHandler.sendWithPromise("GetJavaScript",null)},getOutline:function(){return this.messageHandler.sendWithPromise("GetOutline",null)},getMetadata:function(){return this.messageHandler.sendWithPromise("GetMetadata",null).then(function(t){return{info:t[0],metadata:t[1]?new D(t[1]):null}})},getStats:function(){return this.messageHandler.sendWithPromise("GetStats",null)},startCleanup:function(){this.messageHandler.sendWithPromise("Cleanup",null).then(function(){for(var t=0,e=this.pageCache.length;t<e;t++){var n=this.pageCache[t];n&&n.cleanup()}this.commonObjs.clear(),this.fontLoader.clear()}.bind(this))}},t}(),tt=function(){function t(){this.objs=Object.create(null)}return t.prototype={ensureObj:function(t){if(this.objs[t])return this.objs[t];var e={capability:y(),data:null,resolved:!1};return this.objs[t]=e,e},get:function(t,e){if(e)return this.ensureObj(t).capability.promise.then(e),null;var n=this.objs[t];return n&&n.resolved||x("Requesting object that isn't resolved yet "+t),n.data},resolve:function(t,e){var n=this.ensureObj(t);n.resolved=!0,n.data=e,n.capability.resolve(e)},isResolved:function(t){var e=this.objs;return!!e[t]&&e[t].resolved},hasData:function(t){return this.isResolved(t)},getData:function(t){var e=this.objs;return e[t]&&e[t].resolved?e[t].data:null},clear:function(){this.objs=Object.create(null)}},t}(),et=function(){function t(t){this._internalRenderTask=t,this.onContinue=null}return t.prototype={get promise(){return this._internalRenderTask.capability.promise},cancel:function(){this._internalRenderTask.cancel()},then:function(t,e){return this.promise.then.apply(this.promise,arguments)}},t}(),nt=function(){function t(t,e,n,r,i,a,s){this.callback=t,this.params=e,this.objs=n,this.commonObjs=r,this.operatorListIdx=null,this.operatorList=i,this.pageNumber=a,this.canvasFactory=s,this.running=!1,this.graphicsReadyCallback=null,this.graphicsReady=!1,this.useRequestAnimationFrame=!1,this.cancelled=!1,this.capability=y(),this.task=new et(this),this._continueBound=this._continue.bind(this),this._scheduleNextBound=this._scheduleNext.bind(this),this._nextBound=this._next.bind(this)}return t.prototype={initializeGraphics:function(t){if(!this.cancelled){j("pdfBug")&&R.StepperManager&&R.StepperManager.enabled&&(this.stepper=R.StepperManager.create(this.pageNumber-1),this.stepper.init(this.operatorList),this.stepper.nextBreakPoint=this.stepper.getNextBreakPoint());var e=this.params;this.gfx=new M(e.canvasContext,this.commonObjs,this.objs,this.canvasFactory,e.imageLayer),this.gfx.beginDrawing(e.transform,e.viewport,t),this.operatorListIdx=0,this.graphicsReady=!0,this.graphicsReadyCallback&&this.graphicsReadyCallback()}},cancel:function(){this.running=!1,this.cancelled=!0,j("pdfjsNext")?this.callback(new N("Rendering cancelled, page "+this.pageNumber,"canvas")):this.callback("cancelled")},operatorListChanged:function(){if(!this.graphicsReady)return void(this.graphicsReadyCallback||(this.graphicsReadyCallback=this._continueBound));this.stepper&&this.stepper.updateOperatorList(this.operatorList),this.running||this._continue()},_continue:function(){this.running=!0,this.cancelled||(this.task.onContinue?this.task.onContinue(this._scheduleNextBound):this._scheduleNext())},_scheduleNext:function(){this.useRequestAnimationFrame&&"undefined"!=typeof window?window.requestAnimationFrame(this._nextBound):Promise.resolve(void 0).then(this._nextBound)},_next:function(){this.cancelled||(this.operatorListIdx=this.gfx.executeOperatorList(this.operatorList,this.operatorListIdx,this._continueBound,this.stepper),this.operatorListIdx===this.operatorList.argsArray.length&&(this.running=!1,this.operatorList.lastChunk&&(this.gfx.endDrawing(),this.callback())))}},t}(),rt=function(){var t=[];return{listen:function(e){S("Global UnsupportedManager.listen is used: use PDFDocumentLoadingTask.onUnsupportedFeature instead"),t.push(e)},notify:function(e){for(var n=0,r=t.length;n<r;n++)t[n](e)}}}();e.version="1.8.172",e.build="8ff1fbe7",e.getDocument=r,e.PDFDataRangeTransport=J,e.PDFWorker=Z,e.PDFDocumentProxy=Q,e.PDFPageProxy=K,e._UnsupportedManager=rt},function(t,e,n){"use strict";var r=n(0),i=r.FONT_IDENTITY_MATRIX,a=r.IDENTITY_MATRIX,s=r.ImageKind,o=r.OPS,c=r.Util,l=r.isNum,h=r.isArray,u=r.warn,d=r.createObjectURL,f={fontStyle:"normal",fontWeight:"normal",fillColor:"#000000"},p=function(){function t(t,e,n){for(var r=-1,i=e;i<n;i++){var a=255&(r^t[i]);r=r>>>8^o[a]}return-1^r}function e(e,n,r,i){var a=i,s=n.length;r[a]=s>>24&255,r[a+1]=s>>16&255,r[a+2]=s>>8&255,r[a+3]=255&s,a+=4,r[a]=255&e.charCodeAt(0),r[a+1]=255&e.charCodeAt(1),r[a+2]=255&e.charCodeAt(2),r[a+3]=255&e.charCodeAt(3),a+=4,r.set(n,a),a+=n.length;var o=t(r,i+4,a);r[a]=o>>24&255,r[a+1]=o>>16&255,r[a+2]=o>>8&255,r[a+3]=255&o}function n(t,e,n){for(var r=1,i=0,a=e;a<n;++a)r=(r+(255&t[a]))%65521,i=(i+r)%65521;return i<<16|r}function r(t,r,o){var c,l,h,u=t.width,f=t.height,p=t.data;switch(r){case s.GRAYSCALE_1BPP:l=0,c=1,h=u+7>>3;break;case s.RGB_24BPP:l=2,c=8,h=3*u;break;case s.RGBA_32BPP:l=6,c=8,h=4*u;break;default:throw new Error("invalid format")}var g,m,A=new Uint8Array((1+h)*f),v=0,b=0;for(g=0;g<f;++g)A[v++]=0,A.set(p.subarray(b,b+h),v),b+=h,v+=h;if(r===s.GRAYSCALE_1BPP)for(v=0,g=0;g<f;g++)for(v++,m=0;m<h;m++)A[v++]^=255;var y=new Uint8Array([u>>24&255,u>>16&255,u>>8&255,255&u,f>>24&255,f>>16&255,f>>8&255,255&f,c,l,0,0,0]),x=A.length,S=Math.ceil(x/65535),w=new Uint8Array(2+x+5*S+4),k=0;w[k++]=120,w[k++]=156;for(var C=0;x>65535;)w[k++]=0,w[k++]=255,w[k++]=255,w[k++]=0,w[k++]=0,w.set(A.subarray(C,C+65535),k),k+=65535,C+=65535,x-=65535;w[k++]=1,w[k++]=255&x,w[k++]=x>>8&255,w[k++]=255&~x,w[k++]=(65535&~x)>>8&255,w.set(A.subarray(C),k),k+=A.length-C;var _=n(A,0,A.length);w[k++]=_>>24&255,w[k++]=_>>16&255,w[k++]=_>>8&255,w[k++]=255&_;var T=i.length+3*a+y.length+w.length,P=new Uint8Array(T),L=0;return P.set(i,L),L+=i.length,e("IHDR",y,P,L),L+=a+y.length,e("IDATA",w,P,L),L+=a+w.length,e("IEND",new Uint8Array(0),P,L),d(P,"image/png",o)}for(var i=new Uint8Array([137,80,78,71,13,10,26,10]),a=12,o=new Int32Array(256),c=0;c<256;c++){for(var l=c,h=0;h<8;h++)l=1&l?3988292384^l>>1&2147483647:l>>1&2147483647;o[c]=l}return function(t,e){return r(t,void 0===t.kind?s.GRAYSCALE_1BPP:t.kind,e)}}(),g=function(){function t(){this.fontSizeScale=1,this.fontWeight=f.fontWeight,this.fontSize=0,this.textMatrix=a,this.fontMatrix=i,this.leading=0,this.x=0,this.y=0,this.lineX=0,this.lineY=0,this.charSpacing=0,this.wordSpacing=0,this.textHScale=1,this.textRise=0,this.fillColor=f.fillColor,this.strokeColor="#000000",this.fillAlpha=1,this.strokeAlpha=1,this.lineWidth=1,this.lineJoin="",this.lineCap="",this.miterLimit=0,this.dashArray=[],this.dashPhase=0,this.dependencies=[],this.activeClipUrl=null,this.clipGroup=null,this.maskId=""}return t.prototype={clone:function(){return Object.create(this)},setCurrentPoint:function(t,e){this.x=t,this.y=e}},t}(),m=function(){function t(t){for(var e=[],n=[],r=t.length,i=0;i<r;i++)"save"!==t[i].fn?"restore"===t[i].fn?e=n.pop():e.push(t[i]):(e.push({fnId:92,fn:"group",items:[]}),n.push(e),e=e[e.length-1].items);return e}function e(t){if(t===(0|t))return t.toString();var e=t.toFixed(10),n=e.length-1;if("0"!==e[n])return e;do{n--}while("0"===e[n]);return e.substr(0,"."===e[n]?n:n+1)}function n(t){if(0===t[4]&&0===t[5]){if(0===t[1]&&0===t[2])return 1===t[0]&&1===t[3]?"":"scale("+e(t[0])+" "+e(t[3])+")";if(t[0]===t[3]&&t[1]===-t[2]){return"rotate("+e(180*Math.acos(t[0])/Math.PI)+")"}}else if(1===t[0]&&0===t[1]&&0===t[2]&&1===t[3])return"translate("+e(t[4])+" "+e(t[5])+")";return"matrix("+e(t[0])+" "+e(t[1])+" "+e(t[2])+" "+e(t[3])+" "+e(t[4])+" "+e(t[5])+")"}function r(t,e,n){this.current=new g,this.transformMatrix=a,this.transformStack=[],this.extraStack=[],this.commonObjs=t,this.objs=e,this.pendingEOFill=!1,this.embedFonts=!1,this.embeddedFonts=Object.create(null),this.cssStyle=null,this.forceDataSchema=!!n}var s="http://www.w3.org/2000/svg",m="http://www.w3.org/1999/xlink",A=["butt","round","square"],v=["miter","round","bevel"],b=0,y=0;return r.prototype={save:function(){this.transformStack.push(this.transformMatrix);var t=this.current;this.extraStack.push(t),this.current=t.clone()},restore:function(){this.transformMatrix=this.transformStack.pop(),this.current=this.extraStack.pop(),this.tgrp=null},group:function(t){this.save(),this.executeOpTree(t),this.restore()},loadDependencies:function(t){
+for(var e=t.fnArray,n=e.length,r=t.argsArray,i=this,a=0;a<n;a++)if(o.dependency===e[a])for(var s=r[a],c=0,l=s.length;c<l;c++){var h,u=s[c],d="g_"===u.substring(0,2);h=d?new Promise(function(t){i.commonObjs.get(u,t)}):new Promise(function(t){i.objs.get(u,t)}),this.current.dependencies.push(h)}return Promise.all(this.current.dependencies)},transform:function(t,e,n,r,i,a){var s=[t,e,n,r,i,a];this.transformMatrix=c.transform(this.transformMatrix,s),this.tgrp=null},getSVG:function(t,e){this.viewport=e;var n=this._initialize(e);return this.loadDependencies(t).then(function(){this.transformMatrix=a;var e=this.convertOpList(t);return this.executeOpTree(e),n}.bind(this))},convertOpList:function(e){var n=e.argsArray,r=e.fnArray,i=r.length,a=[],s=[];for(var c in o)a[o[c]]=c;for(var l=0;l<i;l++){var h=r[l];s.push({fnId:h,fn:a[h],args:n[l]})}return t(s)},executeOpTree:function(t){for(var e=t.length,n=0;n<e;n++){var r=t[n].fn,i=t[n].fnId,a=t[n].args;switch(0|i){case o.beginText:this.beginText();break;case o.setLeading:this.setLeading(a);break;case o.setLeadingMoveText:this.setLeadingMoveText(a[0],a[1]);break;case o.setFont:this.setFont(a);break;case o.showText:case o.showSpacedText:this.showText(a[0]);break;case o.endText:this.endText();break;case o.moveText:this.moveText(a[0],a[1]);break;case o.setCharSpacing:this.setCharSpacing(a[0]);break;case o.setWordSpacing:this.setWordSpacing(a[0]);break;case o.setHScale:this.setHScale(a[0]);break;case o.setTextMatrix:this.setTextMatrix(a[0],a[1],a[2],a[3],a[4],a[5]);break;case o.setLineWidth:this.setLineWidth(a[0]);break;case o.setLineJoin:this.setLineJoin(a[0]);break;case o.setLineCap:this.setLineCap(a[0]);break;case o.setMiterLimit:this.setMiterLimit(a[0]);break;case o.setFillRGBColor:this.setFillRGBColor(a[0],a[1],a[2]);break;case o.setStrokeRGBColor:this.setStrokeRGBColor(a[0],a[1],a[2]);break;case o.setDash:this.setDash(a[0],a[1]);break;case o.setGState:this.setGState(a[0]);break;case o.fill:this.fill();break;case o.eoFill:this.eoFill();break;case o.stroke:this.stroke();break;case o.fillStroke:this.fillStroke();break;case o.eoFillStroke:this.eoFillStroke();break;case o.clip:this.clip("nonzero");break;case o.eoClip:this.clip("evenodd");break;case o.paintSolidColorImageMask:this.paintSolidColorImageMask();break;case o.paintJpegXObject:this.paintJpegXObject(a[0],a[1],a[2]);break;case o.paintImageXObject:this.paintImageXObject(a[0]);break;case o.paintInlineImageXObject:this.paintInlineImageXObject(a[0]);break;case o.paintImageMaskXObject:this.paintImageMaskXObject(a[0]);break;case o.paintFormXObjectBegin:this.paintFormXObjectBegin(a[0],a[1]);break;case o.paintFormXObjectEnd:this.paintFormXObjectEnd();break;case o.closePath:this.closePath();break;case o.closeStroke:this.closeStroke();break;case o.closeFillStroke:this.closeFillStroke();break;case o.nextLine:this.nextLine();break;case o.transform:this.transform(a[0],a[1],a[2],a[3],a[4],a[5]);break;case o.constructPath:this.constructPath(a[0],a[1]);break;case o.endPath:this.endPath();break;case 92:this.group(t[n].items);break;default:u("Unimplemented operator "+r)}}},setWordSpacing:function(t){this.current.wordSpacing=t},setCharSpacing:function(t){this.current.charSpacing=t},nextLine:function(){this.moveText(0,this.current.leading)},setTextMatrix:function(t,n,r,i,a,o){var c=this.current;this.current.textMatrix=this.current.lineMatrix=[t,n,r,i,a,o],this.current.x=this.current.lineX=0,this.current.y=this.current.lineY=0,c.xcoords=[],c.tspan=document.createElementNS(s,"svg:tspan"),c.tspan.setAttributeNS(null,"font-family",c.fontFamily),c.tspan.setAttributeNS(null,"font-size",e(c.fontSize)+"px"),c.tspan.setAttributeNS(null,"y",e(-c.y)),c.txtElement=document.createElementNS(s,"svg:text"),c.txtElement.appendChild(c.tspan)},beginText:function(){this.current.x=this.current.lineX=0,this.current.y=this.current.lineY=0,this.current.textMatrix=a,this.current.lineMatrix=a,this.current.tspan=document.createElementNS(s,"svg:tspan"),this.current.txtElement=document.createElementNS(s,"svg:text"),this.current.txtgrp=document.createElementNS(s,"svg:g"),this.current.xcoords=[]},moveText:function(t,n){var r=this.current;this.current.x=this.current.lineX+=t,this.current.y=this.current.lineY+=n,r.xcoords=[],r.tspan=document.createElementNS(s,"svg:tspan"),r.tspan.setAttributeNS(null,"font-family",r.fontFamily),r.tspan.setAttributeNS(null,"font-size",e(r.fontSize)+"px"),r.tspan.setAttributeNS(null,"y",e(-r.y))},showText:function(t){var r=this.current,i=r.font,a=r.fontSize;if(0!==a){var s,o=r.charSpacing,c=r.wordSpacing,h=r.fontDirection,u=r.textHScale*h,d=t.length,p=i.vertical,g=a*r.fontMatrix[0],m=0;for(s=0;s<d;++s){var A=t[s];if(null!==A)if(l(A))m+=-A*a*.001;else{r.xcoords.push(r.x+m*u);var v=A.width,b=A.fontChar,y=v*g+o*h;m+=y,r.tspan.textContent+=b}else m+=h*c}p?r.y-=m*u:r.x+=m*u,r.tspan.setAttributeNS(null,"x",r.xcoords.map(e).join(" ")),r.tspan.setAttributeNS(null,"y",e(-r.y)),r.tspan.setAttributeNS(null,"font-family",r.fontFamily),r.tspan.setAttributeNS(null,"font-size",e(r.fontSize)+"px"),r.fontStyle!==f.fontStyle&&r.tspan.setAttributeNS(null,"font-style",r.fontStyle),r.fontWeight!==f.fontWeight&&r.tspan.setAttributeNS(null,"font-weight",r.fontWeight),r.fillColor!==f.fillColor&&r.tspan.setAttributeNS(null,"fill",r.fillColor),r.txtElement.setAttributeNS(null,"transform",n(r.textMatrix)+" scale(1, -1)"),r.txtElement.setAttributeNS("http://www.w3.org/XML/1998/namespace","xml:space","preserve"),r.txtElement.appendChild(r.tspan),r.txtgrp.appendChild(r.txtElement),this._ensureTransformGroup().appendChild(r.txtElement)}},setLeadingMoveText:function(t,e){this.setLeading(-e),this.moveText(t,e)},addFontStyle:function(t){this.cssStyle||(this.cssStyle=document.createElementNS(s,"svg:style"),this.cssStyle.setAttributeNS(null,"type","text/css"),this.defs.appendChild(this.cssStyle));var e=d(t.data,t.mimetype,this.forceDataSchema);this.cssStyle.textContent+='@font-face { font-family: "'+t.loadedName+'"; src: url('+e+"); }\n"},setFont:function(t){var n=this.current,r=this.commonObjs.get(t[0]),a=t[1];this.current.font=r,this.embedFonts&&r.data&&!this.embeddedFonts[r.loadedName]&&(this.addFontStyle(r),this.embeddedFonts[r.loadedName]=r),n.fontMatrix=r.fontMatrix?r.fontMatrix:i;var o=r.black?r.bold?"bolder":"bold":r.bold?"bold":"normal",c=r.italic?"italic":"normal";a<0?(a=-a,n.fontDirection=-1):n.fontDirection=1,n.fontSize=a,n.fontFamily=r.loadedName,n.fontWeight=o,n.fontStyle=c,n.tspan=document.createElementNS(s,"svg:tspan"),n.tspan.setAttributeNS(null,"y",e(-n.y)),n.xcoords=[]},endText:function(){},setLineWidth:function(t){this.current.lineWidth=t},setLineCap:function(t){this.current.lineCap=A[t]},setLineJoin:function(t){this.current.lineJoin=v[t]},setMiterLimit:function(t){this.current.miterLimit=t},setStrokeRGBColor:function(t,e,n){var r=c.makeCssRgb(t,e,n);this.current.strokeColor=r},setFillRGBColor:function(t,e,n){var r=c.makeCssRgb(t,e,n);this.current.fillColor=r,this.current.tspan=document.createElementNS(s,"svg:tspan"),this.current.xcoords=[]},setDash:function(t,e){this.current.dashArray=t,this.current.dashPhase=e},constructPath:function(t,n){var r=this.current,i=r.x,a=r.y;r.path=document.createElementNS(s,"svg:path");for(var c=[],l=t.length,h=0,u=0;h<l;h++)switch(0|t[h]){case o.rectangle:i=n[u++],a=n[u++];var d=n[u++],f=n[u++],p=i+d,g=a+f;c.push("M",e(i),e(a),"L",e(p),e(a),"L",e(p),e(g),"L",e(i),e(g),"Z");break;case o.moveTo:i=n[u++],a=n[u++],c.push("M",e(i),e(a));break;case o.lineTo:i=n[u++],a=n[u++],c.push("L",e(i),e(a));break;case o.curveTo:i=n[u+4],a=n[u+5],c.push("C",e(n[u]),e(n[u+1]),e(n[u+2]),e(n[u+3]),e(i),e(a)),u+=6;break;case o.curveTo2:i=n[u+2],a=n[u+3],c.push("C",e(i),e(a),e(n[u]),e(n[u+1]),e(n[u+2]),e(n[u+3])),u+=4;break;case o.curveTo3:i=n[u+2],a=n[u+3],c.push("C",e(n[u]),e(n[u+1]),e(i),e(a),e(i),e(a)),u+=4;break;case o.closePath:c.push("Z")}r.path.setAttributeNS(null,"d",c.join(" ")),r.path.setAttributeNS(null,"stroke-miterlimit",e(r.miterLimit)),r.path.setAttributeNS(null,"stroke-linecap",r.lineCap),r.path.setAttributeNS(null,"stroke-linejoin",r.lineJoin),r.path.setAttributeNS(null,"stroke-width",e(r.lineWidth)+"px"),r.path.setAttributeNS(null,"stroke-dasharray",r.dashArray.map(e).join(" ")),r.path.setAttributeNS(null,"stroke-dashoffset",e(r.dashPhase)+"px"),r.path.setAttributeNS(null,"fill","none"),this._ensureTransformGroup().appendChild(r.path),r.element=r.path,r.setCurrentPoint(i,a)},endPath:function(){},clip:function(t){var e=this.current,r="clippath"+b;b++;var i=document.createElementNS(s,"svg:clipPath");i.setAttributeNS(null,"id",r),i.setAttributeNS(null,"transform",n(this.transformMatrix));var a=e.element.cloneNode();"evenodd"===t?a.setAttributeNS(null,"clip-rule","evenodd"):a.setAttributeNS(null,"clip-rule","nonzero"),i.appendChild(a),this.defs.appendChild(i),e.activeClipUrl&&(e.clipGroup=null,this.extraStack.forEach(function(t){t.clipGroup=null})),e.activeClipUrl="url(#"+r+")",this.tgrp=null},closePath:function(){var t=this.current,e=t.path.getAttributeNS(null,"d");e+="Z",t.path.setAttributeNS(null,"d",e)},setLeading:function(t){this.current.leading=-t},setTextRise:function(t){this.current.textRise=t},setHScale:function(t){this.current.textHScale=t/100},setGState:function(t){for(var e=0,n=t.length;e<n;e++){var r=t[e],i=r[0],a=r[1];switch(i){case"LW":this.setLineWidth(a);break;case"LC":this.setLineCap(a);break;case"LJ":this.setLineJoin(a);break;case"ML":this.setMiterLimit(a);break;case"D":this.setDash(a[0],a[1]);break;case"Font":this.setFont(a);break;default:u("Unimplemented graphic state "+i)}}},fill:function(){var t=this.current;t.element.setAttributeNS(null,"fill",t.fillColor)},stroke:function(){var t=this.current;t.element.setAttributeNS(null,"stroke",t.strokeColor),t.element.setAttributeNS(null,"fill","none")},eoFill:function(){var t=this.current;t.element.setAttributeNS(null,"fill",t.fillColor),t.element.setAttributeNS(null,"fill-rule","evenodd")},fillStroke:function(){this.stroke(),this.fill()},eoFillStroke:function(){this.current.element.setAttributeNS(null,"fill-rule","evenodd"),this.fillStroke()},closeStroke:function(){this.closePath(),this.stroke()},closeFillStroke:function(){this.closePath(),this.fillStroke()},paintSolidColorImageMask:function(){var t=this.current,e=document.createElementNS(s,"svg:rect");e.setAttributeNS(null,"x","0"),e.setAttributeNS(null,"y","0"),e.setAttributeNS(null,"width","1px"),e.setAttributeNS(null,"height","1px"),e.setAttributeNS(null,"fill",t.fillColor),this._ensureTransformGroup().appendChild(e)},paintJpegXObject:function(t,n,r){var i=this.objs.get(t),a=document.createElementNS(s,"svg:image");a.setAttributeNS(m,"xlink:href",i.src),a.setAttributeNS(null,"width",i.width+"px"),a.setAttributeNS(null,"height",i.height+"px"),a.setAttributeNS(null,"x","0"),a.setAttributeNS(null,"y",e(-r)),a.setAttributeNS(null,"transform","scale("+e(1/n)+" "+e(-1/r)+")"),this._ensureTransformGroup().appendChild(a)},paintImageXObject:function(t){var e=this.objs.get(t);if(!e)return void u("Dependent image isn't ready yet");this.paintInlineImageXObject(e)},paintInlineImageXObject:function(t,n){var r=t.width,i=t.height,a=p(t,this.forceDataSchema),o=document.createElementNS(s,"svg:rect");o.setAttributeNS(null,"x","0"),o.setAttributeNS(null,"y","0"),o.setAttributeNS(null,"width",e(r)),o.setAttributeNS(null,"height",e(i)),this.current.element=o,this.clip("nonzero");var c=document.createElementNS(s,"svg:image");c.setAttributeNS(m,"xlink:href",a),c.setAttributeNS(null,"x","0"),c.setAttributeNS(null,"y",e(-i)),c.setAttributeNS(null,"width",e(r)+"px"),c.setAttributeNS(null,"height",e(i)+"px"),c.setAttributeNS(null,"transform","scale("+e(1/r)+" "+e(-1/i)+")"),n?n.appendChild(c):this._ensureTransformGroup().appendChild(c)},paintImageMaskXObject:function(t){var n=this.current,r=t.width,i=t.height,a=n.fillColor;n.maskId="mask"+y++;var o=document.createElementNS(s,"svg:mask");o.setAttributeNS(null,"id",n.maskId);var c=document.createElementNS(s,"svg:rect");c.setAttributeNS(null,"x","0"),c.setAttributeNS(null,"y","0"),c.setAttributeNS(null,"width",e(r)),c.setAttributeNS(null,"height",e(i)),c.setAttributeNS(null,"fill",a),c.setAttributeNS(null,"mask","url(#"+n.maskId+")"),this.defs.appendChild(o),this._ensureTransformGroup().appendChild(c),this.paintInlineImageXObject(t,o)},paintFormXObjectBegin:function(t,n){if(h(t)&&6===t.length&&this.transform(t[0],t[1],t[2],t[3],t[4],t[5]),h(n)&&4===n.length){var r=n[2]-n[0],i=n[3]-n[1],a=document.createElementNS(s,"svg:rect");a.setAttributeNS(null,"x",n[0]),a.setAttributeNS(null,"y",n[1]),a.setAttributeNS(null,"width",e(r)),a.setAttributeNS(null,"height",e(i)),this.current.element=a,this.clip("nonzero"),this.endPath()}},paintFormXObjectEnd:function(){},_initialize:function(t){var e=document.createElementNS(s,"svg:svg");e.setAttributeNS(null,"version","1.1"),e.setAttributeNS(null,"width",t.width+"px"),e.setAttributeNS(null,"height",t.height+"px"),e.setAttributeNS(null,"preserveAspectRatio","none"),e.setAttributeNS(null,"viewBox","0 0 "+t.width+" "+t.height);var r=document.createElementNS(s,"svg:defs");e.appendChild(r),this.defs=r;var i=document.createElementNS(s,"svg:g");return i.setAttributeNS(null,"transform",n(t.transform)),e.appendChild(i),this.svg=i,e},_ensureClipGroup:function(){if(!this.current.clipGroup){var t=document.createElementNS(s,"svg:g");t.setAttributeNS(null,"clip-path",this.current.activeClipUrl),this.svg.appendChild(t),this.current.clipGroup=t}return this.current.clipGroup},_ensureTransformGroup:function(){return this.tgrp||(this.tgrp=document.createElementNS(s,"svg:g"),this.tgrp.setAttributeNS(null,"transform",n(this.transformMatrix)),this.current.activeClipUrl?this._ensureClipGroup().appendChild(this.tgrp):this.svg.appendChild(this.tgrp)),this.tgrp}},r}();e.SVGGraphics=m},function(t,e,n){"use strict";var r=n(0),i=n(1),a=r.Util,s=r.createPromiseCapability,o=i.CustomStyle,c=i.getDefaultSetting,l=function(){function t(t){return!f.test(t)}function e(e,n,r){var i=document.createElement("div"),s={style:null,angle:0,canvasWidth:0,isWhitespace:!1,originalTransform:null,paddingBottom:0,paddingLeft:0,paddingRight:0,paddingTop:0,scale:1};if(e._textDivs.push(i),t(n.str))return s.isWhitespace=!0,void e._textDivProperties.set(i,s);var o=a.transform(e._viewport.transform,n.transform),l=Math.atan2(o[1],o[0]),h=r[n.fontName];h.vertical&&(l+=Math.PI/2);var u=Math.sqrt(o[2]*o[2]+o[3]*o[3]),d=u;h.ascent?d=h.ascent*d:h.descent&&(d=(1+h.descent)*d);var f,g;if(0===l?(f=o[4],g=o[5]-d):(f=o[4]+d*Math.sin(l),g=o[5]-d*Math.cos(l)),p[1]=f,p[3]=g,p[5]=u,p[7]=h.fontFamily,s.style=p.join(""),i.setAttribute("style",s.style),i.textContent=n.str,c("pdfBug")&&(i.dataset.fontName=n.fontName),0!==l&&(s.angle=l*(180/Math.PI)),n.str.length>1&&(h.vertical?s.canvasWidth=n.height*e._viewport.scale:s.canvasWidth=n.width*e._viewport.scale),e._textDivProperties.set(i,s),e._enhanceTextSelection){var m=1,A=0;0!==l&&(m=Math.cos(l),A=Math.sin(l));var v,b,y=(h.vertical?n.height:n.width)*e._viewport.scale,x=u;0!==l?(v=[m,A,-A,m,f,g],b=a.getAxialAlignedBoundingBox([0,0,y,x],v)):b=[f,g,f+y,g+x],e._bounds.push({left:b[0],top:b[1],right:b[2],bottom:b[3],div:i,size:[y,x],m:v})}}function n(t){if(!t._canceled){var e=t._container,n=t._textDivs,r=t._capability,i=n.length;if(i>d)return t._renderingDone=!0,void r.resolve();var a=document.createElement("canvas");a.mozOpaque=!0;for(var s,c,l=a.getContext("2d",{alpha:!1}),h=0;h<i;h++){var u=n[h],f=t._textDivProperties.get(u);if(!f.isWhitespace){var p=u.style.fontSize,g=u.style.fontFamily;p===s&&g===c||(l.font=p+" "+g,s=p,c=g);var m=l.measureText(u.textContent).width;e.appendChild(u);var A="";0!==f.canvasWidth&&m>0&&(f.scale=f.canvasWidth/m,A="scaleX("+f.scale+")"),0!==f.angle&&(A="rotate("+f.angle+"deg) "+A),""!==A&&(f.originalTransform=A,o.setProp("transform",u,A)),t._textDivProperties.set(u,f)}}t._renderingDone=!0,r.resolve()}}function r(t){for(var e=t._bounds,n=t._viewport,r=i(n.width,n.height,e),s=0;s<r.length;s++){var o=e[s].div,c=t._textDivProperties.get(o);if(0!==c.angle){var l=r[s],h=e[s],u=h.m,d=u[0],f=u[1],p=[[0,0],[0,h.size[1]],[h.size[0],0],h.size],g=new Float64Array(64);p.forEach(function(t,e){var n=a.applyTransform(t,u);g[e+0]=d&&(l.left-n[0])/d,g[e+4]=f&&(l.top-n[1])/f,g[e+8]=d&&(l.right-n[0])/d,g[e+12]=f&&(l.bottom-n[1])/f,g[e+16]=f&&(l.left-n[0])/-f,g[e+20]=d&&(l.top-n[1])/d,g[e+24]=f&&(l.right-n[0])/-f,g[e+28]=d&&(l.bottom-n[1])/d,g[e+32]=d&&(l.left-n[0])/-d,g[e+36]=f&&(l.top-n[1])/-f,g[e+40]=d&&(l.right-n[0])/-d,g[e+44]=f&&(l.bottom-n[1])/-f,g[e+48]=f&&(l.left-n[0])/f,g[e+52]=d&&(l.top-n[1])/-d,g[e+56]=f&&(l.right-n[0])/f,g[e+60]=d&&(l.bottom-n[1])/-d});var m=function(t,e,n){for(var r=0,i=0;i<n;i++){var a=t[e++];a>0&&(r=r?Math.min(a,r):a)}return r},A=1+Math.min(Math.abs(d),Math.abs(f));c.paddingLeft=m(g,32,16)/A,c.paddingTop=m(g,48,16)/A,c.paddingRight=m(g,0,16)/A,c.paddingBottom=m(g,16,16)/A,t._textDivProperties.set(o,c)}else c.paddingLeft=e[s].left-r[s].left,c.paddingTop=e[s].top-r[s].top,c.paddingRight=r[s].right-e[s].right,c.paddingBottom=r[s].bottom-e[s].bottom,t._textDivProperties.set(o,c)}}function i(t,e,n){var r=n.map(function(t,e){return{x1:t.left,y1:t.top,x2:t.right,y2:t.bottom,index:e,x1New:void 0,x2New:void 0}});l(t,r);var i=new Array(n.length);return r.forEach(function(t){var e=t.index;i[e]={left:t.x1New,top:0,right:t.x2New,bottom:0}}),n.map(function(e,n){var a=i[n],s=r[n];s.x1=e.top,s.y1=t-a.right,s.x2=e.bottom,s.y2=t-a.left,s.index=n,s.x1New=void 0,s.x2New=void 0}),l(e,r),r.forEach(function(t){var e=t.index;i[e].top=t.x1New,i[e].bottom=t.x2New}),i}function l(t,e){e.sort(function(t,e){return t.x1-e.x1||t.index-e.index});var n={x1:-1/0,y1:-1/0,x2:0,y2:1/0,index:-1,x1New:0,x2New:0},r=[{start:-1/0,end:1/0,boundary:n}];e.forEach(function(t){for(var e=0;e<r.length&&r[e].end<=t.y1;)e++;for(var n=r.length-1;n>=0&&r[n].start>=t.y2;)n--;var i,a,s,o,c=-1/0;for(s=e;s<=n;s++){i=r[s],a=i.boundary;var l;l=a.x2>t.x1?a.index>t.index?a.x1New:t.x1:void 0===a.x2New?(a.x2+t.x1)/2:a.x2New,l>c&&(c=l)}for(t.x1New=c,s=e;s<=n;s++)i=r[s],a=i.boundary,void 0===a.x2New?a.x2>t.x1?a.index>t.index&&(a.x2New=a.x2):a.x2New=c:a.x2New>c&&(a.x2New=Math.max(c,a.x2));var h=[],u=null;for(s=e;s<=n;s++){i=r[s],a=i.boundary;var d=a.x2>t.x2?a:t;u===d?h[h.length-1].end=i.end:(h.push({start:i.start,end:i.end,boundary:d}),u=d)}for(r[e].start<t.y1&&(h[0].start=t.y1,h.unshift({start:r[e].start,end:t.y1,boundary:r[e].boundary})),t.y2<r[n].end&&(h[h.length-1].end=t.y2,h.push({start:t.y2,end:r[n].end,boundary:r[n].boundary})),s=e;s<=n;s++)if(i=r[s],a=i.boundary,void 0===a.x2New){var f=!1;for(o=e-1;!f&&o>=0&&r[o].start>=a.y1;o--)f=r[o].boundary===a;for(o=n+1;!f&&o<r.length&&r[o].end<=a.y2;o++)f=r[o].boundary===a;for(o=0;!f&&o<h.length;o++)f=h[o].boundary===a;f||(a.x2New=c)}Array.prototype.splice.apply(r,[e,n-e+1].concat(h))}),r.forEach(function(e){var n=e.boundary;void 0===n.x2New&&(n.x2New=Math.max(t,n.x2))})}function h(t,e,n,r,i){this._textContent=t,this._container=e,this._viewport=n,this._textDivs=r||[],this._textDivProperties=new WeakMap,this._renderingDone=!1,this._canceled=!1,this._capability=s(),this._renderTimer=null,this._bounds=[],this._enhanceTextSelection=!!i}function u(t){var e=new h(t.textContent,t.container,t.viewport,t.textDivs,t.enhanceTextSelection);return e._render(t.timeout),e}var d=1e5,f=/\S/,p=["left: ",0,"px; top: ",0,"px; font-size: ",0,"px; font-family: ","",";"];return h.prototype={get promise(){return this._capability.promise},cancel:function(){this._canceled=!0,null!==this._renderTimer&&(clearTimeout(this._renderTimer),this._renderTimer=null),this._capability.reject("canceled")},_render:function(t){for(var r=this._textContent.items,i=this._textContent.styles,a=0,s=r.length;a<s;a++)e(this,r[a],i);if(t){var o=this;this._renderTimer=setTimeout(function(){n(o),o._renderTimer=null},t)}else n(this)},expandTextDivs:function(t){if(this._enhanceTextSelection&&this._renderingDone){null!==this._bounds&&(r(this),this._bounds=null);for(var e=0,n=this._textDivs.length;e<n;e++){var i=this._textDivs[e],a=this._textDivProperties.get(i);if(!a.isWhitespace)if(t){var s="",c="";1!==a.scale&&(s="scaleX("+a.scale+")"),0!==a.angle&&(s="rotate("+a.angle+"deg) "+s),0!==a.paddingLeft&&(c+=" padding-left: "+a.paddingLeft/a.scale+"px;",s+=" translateX("+-a.paddingLeft/a.scale+"px)"),0!==a.paddingTop&&(c+=" padding-top: "+a.paddingTop+"px;",s+=" translateY("+-a.paddingTop+"px)"),0!==a.paddingRight&&(c+=" padding-right: "+a.paddingRight/a.scale+"px;"),0!==a.paddingBottom&&(c+=" padding-bottom: "+a.paddingBottom+"px;"),""!==c&&i.setAttribute("style",a.style+c),""!==s&&o.setProp("transform",i,s)}else i.style.padding=0,o.setProp("transform",i,a.originalTransform||"")}}}},u}();e.renderTextLayer=l},function(t,e,n){"use strict";var r;r=function(){return this}();try{r=r||Function("return this")()||(0,eval)("this")}catch(t){"object"==typeof window&&(r=window)}t.exports=r},function(t,e,n){"use strict";function r(t){return t.replace(/>\\376\\377([^<]+)/g,function(t,e){for(var n=e.replace(/\\([0-3])([0-7])([0-7])/g,function(t,e,n,r){return String.fromCharCode(64*e+8*n+1*r)}),r="",i=0;i<n.length;i+=2){var a=256*n.charCodeAt(i)+n.charCodeAt(i+1);r+=a>=32&&a<127&&60!==a&&62!==a&&38!==a?String.fromCharCode(a):"&#x"+(65536+a).toString(16).substring(1)+";"}return">"+r})}function i(t){if("string"==typeof t){t=r(t);t=(new DOMParser).parseFromString(t,"application/xml")}else t instanceof Document||s("Metadata: Invalid metadata object");this.metaDocument=t,this.metadata=Object.create(null),this.parse()}var a=n(0),s=a.error;i.prototype={parse:function(){var t=this.metaDocument,e=t.documentElement;if("rdf:rdf"!==e.nodeName.toLowerCase())for(e=e.firstChild;e&&"rdf:rdf"!==e.nodeName.toLowerCase();)e=e.nextSibling;var n=e?e.nodeName.toLowerCase():null;if(e&&"rdf:rdf"===n&&e.hasChildNodes()){var r,i,a,s,o,c,l,h=e.childNodes;for(s=0,c=h.length;s<c;s++)if(r=h[s],"rdf:description"===r.nodeName.toLowerCase())for(o=0,l=r.childNodes.length;o<l;o++)"#text"!==r.childNodes[o].nodeName.toLowerCase()&&(i=r.childNodes[o],a=i.nodeName.toLowerCase(),this.metadata[a]=i.textContent.trim())}},get:function(t){return this.metadata[t]||null},has:function(t){return void 0!==this.metadata[t]}},e.Metadata=i},function(t,e,n){"use strict";var r=n(0),i=n(1),a=r.shadow,s=i.getDefaultSetting,o=function(){function t(t,e,n){var r=t.createShader(n);if(t.shaderSource(r,e),t.compileShader(r),!t.getShaderParameter(r,t.COMPILE_STATUS)){var i=t.getShaderInfoLog(r);throw new Error("Error during shader compilation: "+i)}return r}function e(e,n){return t(e,n,e.VERTEX_SHADER)}function n(e,n){return t(e,n,e.FRAGMENT_SHADER)}function r(t,e){for(var n=t.createProgram(),r=0,i=e.length;r<i;++r)t.attachShader(n,e[r]);if(t.linkProgram(n),!t.getProgramParameter(n,t.LINK_STATUS)){var a=t.getProgramInfoLog(n);throw new Error("Error during program linking: "+a)}return n}function i(t,e,n){t.activeTexture(n);var r=t.createTexture();return t.bindTexture(t.TEXTURE_2D,r),t.texParameteri(t.TEXTURE_2D,t.TEXTURE_WRAP_S,t.CLAMP_TO_EDGE),t.texParameteri(t.TEXTURE_2D,t.TEXTURE_WRAP_T,t.CLAMP_TO_EDGE),t.texParameteri(t.TEXTURE_2D,t.TEXTURE_MIN_FILTER,t.NEAREST),t.texParameteri(t.TEXTURE_2D,t.TEXTURE_MAG_FILTER,t.NEAREST),t.texImage2D(t.TEXTURE_2D,0,t.RGBA,t.RGBA,t.UNSIGNED_BYTE,e),r}function o(){f||(p=document.createElement("canvas"),f=p.getContext("webgl",{premultipliedalpha:!1}))}function c(){var t,i;o(),t=p,p=null,i=f,f=null;var a=e(i,g),s=n(i,m),c=r(i,[a,s]);i.useProgram(c);var l={};l.gl=i,l.canvas=t,l.resolutionLocation=i.getUniformLocation(c,"u_resolution"),l.positionLocation=i.getAttribLocation(c,"a_position"),l.backdropLocation=i.getUniformLocation(c,"u_backdrop"),l.subtypeLocation=i.getUniformLocation(c,"u_subtype");var h=i.getAttribLocation(c,"a_texCoord"),u=i.getUniformLocation(c,"u_image"),d=i.getUniformLocation(c,"u_mask"),v=i.createBuffer();i.bindBuffer(i.ARRAY_BUFFER,v),i.bufferData(i.ARRAY_BUFFER,new Float32Array([0,0,1,0,0,1,0,1,1,0,1,1]),i.STATIC_DRAW),i.enableVertexAttribArray(h),i.vertexAttribPointer(h,2,i.FLOAT,!1,0,0),i.uniform1i(u,0),i.uniform1i(d,1),A=l}function l(t,e,n){var r=t.width,a=t.height;A||c();var s=A,o=s.canvas,l=s.gl;o.width=r,o.height=a,l.viewport(0,0,l.drawingBufferWidth,l.drawingBufferHeight),l.uniform2f(s.resolutionLocation,r,a),n.backdrop?l.uniform4f(s.resolutionLocation,n.backdrop[0],n.backdrop[1],n.backdrop[2],1):l.uniform4f(s.resolutionLocation,0,0,0,0),l.uniform1i(s.subtypeLocation,"Luminosity"===n.subtype?1:0);var h=i(l,t,l.TEXTURE0),u=i(l,e,l.TEXTURE1),d=l.createBuffer();return l.bindBuffer(l.ARRAY_BUFFER,d),l.bufferData(l.ARRAY_BUFFER,new Float32Array([0,0,r,0,0,a,0,a,r,0,r,a]),l.STATIC_DRAW),l.enableVertexAttribArray(s.positionLocation),l.vertexAttribPointer(s.positionLocation,2,l.FLOAT,!1,0,0),l.clearColor(0,0,0,0),l.enable(l.BLEND),l.blendFunc(l.ONE,l.ONE_MINUS_SRC_ALPHA),l.clear(l.COLOR_BUFFER_BIT),l.drawArrays(l.TRIANGLES,0,6),l.flush(),l.deleteTexture(h),l.deleteTexture(u),l.deleteBuffer(d),o}function h(){var t,i;o(),t=p,p=null,i=f,f=null;var a=e(i,v),s=n(i,b),c=r(i,[a,s]);i.useProgram(c);var l={};l.gl=i,l.canvas=t,l.resolutionLocation=i.getUniformLocation(c,"u_resolution"),l.scaleLocation=i.getUniformLocation(c,"u_scale"),l.offsetLocation=i.getUniformLocation(c,"u_offset"),l.positionLocation=i.getAttribLocation(c,"a_position"),l.colorLocation=i.getAttribLocation(c,"a_color"),y=l}function u(t,e,n,r,i){y||h();var a=y,s=a.canvas,o=a.gl;s.width=t,s.height=e,o.viewport(0,0,o.drawingBufferWidth,o.drawingBufferHeight),o.uniform2f(a.resolutionLocation,t,e);var c,l,u,d=0;for(c=0,l=r.length;c<l;c++)switch(r[c].type){case"lattice":u=r[c].coords.length/r[c].verticesPerRow|0,d+=(u-1)*(r[c].verticesPerRow-1)*6;break;case"triangles":d+=r[c].coords.length}var f=new Float32Array(2*d),p=new Uint8Array(3*d),g=i.coords,m=i.colors,A=0,v=0;for(c=0,l=r.length;c<l;c++){var b=r[c],x=b.coords,S=b.colors;switch(b.type){case"lattice":var w=b.verticesPerRow;u=x.length/w|0;for(var k=1;k<u;k++)for(var C=k*w+1,_=1;_<w;_++,C++)f[A]=g[x[C-w-1]],f[A+1]=g[x[C-w-1]+1],f[A+2]=g[x[C-w]],f[A+3]=g[x[C-w]+1],f[A+4]=g[x[C-1]],f[A+5]=g[x[C-1]+1],p[v]=m[S[C-w-1]],p[v+1]=m[S[C-w-1]+1],p[v+2]=m[S[C-w-1]+2],p[v+3]=m[S[C-w]],p[v+4]=m[S[C-w]+1],p[v+5]=m[S[C-w]+2],p[v+6]=m[S[C-1]],p[v+7]=m[S[C-1]+1],p[v+8]=m[S[C-1]+2],f[A+6]=f[A+2],f[A+7]=f[A+3],f[A+8]=f[A+4],f[A+9]=f[A+5],f[A+10]=g[x[C]],f[A+11]=g[x[C]+1],p[v+9]=p[v+3],p[v+10]=p[v+4],p[v+11]=p[v+5],p[v+12]=p[v+6],p[v+13]=p[v+7],p[v+14]=p[v+8],p[v+15]=m[S[C]],p[v+16]=m[S[C]+1],p[v+17]=m[S[C]+2],A+=12,v+=18;break;case"triangles":for(var T=0,P=x.length;T<P;T++)f[A]=g[x[T]],f[A+1]=g[x[T]+1],p[v]=m[S[T]],p[v+1]=m[S[T]+1],p[v+2]=m[S[T]+2],A+=2,v+=3}}n?o.clearColor(n[0]/255,n[1]/255,n[2]/255,1):o.clearColor(0,0,0,0),o.clear(o.COLOR_BUFFER_BIT);var L=o.createBuffer();o.bindBuffer(o.ARRAY_BUFFER,L),o.bufferData(o.ARRAY_BUFFER,f,o.STATIC_DRAW),o.enableVertexAttribArray(a.positionLocation),o.vertexAttribPointer(a.positionLocation,2,o.FLOAT,!1,0,0);var E=o.createBuffer();return o.bindBuffer(o.ARRAY_BUFFER,E),o.bufferData(o.ARRAY_BUFFER,p,o.STATIC_DRAW),o.enableVertexAttribArray(a.colorLocation),o.vertexAttribPointer(a.colorLocation,3,o.UNSIGNED_BYTE,!1,0,0),o.uniform2f(a.scaleLocation,i.scaleX,i.scaleY),o.uniform2f(a.offsetLocation,i.offsetX,i.offsetY),o.drawArrays(o.TRIANGLES,0,d),o.flush(),o.deleteBuffer(L),o.deleteBuffer(E),s}function d(){A&&A.canvas&&(A.canvas.width=0,A.canvas.height=0),y&&y.canvas&&(y.canvas.width=0,y.canvas.height=0),A=null,y=null}var f,p,g=" attribute vec2 a_position; attribute vec2 a_texCoord; uniform vec2 u_resolution; varying vec2 v_texCoord; void main() { vec2 clipSpace = (a_position / u_resolution) * 2.0 - 1.0; gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1); v_texCoord = a_texCoord; } ",m=" precision mediump float; uniform vec4 u_backdrop; uniform int u_subtype; uniform sampler2D u_image; uniform sampler2D u_mask; varying vec2 v_texCoord; void main() { vec4 imageColor = texture2D(u_image, v_texCoord); vec4 maskColor = texture2D(u_mask, v_texCoord); if (u_backdrop.a > 0.0) { maskColor.rgb = maskColor.rgb * maskColor.a + u_backdrop.rgb * (1.0 - maskColor.a); } float lum; if (u_subtype == 0) { lum = maskColor.a; } else { lum = maskColor.r * 0.3 + maskColor.g * 0.59 + maskColor.b * 0.11; } imageColor.a *= lum; imageColor.rgb *= imageColor.a; gl_FragColor = imageColor; } ",A=null,v=" attribute vec2 a_position; attribute vec3 a_color; uniform vec2 u_resolution; uniform vec2 u_scale; uniform vec2 u_offset; varying vec4 v_color; void main() { vec2 position = (a_position + u_offset) * u_scale; vec2 clipSpace = (position / u_resolution) * 2.0 - 1.0; gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1); v_color = vec4(a_color / 255.0, 1.0); } ",b=" precision mediump float; varying vec4 v_color; void main() { gl_FragColor = v_color; } ",y=null;return{get isEnabled(){if(s("disableWebGL"))return!1;var t=!1;try{o(),t=!!f}catch(t){}return a(this,"isEnabled",t)},composeSMask:l,drawFigures:u,clear:d}}();e.WebGLUtils=o},function(t,e,n){"use strict";var r=n(0),i=n(1),a=n(3),s=n(2),o=n(5),c=n(7),l=n(4),h=r.globalScope,u=r.deprecated,d=r.warn,f=i.LinkTarget,p=i.DEFAULT_LINK_REL,g="undefined"==typeof window;h.PDFJS||(h.PDFJS={});var m=h.PDFJS;m.version="1.8.172",m.build="8ff1fbe7",m.pdfBug=!1,
+void 0!==m.verbosity&&r.setVerbosityLevel(m.verbosity),delete m.verbosity,Object.defineProperty(m,"verbosity",{get:function(){return r.getVerbosityLevel()},set:function(t){r.setVerbosityLevel(t)},enumerable:!0,configurable:!0}),m.VERBOSITY_LEVELS=r.VERBOSITY_LEVELS,m.OPS=r.OPS,m.UNSUPPORTED_FEATURES=r.UNSUPPORTED_FEATURES,m.isValidUrl=i.isValidUrl,m.shadow=r.shadow,m.createBlob=r.createBlob,m.createObjectURL=function(t,e){return r.createObjectURL(t,e,m.disableCreateObjectURL)},Object.defineProperty(m,"isLittleEndian",{configurable:!0,get:function(){var t=r.isLittleEndian();return r.shadow(m,"isLittleEndian",t)}}),m.removeNullCharacters=r.removeNullCharacters,m.PasswordResponses=r.PasswordResponses,m.PasswordException=r.PasswordException,m.UnknownErrorException=r.UnknownErrorException,m.InvalidPDFException=r.InvalidPDFException,m.MissingPDFException=r.MissingPDFException,m.UnexpectedResponseException=r.UnexpectedResponseException,m.Util=r.Util,m.PageViewport=r.PageViewport,m.createPromiseCapability=r.createPromiseCapability,m.maxImageSize=void 0===m.maxImageSize?-1:m.maxImageSize,m.cMapUrl=void 0===m.cMapUrl?null:m.cMapUrl,m.cMapPacked=void 0!==m.cMapPacked&&m.cMapPacked,m.disableFontFace=void 0!==m.disableFontFace&&m.disableFontFace,m.imageResourcesPath=void 0===m.imageResourcesPath?"":m.imageResourcesPath,m.disableWorker=void 0!==m.disableWorker&&m.disableWorker,m.workerSrc=void 0===m.workerSrc?null:m.workerSrc,m.workerPort=void 0===m.workerPort?null:m.workerPort,m.disableRange=void 0!==m.disableRange&&m.disableRange,m.disableStream=void 0!==m.disableStream&&m.disableStream,m.disableAutoFetch=void 0!==m.disableAutoFetch&&m.disableAutoFetch,m.pdfBug=void 0!==m.pdfBug&&m.pdfBug,m.postMessageTransfers=void 0===m.postMessageTransfers||m.postMessageTransfers,m.disableCreateObjectURL=void 0!==m.disableCreateObjectURL&&m.disableCreateObjectURL,m.disableWebGL=void 0===m.disableWebGL||m.disableWebGL,m.externalLinkTarget=void 0===m.externalLinkTarget?f.NONE:m.externalLinkTarget,m.externalLinkRel=void 0===m.externalLinkRel?p:m.externalLinkRel,m.isEvalSupported=void 0===m.isEvalSupported||m.isEvalSupported,m.pdfjsNext=void 0!==m.pdfjsNext&&m.pdfjsNext;var A=m.openExternalLinksInNewWindow;delete m.openExternalLinksInNewWindow,Object.defineProperty(m,"openExternalLinksInNewWindow",{get:function(){return m.externalLinkTarget===f.BLANK},set:function(t){if(t&&u('PDFJS.openExternalLinksInNewWindow, please use "PDFJS.externalLinkTarget = PDFJS.LinkTarget.BLANK" instead.'),m.externalLinkTarget!==f.NONE)return void d("PDFJS.externalLinkTarget is already initialized");m.externalLinkTarget=t?f.BLANK:f.NONE},enumerable:!0,configurable:!0}),A&&(m.openExternalLinksInNewWindow=A),m.getDocument=a.getDocument,m.PDFDataRangeTransport=a.PDFDataRangeTransport,m.PDFWorker=a.PDFWorker,Object.defineProperty(m,"hasCanvasTypedArrays",{configurable:!0,get:function(){var t=i.hasCanvasTypedArrays();return r.shadow(m,"hasCanvasTypedArrays",t)}}),m.CustomStyle=i.CustomStyle,m.LinkTarget=f,m.addLinkAttributes=i.addLinkAttributes,m.getFilenameFromUrl=i.getFilenameFromUrl,m.isExternalLinkTargetSet=i.isExternalLinkTargetSet,m.AnnotationLayer=s.AnnotationLayer,m.renderTextLayer=o.renderTextLayer,m.Metadata=c.Metadata,m.SVGGraphics=l.SVGGraphics,m.UnsupportedManager=a._UnsupportedManager,e.globalScope=h,e.isWorker=g,e.PDFJS=h.PDFJS},function(t,e,n){"use strict";function r(t){t.mozCurrentTransform||(t._originalSave=t.save,t._originalRestore=t.restore,t._originalRotate=t.rotate,t._originalScale=t.scale,t._originalTranslate=t.translate,t._originalTransform=t.transform,t._originalSetTransform=t.setTransform,t._transformMatrix=t._transformMatrix||[1,0,0,1,0,0],t._transformStack=[],Object.defineProperty(t,"mozCurrentTransform",{get:function(){return this._transformMatrix}}),Object.defineProperty(t,"mozCurrentTransformInverse",{get:function(){var t=this._transformMatrix,e=t[0],n=t[1],r=t[2],i=t[3],a=t[4],s=t[5],o=e*i-n*r,c=n*r-e*i;return[i/o,n/c,r/c,e/o,(i*a-r*s)/c,(n*a-e*s)/o]}}),t.save=function(){var t=this._transformMatrix;this._transformStack.push(t),this._transformMatrix=t.slice(0,6),this._originalSave()},t.restore=function(){var t=this._transformStack.pop();t&&(this._transformMatrix=t,this._originalRestore())},t.translate=function(t,e){var n=this._transformMatrix;n[4]=n[0]*t+n[2]*e+n[4],n[5]=n[1]*t+n[3]*e+n[5],this._originalTranslate(t,e)},t.scale=function(t,e){var n=this._transformMatrix;n[0]=n[0]*t,n[1]=n[1]*t,n[2]=n[2]*e,n[3]=n[3]*e,this._originalScale(t,e)},t.transform=function(e,n,r,i,a,s){var o=this._transformMatrix;this._transformMatrix=[o[0]*e+o[2]*n,o[1]*e+o[3]*n,o[0]*r+o[2]*i,o[1]*r+o[3]*i,o[0]*a+o[2]*s+o[4],o[1]*a+o[3]*s+o[5]],t._originalTransform(e,n,r,i,a,s)},t.setTransform=function(e,n,r,i,a,s){this._transformMatrix=[e,n,r,i,a,s],t._originalSetTransform(e,n,r,i,a,s)},t.rotate=function(t){var e=Math.cos(t),n=Math.sin(t),r=this._transformMatrix;this._transformMatrix=[r[0]*e+r[2]*n,r[1]*e+r[3]*n,r[0]*-n+r[2]*e,r[1]*-n+r[3]*e,r[4],r[5]],this._originalRotate(t)})}function i(t){var e,n,r,i,a=t.width,s=t.height,o=a+1,c=new Uint8Array(o*(s+1)),l=new Uint8Array([0,2,4,0,1,0,5,4,8,10,0,8,0,2,1,0]),h=a+7&-8,u=t.data,d=new Uint8Array(h*s),f=0;for(e=0,i=u.length;e<i;e++)for(var p=128,g=u[e];p>0;)d[f++]=g&p?0:255,p>>=1;var m=0;for(f=0,0!==d[f]&&(c[0]=1,++m),n=1;n<a;n++)d[f]!==d[f+1]&&(c[n]=d[f]?2:1,++m),f++;for(0!==d[f]&&(c[n]=2,++m),e=1;e<s;e++){f=e*h,r=e*o,d[f-h]!==d[f]&&(c[r]=d[f]?1:8,++m);var A=(d[f]?4:0)+(d[f-h]?8:0);for(n=1;n<a;n++)A=(A>>2)+(d[f+1]?4:0)+(d[f-h+1]?8:0),l[A]&&(c[r+n]=l[A],++m),f++;if(d[f-h]!==d[f]&&(c[r+n]=d[f]?2:4,++m),m>1e3)return null}for(f=h*(s-1),r=e*o,0!==d[f]&&(c[r]=8,++m),n=1;n<a;n++)d[f]!==d[f+1]&&(c[r+n]=d[f]?4:8,++m),f++;if(0!==d[f]&&(c[r+n]=4,++m),m>1e3)return null;var v=new Int32Array([0,o,-1,0,-o,0,0,0,1]),b=[];for(e=0;m&&e<=s;e++){for(var y=e*o,x=y+a;y<x&&!c[y];)y++;if(y!==x){var S,w=[y%o,e],k=c[y],C=y;do{var _=v[k];do{y+=_}while(!c[y]);S=c[y],5!==S&&10!==S?(k=S,c[y]=0):(k=S&51*k>>4,c[y]&=k>>2|k<<2),w.push(y%o),w.push(y/o|0),--m}while(C!==y);b.push(w),--e}}return function(t){t.save(),t.scale(1/a,-1/s),t.translate(0,-s),t.beginPath();for(var e=0,n=b.length;e<n;e++){var r=b[e];t.moveTo(r[0],r[1]);for(var i=2,o=r.length;i<o;i+=2)t.lineTo(r[i],r[i+1])}t.fill(),t.beginPath(),t.restore()}}var a=n(0),s=n(1),o=n(12),c=n(8),l=a.FONT_IDENTITY_MATRIX,h=a.IDENTITY_MATRIX,u=a.ImageKind,d=a.OPS,f=a.TextRenderingMode,p=a.Uint32ArrayView,g=a.Util,m=a.assert,A=a.info,v=a.isNum,b=a.isArray,y=a.isLittleEndian,x=a.error,S=a.shadow,w=a.warn,k=o.TilingPattern,C=o.getShadingPatternFromIR,_=c.WebGLUtils,T=s.hasCanvasTypedArrays,P=16,L={get value(){return S(L,"value",T())}},E={get value(){return S(E,"value",y())}},R=function(){function t(t){this.canvasFactory=t,this.cache=Object.create(null)}return t.prototype={getCanvas:function(t,e,n,i){var a;return void 0!==this.cache[t]?(a=this.cache[t],this.canvasFactory.reset(a,e,n),a.context.setTransform(1,0,0,1,0,0)):(a=this.canvasFactory.create(e,n),this.cache[t]=a),i&&r(a.context),a},clear:function(){for(var t in this.cache){var e=this.cache[t];this.canvasFactory.destroy(e),delete this.cache[t]}}},t}(),I=function(){function t(t){this.alphaIsShape=!1,this.fontSize=0,this.fontSizeScale=1,this.textMatrix=h,this.textMatrixScale=1,this.fontMatrix=l,this.leading=0,this.x=0,this.y=0,this.lineX=0,this.lineY=0,this.charSpacing=0,this.wordSpacing=0,this.textHScale=1,this.textRenderingMode=f.FILL,this.textRise=0,this.fillColor="#000000",this.strokeColor="#000000",this.patternFill=!1,this.fillAlpha=1,this.strokeAlpha=1,this.lineWidth=1,this.activeSMask=null,this.resumeSMaskCtx=null,this.old=t}return t.prototype={clone:function(){return Object.create(this)},setCurrentPoint:function(t,e){this.x=t,this.y=e}},t}(),F=function(){function t(t,e,n,i,a){this.ctx=t,this.current=new I,this.stateStack=[],this.pendingClip=null,this.pendingEOFill=!1,this.res=null,this.xobjs=null,this.commonObjs=e,this.objs=n,this.canvasFactory=i,this.imageLayer=a,this.groupStack=[],this.processingType3=null,this.baseTransform=null,this.baseTransformStack=[],this.groupLevel=0,this.smaskStack=[],this.smaskCounter=0,this.tempSMask=null,this.cachedCanvases=new R(this.canvasFactory),t&&r(t),this.cachedGetSinglePixelWidth=null}function e(t,e){if("undefined"!=typeof ImageData&&e instanceof ImageData)return void t.putImageData(e,0,0);var n,r,i,a,s,o=e.height,c=e.width,l=o%P,h=(o-l)/P,d=0===l?h:h+1,f=t.createImageData(c,P),g=0,m=e.data,A=f.data;if(e.kind===u.GRAYSCALE_1BPP){var v=m.byteLength,b=L.value?new Uint32Array(A.buffer):new p(A),y=b.length,S=c+7>>3,w=4294967295,k=E.value||!L.value?4278190080:255;for(r=0;r<d;r++){for(a=r<h?P:l,n=0,i=0;i<a;i++){for(var C=v-g,_=0,T=C>S?c:8*C-7,R=-8&T,I=0,F=0;_<R;_+=8)F=m[g++],b[n++]=128&F?w:k,b[n++]=64&F?w:k,b[n++]=32&F?w:k,b[n++]=16&F?w:k,b[n++]=8&F?w:k,b[n++]=4&F?w:k,b[n++]=2&F?w:k,b[n++]=1&F?w:k;for(;_<T;_++)0===I&&(F=m[g++],I=128),b[n++]=F&I?w:k,I>>=1}for(;n<y;)b[n++]=0;t.putImageData(f,0,r*P)}}else if(e.kind===u.RGBA_32BPP){for(i=0,s=c*P*4,r=0;r<h;r++)A.set(m.subarray(g,g+s)),g+=s,t.putImageData(f,0,i),i+=P;r<d&&(s=c*l*4,A.set(m.subarray(g,g+s)),t.putImageData(f,0,i))}else if(e.kind===u.RGB_24BPP)for(a=P,s=c*a,r=0;r<d;r++){for(r>=h&&(a=l,s=c*a),n=0,i=s;i--;)A[n++]=m[g++],A[n++]=m[g++],A[n++]=m[g++],A[n++]=255;t.putImageData(f,0,r*P)}else x("bad image kind: "+e.kind)}function n(t,e){for(var n=e.height,r=e.width,i=n%P,a=(n-i)/P,s=0===i?a:a+1,o=t.createImageData(r,P),c=0,l=e.data,h=o.data,u=0;u<s;u++){for(var d=u<a?P:i,f=3,p=0;p<d;p++)for(var g=0,m=0;m<r;m++){if(!g){var A=l[c++];g=128}h[f]=A&g?0:255,f+=4,g>>=1}t.putImageData(o,0,u*P)}}function a(t,e){for(var n=["strokeStyle","fillStyle","fillRule","globalAlpha","lineWidth","lineCap","lineJoin","miterLimit","globalCompositeOperation","font"],r=0,i=n.length;r<i;r++){var a=n[r];void 0!==t[a]&&(e[a]=t[a])}void 0!==t.setLineDash&&(e.setLineDash(t.getLineDash()),e.lineDashOffset=t.lineDashOffset)}function s(t,e,n,r){for(var i=t.length,a=3;a<i;a+=4){var s=t[a];if(0===s)t[a-3]=e,t[a-2]=n,t[a-1]=r;else if(s<255){var o=255-s;t[a-3]=t[a-3]*s+e*o>>8,t[a-2]=t[a-2]*s+n*o>>8,t[a-1]=t[a-1]*s+r*o>>8}}}function o(t,e,n){for(var r=t.length,i=3;i<r;i+=4){var a=n?n[t[i]]:t[i];e[i]=e[i]*a*(1/255)|0}}function c(t,e,n){for(var r=t.length,i=3;i<r;i+=4){var a=77*t[i-3]+152*t[i-2]+28*t[i-1];e[i]=n?e[i]*n[a>>8]>>8:e[i]*a>>16}}function y(t,e,n,r,i,a,l){var h,u=!!a,d=u?a[0]:0,f=u?a[1]:0,p=u?a[2]:0;h="Luminosity"===i?c:o;for(var g=Math.min(r,Math.ceil(1048576/n)),m=0;m<r;m+=g){var A=Math.min(g,r-m),v=t.getImageData(0,m,n,A),b=e.getImageData(0,m,n,A);u&&s(v.data,d,f,p),h(v.data,b.data,l),t.putImageData(b,0,m)}}function T(t,e,n){var r=e.canvas,i=e.context;t.setTransform(e.scaleX,0,0,e.scaleY,e.offsetX,e.offsetY);var a=e.backdrop||null;if(!e.transferMap&&_.isEnabled){var s=_.composeSMask(n.canvas,r,{subtype:e.subtype,backdrop:a});return t.setTransform(1,0,0,1,0,0),void t.drawImage(s,e.offsetX,e.offsetY)}y(i,n,r.width,r.height,e.subtype,a,e.transferMap),t.drawImage(r,0,0)}var F=["butt","round","square"],O=["miter","round","bevel"],M={},D={};t.prototype={beginDrawing:function(t,e,n){var r=this.ctx.canvas.width,i=this.ctx.canvas.height;if(this.ctx.save(),this.ctx.fillStyle="rgb(255, 255, 255)",this.ctx.fillRect(0,0,r,i),this.ctx.restore(),n){var a=this.cachedCanvases.getCanvas("transparent",r,i,!0);this.compositeCtx=this.ctx,this.transparentCanvas=a.canvas,this.ctx=a.context,this.ctx.save(),this.ctx.transform.apply(this.ctx,this.compositeCtx.mozCurrentTransform)}this.ctx.save(),t&&this.ctx.transform.apply(this.ctx,t),this.ctx.transform.apply(this.ctx,e.transform),this.baseTransform=this.ctx.mozCurrentTransform.slice(),this.imageLayer&&this.imageLayer.beginLayout()},executeOperatorList:function(t,e,n,r){var i=t.argsArray,a=t.fnArray,s=e||0,o=i.length;if(o===s)return s;for(var c,l=o-s>10&&"function"==typeof n,h=l?Date.now()+15:0,u=0,f=this.commonObjs,p=this.objs;;){if(void 0!==r&&s===r.nextBreakPoint)return r.breakIt(s,n),s;if((c=a[s])!==d.dependency)this[c].apply(this,i[s]);else for(var g=i[s],m=0,A=g.length;m<A;m++){var v=g[m],b="g"===v[0]&&"_"===v[1],y=b?f:p;if(!y.isResolved(v))return y.get(v,n),s}if(++s===o)return s;if(l&&++u>10){if(Date.now()>h)return n(),s;u=0}}},endDrawing:function(){null!==this.current.activeSMask&&this.endSMaskGroup(),this.ctx.restore(),this.transparentCanvas&&(this.ctx=this.compositeCtx,this.ctx.save(),this.ctx.setTransform(1,0,0,1,0,0),this.ctx.drawImage(this.transparentCanvas,0,0),this.ctx.restore(),this.transparentCanvas=null),this.cachedCanvases.clear(),_.clear(),this.imageLayer&&this.imageLayer.endLayout()},setLineWidth:function(t){this.current.lineWidth=t,this.ctx.lineWidth=t},setLineCap:function(t){this.ctx.lineCap=F[t]},setLineJoin:function(t){this.ctx.lineJoin=O[t]},setMiterLimit:function(t){this.ctx.miterLimit=t},setDash:function(t,e){var n=this.ctx;void 0!==n.setLineDash&&(n.setLineDash(t),n.lineDashOffset=e)},setRenderingIntent:function(t){},setFlatness:function(t){},setGState:function(t){for(var e=0,n=t.length;e<n;e++){var r=t[e],i=r[0],a=r[1];switch(i){case"LW":this.setLineWidth(a);break;case"LC":this.setLineCap(a);break;case"LJ":this.setLineJoin(a);break;case"ML":this.setMiterLimit(a);break;case"D":this.setDash(a[0],a[1]);break;case"RI":this.setRenderingIntent(a);break;case"FL":this.setFlatness(a);break;case"Font":this.setFont(a[0],a[1]);break;case"CA":this.current.strokeAlpha=r[1];break;case"ca":this.current.fillAlpha=r[1],this.ctx.globalAlpha=r[1];break;case"BM":if(a&&a.name&&"Normal"!==a.name){var s=a.name.replace(/([A-Z])/g,function(t){return"-"+t.toLowerCase()}).substring(1);this.ctx.globalCompositeOperation=s,this.ctx.globalCompositeOperation!==s&&w('globalCompositeOperation "'+s+'" is not supported')}else this.ctx.globalCompositeOperation="source-over";break;case"SMask":this.current.activeSMask&&(this.stateStack.length>0&&this.stateStack[this.stateStack.length-1].activeSMask===this.current.activeSMask?this.suspendSMaskGroup():this.endSMaskGroup()),this.current.activeSMask=a?this.tempSMask:null,this.current.activeSMask&&this.beginSMaskGroup(),this.tempSMask=null}}},beginSMaskGroup:function(){var t=this.current.activeSMask,e=t.canvas.width,n=t.canvas.height,r="smaskGroupAt"+this.groupLevel,i=this.cachedCanvases.getCanvas(r,e,n,!0),s=this.ctx,o=s.mozCurrentTransform;this.ctx.save();var c=i.context;c.scale(1/t.scaleX,1/t.scaleY),c.translate(-t.offsetX,-t.offsetY),c.transform.apply(c,o),t.startTransformInverse=c.mozCurrentTransformInverse,a(s,c),this.ctx=c,this.setGState([["BM","Normal"],["ca",1],["CA",1]]),this.groupStack.push(s),this.groupLevel++},suspendSMaskGroup:function(){var t=this.ctx;this.groupLevel--,this.ctx=this.groupStack.pop(),T(this.ctx,this.current.activeSMask,t),this.ctx.restore(),this.ctx.save(),a(t,this.ctx),this.current.resumeSMaskCtx=t;var e=g.transform(this.current.activeSMask.startTransformInverse,t.mozCurrentTransform);this.ctx.transform.apply(this.ctx,e),t.save(),t.setTransform(1,0,0,1,0,0),t.clearRect(0,0,t.canvas.width,t.canvas.height),t.restore()},resumeSMaskGroup:function(){var t=this.current.resumeSMaskCtx,e=this.ctx;this.ctx=t,this.groupStack.push(e),this.groupLevel++},endSMaskGroup:function(){var t=this.ctx;this.groupLevel--,this.ctx=this.groupStack.pop(),T(this.ctx,this.current.activeSMask,t),this.ctx.restore(),a(t,this.ctx);var e=g.transform(this.current.activeSMask.startTransformInverse,t.mozCurrentTransform);this.ctx.transform.apply(this.ctx,e)},save:function(){this.ctx.save();var t=this.current;this.stateStack.push(t),this.current=t.clone(),this.current.resumeSMaskCtx=null},restore:function(){this.current.resumeSMaskCtx&&this.resumeSMaskGroup(),null===this.current.activeSMask||0!==this.stateStack.length&&this.stateStack[this.stateStack.length-1].activeSMask===this.current.activeSMask||this.endSMaskGroup(),0!==this.stateStack.length&&(this.current=this.stateStack.pop(),this.ctx.restore(),this.pendingClip=null,this.cachedGetSinglePixelWidth=null)},transform:function(t,e,n,r,i,a){this.ctx.transform(t,e,n,r,i,a),this.cachedGetSinglePixelWidth=null},constructPath:function(t,e){for(var n=this.ctx,r=this.current,i=r.x,a=r.y,s=0,o=0,c=t.length;s<c;s++)switch(0|t[s]){case d.rectangle:i=e[o++],a=e[o++];var l=e[o++],h=e[o++];0===l&&(l=this.getSinglePixelWidth()),0===h&&(h=this.getSinglePixelWidth());var u=i+l,f=a+h;this.ctx.moveTo(i,a),this.ctx.lineTo(u,a),this.ctx.lineTo(u,f),this.ctx.lineTo(i,f),this.ctx.lineTo(i,a),this.ctx.closePath();break;case d.moveTo:i=e[o++],a=e[o++],n.moveTo(i,a);break;case d.lineTo:i=e[o++],a=e[o++],n.lineTo(i,a);break;case d.curveTo:i=e[o+4],a=e[o+5],n.bezierCurveTo(e[o],e[o+1],e[o+2],e[o+3],i,a),o+=6;break;case d.curveTo2:n.bezierCurveTo(i,a,e[o],e[o+1],e[o+2],e[o+3]),i=e[o+2],a=e[o+3],o+=4;break;case d.curveTo3:i=e[o+2],a=e[o+3],n.bezierCurveTo(e[o],e[o+1],i,a,i,a),o+=4;break;case d.closePath:n.closePath()}r.setCurrentPoint(i,a)},closePath:function(){this.ctx.closePath()},stroke:function(t){t=void 0===t||t;var e=this.ctx,n=this.current.strokeColor;e.lineWidth=Math.max(.65*this.getSinglePixelWidth(),this.current.lineWidth),e.globalAlpha=this.current.strokeAlpha,n&&n.hasOwnProperty("type")&&"Pattern"===n.type?(e.save(),e.strokeStyle=n.getPattern(e,this),e.stroke(),e.restore()):e.stroke(),t&&this.consumePath(),e.globalAlpha=this.current.fillAlpha},closeStroke:function(){this.closePath(),this.stroke()},fill:function(t){t=void 0===t||t;var e=this.ctx,n=this.current.fillColor,r=this.current.patternFill,i=!1;r&&(e.save(),this.baseTransform&&e.setTransform.apply(e,this.baseTransform),e.fillStyle=n.getPattern(e,this),i=!0),this.pendingEOFill?(e.fill("evenodd"),this.pendingEOFill=!1):e.fill(),i&&e.restore(),t&&this.consumePath()},eoFill:function(){this.pendingEOFill=!0,this.fill()},fillStroke:function(){this.fill(!1),this.stroke(!1),this.consumePath()},eoFillStroke:function(){this.pendingEOFill=!0,this.fillStroke()},closeFillStroke:function(){this.closePath(),this.fillStroke()},closeEOFillStroke:function(){this.pendingEOFill=!0,this.closePath(),this.fillStroke()},endPath:function(){this.consumePath()},clip:function(){this.pendingClip=M},eoClip:function(){this.pendingClip=D},beginText:function(){this.current.textMatrix=h,this.current.textMatrixScale=1,this.current.x=this.current.lineX=0,this.current.y=this.current.lineY=0},endText:function(){var t=this.pendingTextPaths,e=this.ctx;if(void 0===t)return void e.beginPath();e.save(),e.beginPath();for(var n=0;n<t.length;n++){var r=t[n];e.setTransform.apply(e,r.transform),e.translate(r.x,r.y),r.addToPath(e,r.fontSize)}e.restore(),e.clip(),e.beginPath(),delete this.pendingTextPaths},setCharSpacing:function(t){this.current.charSpacing=t},setWordSpacing:function(t){this.current.wordSpacing=t},setHScale:function(t){this.current.textHScale=t/100},setLeading:function(t){this.current.leading=-t},setFont:function(t,e){var n=this.commonObjs.get(t),r=this.current;if(n||x("Can't find font for "+t),r.fontMatrix=n.fontMatrix?n.fontMatrix:l,0!==r.fontMatrix[0]&&0!==r.fontMatrix[3]||w("Invalid font matrix for font "+t),e<0?(e=-e,r.fontDirection=-1):r.fontDirection=1,this.current.font=n,this.current.fontSize=e,!n.isType3Font){var i=n.loadedName||"sans-serif",a=n.black?"900":n.bold?"bold":"normal",s=n.italic?"italic":"normal",o='"'+i+'", '+n.fallbackName,c=e<16?16:e>100?100:e;this.current.fontSizeScale=e/c;var h=s+" "+a+" "+c+"px "+o;this.ctx.font=h}},setTextRenderingMode:function(t){this.current.textRenderingMode=t},setTextRise:function(t){this.current.textRise=t},moveText:function(t,e){this.current.x=this.current.lineX+=t,this.current.y=this.current.lineY+=e},setLeadingMoveText:function(t,e){this.setLeading(-e),this.moveText(t,e)},setTextMatrix:function(t,e,n,r,i,a){this.current.textMatrix=[t,e,n,r,i,a],this.current.textMatrixScale=Math.sqrt(t*t+e*e),this.current.x=this.current.lineX=0,this.current.y=this.current.lineY=0},nextLine:function(){this.moveText(0,this.current.leading)},paintChar:function(t,e,n){var r,i=this.ctx,a=this.current,s=a.font,o=a.textRenderingMode,c=a.fontSize/a.fontSizeScale,l=o&f.FILL_STROKE_MASK,h=!!(o&f.ADD_TO_PATH_FLAG);if((s.disableFontFace||h)&&(r=s.getPathGenerator(this.commonObjs,t)),s.disableFontFace?(i.save(),i.translate(e,n),i.beginPath(),r(i,c),l!==f.FILL&&l!==f.FILL_STROKE||i.fill(),l!==f.STROKE&&l!==f.FILL_STROKE||i.stroke(),i.restore()):(l!==f.FILL&&l!==f.FILL_STROKE||i.fillText(t,e,n),l!==f.STROKE&&l!==f.FILL_STROKE||i.strokeText(t,e,n)),h){(this.pendingTextPaths||(this.pendingTextPaths=[])).push({transform:i.mozCurrentTransform,x:e,y:n,fontSize:c,addToPath:r})}},get isFontSubpixelAAEnabled(){var t=this.canvasFactory.create(10,10).context;t.scale(1.5,1),t.fillText("I",0,10);for(var e=t.getImageData(0,0,10,10).data,n=!1,r=3;r<e.length;r+=4)if(e[r]>0&&e[r]<255){n=!0;break}return S(this,"isFontSubpixelAAEnabled",n)},showText:function(t){var e=this.current,n=e.font;if(n.isType3Font)return this.showType3Text(t);var r=e.fontSize;if(0!==r){var i=this.ctx,a=e.fontSizeScale,s=e.charSpacing,o=e.wordSpacing,c=e.fontDirection,l=e.textHScale*c,h=t.length,u=n.vertical,d=u?1:-1,p=n.defaultVMetrics,g=r*e.fontMatrix[0],m=e.textRenderingMode===f.FILL&&!n.disableFontFace;i.save(),i.transform.apply(i,e.textMatrix),i.translate(e.x,e.y+e.textRise),e.patternFill&&(i.fillStyle=e.fillColor.getPattern(i,this)),c>0?i.scale(l,-1):i.scale(l,1);var A=e.lineWidth,b=e.textMatrixScale;if(0===b||0===A){var y=e.textRenderingMode&f.FILL_STROKE_MASK;y!==f.STROKE&&y!==f.FILL_STROKE||(this.cachedGetSinglePixelWidth=null,A=.65*this.getSinglePixelWidth())}else A/=b;1!==a&&(i.scale(a,a),A/=a),i.lineWidth=A;var x,S=0;for(x=0;x<h;++x){var w=t[x];if(v(w))S+=d*w*r/1e3;else{var k,C,_,T,P=!1,L=(w.isSpace?o:0)+s,E=w.fontChar,R=w.accent,I=w.width;if(u){var F,O,M;F=w.vmetric||p,O=w.vmetric?F[1]:.5*I,O=-O*g,M=F[2]*g,I=F?-F[0]:I,k=O/a,C=(S+M)/a}else k=S/a,C=0;if(n.remeasure&&I>0){var D=1e3*i.measureText(E).width/r*a;if(I<D&&this.isFontSubpixelAAEnabled){var N=I/D;P=!0,i.save(),i.scale(N,1),k/=N}else I!==D&&(k+=(I-D)/2e3*r/a)}(w.isInFont||n.missingFile)&&(m&&!R?i.fillText(E,k,C):(this.paintChar(E,k,C),R&&(_=k+R.offset.x/a,T=C-R.offset.y/a,this.paintChar(R.fontChar,_,T))));S+=I*g+L*c,P&&i.restore()}}u?e.y-=S*l:e.x+=S*l,i.restore()}},showType3Text:function(t){var e,n,r,i,a=this.ctx,s=this.current,o=s.font,c=s.fontSize,h=s.fontDirection,u=o.vertical?1:-1,d=s.charSpacing,p=s.wordSpacing,m=s.textHScale*h,A=s.fontMatrix||l,b=t.length,y=s.textRenderingMode===f.INVISIBLE;if(!y&&0!==c){for(this.cachedGetSinglePixelWidth=null,a.save(),a.transform.apply(a,s.textMatrix),a.translate(s.x,s.y),a.scale(m,h),e=0;e<b;++e)if(n=t[e],v(n))i=u*n*c/1e3,this.ctx.translate(i,0),s.x+=i*m;else{var x=(n.isSpace?p:0)+d,S=o.charProcOperatorList[n.operatorListId];if(S){this.processingType3=n,this.save(),a.scale(c,c),a.transform.apply(a,A),this.executeOperatorList(S),this.restore();var k=g.applyTransform([n.width,0],A);r=k[0]*c+x,a.translate(r,0),s.x+=r*m}else w('Type3 character "'+n.operatorListId+'" is not available')}a.restore(),this.processingType3=null}},setCharWidth:function(t,e){},setCharWidthAndBounds:function(t,e,n,r,i,a){this.ctx.rect(n,r,i-n,a-r),this.clip(),this.endPath()},getColorN_Pattern:function(e){var n;if("TilingPattern"===e[0]){var r=e[1],i=this.baseTransform||this.ctx.mozCurrentTransform.slice(),a=this,s={createCanvasGraphics:function(e){return new t(e,a.commonObjs,a.objs,a.canvasFactory)}};n=new k(e,r,this.ctx,s,i)}else n=C(e);return n},setStrokeColorN:function(){this.current.strokeColor=this.getColorN_Pattern(arguments)},setFillColorN:function(){this.current.fillColor=this.getColorN_Pattern(arguments),this.current.patternFill=!0},setStrokeRGBColor:function(t,e,n){var r=g.makeCssRgb(t,e,n);this.ctx.strokeStyle=r,this.current.strokeColor=r},setFillRGBColor:function(t,e,n){var r=g.makeCssRgb(t,e,n);this.ctx.fillStyle=r,this.current.fillColor=r,this.current.patternFill=!1},shadingFill:function(t){var e=this.ctx;this.save();var n=C(t);e.fillStyle=n.getPattern(e,this,!0);var r=e.mozCurrentTransformInverse;if(r){var i=e.canvas,a=i.width,s=i.height,o=g.applyTransform([0,0],r),c=g.applyTransform([0,s],r),l=g.applyTransform([a,0],r),h=g.applyTransform([a,s],r),u=Math.min(o[0],c[0],l[0],h[0]),d=Math.min(o[1],c[1],l[1],h[1]),f=Math.max(o[0],c[0],l[0],h[0]),p=Math.max(o[1],c[1],l[1],h[1]);this.ctx.fillRect(u,d,f-u,p-d)}else this.ctx.fillRect(-1e10,-1e10,2e10,2e10);this.restore()},beginInlineImage:function(){x("Should not call beginInlineImage")},beginImageData:function(){x("Should not call beginImageData")},paintFormXObjectBegin:function(t,e){if(this.save(),this.baseTransformStack.push(this.baseTransform),b(t)&&6===t.length&&this.transform.apply(this,t),this.baseTransform=this.ctx.mozCurrentTransform,b(e)&&4===e.length){var n=e[2]-e[0],r=e[3]-e[1];this.ctx.rect(e[0],e[1],n,r),this.clip(),this.endPath()}},paintFormXObjectEnd:function(){this.restore(),this.baseTransform=this.baseTransformStack.pop()},beginGroup:function(t){this.save();var e=this.ctx;t.isolated||A("TODO: Support non-isolated groups."),t.knockout&&w("Knockout groups not supported.");var n=e.mozCurrentTransform;t.matrix&&e.transform.apply(e,t.matrix),m(t.bbox,"Bounding box is required.");var r=g.getAxialAlignedBoundingBox(t.bbox,e.mozCurrentTransform),i=[0,0,e.canvas.width,e.canvas.height];r=g.intersect(r,i)||[0,0,0,0];var s=Math.floor(r[0]),o=Math.floor(r[1]),c=Math.max(Math.ceil(r[2])-s,1),l=Math.max(Math.ceil(r[3])-o,1),h=1,u=1;c>4096&&(h=c/4096,c=4096),l>4096&&(u=l/4096,l=4096);var d="groupAt"+this.groupLevel;t.smask&&(d+="_smask_"+this.smaskCounter++%2);var f=this.cachedCanvases.getCanvas(d,c,l,!0),p=f.context;p.scale(1/h,1/u),p.translate(-s,-o),p.transform.apply(p,n),t.smask?this.smaskStack.push({canvas:f.canvas,context:p,offsetX:s,offsetY:o,scaleX:h,scaleY:u,subtype:t.smask.subtype,backdrop:t.smask.backdrop,transferMap:t.smask.transferMap||null,startTransformInverse:null}):(e.setTransform(1,0,0,1,0,0),e.translate(s,o),e.scale(h,u)),a(e,p),this.ctx=p,this.setGState([["BM","Normal"],["ca",1],["CA",1]]),this.groupStack.push(e),this.groupLevel++,this.current.activeSMask=null},endGroup:function(t){this.groupLevel--;var e=this.ctx;this.ctx=this.groupStack.pop(),void 0!==this.ctx.imageSmoothingEnabled?this.ctx.imageSmoothingEnabled=!1:this.ctx.mozImageSmoothingEnabled=!1,t.smask?this.tempSMask=this.smaskStack.pop():this.ctx.drawImage(e.canvas,0,0),this.restore()},beginAnnotations:function(){this.save(),this.current=new I,this.baseTransform&&this.ctx.setTransform.apply(this.ctx,this.baseTransform)},endAnnotations:function(){this.restore()},beginAnnotation:function(t,e,n){if(this.save(),b(t)&&4===t.length){var r=t[2]-t[0],i=t[3]-t[1];this.ctx.rect(t[0],t[1],r,i),this.clip(),this.endPath()}this.transform.apply(this,e),this.transform.apply(this,n)},endAnnotation:function(){this.restore()},paintJpegXObject:function(t,e,n){var r=this.objs.get(t);if(!r)return void w("Dependent image isn't ready yet");this.save();var i=this.ctx;if(i.scale(1/e,-1/n),i.drawImage(r,0,0,r.width,r.height,0,-n,e,n),this.imageLayer){var a=i.mozCurrentTransformInverse,s=this.getCanvasPosition(0,0);this.imageLayer.appendImage({objId:t,left:s[0],top:s[1],width:e/a[0],height:n/a[3]})}this.restore()},paintImageMaskXObject:function(t){var e=this.ctx,r=t.width,a=t.height,s=this.current.fillColor,o=this.current.patternFill,c=this.processingType3;if(c&&void 0===c.compiled&&(c.compiled=r<=1e3&&a<=1e3?i({data:t.data,width:r,height:a}):null),c&&c.compiled)return void c.compiled(e);var l=this.cachedCanvases.getCanvas("maskCanvas",r,a),h=l.context;h.save(),n(h,t),h.globalCompositeOperation="source-in",h.fillStyle=o?s.getPattern(h,this):s,h.fillRect(0,0,r,a),h.restore(),this.paintInlineImageXObject(l.canvas)},paintImageMaskXObjectRepeat:function(t,e,r,i){var a=t.width,s=t.height,o=this.current.fillColor,c=this.current.patternFill,l=this.cachedCanvases.getCanvas("maskCanvas",a,s),h=l.context;h.save(),n(h,t),h.globalCompositeOperation="source-in",h.fillStyle=c?o.getPattern(h,this):o,h.fillRect(0,0,a,s),h.restore();for(var u=this.ctx,d=0,f=i.length;d<f;d+=2)u.save(),u.transform(e,0,0,r,i[d],i[d+1]),u.scale(1,-1),u.drawImage(l.canvas,0,0,a,s,0,-1,1,1),u.restore()},paintImageMaskXObjectGroup:function(t){for(var e=this.ctx,r=this.current.fillColor,i=this.current.patternFill,a=0,s=t.length;a<s;a++){var o=t[a],c=o.width,l=o.height,h=this.cachedCanvases.getCanvas("maskCanvas",c,l),u=h.context;u.save(),n(u,o),u.globalCompositeOperation="source-in",u.fillStyle=i?r.getPattern(u,this):r,u.fillRect(0,0,c,l),u.restore(),e.save(),e.transform.apply(e,o.transform),e.scale(1,-1),e.drawImage(h.canvas,0,0,c,l,0,-1,1,1),e.restore()}},paintImageXObject:function(t){var e=this.objs.get(t);if(!e)return void w("Dependent image isn't ready yet");this.paintInlineImageXObject(e)},paintImageXObjectRepeat:function(t,e,n,r){var i=this.objs.get(t);if(!i)return void w("Dependent image isn't ready yet");for(var a=i.width,s=i.height,o=[],c=0,l=r.length;c<l;c+=2)o.push({transform:[e,0,0,n,r[c],r[c+1]],x:0,y:0,w:a,h:s});this.paintInlineImageXObjectGroup(i,o)},paintInlineImageXObject:function(t){var n=t.width,r=t.height,i=this.ctx;this.save(),i.scale(1/n,-1/r);var a,s,o=i.mozCurrentTransformInverse,c=o[0],l=o[1],h=Math.max(Math.sqrt(c*c+l*l),1),u=o[2],d=o[3],f=Math.max(Math.sqrt(u*u+d*d),1);if(t instanceof HTMLElement||!t.data)a=t;else{s=this.cachedCanvases.getCanvas("inlineImage",n,r);var p=s.context;e(p,t),a=s.canvas}for(var g=n,m=r,A="prescale1";h>2&&g>1||f>2&&m>1;){var v=g,b=m;h>2&&g>1&&(v=Math.ceil(g/2),h/=g/v),f>2&&m>1&&(b=Math.ceil(m/2),f/=m/b),s=this.cachedCanvases.getCanvas(A,v,b),p=s.context,p.clearRect(0,0,v,b),p.drawImage(a,0,0,g,m,0,0,v,b),a=s.canvas,g=v,m=b,A="prescale1"===A?"prescale2":"prescale1"}if(i.drawImage(a,0,0,g,m,0,-r,n,r),this.imageLayer){var y=this.getCanvasPosition(0,-r);this.imageLayer.appendImage({imgData:t,left:y[0],top:y[1],width:n/o[0],height:r/o[3]})}this.restore()},paintInlineImageXObjectGroup:function(t,n){var r=this.ctx,i=t.width,a=t.height,s=this.cachedCanvases.getCanvas("inlineImage",i,a);e(s.context,t);for(var o=0,c=n.length;o<c;o++){var l=n[o];if(r.save(),r.transform.apply(r,l.transform),r.scale(1,-1),r.drawImage(s.canvas,l.x,l.y,l.w,l.h,0,-1,1,1),this.imageLayer){var h=this.getCanvasPosition(l.x,l.y);this.imageLayer.appendImage({imgData:t,left:h[0],top:h[1],width:i,height:a})}r.restore()}},paintSolidColorImageMask:function(){this.ctx.fillRect(0,0,1,1)},paintXObject:function(){w("Unsupported 'paintXObject' command.")},markPoint:function(t){},markPointProps:function(t,e){},beginMarkedContent:function(t){},beginMarkedContentProps:function(t,e){},endMarkedContent:function(){},beginCompat:function(){},endCompat:function(){},consumePath:function(){var t=this.ctx;this.pendingClip&&(this.pendingClip===D?t.clip("evenodd"):t.clip(),this.pendingClip=null),t.beginPath()},getSinglePixelWidth:function(t){if(null===this.cachedGetSinglePixelWidth){this.ctx.save();var e=this.ctx.mozCurrentTransformInverse;this.ctx.restore(),this.cachedGetSinglePixelWidth=Math.sqrt(Math.max(e[0]*e[0]+e[1]*e[1],e[2]*e[2]+e[3]*e[3]))}return this.cachedGetSinglePixelWidth},getCanvasPosition:function(t,e){var n=this.ctx.mozCurrentTransform;return[n[0]*t+n[2]*e+n[4],n[1]*t+n[3]*e+n[5]]}};for(var N in d)t.prototype[d[N]]=t.prototype[N];return t}();e.CanvasGraphics=F},function(t,e,n){"use strict";function r(t){this.docId=t,this.styleElement=null,this.nativeFontFaces=[],this.loadTestFontId=0,this.loadingContext={requests:[],nextRequestId:0}}var i=n(0),a=i.assert,s=i.bytesToString,o=i.string32,c=i.shadow,l=i.warn;r.prototype={insertRule:function(t){var e=this.styleElement;e||(e=this.styleElement=document.createElement("style"),e.id="PDFJS_FONT_STYLE_TAG_"+this.docId,document.documentElement.getElementsByTagName("head")[0].appendChild(e));var n=e.sheet;n.insertRule(t,n.cssRules.length)},clear:function(){this.styleElement&&(this.styleElement.remove(),this.styleElement=null),this.nativeFontFaces.forEach(function(t){document.fonts.delete(t)}),this.nativeFontFaces.length=0}};var h=function(){
+return atob("T1RUTwALAIAAAwAwQ0ZGIDHtZg4AAAOYAAAAgUZGVE1lkzZwAAAEHAAAABxHREVGABQAFQAABDgAAAAeT1MvMlYNYwkAAAEgAAAAYGNtYXABDQLUAAACNAAAAUJoZWFk/xVFDQAAALwAAAA2aGhlYQdkA+oAAAD0AAAAJGhtdHgD6AAAAAAEWAAAAAZtYXhwAAJQAAAAARgAAAAGbmFtZVjmdH4AAAGAAAAAsXBvc3T/hgAzAAADeAAAACAAAQAAAAEAALZRFsRfDzz1AAsD6AAAAADOBOTLAAAAAM4KHDwAAAAAA+gDIQAAAAgAAgAAAAAAAAABAAADIQAAAFoD6AAAAAAD6AABAAAAAAAAAAAAAAAAAAAAAQAAUAAAAgAAAAQD6AH0AAUAAAKKArwAAACMAooCvAAAAeAAMQECAAACAAYJAAAAAAAAAAAAAQAAAAAAAAAAAAAAAFBmRWQAwAAuAC4DIP84AFoDIQAAAAAAAQAAAAAAAAAAACAAIAABAAAADgCuAAEAAAAAAAAAAQAAAAEAAAAAAAEAAQAAAAEAAAAAAAIAAQAAAAEAAAAAAAMAAQAAAAEAAAAAAAQAAQAAAAEAAAAAAAUAAQAAAAEAAAAAAAYAAQAAAAMAAQQJAAAAAgABAAMAAQQJAAEAAgABAAMAAQQJAAIAAgABAAMAAQQJAAMAAgABAAMAAQQJAAQAAgABAAMAAQQJAAUAAgABAAMAAQQJAAYAAgABWABYAAAAAAAAAwAAAAMAAAAcAAEAAAAAADwAAwABAAAAHAAEACAAAAAEAAQAAQAAAC7//wAAAC7////TAAEAAAAAAAABBgAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAAD/gwAyAAAAAQAAAAAAAAAAAAAAAAAAAAABAAQEAAEBAQJYAAEBASH4DwD4GwHEAvgcA/gXBIwMAYuL+nz5tQXkD5j3CBLnEQACAQEBIVhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYAAABAQAADwACAQEEE/t3Dov6fAH6fAT+fPp8+nwHDosMCvm1Cvm1DAz6fBQAAAAAAAABAAAAAMmJbzEAAAAAzgTjFQAAAADOBOQpAAEAAAAAAAAADAAUAAQAAAABAAAAAgABAAAAAAAAAAAD6AAAAAAAAA==")};Object.defineProperty(r.prototype,"loadTestFont",{get:function(){return c(this,"loadTestFont",h())},configurable:!0}),r.prototype.addNativeFontFace=function(t){this.nativeFontFaces.push(t),document.fonts.add(t)},r.prototype.bind=function(t,e){for(var n=[],i=[],a=[],s=r.isFontLoadingAPISupported&&!r.isSyncFontLoadingSupported,o=0,c=t.length;o<c;o++){var h=t[o];if(!h.attached&&!1!==h.loading)if(h.attached=!0,s){var u=h.createNativeFontFace();u&&(this.addNativeFontFace(u),a.push(function(t){return t.loaded.catch(function(e){l('Failed to load font "'+t.family+'": '+e)})}(u)))}else{var d=h.createFontFaceRule();d&&(this.insertRule(d),n.push(d),i.push(h))}}var f=this.queueLoadingCallback(e);s?Promise.all(a).then(function(){f.complete()}):n.length>0&&!r.isSyncFontLoadingSupported?this.prepareFontLoadEvent(n,i,f):f.complete()},r.prototype.queueLoadingCallback=function(t){function e(){for(a(!i.end,"completeRequest() cannot be called twice"),i.end=Date.now();n.requests.length>0&&n.requests[0].end;){var t=n.requests.shift();setTimeout(t.callback,0)}}var n=this.loadingContext,r="pdfjs-font-loading-"+n.nextRequestId++,i={id:r,complete:e,callback:t,started:Date.now()};return n.requests.push(i),i},r.prototype.prepareFontLoadEvent=function(t,e,n){function r(t,e){return t.charCodeAt(e)<<24|t.charCodeAt(e+1)<<16|t.charCodeAt(e+2)<<8|255&t.charCodeAt(e+3)}function i(t,e,n,r){return t.substr(0,e)+r+t.substr(e+n)}function a(t,e){return++d>30?(l("Load test font never loaded."),void e()):(u.font="30px "+t,u.fillText(".",0,20),u.getImageData(0,0,1,1).data[3]>0?void e():void setTimeout(a.bind(null,t,e)))}var s,c,h=document.createElement("canvas");h.width=1,h.height=1;var u=h.getContext("2d"),d=0,f="lt"+Date.now()+this.loadTestFontId++,p=this.loadTestFont;p=i(p,976,f.length,f);var g=r(p,16);for(s=0,c=f.length-3;s<c;s+=4)g=g-1482184792+r(f,s)|0;s<f.length&&(g=g-1482184792+r(f+"XXX",s)|0),p=i(p,16,4,o(g));var m="url(data:font/opentype;base64,"+btoa(p)+");",A='@font-face { font-family:"'+f+'";src:'+m+"}";this.insertRule(A);var v=[];for(s=0,c=e.length;s<c;s++)v.push(e[s].loadedName);v.push(f);var b=document.createElement("div");for(b.setAttribute("style","visibility: hidden;width: 10px; height: 10px;position: absolute; top: 0px; left: 0px;"),s=0,c=v.length;s<c;++s){var y=document.createElement("span");y.textContent="Hi",y.style.fontFamily=v[s],b.appendChild(y)}document.body.appendChild(b),a(f,function(){document.body.removeChild(b),n.complete()})},r.isFontLoadingAPISupported="undefined"!=typeof document&&!!document.fonts;var u=function(){if("undefined"==typeof navigator)return!0;var t=!1,e=/Mozilla\/5.0.*?rv:(\d+).*? Gecko/.exec(navigator.userAgent);return e&&e[1]>=14&&(t=!0),t};Object.defineProperty(r,"isSyncFontLoadingSupported",{get:function(){return c(r,"isSyncFontLoadingSupported",u())},enumerable:!0,configurable:!0});var d={get value(){return c(this,"value",i.isEvalSupported())}},f=function(){function t(t,e){this.compiledGlyphs=Object.create(null);for(var n in t)this[n]=t[n];this.options=e}return t.prototype={createNativeFontFace:function(){if(!this.data)return null;if(this.options.disableFontFace)return this.disableFontFace=!0,null;var t=new FontFace(this.loadedName,this.data,{});return this.options.fontRegistry&&this.options.fontRegistry.registerFont(this),t},createFontFaceRule:function(){if(!this.data)return null;if(this.options.disableFontFace)return this.disableFontFace=!0,null;var t=s(new Uint8Array(this.data)),e=this.loadedName,n="url(data:"+this.mimetype+";base64,"+btoa(t)+");",r='@font-face { font-family:"'+e+'";src:'+n+"}";return this.options.fontRegistry&&this.options.fontRegistry.registerFont(this,n),r},getPathGenerator:function(t,e){if(!(e in this.compiledGlyphs)){var n,r,i,a=t.get(this.loadedName+"_path_"+e);if(this.options.isEvalSupported&&d.value){var s,o="";for(r=0,i=a.length;r<i;r++)n=a[r],s=void 0!==n.args?n.args.join(","):"",o+="c."+n.cmd+"("+s+");\n";this.compiledGlyphs[e]=new Function("c","size",o)}else this.compiledGlyphs[e]=function(t,e){for(r=0,i=a.length;r<i;r++)n=a[r],"scale"===n.cmd&&(n.args=[e,-e]),t[n.cmd].apply(t,n.args)}}return this.compiledGlyphs[e]}},t}();e.FontFaceObject=f,e.FontLoader=r},function(t,e,n){"use strict";function r(t){var e=u[t[0]];return e||l("Unknown IR type: "+t[0]),e.fromIR(t)}var i=n(0),a=n(8),s=i.Util,o=i.info,c=i.isArray,l=i.error,h=a.WebGLUtils,u={};u.RadialAxial={fromIR:function(t){var e=t[1],n=t[2],r=t[3],i=t[4],a=t[5],s=t[6];return{type:"Pattern",getPattern:function(t){var o;"axial"===e?o=t.createLinearGradient(r[0],r[1],i[0],i[1]):"radial"===e&&(o=t.createRadialGradient(r[0],r[1],a,i[0],i[1],s));for(var c=0,l=n.length;c<l;++c){var h=n[c];o.addColorStop(h[0],h[1])}return o}}}};var d=function(){function t(t,e,n,r,i,a,s,o){var c,l=e.coords,h=e.colors,u=t.data,d=4*t.width;l[n+1]>l[r+1]&&(c=n,n=r,r=c,c=a,a=s,s=c),l[r+1]>l[i+1]&&(c=r,r=i,i=c,c=s,s=o,o=c),l[n+1]>l[r+1]&&(c=n,n=r,r=c,c=a,a=s,s=c);var f=(l[n]+e.offsetX)*e.scaleX,p=(l[n+1]+e.offsetY)*e.scaleY,g=(l[r]+e.offsetX)*e.scaleX,m=(l[r+1]+e.offsetY)*e.scaleY,A=(l[i]+e.offsetX)*e.scaleX,v=(l[i+1]+e.offsetY)*e.scaleY;if(!(p>=v))for(var b,y,x,S,w,k,C,_,T,P=h[a],L=h[a+1],E=h[a+2],R=h[s],I=h[s+1],F=h[s+2],O=h[o],M=h[o+1],D=h[o+2],N=Math.round(p),j=Math.round(v),U=N;U<=j;U++){U<m?(T=U<p?0:p===m?1:(p-U)/(p-m),b=f-(f-g)*T,y=P-(P-R)*T,x=L-(L-I)*T,S=E-(E-F)*T):(T=U>v?1:m===v?0:(m-U)/(m-v),b=g-(g-A)*T,y=R-(R-O)*T,x=I-(I-M)*T,S=F-(F-D)*T),T=U<p?0:U>v?1:(p-U)/(p-v),w=f-(f-A)*T,k=P-(P-O)*T,C=L-(L-M)*T,_=E-(E-D)*T;for(var B=Math.round(Math.min(b,w)),W=Math.round(Math.max(b,w)),G=d*U+4*B,X=B;X<=W;X++)T=(b-X)/(b-w),T=T<0?0:T>1?1:T,u[G++]=y-(y-k)*T|0,u[G++]=x-(x-C)*T|0,u[G++]=S-(S-_)*T|0,u[G++]=255}}function e(e,n,r){var i,a,s=n.coords,o=n.colors;switch(n.type){case"lattice":var c=n.verticesPerRow,h=Math.floor(s.length/c)-1,u=c-1;for(i=0;i<h;i++)for(var d=i*c,f=0;f<u;f++,d++)t(e,r,s[d],s[d+1],s[d+c],o[d],o[d+1],o[d+c]),t(e,r,s[d+c+1],s[d+1],s[d+c],o[d+c+1],o[d+1],o[d+c]);break;case"triangles":for(i=0,a=s.length;i<a;i+=3)t(e,r,s[i],s[i+1],s[i+2],o[i],o[i+1],o[i+2]);break;default:l("illigal figure")}}function n(t,n,r,i,a,s,o){var c,l,u,d,f=Math.floor(t[0]),p=Math.floor(t[1]),g=Math.ceil(t[2])-f,m=Math.ceil(t[3])-p,A=Math.min(Math.ceil(Math.abs(g*n[0]*1.1)),3e3),v=Math.min(Math.ceil(Math.abs(m*n[1]*1.1)),3e3),b=g/A,y=m/v,x={coords:r,colors:i,offsetX:-f,offsetY:-p,scaleX:1/b,scaleY:1/y},S=A+4,w=v+4;if(h.isEnabled)c=h.drawFigures(A,v,s,a,x),l=o.getCanvas("mesh",S,w,!1),l.context.drawImage(c,2,2),c=l.canvas;else{l=o.getCanvas("mesh",S,w,!1);var k=l.context,C=k.createImageData(A,v);if(s){var _=C.data;for(u=0,d=_.length;u<d;u+=4)_[u]=s[0],_[u+1]=s[1],_[u+2]=s[2],_[u+3]=255}for(u=0;u<a.length;u++)e(C,a[u],x);k.putImageData(C,2,2),c=l.canvas}return{canvas:c,offsetX:f-2*b,offsetY:p-2*y,scaleX:b,scaleY:y}}return n}();u.Mesh={fromIR:function(t){var e=t[2],n=t[3],r=t[4],i=t[5],a=t[6],o=t[8];return{type:"Pattern",getPattern:function(t,c,l){var h;if(l)h=s.singularValueDecompose2dScale(t.mozCurrentTransform);else if(h=s.singularValueDecompose2dScale(c.baseTransform),a){var u=s.singularValueDecompose2dScale(a);h=[h[0]*u[0],h[1]*u[1]]}var f=d(i,h,e,n,r,l?null:o,c.cachedCanvases);return l||(t.setTransform.apply(t,c.baseTransform),a&&t.transform.apply(t,a)),t.translate(f.offsetX,f.offsetY),t.scale(f.scaleX,f.scaleY),t.createPattern(f.canvas,"no-repeat")}}}},u.Dummy={fromIR:function(){return{type:"Pattern",getPattern:function(){return"hotpink"}}}};var f=function(){function t(t,e,n,r,i){this.operatorList=t[2],this.matrix=t[3]||[1,0,0,1,0,0],this.bbox=s.normalizeRect(t[4]),this.xstep=t[5],this.ystep=t[6],this.paintType=t[7],this.tilingType=t[8],this.color=e,this.canvasGraphicsFactory=r,this.baseTransform=i,this.type="Pattern",this.ctx=n}var e={COLORED:1,UNCOLORED:2};return t.prototype={createPatternCanvas:function(t){var e=this.operatorList,n=this.bbox,r=this.xstep,i=this.ystep,a=this.paintType,c=this.tilingType,l=this.color,h=this.canvasGraphicsFactory;o("TilingType: "+c);var u=n[0],d=n[1],f=n[2],p=n[3],g=[u,d],m=[u+r,d+i],A=m[0]-g[0],v=m[1]-g[1],b=s.singularValueDecompose2dScale(this.matrix),y=s.singularValueDecompose2dScale(this.baseTransform),x=[b[0]*y[0],b[1]*y[1]];A=Math.min(Math.ceil(Math.abs(A*x[0])),3e3),v=Math.min(Math.ceil(Math.abs(v*x[1])),3e3);var S=t.cachedCanvases.getCanvas("pattern",A,v,!0),w=S.context,k=h.createCanvasGraphics(w);k.groupLevel=t.groupLevel,this.setFillAndStrokeStyleToContext(w,a,l),this.setScale(A,v,r,i),this.transformToScale(k);var C=[1,0,0,1,-g[0],-g[1]];return k.transform.apply(k,C),this.clipBbox(k,n,u,d,f,p),k.executeOperatorList(e),S.canvas},setScale:function(t,e,n,r){this.scale=[t/n,e/r]},transformToScale:function(t){var e=this.scale,n=[e[0],0,0,e[1],0,0];t.transform.apply(t,n)},scaleToContext:function(){var t=this.scale;this.ctx.scale(1/t[0],1/t[1])},clipBbox:function(t,e,n,r,i,a){if(c(e)&&4===e.length){var s=i-n,o=a-r;t.ctx.rect(n,r,s,o),t.clip(),t.endPath()}},setFillAndStrokeStyleToContext:function(t,n,r){switch(n){case e.COLORED:var i=this.ctx;t.fillStyle=i.fillStyle,t.strokeStyle=i.strokeStyle;break;case e.UNCOLORED:var a=s.makeCssRgb(r[0],r[1],r[2]);t.fillStyle=a,t.strokeStyle=a;break;default:l("Unsupported paint type: "+n)}},getPattern:function(t,e){var n=this.createPatternCanvas(e);return t=this.ctx,t.setTransform.apply(t,this.baseTransform),t.transform.apply(t,this.matrix),this.scaleToContext(),t.createPattern(n,"repeat")}},t}();e.getShadingPatternFromIR=r,e.TilingPattern=f},function(t,e,n){"use strict";var r=n(0),i=n(9),a=n(3),s=n(5),o=n(2),c=n(1),l=n(4);e.PDFJS=i.PDFJS,e.build=a.build,e.version=a.version,e.getDocument=a.getDocument,e.PDFDataRangeTransport=a.PDFDataRangeTransport,e.PDFWorker=a.PDFWorker,e.renderTextLayer=s.renderTextLayer,e.AnnotationLayer=o.AnnotationLayer,e.CustomStyle=c.CustomStyle,e.createPromiseCapability=r.createPromiseCapability,e.PasswordResponses=r.PasswordResponses,e.InvalidPDFException=r.InvalidPDFException,e.MissingPDFException=r.MissingPDFException,e.SVGGraphics=l.SVGGraphics,e.UnexpectedResponseException=r.UnexpectedResponseException,e.OPS=r.OPS,e.UNSUPPORTED_FEATURES=r.UNSUPPORTED_FEATURES,e.isValidUrl=c.isValidUrl,e.createValidAbsoluteUrl=r.createValidAbsoluteUrl,e.createObjectURL=r.createObjectURL,e.removeNullCharacters=r.removeNullCharacters,e.shadow=r.shadow,e.createBlob=r.createBlob,e.RenderingCancelledException=c.RenderingCancelledException,e.getFilenameFromUrl=c.getFilenameFromUrl,e.addLinkAttributes=c.addLinkAttributes},function(t,e,n){"use strict";(function(t){if("undefined"==typeof PDFJS||!PDFJS.compatibilityChecked){var e="undefined"!=typeof window?window:void 0!==t?t:"undefined"!=typeof self?self:void 0,n="undefined"!=typeof navigator&&navigator.userAgent||"",r=/Android/.test(n),i=/Android\s[0-2][^\d]/.test(n),a=/Android\s[0-4][^\d]/.test(n),s=n.indexOf("Chrom")>=0,o=/Chrome\/(39|40)\./.test(n),c=n.indexOf("CriOS")>=0,l=n.indexOf("Trident")>=0,h=/\b(iPad|iPhone|iPod)(?=;)/.test(n),u=n.indexOf("Opera")>=0,d=/Safari\//.test(n)&&!/(Chrome\/|Android\s)/.test(n),f="object"==typeof window&&"object"==typeof document;"undefined"==typeof PDFJS&&(e.PDFJS={}),PDFJS.compatibilityChecked=!0,function(){function t(t,e){return new r(this.slice(t,e))}function n(t,e){arguments.length<2&&(e=0);for(var n=0,r=t.length;n<r;++n,++e)this[e]=255&t[n]}function r(e){var r,i,a;if("number"==typeof e)for(r=[],i=0;i<e;++i)r[i]=0;else if("slice"in e)r=e.slice(0);else for(r=[],i=0,a=e.length;i<a;++i)r[i]=e[i];return r.subarray=t,r.buffer=r,r.byteLength=r.length,r.set=n,"object"==typeof e&&e.buffer&&(r.buffer=e.buffer),r}if("undefined"!=typeof Uint8Array)return void 0===Uint8Array.prototype.subarray&&(Uint8Array.prototype.subarray=function(t,e){return new Uint8Array(this.slice(t,e))},Float32Array.prototype.subarray=function(t,e){return new Float32Array(this.slice(t,e))}),void("undefined"==typeof Float64Array&&(e.Float64Array=Float32Array));e.Uint8Array=r,e.Int8Array=r,e.Uint32Array=r,e.Int32Array=r,e.Uint16Array=r,e.Float32Array=r,e.Float64Array=r}(),function(){e.URL||(e.URL=e.webkitURL)}(),function(){if(void 0!==Object.defineProperty){var t=!0;try{f&&Object.defineProperty(new Image,"id",{value:"test"});var e=function(){};e.prototype={get id(){}},Object.defineProperty(new e,"id",{value:"",configurable:!0,enumerable:!0,writable:!1})}catch(e){t=!1}if(t)return}Object.defineProperty=function(t,e,n){delete t[e],"get"in n&&t.__defineGetter__(e,n.get),"set"in n&&t.__defineSetter__(e,n.set),"value"in n&&(t.__defineSetter__(e,function(t){return this.__defineGetter__(e,function(){return t}),t}),t[e]=n.value)}}(),function(){if("undefined"!=typeof XMLHttpRequest){var t=XMLHttpRequest.prototype,e=new XMLHttpRequest;if("overrideMimeType"in e||Object.defineProperty(t,"overrideMimeType",{value:function(t){}}),!("responseType"in e)){if(Object.defineProperty(t,"responseType",{get:function(){return this._responseType||"text"},set:function(t){"text"!==t&&"arraybuffer"!==t||(this._responseType=t,"arraybuffer"===t&&"function"==typeof this.overrideMimeType&&this.overrideMimeType("text/plain; charset=x-user-defined"))}}),"undefined"!=typeof VBArray)return void Object.defineProperty(t,"response",{get:function(){return"arraybuffer"===this.responseType?new Uint8Array(new VBArray(this.responseBody).toArray()):this.responseText}});Object.defineProperty(t,"response",{get:function(){if("arraybuffer"!==this.responseType)return this.responseText;var t,e=this.responseText,n=e.length,r=new Uint8Array(n);for(t=0;t<n;++t)r[t]=255&e.charCodeAt(t);return r.buffer}})}}}(),function(){if(!("btoa"in e)){var t="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";e.btoa=function(e){var n,r,i="";for(n=0,r=e.length;n<r;n+=3){var a=255&e.charCodeAt(n),s=255&e.charCodeAt(n+1),o=255&e.charCodeAt(n+2),c=a>>2,l=(3&a)<<4|s>>4,h=n+1<r?(15&s)<<2|o>>6:64,u=n+2<r?63&o:64;i+=t.charAt(c)+t.charAt(l)+t.charAt(h)+t.charAt(u)}return i}}}(),function(){if(!("atob"in e)){e.atob=function(t){if(t=t.replace(/=+$/,""),t.length%4==1)throw new Error("bad atob input");for(var e,n,r=0,i=0,a="";n=t.charAt(i++);~n&&(e=r%4?64*e+n:n,r++%4)?a+=String.fromCharCode(255&e>>(-2*r&6)):0)n="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".indexOf(n);return a}}}(),function(){void 0===Function.prototype.bind&&(Function.prototype.bind=function(t){var e=this,n=Array.prototype.slice.call(arguments,1);return function(){var r=n.concat(Array.prototype.slice.call(arguments));return e.apply(t,r)}})}(),function(){if(f){"dataset"in document.createElement("div")||Object.defineProperty(HTMLElement.prototype,"dataset",{get:function(){if(this._dataset)return this._dataset;for(var t={},e=0,n=this.attributes.length;e<n;e++){var r=this.attributes[e];if("data-"===r.name.substring(0,5)){t[r.name.substring(5).replace(/\-([a-z])/g,function(t,e){return e.toUpperCase()})]=r.value}}return Object.defineProperty(this,"_dataset",{value:t,writable:!1,enumerable:!1}),t},enumerable:!0})}}(),function(){function t(t,e,n,r){var i=t.className||"",a=i.split(/\s+/g);""===a[0]&&a.shift();var s=a.indexOf(e);return s<0&&n&&a.push(e),s>=0&&r&&a.splice(s,1),t.className=a.join(" "),s>=0}if(f){if(!("classList"in document.createElement("div"))){var e={add:function(e){t(this.element,e,!0,!1)},contains:function(e){return t(this.element,e,!1,!1)},remove:function(e){t(this.element,e,!1,!0)},toggle:function(e){t(this.element,e,!0,!0)}};Object.defineProperty(HTMLElement.prototype,"classList",{get:function(){if(this._classList)return this._classList;var t=Object.create(e,{element:{value:this,writable:!1,enumerable:!0}});return Object.defineProperty(this,"_classList",{value:t,writable:!1,enumerable:!1}),t},enumerable:!0})}}}(),function(){if(!("undefined"==typeof importScripts||"console"in e)){var t={},n={log:function(){var t=Array.prototype.slice.call(arguments);e.postMessage({targetName:"main",action:"console_log",data:t})},error:function(){var t=Array.prototype.slice.call(arguments);e.postMessage({targetName:"main",action:"console_error",data:t})},time:function(e){t[e]=Date.now()},timeEnd:function(e){var n=t[e];if(!n)throw new Error("Unknown timer name "+e);this.log("Timer:",e,Date.now()-n)}};e.console=n}}(),function(){if(f)"console"in window?"bind"in console.log||(console.log=function(t){return function(e){return t(e)}}(console.log),console.error=function(t){return function(e){return t(e)}}(console.error),console.warn=function(t){return function(e){return t(e)}}(console.warn)):window.console={log:function(){},error:function(){},warn:function(){}}}(),function(){function t(t){e(t.target)&&t.stopPropagation()}function e(t){return t.disabled||t.parentNode&&e(t.parentNode)}u&&document.addEventListener("click",t,!0)}(),function(){(l||c)&&(PDFJS.disableCreateObjectURL=!0)}(),function(){"undefined"!=typeof navigator&&("language"in navigator||(PDFJS.locale=navigator.userLanguage||"en-US"))}(),function(){(d||i||o||h)&&(PDFJS.disableRange=!0,PDFJS.disableStream=!0)}(),function(){f&&(history.pushState&&!i||(PDFJS.disableHistory=!0))}(),function(){if(f)if(window.CanvasPixelArray)"function"!=typeof window.CanvasPixelArray.prototype.set&&(window.CanvasPixelArray.prototype.set=function(t){for(var e=0,n=this.length;e<n;e++)this[e]=t[e]});else{var t,e=!1;if(s?(t=n.match(/Chrom(e|ium)\/([0-9]+)\./),e=t&&parseInt(t[2])<21):r?e=a:d&&(t=n.match(/Version\/([0-9]+)\.([0-9]+)\.([0-9]+) Safari\//),e=t&&parseInt(t[1])<6),e){var i=window.CanvasRenderingContext2D.prototype,o=i.createImageData;i.createImageData=function(t,e){var n=o.call(this,t,e);return n.data.set=function(t){for(var e=0,n=this.length;e<n;e++)this[e]=t[e]},n},i=null}}}(),function(){function t(){window.requestAnimationFrame=function(t){return window.setTimeout(t,20)},window.cancelAnimationFrame=function(t){window.clearTimeout(t)}}if(f)h?t():"requestAnimationFrame"in window||(window.requestAnimationFrame=window.mozRequestAnimationFrame||window.webkitRequestAnimationFrame,"requestAnimationFrame"in window||t())}(),function(){(h||r)&&(PDFJS.maxCanvasPixels=5242880)}(),function(){f&&l&&window.parent!==window&&(PDFJS.disableFullscreen=!0)}(),function(){f&&("currentScript"in document||Object.defineProperty(document,"currentScript",{get:function(){var t=document.getElementsByTagName("script");return t[t.length-1]},enumerable:!0,configurable:!0}))}(),function(){if(f){var t=document.createElement("input");try{t.type="number"}catch(r){var e=t.constructor.prototype,n=Object.getOwnPropertyDescriptor(e,"type");Object.defineProperty(e,"type",{get:function(){return n.get.call(this)},set:function(t){n.set.call(this,"number"===t?"text":t)},enumerable:!0,configurable:!0})}}}(),function(){if(f&&document.attachEvent){var t=document.constructor.prototype,e=Object.getOwnPropertyDescriptor(t,"readyState");Object.defineProperty(t,"readyState",{get:function(){var t=e.get.call(this);return"interactive"===t?"loading":t},set:function(t){e.set.call(this,t)},enumerable:!0,configurable:!0})}}(),function(){f&&void 0===Element.prototype.remove&&(Element.prototype.remove=function(){this.parentNode&&this.parentNode.removeChild(this)})}(),function(){if(e.Promise)return"function"!=typeof e.Promise.all&&(e.Promise.all=function(t){var n,r,i=0,a=[],s=new e.Promise(function(t,e){n=t,r=e});return t.forEach(function(t,e){i++,t.then(function(t){a[e]=t,0===--i&&n(a)},r)}),0===i&&n(a),s}),"function"!=typeof e.Promise.resolve&&(e.Promise.resolve=function(t){return new e.Promise(function(e){e(t)})}),"function"!=typeof e.Promise.reject&&(e.Promise.reject=function(t){return new e.Promise(function(e,n){n(t)})}),void("function"!=typeof e.Promise.prototype.catch&&(e.Promise.prototype.catch=function(t){return e.Promise.prototype.then(void 0,t)}));var t=2,n={handlers:[],running:!1,unhandledRejections:[],pendingRejectionCheck:!1,scheduleHandlers:function(t){0!==t._status&&(this.handlers=this.handlers.concat(t._handlers),t._handlers=[],this.running||(this.running=!0,setTimeout(this.runHandlers.bind(this),0)))},runHandlers:function(){for(var e=Date.now()+1;this.handlers.length>0;){var n=this.handlers.shift(),r=n.thisPromise._status,i=n.thisPromise._value;try{1===r?"function"==typeof n.onResolve&&(i=n.onResolve(i)):"function"==typeof n.onReject&&(i=n.onReject(i),r=1,n.thisPromise._unhandledRejection&&this.removeUnhandeledRejection(n.thisPromise))}catch(e){r=t,i=e}if(n.nextPromise._updateStatus(r,i),Date.now()>=e)break}if(this.handlers.length>0)return void setTimeout(this.runHandlers.bind(this),0);this.running=!1},addUnhandledRejection:function(t){this.unhandledRejections.push({promise:t,time:Date.now()}),this.scheduleRejectionCheck()},removeUnhandeledRejection:function(t){t._unhandledRejection=!1;for(var e=0;e<this.unhandledRejections.length;e++)this.unhandledRejections[e].promise===t&&(this.unhandledRejections.splice(e),e--)},scheduleRejectionCheck:function(){this.pendingRejectionCheck||(this.pendingRejectionCheck=!0,setTimeout(function(){this.pendingRejectionCheck=!1;for(var t=Date.now(),e=0;e<this.unhandledRejections.length;e++)if(t-this.unhandledRejections[e].time>500){var n=this.unhandledRejections[e].promise._value,r="Unhandled rejection: "+n;n.stack&&(r+="\n"+n.stack);try{throw new Error(r)}catch(t){console.warn(r)}this.unhandledRejections.splice(e),e--}this.unhandledRejections.length&&this.scheduleRejectionCheck()}.bind(this),500))}},r=function(t){this._status=0,this._handlers=[];try{t.call(this,this._resolve.bind(this),this._reject.bind(this))}catch(t){this._reject(t)}};r.all=function(e){function n(e){s._status!==t&&(c=[],a(e))}var i,a,s=new r(function(t,e){i=t,a=e}),o=e.length,c=[];if(0===o)return i(c),s;for(var l=0,h=e.length;l<h;++l){var u=e[l],d=function(e){return function(n){s._status!==t&&(c[e]=n,0===--o&&i(c))}}(l);r.isPromise(u)?u.then(d,n):d(u)}return s},r.isPromise=function(t){return t&&"function"==typeof t.then},r.resolve=function(t){return new r(function(e){e(t)})},r.reject=function(t){return new r(function(e,n){n(t)})},r.prototype={_status:null,_value:null,_handlers:null,_unhandledRejection:null,_updateStatus:function(e,i){if(1!==this._status&&this._status!==t){if(1===e&&r.isPromise(i))return void i.then(this._updateStatus.bind(this,1),this._updateStatus.bind(this,t));this._status=e,this._value=i,e===t&&0===this._handlers.length&&(this._unhandledRejection=!0,n.addUnhandledRejection(this)),n.scheduleHandlers(this)}},_resolve:function(t){this._updateStatus(1,t)},_reject:function(e){this._updateStatus(t,e)},then:function(t,e){var i=new r(function(t,e){this.resolve=t,this.reject=e});return this._handlers.push({thisPromise:this,onResolve:t,onReject:e,nextPromise:i}),n.scheduleHandlers(this),i},catch:function(t){return this.then(void 0,t)}},e.Promise=r}(),function(){function t(){this.id="$weakmap"+n++}if(!e.WeakMap){var n=0;t.prototype={has:function(t){return!!Object.getOwnPropertyDescriptor(t,this.id)},get:function(t,e){return this.has(t)?t[this.id]:e},set:function(t,e){Object.defineProperty(t,this.id,{value:e,enumerable:!1,configurable:!0})},delete:function(t){delete t[this.id]}},e.WeakMap=t}}(),function(){function t(t){return void 0!==u[t]}function n(){o.call(this),this._isInvalid=!0}function r(t){return""===t&&n.call(this),t.toLowerCase()}function i(t){var e=t.charCodeAt(0);return e>32&&e<127&&-1===[34,35,60,62,63,96].indexOf(e)?t:encodeURIComponent(t)}function a(t){var e=t.charCodeAt(0);return e>32&&e<127&&-1===[34,35,60,62,96].indexOf(e)?t:encodeURIComponent(t)}function s(e,s,o){function c(t){b.push(t)}var l=s||"scheme start",h=0,m="",A=!1,v=!1,b=[];t:for(;(e[h-1]!==f||0===h)&&!this._isInvalid;){var y=e[h];switch(l){case"scheme start":if(!y||!p.test(y)){if(s){c("Invalid scheme.");break t}m="",l="no scheme";continue}m+=y.toLowerCase(),l="scheme";break;case"scheme":if(y&&g.test(y))m+=y.toLowerCase();else{if(":"!==y){if(s){if(y===f)break t;c("Code point not allowed in scheme: "+y);break t}m="",h=0,l="no scheme";continue}if(this._scheme=m,m="",s)break t;t(this._scheme)&&(this._isRelative=!0),l="file"===this._scheme?"relative":this._isRelative&&o&&o._scheme===this._scheme?"relative or authority":this._isRelative?"authority first slash":"scheme data"}break;case"scheme data":"?"===y?(this._query="?",l="query"):"#"===y?(this._fragment="#",l="fragment"):y!==f&&"\t"!==y&&"\n"!==y&&"\r"!==y&&(this._schemeData+=i(y));break;case"no scheme":if(o&&t(o._scheme)){l="relative";continue}c("Missing scheme."),n.call(this);break;case"relative or authority":if("/"!==y||"/"!==e[h+1]){c("Expected /, got: "+y),l="relative";continue}l="authority ignore slashes";break;case"relative":if(this._isRelative=!0,"file"!==this._scheme&&(this._scheme=o._scheme),y===f){this._host=o._host,this._port=o._port,this._path=o._path.slice(),this._query=o._query,this._username=o._username,this._password=o._password;break t}if("/"===y||"\\"===y)"\\"===y&&c("\\ is an invalid code point."),l="relative slash";else if("?"===y)this._host=o._host,this._port=o._port,this._path=o._path.slice(),this._query="?",this._username=o._username,this._password=o._password,l="query";else{if("#"!==y){var x=e[h+1],S=e[h+2];("file"!==this._scheme||!p.test(y)||":"!==x&&"|"!==x||S!==f&&"/"!==S&&"\\"!==S&&"?"!==S&&"#"!==S)&&(this._host=o._host,this._port=o._port,this._username=o._username,this._password=o._password,this._path=o._path.slice(),this._path.pop()),l="relative path";continue}this._host=o._host,this._port=o._port,this._path=o._path.slice(),this._query=o._query,this._fragment="#",this._username=o._username,this._password=o._password,l="fragment"}break;case"relative slash":if("/"!==y&&"\\"!==y){"file"!==this._scheme&&(this._host=o._host,this._port=o._port,this._username=o._username,this._password=o._password),l="relative path";continue}"\\"===y&&c("\\ is an invalid code point."),l="file"===this._scheme?"file host":"authority ignore slashes";break;case"authority first slash":if("/"!==y){c("Expected '/', got: "+y),l="authority ignore slashes";continue}l="authority second slash";break;case"authority second slash":if(l="authority ignore slashes","/"!==y){c("Expected '/', got: "+y);continue}break;case"authority ignore slashes":if("/"!==y&&"\\"!==y){l="authority";continue}c("Expected authority, got: "+y);break;case"authority":if("@"===y){A&&(c("@ already seen."),m+="%40"),A=!0;for(var w=0;w<m.length;w++){var k=m[w];if("\t"!==k&&"\n"!==k&&"\r"!==k)if(":"!==k||null!==this._password){var C=i(k);null!==this._password?this._password+=C:this._username+=C}else this._password="";else c("Invalid whitespace in authority.")}m=""}else{if(y===f||"/"===y||"\\"===y||"?"===y||"#"===y){h-=m.length,m="",l="host";continue}m+=y}break;case"file host":if(y===f||"/"===y||"\\"===y||"?"===y||"#"===y){2!==m.length||!p.test(m[0])||":"!==m[1]&&"|"!==m[1]?0===m.length?l="relative path start":(this._host=r.call(this,m),m="",l="relative path start"):l="relative path";continue}"\t"===y||"\n"===y||"\r"===y?c("Invalid whitespace in file host."):m+=y;break;case"host":case"hostname":if(":"!==y||v){if(y===f||"/"===y||"\\"===y||"?"===y||"#"===y){if(this._host=r.call(this,m),m="",l="relative path start",s)break t;continue}"\t"!==y&&"\n"!==y&&"\r"!==y?("["===y?v=!0:"]"===y&&(v=!1),m+=y):c("Invalid code point in host/hostname: "+y)}else if(this._host=r.call(this,m),m="",l="port","hostname"===s)break t;break;case"port":if(/[0-9]/.test(y))m+=y;else{if(y===f||"/"===y||"\\"===y||"?"===y||"#"===y||s){if(""!==m){var _=parseInt(m,10);_!==u[this._scheme]&&(this._port=_+""),m=""}if(s)break t;l="relative path start";continue}"\t"===y||"\n"===y||"\r"===y?c("Invalid code point in port: "+y):n.call(this)}break;case"relative path start":if("\\"===y&&c("'\\' not allowed in path."),l="relative path","/"!==y&&"\\"!==y)continue;break;case"relative path":if(y!==f&&"/"!==y&&"\\"!==y&&(s||"?"!==y&&"#"!==y))"\t"!==y&&"\n"!==y&&"\r"!==y&&(m+=i(y));else{"\\"===y&&c("\\ not allowed in relative path.");var T;(T=d[m.toLowerCase()])&&(m=T),".."===m?(this._path.pop(),"/"!==y&&"\\"!==y&&this._path.push("")):"."===m&&"/"!==y&&"\\"!==y?this._path.push(""):"."!==m&&("file"===this._scheme&&0===this._path.length&&2===m.length&&p.test(m[0])&&"|"===m[1]&&(m=m[0]+":"),this._path.push(m)),m="","?"===y?(this._query="?",l="query"):"#"===y&&(this._fragment="#",l="fragment")}break;case"query":s||"#"!==y?y!==f&&"\t"!==y&&"\n"!==y&&"\r"!==y&&(this._query+=a(y)):(this._fragment="#",l="fragment");break;case"fragment":y!==f&&"\t"!==y&&"\n"!==y&&"\r"!==y&&(this._fragment+=y)}h++}}function o(){this._scheme="",this._schemeData="",this._username="",this._password=null,this._host="",this._port="",this._path=[],this._query="",this._fragment="",this._isInvalid=!1,this._isRelative=!1}function c(t,e){void 0===e||e instanceof c||(e=new c(String(e))),this._url=t,o.call(this);var n=t.replace(/^[ \t\r\n\f]+|[ \t\r\n\f]+$/g,"");s.call(this,n,null,e)}var l=!1;try{if("function"==typeof URL&&"object"==typeof URL.prototype&&"origin"in URL.prototype){var h=new URL("b","http://a");h.pathname="c%20d",l="http://a/c%20d"===h.href}}catch(t){}if(!l){var u=Object.create(null);u.ftp=21,u.file=0,u.gopher=70,u.http=80,u.https=443,u.ws=80,u.wss=443;var d=Object.create(null);d["%2e"]=".",d[".%2e"]="..",d["%2e."]="..",d["%2e%2e"]="..";var f,p=/[a-zA-Z]/,g=/[a-zA-Z0-9\+\-\.]/;c.prototype={toString:function(){return this.href},get href(){if(this._isInvalid)return this._url;var t="";return""===this._username&&null===this._password||(t=this._username+(null!==this._password?":"+this._password:"")+"@"),this.protocol+(this._isRelative?"//"+t+this.host:"")+this.pathname+this._query+this._fragment},set href(t){o.call(this),s.call(this,t)},get protocol(){return this._scheme+":"},set protocol(t){this._isInvalid||s.call(this,t+":","scheme start")},get host(){return this._isInvalid?"":this._port?this._host+":"+this._port:this._host},set host(t){!this._isInvalid&&this._isRelative&&s.call(this,t,"host")},get hostname(){return this._host},set hostname(t){!this._isInvalid&&this._isRelative&&s.call(this,t,"hostname")},get port(){return this._port},set port(t){!this._isInvalid&&this._isRelative&&s.call(this,t,"port")},get pathname(){return this._isInvalid?"":this._isRelative?"/"+this._path.join("/"):this._schemeData},set pathname(t){!this._isInvalid&&this._isRelative&&(this._path=[],s.call(this,t,"relative path start"))},get search(){return this._isInvalid||!this._query||"?"===this._query?"":this._query},set search(t){!this._isInvalid&&this._isRelative&&(this._query="?","?"===t[0]&&(t=t.slice(1)),s.call(this,t,"query"))},get hash(){return this._isInvalid||!this._fragment||"#"===this._fragment?"":this._fragment},set hash(t){this._isInvalid||(this._fragment="#","#"===t[0]&&(t=t.slice(1)),
+s.call(this,t,"fragment"))},get origin(){var t;if(this._isInvalid||!this._scheme)return"";switch(this._scheme){case"data":case"file":case"javascript":case"mailto":return"null"}return t=this.host,t?this._scheme+"://"+t:""}};var m=e.URL;m&&(c.createObjectURL=function(t){return m.createObjectURL.apply(m,arguments)},c.revokeObjectURL=function(t){m.revokeObjectURL(t)}),e.URL=c}}()}}).call(e,n(6))}])}); \ No newline at end of file
diff --git a/vendor/assets/javascripts/pdf.worker.js b/vendor/assets/javascripts/pdf.worker.js
index 970caaaba86..6caaf016ebc 100644..100755
--- a/vendor/assets/javascripts/pdf.worker.js
+++ b/vendor/assets/javascripts/pdf.worker.js
@@ -1,274 +1,4 @@
-(function webpackUniversalModuleDefinition(root, factory) {
- if(typeof exports === 'object' && typeof module === 'object')
- module.exports = factory();
- else if(typeof define === 'function' && define.amd)
- define("PDFLab", [], factory);
- else if(typeof exports === 'object')
- exports["PDFLab"] = factory();
- else
- root["PDFLab"] = factory();
-})(this, function() {
-return /******/ (function(modules) { // webpackBootstrap
-/******/ // The module cache
-/******/ var installedModules = {};
-/******/
-/******/ // The require function
-/******/ function __webpack_require__(moduleId) {
-/******/
-/******/ // Check if module is in cache
-/******/ if(installedModules[moduleId])
-/******/ return installedModules[moduleId].exports;
-/******/
-/******/ // Create a new module (and put it into the cache)
-/******/ var module = installedModules[moduleId] = {
-/******/ i: moduleId,
-/******/ l: false,
-/******/ exports: {}
-/******/ };
-/******/
-/******/ // Execute the module function
-/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
-/******/
-/******/ // Flag the module as loaded
-/******/ module.l = true;
-/******/
-/******/ // Return the exports of the module
-/******/ return module.exports;
-/******/ }
-/******/
-/******/
-/******/ // expose the modules object (__webpack_modules__)
-/******/ __webpack_require__.m = modules;
-/******/
-/******/ // expose the module cache
-/******/ __webpack_require__.c = installedModules;
-/******/
-/******/ // identity function for calling harmony imports with the correct context
-/******/ __webpack_require__.i = function(value) { return value; };
-/******/
-/******/ // define getter function for harmony exports
-/******/ __webpack_require__.d = function(exports, name, getter) {
-/******/ if(!__webpack_require__.o(exports, name)) {
-/******/ Object.defineProperty(exports, name, {
-/******/ configurable: false,
-/******/ enumerable: true,
-/******/ get: getter
-/******/ });
-/******/ }
-/******/ };
-/******/
-/******/ // getDefaultExport function for compatibility with non-harmony modules
-/******/ __webpack_require__.n = function(module) {
-/******/ var getter = module && module.__esModule ?
-/******/ function getDefault() { return module['default']; } :
-/******/ function getModuleExports() { return module; };
-/******/ __webpack_require__.d(getter, 'a', getter);
-/******/ return getter;
-/******/ };
-/******/
-/******/ // Object.prototype.hasOwnProperty.call
-/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
-/******/
-/******/ // __webpack_public_path__
-/******/ __webpack_require__.p = "";
-/******/
-/******/ // Load entry module and return exports
-/******/ return __webpack_require__(__webpack_require__.s = 24);
-/******/ })
-/************************************************************************/
-/******/ ({
-
-/***/ 0:
-/***/ (function(module, exports) {
-
-// shim for using process in browser
-var process = module.exports = {};
-
-// cached from whatever global is present so that test runners that stub it
-// don't break things. But we need to wrap it in a try catch in case it is
-// wrapped in strict mode code which doesn't define any globals. It's inside a
-// function because try/catches deoptimize in certain engines.
-
-var cachedSetTimeout;
-var cachedClearTimeout;
-
-function defaultSetTimout() {
- throw new Error('setTimeout has not been defined');
-}
-function defaultClearTimeout () {
- throw new Error('clearTimeout has not been defined');
-}
-(function () {
- try {
- if (typeof setTimeout === 'function') {
- cachedSetTimeout = setTimeout;
- } else {
- cachedSetTimeout = defaultSetTimout;
- }
- } catch (e) {
- cachedSetTimeout = defaultSetTimout;
- }
- try {
- if (typeof clearTimeout === 'function') {
- cachedClearTimeout = clearTimeout;
- } else {
- cachedClearTimeout = defaultClearTimeout;
- }
- } catch (e) {
- cachedClearTimeout = defaultClearTimeout;
- }
-} ())
-function runTimeout(fun) {
- if (cachedSetTimeout === setTimeout) {
- //normal enviroments in sane situations
- return setTimeout(fun, 0);
- }
- // if setTimeout wasn't available but was latter defined
- if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) {
- cachedSetTimeout = setTimeout;
- return setTimeout(fun, 0);
- }
- try {
- // when when somebody has screwed with setTimeout but no I.E. maddness
- return cachedSetTimeout(fun, 0);
- } catch(e){
- try {
- // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally
- return cachedSetTimeout.call(null, fun, 0);
- } catch(e){
- // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error
- return cachedSetTimeout.call(this, fun, 0);
- }
- }
-
-
-}
-function runClearTimeout(marker) {
- if (cachedClearTimeout === clearTimeout) {
- //normal enviroments in sane situations
- return clearTimeout(marker);
- }
- // if clearTimeout wasn't available but was latter defined
- if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) {
- cachedClearTimeout = clearTimeout;
- return clearTimeout(marker);
- }
- try {
- // when when somebody has screwed with setTimeout but no I.E. maddness
- return cachedClearTimeout(marker);
- } catch (e){
- try {
- // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally
- return cachedClearTimeout.call(null, marker);
- } catch (e){
- // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error.
- // Some versions of I.E. have different rules for clearTimeout vs setTimeout
- return cachedClearTimeout.call(this, marker);
- }
- }
-
-
-
-}
-var queue = [];
-var draining = false;
-var currentQueue;
-var queueIndex = -1;
-
-function cleanUpNextTick() {
- if (!draining || !currentQueue) {
- return;
- }
- draining = false;
- if (currentQueue.length) {
- queue = currentQueue.concat(queue);
- } else {
- queueIndex = -1;
- }
- if (queue.length) {
- drainQueue();
- }
-}
-
-function drainQueue() {
- if (draining) {
- return;
- }
- var timeout = runTimeout(cleanUpNextTick);
- draining = true;
-
- var len = queue.length;
- while(len) {
- currentQueue = queue;
- queue = [];
- while (++queueIndex < len) {
- if (currentQueue) {
- currentQueue[queueIndex].run();
- }
- }
- queueIndex = -1;
- len = queue.length;
- }
- currentQueue = null;
- draining = false;
- runClearTimeout(timeout);
-}
-
-process.nextTick = function (fun) {
- var args = new Array(arguments.length - 1);
- if (arguments.length > 1) {
- for (var i = 1; i < arguments.length; i++) {
- args[i - 1] = arguments[i];
- }
- }
- queue.push(new Item(fun, args));
- if (queue.length === 1 && !draining) {
- runTimeout(drainQueue);
- }
-};
-
-// v8 likes predictible objects
-function Item(fun, array) {
- this.fun = fun;
- this.array = array;
-}
-Item.prototype.run = function () {
- this.fun.apply(null, this.array);
-};
-process.title = 'browser';
-process.browser = true;
-process.env = {};
-process.argv = [];
-process.version = ''; // empty string to avoid regexp issues
-process.versions = {};
-
-function noop() {}
-
-process.on = noop;
-process.addListener = noop;
-process.once = noop;
-process.off = noop;
-process.removeListener = noop;
-process.removeAllListeners = noop;
-process.emit = noop;
-
-process.binding = function (name) {
- throw new Error('process.binding is not supported');
-};
-
-process.cwd = function () { return '/' };
-process.chdir = function (dir) {
- throw new Error('process.chdir is not supported');
-};
-process.umask = function() { return 0; };
-
-
-/***/ }),
-
-/***/ 1:
-/***/ (function(module, exports, __webpack_require__) {
-
-/* WEBPACK VAR INJECTION */(function(process) {/* Copyright 2017 Mozilla Foundation
+/* Copyright 2017 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -284,7 +14,7 @@ process.umask = function() { return 0; };
*/
(function webpackUniversalModuleDefinition(root, factory) {
- if(true)
+ if(typeof exports === 'object' && typeof module === 'object')
module.exports = factory();
else if(typeof define === 'function' && define.amd)
define("pdfjs-dist/build/pdf.worker", [], factory);
@@ -38605,35 +38335,4 @@ if (typeof PDFJS === 'undefined' || !PDFJS.compatibilityChecked) {
/***/ })
/******/ ]);
-});
-/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(0)))
-
-/***/ }),
-
-/***/ 24:
-/***/ (function(module, exports, __webpack_require__) {
-
-/* Copyright 2016 Mozilla Foundation
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-/* eslint-disable strict */
-
-(typeof window !== 'undefined' ? window : {}).pdfjsDistBuildPdfWorker =
- __webpack_require__(1);
-
-
-/***/ })
-
-/******/ });
}); \ No newline at end of file
diff --git a/vendor/assets/javascripts/pdf.worker.min.js b/vendor/assets/javascripts/pdf.worker.min.js
new file mode 100755
index 00000000000..3503a8f46ca
--- /dev/null
+++ b/vendor/assets/javascripts/pdf.worker.min.js
@@ -0,0 +1,19 @@
+!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define("pdfjs-dist/build/pdf.worker",[],t):"object"==typeof exports?exports["pdfjs-dist/build/pdf.worker"]=t():e["pdfjs-dist/build/pdf.worker"]=e.pdfjsDistBuildPdfWorker=t()}(this,function(){return function(e){function t(r){if(a[r])return a[r].exports;var i=a[r]={i:r,l:!1,exports:{}};e[r].call(i.exports,i,i.exports,t);i.l=!0;return i.exports}var a={};t.m=e;t.c=a;t.i=function(e){return e};t.d=function(e,a,r){t.o(e,a)||Object.defineProperty(e,a,{configurable:!1,enumerable:!0,get:r})};t.n=function(e){var a=e&&e.__esModule?function(){return e.default}:function(){return e};t.d(a,"a",a);return a};t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)};t.p="";return t(t.s=36)}([function(e,t,a){"use strict";(function(e){function r(e){ae=e}function i(){return ae}function n(e){ae>=$.infos&&console.log("Info: "+e)}function s(e){ae>=$.warnings&&console.log("Warning: "+e)}function o(e){console.log("Deprecated API usage: "+e)}function c(e){if(ae>=$.errors){console.log("Error: "+e);console.log(l())}throw new Error(e)}function l(){try{throw new Error}catch(e){return e.stack?e.stack.split("\n").slice(2).join("\n"):""}}function h(e,t){e||c(t)}function u(e,t){try{var a=new URL(e);if(!a.origin||"null"===a.origin)return!1}catch(e){return!1}var r=new URL(t,a);return a.origin===r.origin}function f(e){if(!e)return!1;switch(e.protocol){case"http:":case"https:":case"ftp:":case"mailto:":case"tel:":return!0;default:return!1}}function d(e,t){if(!e)return null;try{var a=t?new URL(e,t):new URL(e);if(f(a))return a}catch(e){}return null}function g(e,t,a){Object.defineProperty(e,t,{value:a,enumerable:!0,configurable:!0,writable:!1});return a}function p(e){var t;return function(){if(e){t=Object.create(null);e(t);e=null}return t}}function m(e){if("string"!=typeof e){s("The argument for removeNullCharacters must be a string.");return e}return e.replace(de,"")}function b(e){h(null!==e&&"object"==typeof e&&void 0!==e.length,"Invalid argument for bytesToString");var t=e.length;if(t<8192)return String.fromCharCode.apply(null,e);for(var a=[],r=0;r<t;r+=8192){var i=Math.min(r+8192,t),n=e.subarray(r,i);a.push(String.fromCharCode.apply(null,n))}return a.join("")}function v(e){h("string"==typeof e,"Invalid argument for stringToBytes");for(var t=e.length,a=new Uint8Array(t),r=0;r<t;++r)a[r]=255&e.charCodeAt(r);return a}function y(e){if(void 0!==e.length)return e.length;h(void 0!==e.byteLength);return e.byteLength}function k(e){if(1===e.length&&e[0]instanceof Uint8Array)return e[0];var t,a,r,i=0,n=e.length;for(t=0;t<n;t++){a=e[t];r=y(a);i+=r}var s=0,o=new Uint8Array(i);for(t=0;t<n;t++){a=e[t];a instanceof Uint8Array||(a="string"==typeof a?v(a):new Uint8Array(a));r=a.byteLength;o.set(a,s);s+=r}return o}function w(e){return String.fromCharCode(e>>24&255,e>>16&255,e>>8&255,255&e)}function C(e){for(var t=1,a=0;e>t;){t<<=1;a++}return a}function x(e,t){return e[t]<<24>>24}function S(e,t){return e[t]<<8|e[t+1]}function A(e,t){return(e[t]<<24|e[t+1]<<16|e[t+2]<<8|e[t+3])>>>0}function I(){var e=new Uint8Array(2);e[0]=1;return 1===new Uint16Array(e.buffer)[0]}function B(){try{new Function("");return!0}catch(e){return!1}}function R(e){var t,a=e.length,r=[];if("þ"===e[0]&&"ÿ"===e[1])for(t=2;t<a;t+=2)r.push(String.fromCharCode(e.charCodeAt(t)<<8|e.charCodeAt(t+1)));else for(t=0;t<a;++t){var i=ve[e.charCodeAt(t)];r.push(i?String.fromCharCode(i):e.charAt(t))}return r.join("")}function T(e){return decodeURIComponent(escape(e))}function O(e){return unescape(encodeURIComponent(e))}function P(e){for(var t in e)return!1;return!0}function M(e){return"boolean"==typeof e}function E(e){return"number"==typeof e&&(0|e)===e}function L(e){return"number"==typeof e}function D(e){return"string"==typeof e}function F(e){return e instanceof Array}function q(e){return"object"==typeof e&&null!==e&&void 0!==e.byteLength}function U(e){return 32===e||9===e||13===e||10===e}function N(){return"undefined"==typeof __pdfjsdev_webpack__&&("object"==typeof process&&process+""=="[object process]")}function j(){var e={};e.promise=new Promise(function(t,a){e.resolve=t;e.reject=a});return e}function _(e,t,a){this.sourceName=e;this.targetName=t;this.comObj=a;this.callbackIndex=1;this.postMessageTransfers=!0;var r=this.callbacksCapabilities=Object.create(null),i=this.actionHandler=Object.create(null);this._onComObjOnMessage=function(e){var t=e.data;if(t.targetName===this.sourceName)if(t.isReply){var n=t.callbackId;if(t.callbackId in r){var s=r[n];delete r[n];"error"in t?s.reject(t.error):s.resolve(t.data)}else c("Cannot resolve callback "+n)}else if(t.action in i){var o=i[t.action];if(t.callbackId){var l=this.sourceName,h=t.sourceName;Promise.resolve().then(function(){return o[0].call(o[1],t.data)}).then(function(e){a.postMessage({sourceName:l,targetName:h,isReply:!0,callbackId:t.callbackId,data:e})},function(e){e instanceof Error&&(e+="");a.postMessage({sourceName:l,targetName:h,isReply:!0,callbackId:t.callbackId,error:e})})}else o[0].call(o[1],t.data)}else c("Unknown action from worker: "+t.action)}.bind(this);a.addEventListener("message",this._onComObjOnMessage)}function z(e,t,a){var r=new Image;r.onload=function(){a.resolve(e,r)};r.onerror=function(){a.resolve(e,null);s("Error during JPEG image loading")};r.src=t}var H=(a(37),"undefined"!=typeof window?window:void 0!==e?e:"undefined"!=typeof self?self:void 0),G=[.001,0,0,.001,0,0],X={FILL:0,STROKE:1,FILL_STROKE:2,INVISIBLE:3,FILL_ADD_TO_PATH:4,STROKE_ADD_TO_PATH:5,FILL_STROKE_ADD_TO_PATH:6,ADD_TO_PATH:7,FILL_STROKE_MASK:3,ADD_TO_PATH_FLAG:4},V={GRAYSCALE_1BPP:1,RGB_24BPP:2,RGBA_32BPP:3},W={TEXT:1,LINK:2,FREETEXT:3,LINE:4,SQUARE:5,CIRCLE:6,POLYGON:7,POLYLINE:8,HIGHLIGHT:9,UNDERLINE:10,SQUIGGLY:11,STRIKEOUT:12,STAMP:13,CARET:14,INK:15,POPUP:16,FILEATTACHMENT:17,SOUND:18,MOVIE:19,WIDGET:20,SCREEN:21,PRINTERMARK:22,TRAPNET:23,WATERMARK:24,THREED:25,REDACT:26},K={INVISIBLE:1,HIDDEN:2,PRINT:4,NOZOOM:8,NOROTATE:16,NOVIEW:32,READONLY:64,LOCKED:128,TOGGLENOVIEW:256,LOCKEDCONTENTS:512},Y={READONLY:1,REQUIRED:2,NOEXPORT:4,MULTILINE:4096,PASSWORD:8192,NOTOGGLETOOFF:16384,RADIO:32768,PUSHBUTTON:65536,COMBO:131072,EDIT:262144,SORT:524288,FILESELECT:1048576,MULTISELECT:2097152,DONOTSPELLCHECK:4194304,DONOTSCROLL:8388608,COMB:16777216,RICHTEXT:33554432,RADIOSINUNISON:33554432,COMMITONSELCHANGE:67108864},J={SOLID:1,DASHED:2,BEVELED:3,INSET:4,UNDERLINE:5},Z={UNKNOWN:0,FLATE:1,LZW:2,DCT:3,JPX:4,JBIG:5,A85:6,AHX:7,CCF:8,RL:9},Q={UNKNOWN:0,TYPE1:1,TYPE1C:2,CIDFONTTYPE0:3,CIDFONTTYPE0C:4,TRUETYPE:5,CIDFONTTYPE2:6,TYPE3:7,OPENTYPE:8,TYPE0:9,MMTYPE1:10},$={errors:0,warnings:1,infos:5},ee={NONE:0,BINARY:1,STREAM:2},te={dependency:1,setLineWidth:2,setLineCap:3,setLineJoin:4,setMiterLimit:5,setDash:6,setRenderingIntent:7,setFlatness:8,setGState:9,save:10,restore:11,transform:12,moveTo:13,lineTo:14,curveTo:15,curveTo2:16,curveTo3:17,closePath:18,rectangle:19,stroke:20,closeStroke:21,fill:22,eoFill:23,fillStroke:24,eoFillStroke:25,closeFillStroke:26,closeEOFillStroke:27,endPath:28,clip:29,eoClip:30,beginText:31,endText:32,setCharSpacing:33,setWordSpacing:34,setHScale:35,setLeading:36,setFont:37,setTextRenderingMode:38,setTextRise:39,moveText:40,setLeadingMoveText:41,setTextMatrix:42,nextLine:43,showText:44,showSpacedText:45,nextLineShowText:46,nextLineSetSpacingShowText:47,setCharWidth:48,setCharWidthAndBounds:49,setStrokeColorSpace:50,setFillColorSpace:51,setStrokeColor:52,setStrokeColorN:53,setFillColor:54,setFillColorN:55,setStrokeGray:56,setFillGray:57,setStrokeRGBColor:58,setFillRGBColor:59,setStrokeCMYKColor:60,setFillCMYKColor:61,shadingFill:62,beginInlineImage:63,beginImageData:64,endInlineImage:65,paintXObject:66,markPoint:67,markPointProps:68,beginMarkedContent:69,beginMarkedContentProps:70,endMarkedContent:71,beginCompat:72,endCompat:73,paintFormXObjectBegin:74,paintFormXObjectEnd:75,beginGroup:76,endGroup:77,beginAnnotations:78,endAnnotations:79,beginAnnotation:80,endAnnotation:81,paintJpegXObject:82,paintImageMaskXObject:83,paintImageMaskXObjectGroup:84,paintImageXObject:85,paintInlineImageXObject:86,paintInlineImageXObjectGroup:87,paintImageXObjectRepeat:88,paintImageMaskXObjectRepeat:89,paintSolidColorImageMask:90,constructPath:91},ae=$.warnings,re={unknown:"unknown",forms:"forms",javaScript:"javaScript",smask:"smask",shadingPattern:"shadingPattern",font:"font"},ie={NEED_PASSWORD:1,INCORRECT_PASSWORD:2},ne=function(){function e(e,t){this.name="PasswordException";this.message=e;this.code=t}e.prototype=new Error;e.constructor=e;return e}(),se=function(){function e(e,t){this.name="UnknownErrorException";this.message=e;this.details=t}e.prototype=new Error;e.constructor=e;return e}(),oe=function(){function e(e){this.name="InvalidPDFException";this.message=e}e.prototype=new Error;e.constructor=e;return e}(),ce=function(){function e(e){this.name="MissingPDFException";this.message=e}e.prototype=new Error;e.constructor=e;return e}(),le=function(){function e(e,t){this.name="UnexpectedResponseException";this.message=e;this.status=t}e.prototype=new Error;e.constructor=e;return e}(),he=function(){function e(e){this.message=e}e.prototype=new Error;e.prototype.name="NotImplementedException";e.constructor=e;return e}(),ue=function(){function e(e,t){this.begin=e;this.end=t;this.message="Missing data ["+e+", "+t+")"}e.prototype=new Error;e.prototype.name="MissingDataException";e.constructor=e;return e}(),fe=function(){function e(e){this.message=e}e.prototype=new Error;e.prototype.name="XRefParseException";e.constructor=e;return e}(),de=/\x00/g,ge=function(){function e(e,t){this.buffer=e;this.byteLength=e.length;this.length=void 0===t?this.byteLength>>2:t;a(this.length)}function t(e){return{get:function(){var t=this.buffer,a=e<<2;return(t[a]|t[a+1]<<8|t[a+2]<<16|t[a+3]<<24)>>>0},set:function(t){var a=this.buffer,r=e<<2;a[r]=255&t;a[r+1]=t>>8&255;a[r+2]=t>>16&255;a[r+3]=t>>>24&255}}}function a(a){for(;r<a;){Object.defineProperty(e.prototype,r,t(r));r++}}e.prototype=Object.create(null);var r=0;return e}();t.Uint32ArrayView=ge;var pe=[1,0,0,1,0,0],me=function(){function e(){}var t=["rgb(",0,",",0,",",0,")"];e.makeCssRgb=function(e,a,r){t[1]=e;t[3]=a;t[5]=r;return t.join("")};e.transform=function(e,t){return[e[0]*t[0]+e[2]*t[1],e[1]*t[0]+e[3]*t[1],e[0]*t[2]+e[2]*t[3],e[1]*t[2]+e[3]*t[3],e[0]*t[4]+e[2]*t[5]+e[4],e[1]*t[4]+e[3]*t[5]+e[5]]};e.applyTransform=function(e,t){return[e[0]*t[0]+e[1]*t[2]+t[4],e[0]*t[1]+e[1]*t[3]+t[5]]};e.applyInverseTransform=function(e,t){var a=t[0]*t[3]-t[1]*t[2];return[(e[0]*t[3]-e[1]*t[2]+t[2]*t[5]-t[4]*t[3])/a,(-e[0]*t[1]+e[1]*t[0]+t[4]*t[1]-t[5]*t[0])/a]};e.getAxialAlignedBoundingBox=function(t,a){var r=e.applyTransform(t,a),i=e.applyTransform(t.slice(2,4),a),n=e.applyTransform([t[0],t[3]],a),s=e.applyTransform([t[2],t[1]],a);return[Math.min(r[0],i[0],n[0],s[0]),Math.min(r[1],i[1],n[1],s[1]),Math.max(r[0],i[0],n[0],s[0]),Math.max(r[1],i[1],n[1],s[1])]};e.inverseTransform=function(e){var t=e[0]*e[3]-e[1]*e[2];return[e[3]/t,-e[1]/t,-e[2]/t,e[0]/t,(e[2]*e[5]-e[4]*e[3])/t,(e[4]*e[1]-e[5]*e[0])/t]};e.apply3dTransform=function(e,t){return[e[0]*t[0]+e[1]*t[1]+e[2]*t[2],e[3]*t[0]+e[4]*t[1]+e[5]*t[2],e[6]*t[0]+e[7]*t[1]+e[8]*t[2]]};e.singularValueDecompose2dScale=function(e){var t=[e[0],e[2],e[1],e[3]],a=e[0]*t[0]+e[1]*t[2],r=e[0]*t[1]+e[1]*t[3],i=e[2]*t[0]+e[3]*t[2],n=e[2]*t[1]+e[3]*t[3],s=(a+n)/2,o=Math.sqrt((a+n)*(a+n)-4*(a*n-i*r))/2,c=s+o||1,l=s-o||1;return[Math.sqrt(c),Math.sqrt(l)]};e.normalizeRect=function(e){var t=e.slice(0);if(e[0]>e[2]){t[0]=e[2];t[2]=e[0]}if(e[1]>e[3]){t[1]=e[3];t[3]=e[1]}return t};e.intersect=function(t,a){function r(e,t){return e-t}var i=[t[0],t[2],a[0],a[2]].sort(r),n=[t[1],t[3],a[1],a[3]].sort(r),s=[];t=e.normalizeRect(t);a=e.normalizeRect(a);if(!(i[0]===t[0]&&i[1]===a[0]||i[0]===a[0]&&i[1]===t[0]))return!1;s[0]=i[1];s[2]=i[2];if(!(n[0]===t[1]&&n[1]===a[1]||n[0]===a[1]&&n[1]===t[1]))return!1;s[1]=n[1];s[3]=n[2];return s};e.sign=function(e){return e<0?-1:1};var a=["","C","CC","CCC","CD","D","DC","DCC","DCCC","CM","","X","XX","XXX","XL","L","LX","LXX","LXXX","XC","","I","II","III","IV","V","VI","VII","VIII","IX"];e.toRoman=function(e,t){h(E(e)&&e>0,"The number should be a positive integer.");for(var r,i=[];e>=1e3;){e-=1e3;i.push("M")}r=e/100|0;e%=100;i.push(a[r]);r=e/10|0;e%=10;i.push(a[10+r]);i.push(a[20+e]);var n=i.join("");return t?n.toLowerCase():n};e.appendToArray=function(e,t){Array.prototype.push.apply(e,t)};e.prependToArray=function(e,t){Array.prototype.unshift.apply(e,t)};e.extendObj=function(e,t){for(var a in t)e[a]=t[a]};e.getInheritableProperty=function(e,t,a){for(;e&&!e.has(t);)e=e.get("Parent");return e?a?e.getArray(t):e.get(t):null};e.inherit=function(e,t,a){e.prototype=Object.create(t.prototype);e.prototype.constructor=e;for(var r in a)e.prototype[r]=a[r]};e.loadScript=function(e,t){var a=document.createElement("script"),r=!1;a.setAttribute("src",e);t&&(a.onload=function(){r||t();r=!0});document.getElementsByTagName("head")[0].appendChild(a)};return e}(),be=function(){function e(e,t,a,r,i,n){this.viewBox=e;this.scale=t;this.rotation=a;this.offsetX=r;this.offsetY=i;var s,o,c,l,h=(e[2]+e[0])/2,u=(e[3]+e[1])/2;a%=360;a=a<0?a+360:a;switch(a){case 180:s=-1;o=0;c=0;l=1;break;case 90:s=0;o=1;c=1;l=0;break;case 270:s=0;o=-1;c=-1;l=0;break;default:s=1;o=0;c=0;l=-1}if(n){c=-c;l=-l}var f,d,g,p;if(0===s){f=Math.abs(u-e[1])*t+r;d=Math.abs(h-e[0])*t+i;g=Math.abs(e[3]-e[1])*t;p=Math.abs(e[2]-e[0])*t}else{f=Math.abs(h-e[0])*t+r;d=Math.abs(u-e[1])*t+i;g=Math.abs(e[2]-e[0])*t;p=Math.abs(e[3]-e[1])*t}this.transform=[s*t,o*t,c*t,l*t,f-s*t*h-c*t*u,d-o*t*h-l*t*u];this.width=g;this.height=p;this.fontScale=t}e.prototype={clone:function(t){t=t||{};var a="scale"in t?t.scale:this.scale,r="rotation"in t?t.rotation:this.rotation;return new e(this.viewBox.slice(),a,r,this.offsetX,this.offsetY,t.dontFlip)},convertToViewportPoint:function(e,t){return me.applyTransform([e,t],this.transform)},convertToViewportRectangle:function(e){var t=me.applyTransform([e[0],e[1]],this.transform),a=me.applyTransform([e[2],e[3]],this.transform);return[t[0],t[1],a[0],a[1]]},convertToPdfPoint:function(e,t){return me.applyInverseTransform([e,t],this.transform)}};return e}(),ve=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,728,711,710,729,733,731,730,732,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8226,8224,8225,8230,8212,8211,402,8260,8249,8250,8722,8240,8222,8220,8221,8216,8217,8218,8482,64257,64258,321,338,352,376,381,305,322,339,353,382,0,8364],ye=function(){function e(e,t,a){for(;e.length<a;)e+=t;return e}function t(){this.started=Object.create(null);this.times=[];this.enabled=!0}t.prototype={time:function(e){if(this.enabled){e in this.started&&s("Timer is already running for "+e);this.started[e]=Date.now()}},timeEnd:function(e){if(this.enabled){e in this.started||s("Timer has not been started for "+e);this.times.push({name:e,start:this.started[e],end:Date.now()});delete this.started[e]}},toString:function(){var t,a,r=this.times,i="",n=0;for(t=0,a=r.length;t<a;++t){var s=r[t].name;s.length>n&&(n=s.length)}for(t=0,a=r.length;t<a;++t){var o=r[t],c=o.end-o.start;i+=e(o.name," ",n)+" "+c+"ms\n"}return i}};return t}(),ke=function(e,t){if("undefined"!=typeof Blob)return new Blob([e],{type:t});s('The "Blob" constructor is not supported.')},we=function(){var e="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";return function(t,a,r){if(!r&&"undefined"!=typeof URL&&URL.createObjectURL){var i=ke(t,a);return URL.createObjectURL(i)}for(var n="data:"+a+";base64,",s=0,o=t.length;s<o;s+=3){var c=255&t[s],l=255&t[s+1],h=255&t[s+2],u=c>>2,f=(3&c)<<4|l>>4,d=s+1<o?(15&l)<<2|h>>6:64,g=s+2<o?63&h:64;n+=e[u]+e[f]+e[d]+e[g]}return n}}();_.prototype={on:function(e,t,a){var r=this.actionHandler;r[e]&&c('There is already an actionName called "'+e+'"');r[e]=[t,a]},send:function(e,t,a){var r={sourceName:this.sourceName,targetName:this.targetName,action:e,data:t};this.postMessage(r,a)},sendWithPromise:function(e,t,a){var r=this.callbackIndex++,i={sourceName:this.sourceName,targetName:this.targetName,action:e,data:t,callbackId:r},n=j();this.callbacksCapabilities[r]=n;try{this.postMessage(i,a)}catch(e){n.reject(e)}return n.promise},postMessage:function(e,t){t&&this.postMessageTransfers?this.comObj.postMessage(e,t):this.comObj.postMessage(e)},destroy:function(){this.comObj.removeEventListener("message",this._onComObjOnMessage)}};t.FONT_IDENTITY_MATRIX=G;t.IDENTITY_MATRIX=pe;t.OPS=te;t.VERBOSITY_LEVELS=$;t.UNSUPPORTED_FEATURES=re;t.AnnotationBorderStyleType=J;t.AnnotationFieldFlag=Y;t.AnnotationFlag=K;t.AnnotationType=W;t.FontType=Q;t.ImageKind=V;t.CMapCompressionType=ee;t.InvalidPDFException=oe;t.MessageHandler=_;t.MissingDataException=ue;t.MissingPDFException=ce;t.NotImplementedException=he;t.PageViewport=be;t.PasswordException=ne;t.PasswordResponses=ie;t.StatTimer=ye;t.StreamType=Z;t.TextRenderingMode=X;t.UnexpectedResponseException=le;t.UnknownErrorException=se;t.Util=me;t.XRefParseException=fe;t.arrayByteLength=y;t.arraysToBytes=k;t.assert=h;t.bytesToString=b;t.createBlob=ke;t.createPromiseCapability=j;t.createObjectURL=we;t.deprecated=o;t.error=c;t.getLookupTableFactory=p;t.getVerbosityLevel=i;t.globalScope=H;t.info=n;t.isArray=F;t.isArrayBuffer=q;t.isBool=M;t.isEmptyObj=P;t.isInt=E;t.isNum=L;t.isString=D;t.isSpace=U;t.isNodeJS=N;t.isSameOrigin=u;t.createValidAbsoluteUrl=d;t.isLittleEndian=I;t.isEvalSupported=B;t.loadJpegStream=z;t.log2=C;t.readInt8=x;t.readUint16=S;t.readUint32=A;t.removeNullCharacters=m;t.setVerbosityLevel=r;t.shadow=g;t.string32=w;t.stringToBytes=v;t.stringToPDFString=R;t.stringToUTF8String=T;t.utf8StringToString=O;t.warn=s}).call(t,a(9))},function(e,t,a){"use strict";function r(e){return e===f}function i(e,t){return e instanceof d&&(void 0===t||e.name===t)}function n(e,t){return e instanceof g&&(void 0===t||e.cmd===t)}function s(e,t){return e instanceof p&&(void 0===t||i(e.get("Type"),t))}function o(e){return e instanceof m}function c(e,t){return e.num===t.num&&e.gen===t.gen}function l(e){return"object"==typeof e&&null!==e&&void 0!==e.getBytes}var h=a(0),u=h.isArray,f={},d=function(){function e(e){this.name=e}e.prototype={};var t=Object.create(null);e.get=function(a){var r=t[a];return r||(t[a]=new e(a))};return e}(),g=function(){function e(e){this.cmd=e}e.prototype={};var t=Object.create(null);e.get=function(a){var r=t[a];return r||(t[a]=new e(a))};return e}(),p=function(){function e(e){this.map=Object.create(null);this.xref=e;this.objId=null;this.suppressEncryption=!1;this.__nonSerializable__=t}var t=function(){return t};e.prototype={assignXref:function(e){this.xref=e},get:function(e,t,a){var r,i=this.xref,n=this.suppressEncryption;if(void 0!==(r=this.map[e])||e in this.map||void 0===t)return i?i.fetchIfRef(r,n):r;if(void 0!==(r=this.map[t])||t in this.map||void 0===a)return i?i.fetchIfRef(r,n):r;r=this.map[a]||null;return i?i.fetchIfRef(r,n):r},getAsync:function(e,t,a){var r,i=this.xref,n=this.suppressEncryption;if(void 0!==(r=this.map[e])||e in this.map||void 0===t)return i?i.fetchIfRefAsync(r,n):Promise.resolve(r);if(void 0!==(r=this.map[t])||t in this.map||void 0===a)return i?i.fetchIfRefAsync(r,n):Promise.resolve(r);r=this.map[a]||null;return i?i.fetchIfRefAsync(r,n):Promise.resolve(r)},getArray:function(e,t,a){var r=this.get(e,t,a),i=this.xref,n=this.suppressEncryption;if(!u(r)||!i)return r;r=r.slice();for(var s=0,c=r.length;s<c;s++)o(r[s])&&(r[s]=i.fetch(r[s],n));return r},getRaw:function(e){return this.map[e]},getKeys:function(){return Object.keys(this.map)},set:function(e,t){this.map[e]=t},has:function(e){return e in this.map},forEach:function(e){for(var t in this.map)e(t,this.get(t))}};e.empty=new e(null);e.merge=function(t,a){for(var r=new e(t),i=0,n=a.length;i<n;i++){var o=a[i];if(s(o))for(var c in o.map)r.map[c]||(r.map[c]=o.map[c])}return r};return e}(),m=function(){function e(e,t){this.num=e;this.gen=t}e.prototype={toString:function(){var e=this.num+"R";0!==this.gen&&(e+=this.gen);return e}};return e}(),b=function(){function e(){this.dict=Object.create(null)}e.prototype={has:function(e){return e.toString()in this.dict},put:function(e){this.dict[e.toString()]=!0},remove:function(e){delete this.dict[e.toString()]}};return e}(),v=function(){function e(){this.dict=Object.create(null)}e.prototype={get:function(e){return this.dict[e.toString()]},has:function(e){return e.toString()in this.dict},put:function(e,t){this.dict[e.toString()]=t},putAlias:function(e,t){this.dict[e.toString()]=this.get(t)},forEach:function(e,t){for(var a in this.dict)e.call(t,this.dict[a])},clear:function(){this.dict=Object.create(null)}};return e}();t.EOF=f;t.Cmd=g;t.Dict=p;t.Name=d;t.Ref=m;t.RefSet=b;t.RefSetCache=v;t.isEOF=r;t.isCmd=n;t.isDict=s;t.isName=i;t.isRef=o;t.isRefsEqual=c;t.isStream=l},function(e,t,a){"use strict";var r=a(0),i=a(1),n=a(28),s=a(29),o=a(15),c=r.Util,l=r.error,h=r.info,u=r.isInt,f=r.isArray,d=r.createObjectURL,g=r.shadow,p=r.isSpace,m=i.Dict,b=i.isDict,v=i.isStream,y=n.Jbig2Image,k=s.JpegImage,w=o.JpxImage,C=function(){function e(e,t,a,r){this.bytes=e instanceof Uint8Array?e:new Uint8Array(e);this.start=t||0;this.pos=this.start;this.end=t+a||this.bytes.length;this.dict=r}e.prototype={get length(){return this.end-this.start},get isEmpty(){return 0===this.length},getByte:function(){return this.pos>=this.end?-1:this.bytes[this.pos++]},getUint16:function(){var e=this.getByte(),t=this.getByte();return-1===e||-1===t?-1:(e<<8)+t},getInt32:function(){return(this.getByte()<<24)+(this.getByte()<<16)+(this.getByte()<<8)+this.getByte()},getBytes:function(e){var t=this.bytes,a=this.pos,r=this.end;if(!e)return t.subarray(a,r);var i=a+e;i>r&&(i=r);this.pos=i;return t.subarray(a,i)},peekByte:function(){var e=this.getByte();this.pos--;return e},peekBytes:function(e){var t=this.getBytes(e);this.pos-=t.length;return t},skip:function(e){e||(e=1);this.pos+=e},reset:function(){this.pos=this.start},moveStart:function(){this.start=this.pos},makeSubStream:function(t,a,r){return new e(this.bytes.buffer,t,a,r)}};return e}(),x=function(){function e(e){for(var t=e.length,a=new Uint8Array(t),r=0;r<t;++r)a[r]=e.charCodeAt(r);C.call(this,a)}e.prototype=C.prototype;return e}(),S=function(){function e(e){this.pos=0;this.bufferLength=0;this.eof=!1;this.buffer=t;this.minBufferLength=512;if(e)for(;this.minBufferLength<e;)this.minBufferLength*=2}var t=new Uint8Array(0);e.prototype={get isEmpty(){for(;!this.eof&&0===this.bufferLength;)this.readBlock();return 0===this.bufferLength},ensureBuffer:function(e){var t=this.buffer;if(e<=t.byteLength)return t;for(var a=this.minBufferLength;a<e;)a*=2;var r=new Uint8Array(a);r.set(t);return this.buffer=r},getByte:function(){for(var e=this.pos;this.bufferLength<=e;){if(this.eof)return-1;this.readBlock()}return this.buffer[this.pos++]},getUint16:function(){var e=this.getByte(),t=this.getByte();return-1===e||-1===t?-1:(e<<8)+t},getInt32:function(){return(this.getByte()<<24)+(this.getByte()<<16)+(this.getByte()<<8)+this.getByte()},getBytes:function(e){var t,a=this.pos;if(e){this.ensureBuffer(a+e);t=a+e;for(;!this.eof&&this.bufferLength<t;)this.readBlock();var r=this.bufferLength;t>r&&(t=r)}else{for(;!this.eof;)this.readBlock();t=this.bufferLength}this.pos=t;return this.buffer.subarray(a,t)},peekByte:function(){var e=this.getByte();this.pos--;return e},peekBytes:function(e){var t=this.getBytes(e);this.pos-=t.length;return t},makeSubStream:function(e,t,a){for(var r=e+t;this.bufferLength<=r&&!this.eof;)this.readBlock();return new C(this.buffer,e,t,a)},skip:function(e){e||(e=1);this.pos+=e},reset:function(){this.pos=0},getBaseStreams:function(){return this.str&&this.str.getBaseStreams?this.str.getBaseStreams():[]}};return e}(),A=function(){function e(e){this.streams=e;S.call(this,null)}e.prototype=Object.create(S.prototype);e.prototype.readBlock=function(){var e=this.streams;if(0!==e.length){var t=e.shift(),a=t.getBytes(),r=this.bufferLength,i=r+a.length;this.ensureBuffer(i).set(a,r);this.bufferLength=i}else this.eof=!0};e.prototype.getBaseStreams=function(){for(var e=[],t=0,a=this.streams.length;t<a;t++){var r=this.streams[t];r.getBaseStreams&&c.appendToArray(e,r.getBaseStreams())}return e};return e}(),I=function(){function e(e,t){this.str=e;this.dict=e.dict;var a=e.getByte(),r=e.getByte();-1!==a&&-1!==r||l("Invalid header in flate stream: "+a+", "+r);8!=(15&a)&&l("Unknown compression method in flate stream: "+a+", "+r);((a<<8)+r)%31!=0&&l("Bad FCHECK in flate stream: "+a+", "+r);32&r&&l("FDICT bit set in flate stream: "+a+", "+r);this.codeSize=0;this.codeBuf=0;S.call(this,t)}var t=new Int32Array([16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]),a=new Int32Array([3,4,5,6,7,8,9,10,65547,65549,65551,65553,131091,131095,131099,131103,196643,196651,196659,196667,262211,262227,262243,262259,327811,327843,327875,327907,258,258,258]),r=new Int32Array([1,2,3,4,65541,65543,131081,131085,196625,196633,262177,262193,327745,327777,393345,393409,459009,459137,524801,525057,590849,591361,657409,658433,724993,727041,794625,798721,868353,876545]),i=[new Int32Array([459008,524368,524304,524568,459024,524400,524336,590016,459016,524384,524320,589984,524288,524416,524352,590048,459012,524376,524312,589968,459028,524408,524344,590032,459020,524392,524328,59e4,524296,524424,524360,590064,459010,524372,524308,524572,459026,524404,524340,590024,459018,524388,524324,589992,524292,524420,524356,590056,459014,524380,524316,589976,459030,524412,524348,590040,459022,524396,524332,590008,524300,524428,524364,590072,459009,524370,524306,524570,459025,524402,524338,590020,459017,524386,524322,589988,524290,524418,524354,590052,459013,524378,524314,589972,459029,524410,524346,590036,459021,524394,524330,590004,524298,524426,524362,590068,459011,524374,524310,524574,459027,524406,524342,590028,459019,524390,524326,589996,524294,524422,524358,590060,459015,524382,524318,589980,459031,524414,524350,590044,459023,524398,524334,590012,524302,524430,524366,590076,459008,524369,524305,524569,459024,524401,524337,590018,459016,524385,524321,589986,524289,524417,524353,590050,459012,524377,524313,589970,459028,524409,524345,590034,459020,524393,524329,590002,524297,524425,524361,590066,459010,524373,524309,524573,459026,524405,524341,590026,459018,524389,524325,589994,524293,524421,524357,590058,459014,524381,524317,589978,459030,524413,524349,590042,459022,524397,524333,590010,524301,524429,524365,590074,459009,524371,524307,524571,459025,524403,524339,590022,459017,524387,524323,589990,524291,524419,524355,590054,459013,524379,524315,589974,459029,524411,524347,590038,459021,524395,524331,590006,524299,524427,524363,590070,459011,524375,524311,524575,459027,524407,524343,590030,459019,524391,524327,589998,524295,524423,524359,590062,459015,524383,524319,589982,459031,524415,524351,590046,459023,524399,524335,590014,524303,524431,524367,590078,459008,524368,524304,524568,459024,524400,524336,590017,459016,524384,524320,589985,524288,524416,524352,590049,459012,524376,524312,589969,459028,524408,524344,590033,459020,524392,524328,590001,524296,524424,524360,590065,459010,524372,524308,524572,459026,524404,524340,590025,459018,524388,524324,589993,524292,524420,524356,590057,459014,524380,524316,589977,459030,524412,524348,590041,459022,524396,524332,590009,524300,524428,524364,590073,459009,524370,524306,524570,459025,524402,524338,590021,459017,524386,524322,589989,524290,524418,524354,590053,459013,524378,524314,589973,459029,524410,524346,590037,459021,524394,524330,590005,524298,524426,524362,590069,459011,524374,524310,524574,459027,524406,524342,590029,459019,524390,524326,589997,524294,524422,524358,590061,459015,524382,524318,589981,459031,524414,524350,590045,459023,524398,524334,590013,524302,524430,524366,590077,459008,524369,524305,524569,459024,524401,524337,590019,459016,524385,524321,589987,524289,524417,524353,590051,459012,524377,524313,589971,459028,524409,524345,590035,459020,524393,524329,590003,524297,524425,524361,590067,459010,524373,524309,524573,459026,524405,524341,590027,459018,524389,524325,589995,524293,524421,524357,590059,459014,524381,524317,589979,459030,524413,524349,590043,459022,524397,524333,590011,524301,524429,524365,590075,459009,524371,524307,524571,459025,524403,524339,590023,459017,524387,524323,589991,524291,524419,524355,590055,459013,524379,524315,589975,459029,524411,524347,590039,459021,524395,524331,590007,524299,524427,524363,590071,459011,524375,524311,524575,459027,524407,524343,590031,459019,524391,524327,589999,524295,524423,524359,590063,459015,524383,524319,589983,459031,524415,524351,590047,459023,524399,524335,590015,524303,524431,524367,590079]),9],n=[new Int32Array([327680,327696,327688,327704,327684,327700,327692,327708,327682,327698,327690,327706,327686,327702,327694,0,327681,327697,327689,327705,327685,327701,327693,327709,327683,327699,327691,327707,327687,327703,327695,0]),5];e.prototype=Object.create(S.prototype);e.prototype.getBits=function(e){for(var t,a=this.str,r=this.codeSize,i=this.codeBuf;r<e;){-1===(t=a.getByte())&&l("Bad encoding in flate stream");i|=t<<r;r+=8}t=i&(1<<e)-1;this.codeBuf=i>>e;this.codeSize=r-=e;return t};e.prototype.getCode=function(e){for(var t,a=this.str,r=e[0],i=e[1],n=this.codeSize,s=this.codeBuf;n<i&&-1!==(t=a.getByte());){s|=t<<n;n+=8}var o=r[s&(1<<i)-1],c=o>>16,h=65535&o;(c<1||n<c)&&l("Bad encoding in flate stream");this.codeBuf=s>>c;this.codeSize=n-c;return h};e.prototype.generateHuffmanTable=function(e){var t,a=e.length,r=0;for(t=0;t<a;++t)e[t]>r&&(r=e[t]);for(var i=1<<r,n=new Int32Array(i),s=1,o=0,c=2;s<=r;++s,o<<=1,c<<=1)for(var l=0;l<a;++l)if(e[l]===s){var h=0,u=o;for(t=0;t<s;++t){h=h<<1|1&u;u>>=1}for(t=h;t<i;t+=c)n[t]=s<<16|l;++o}return[n,r]};e.prototype.readBlock=function(){var e,s,o=this.str,c=this.getBits(3);1&c&&(this.eof=!0);c>>=1;if(0!==c){var h,u;if(1===c){h=i;u=n}else if(2===c){var f,d=this.getBits(5)+257,g=this.getBits(5)+1,p=this.getBits(4)+4,m=new Uint8Array(t.length);for(f=0;f<p;++f)m[t[f]]=this.getBits(3);var b=this.generateHuffmanTable(m);s=0;f=0;for(var v,y,k,w=d+g,C=new Uint8Array(w);f<w;){var x=this.getCode(b);if(16===x){v=2;y=3;k=s}else if(17===x){v=3;y=3;k=s=0}else{if(18!==x){C[f++]=s=x;continue}v=7;y=11;k=s=0}for(var S=this.getBits(v)+y;S-- >0;)C[f++]=k}h=this.generateHuffmanTable(C.subarray(0,d));u=this.generateHuffmanTable(C.subarray(d,w))}else l("Unknown block type in flate stream");e=this.buffer;for(var A=e?e.length:0,I=this.bufferLength;;){var B=this.getCode(h);if(B<256){if(I+1>=A){e=this.ensureBuffer(I+1);A=e.length}e[I++]=B}else{if(256===B){this.bufferLength=I;return}B-=257;B=a[B];var R=B>>16;R>0&&(R=this.getBits(R));s=(65535&B)+R;B=this.getCode(u);B=r[B];R=B>>16;R>0&&(R=this.getBits(R));var T=(65535&B)+R;if(I+s>=A){e=this.ensureBuffer(I+s);A=e.length}for(var O=0;O<s;++O,++I)e[I]=e[I-T]}}}else{var P;-1===(P=o.getByte())&&l("Bad block header in flate stream");var M=P;-1===(P=o.getByte())&&l("Bad block header in flate stream");M|=P<<8;-1===(P=o.getByte())&&l("Bad block header in flate stream");var E=P;-1===(P=o.getByte())&&l("Bad block header in flate stream");E|=P<<8;E===(65535&~M)||0===M&&0===E||l("Bad uncompressed block length in flate stream");this.codeBuf=0;this.codeSize=0;var L=this.bufferLength;e=this.ensureBuffer(L+M);var D=L+M;this.bufferLength=D;if(0===M)-1===o.peekByte()&&(this.eof=!0);else for(var F=L;F<D;++F){if(-1===(P=o.getByte())){this.eof=!0;break}e[F]=P}}};return e}(),B=function(){function e(e,t,a){if(!b(a))return e;var r=this.predictor=a.get("Predictor")||1;if(r<=1)return e;2!==r&&(r<10||r>15)&&l("Unsupported predictor: "+r);this.readBlock=2===r?this.readBlockTiff:this.readBlockPng;this.str=e;this.dict=e.dict;var i=this.colors=a.get("Colors")||1,n=this.bits=a.get("BitsPerComponent")||8,s=this.columns=a.get("Columns")||1;this.pixBytes=i*n+7>>3;this.rowBytes=s*i*n+7>>3;S.call(this,t);return this}e.prototype=Object.create(S.prototype)
+;e.prototype.readBlockTiff=function(){var e=this.rowBytes,t=this.bufferLength,a=this.ensureBuffer(t+e),r=this.bits,i=this.colors,n=this.str.getBytes(e);this.eof=!n.length;if(!this.eof){var s,o=0,c=0,l=0,h=0,u=t;if(1===r&&1===i)for(s=0;s<e;++s){var f=n[s]^o;f^=f>>1;f^=f>>2;f^=f>>4;o=(1&f)<<7;a[u++]=f}else if(8===r){for(s=0;s<i;++s)a[u++]=n[s];for(;s<e;++s){a[u]=a[u-i]+n[s];u++}}else{var d=new Uint8Array(i+1),g=(1<<r)-1,p=0,m=t,b=this.columns;for(s=0;s<b;++s)for(var v=0;v<i;++v){if(l<r){o=o<<8|255&n[p++];l+=8}d[v]=d[v]+(o>>l-r)&g;l-=r;c=c<<r|d[v];h+=r;if(h>=8){a[m++]=c>>h-8&255;h-=8}}h>0&&(a[m++]=(c<<8-h)+(o&(1<<8-h)-1))}this.bufferLength+=e}};e.prototype.readBlockPng=function(){var e=this.rowBytes,t=this.pixBytes,a=this.str.getByte(),r=this.str.getBytes(e);this.eof=!r.length;if(!this.eof){var i=this.bufferLength,n=this.ensureBuffer(i+e),s=n.subarray(i-e,i);0===s.length&&(s=new Uint8Array(e));var o,c,h,u=i;switch(a){case 0:for(o=0;o<e;++o)n[u++]=r[o];break;case 1:for(o=0;o<t;++o)n[u++]=r[o];for(;o<e;++o){n[u]=n[u-t]+r[o]&255;u++}break;case 2:for(o=0;o<e;++o)n[u++]=s[o]+r[o]&255;break;case 3:for(o=0;o<t;++o)n[u++]=(s[o]>>1)+r[o];for(;o<e;++o){n[u]=(s[o]+n[u-t]>>1)+r[o]&255;u++}break;case 4:for(o=0;o<t;++o){c=s[o];h=r[o];n[u++]=c+h}for(;o<e;++o){c=s[o];var f=s[o-t],d=n[u-t],g=d+c-f,p=g-d;p<0&&(p=-p);var m=g-c;m<0&&(m=-m);var b=g-f;b<0&&(b=-b);h=r[o];n[u++]=p<=m&&p<=b?d+h:m<=b?c+h:f+h}break;default:l("Unsupported predictor: "+a)}this.bufferLength+=e}};return e}(),R=function(){function e(e,t,a,r){for(var i;-1!==(i=e.getByte());)if(255===i){e.skip(-1);break}this.stream=e;this.maybeLength=t;this.dict=a;this.params=r;S.call(this,t)}e.prototype=Object.create(S.prototype);Object.defineProperty(e.prototype,"bytes",{get:function(){return g(this,"bytes",this.stream.getBytes(this.maybeLength))},configurable:!0});e.prototype.ensureBuffer=function(e){if(!this.bufferLength){var t=new k,a=this.dict.getArray("Decode","D");if(this.forceRGB&&f(a)){for(var r=this.dict.get("BitsPerComponent")||8,i=a.length,n=new Int32Array(i),s=!1,o=(1<<r)-1,c=0;c<i;c+=2){n[c]=256*(a[c+1]-a[c])|0;n[c+1]=a[c]*o|0;256===n[c]&&0===n[c+1]||(s=!0)}s&&(t.decodeTransform=n)}if(b(this.params)){var l=this.params.get("ColorTransform");u(l)&&(t.colorTransform=l)}t.parse(this.bytes);var h=t.getData(this.drawWidth,this.drawHeight,this.forceRGB);this.buffer=h;this.bufferLength=h.length;this.eof=!0}};e.prototype.getBytes=function(e){this.ensureBuffer();return this.buffer};e.prototype.getIR=function(e){return d(this.bytes,"image/jpeg",e)};return e}(),T=function(){function e(e,t,a,r){this.stream=e;this.maybeLength=t;this.dict=a;this.params=r;S.call(this,t)}e.prototype=Object.create(S.prototype);Object.defineProperty(e.prototype,"bytes",{get:function(){return g(this,"bytes",this.stream.getBytes(this.maybeLength))},configurable:!0});e.prototype.ensureBuffer=function(e){if(!this.bufferLength){var t=new w;t.parse(this.bytes);var a=t.width,r=t.height,i=t.componentsCount,n=t.tiles.length;if(1===n)this.buffer=t.tiles[0].items;else{for(var s=new Uint8Array(a*r*i),o=0;o<n;o++)for(var c=t.tiles[o],l=c.width,h=c.height,u=c.left,f=c.top,d=c.items,g=0,p=(a*f+u)*i,m=a*i,b=l*i,v=0;v<h;v++){var y=d.subarray(g,g+b);s.set(y,p);g+=b;p+=m}this.buffer=s}this.bufferLength=this.buffer.length;this.eof=!0}};return e}(),O=function(){function e(e,t,a,r){this.stream=e;this.maybeLength=t;this.dict=a;this.params=r;S.call(this,t)}e.prototype=Object.create(S.prototype);Object.defineProperty(e.prototype,"bytes",{get:function(){return g(this,"bytes",this.stream.getBytes(this.maybeLength))},configurable:!0});e.prototype.ensureBuffer=function(e){if(!this.bufferLength){var t=new y,a=[];if(b(this.params)){var r=this.params.get("JBIG2Globals");if(v(r)){var i=r.getBytes();a.push({data:i,start:0,end:i.length})}}a.push({data:this.bytes,start:0,end:this.bytes.length});for(var n=t.parseChunks(a),s=n.length,o=0;o<s;o++)n[o]^=255;this.buffer=n;this.bufferLength=s;this.eof=!0}};return e}(),P=function(){function e(e,t,a){this.str=e;this.dict=e.dict;this.decrypt=a;this.nextChunk=null;this.initialized=!1;S.call(this,t)}e.prototype=Object.create(S.prototype);e.prototype.readBlock=function(){var e;if(this.initialized)e=this.nextChunk;else{e=this.str.getBytes(512);this.initialized=!0}if(e&&0!==e.length){this.nextChunk=this.str.getBytes(512);var t=this.nextChunk&&this.nextChunk.length>0;e=(0,this.decrypt)(e,!t);var a,r=this.bufferLength,i=e.length,n=this.ensureBuffer(r+i);for(a=0;a<i;a++)n[r++]=e[a];this.bufferLength=r}else this.eof=!0};return e}(),M=function(){function e(e,t){this.str=e;this.dict=e.dict;this.input=new Uint8Array(5);t&&(t*=.8);S.call(this,t)}e.prototype=Object.create(S.prototype);e.prototype.readBlock=function(){for(var e=this.str,t=e.getByte();p(t);)t=e.getByte();if(-1!==t&&126!==t){var a,r,i=this.bufferLength;if(122===t){a=this.ensureBuffer(i+4);for(r=0;r<4;++r)a[i+r]=0;this.bufferLength+=4}else{var n=this.input;n[0]=t;for(r=1;r<5;++r){t=e.getByte();for(;p(t);)t=e.getByte();n[r]=t;if(-1===t||126===t)break}a=this.ensureBuffer(i+r-1);this.bufferLength+=r-1;if(r<5){for(;r<5;++r)n[r]=117;this.eof=!0}var s=0;for(r=0;r<5;++r)s=85*s+(n[r]-33);for(r=3;r>=0;--r){a[i+r]=255&s;s>>=8}}}else this.eof=!0};return e}(),E=function(){function e(e,t){this.str=e;this.dict=e.dict;this.firstDigit=-1;t&&(t*=.5);S.call(this,t)}e.prototype=Object.create(S.prototype);e.prototype.readBlock=function(){var e=this.str.getBytes(8e3);if(e.length){for(var t=e.length+1>>1,a=this.ensureBuffer(this.bufferLength+t),r=this.bufferLength,i=this.firstDigit,n=0,s=e.length;n<s;n++){var o,c=e[n];if(c>=48&&c<=57)o=15&c;else{if(!(c>=65&&c<=70||c>=97&&c<=102)){if(62===c){this.eof=!0;break}continue}o=9+(15&c)}if(i<0)i=o;else{a[r++]=i<<4|o;i=-1}}if(i>=0&&this.eof){a[r++]=i<<4;i=-1}this.firstDigit=i;this.bufferLength=r}else this.eof=!0};return e}(),L=function(){function e(e,t){this.str=e;this.dict=e.dict;S.call(this,t)}e.prototype=Object.create(S.prototype);e.prototype.readBlock=function(){var e=this.str.getBytes(2);if(!e||e.length<2||128===e[0])this.eof=!0;else{var t,a=this.bufferLength,r=e[0];if(r<128){t=this.ensureBuffer(a+r+1);t[a++]=e[1];if(r>0){var i=this.str.getBytes(r);t.set(i,a);a+=r}}else{r=257-r;var n=e[1];t=this.ensureBuffer(a+r+1);for(var s=0;s<r;s++)t[a++]=n}this.bufferLength=a}};return e}(),D=function(){function e(e,t,a){this.str=e;this.dict=e.dict;a=a||m.empty;this.encoding=a.get("K")||0;this.eoline=a.get("EndOfLine")||!1;this.byteAlign=a.get("EncodedByteAlign")||!1;this.columns=a.get("Columns")||1728;this.rows=a.get("Rows")||0;var r=a.get("EndOfBlock");null!==r&&void 0!==r||(r=!0);this.eoblock=r;this.black=a.get("BlackIs1")||!1;this.codingLine=new Uint32Array(this.columns+1);this.refLine=new Uint32Array(this.columns+2);this.codingLine[0]=this.columns;this.codingPos=0;this.row=0;this.nextLine2D=this.encoding<0;this.inputBits=0;this.inputBuf=0;this.outputBits=0;for(var i;0===(i=this.lookBits(12));)this.eatBits(1);1===i&&this.eatBits(12);if(this.encoding>0){this.nextLine2D=!this.lookBits(1);this.eatBits(1)}S.call(this,t)}var t=[[-1,-1],[-1,-1],[7,8],[7,7],[6,6],[6,6],[6,5],[6,5],[4,0],[4,0],[4,0],[4,0],[4,0],[4,0],[4,0],[4,0],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,3],[3,3],[3,3],[3,3],[3,3],[3,3],[3,3],[3,3],[3,3],[3,3],[3,3],[3,3],[3,3],[3,3],[3,3],[3,3],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2]],a=[[-1,-1],[12,-2],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[11,1792],[11,1792],[12,1984],[12,2048],[12,2112],[12,2176],[12,2240],[12,2304],[11,1856],[11,1856],[11,1920],[11,1920],[12,2368],[12,2432],[12,2496],[12,2560]],r=[[-1,-1],[-1,-1],[-1,-1],[-1,-1],[8,29],[8,29],[8,30],[8,30],[8,45],[8,45],[8,46],[8,46],[7,22],[7,22],[7,22],[7,22],[7,23],[7,23],[7,23],[7,23],[8,47],[8,47],[8,48],[8,48],[6,13],[6,13],[6,13],[6,13],[6,13],[6,13],[6,13],[6,13],[7,20],[7,20],[7,20],[7,20],[8,33],[8,33],[8,34],[8,34],[8,35],[8,35],[8,36],[8,36],[8,37],[8,37],[8,38],[8,38],[7,19],[7,19],[7,19],[7,19],[8,31],[8,31],[8,32],[8,32],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,12],[6,12],[6,12],[6,12],[6,12],[6,12],[6,12],[6,12],[8,53],[8,53],[8,54],[8,54],[7,26],[7,26],[7,26],[7,26],[8,39],[8,39],[8,40],[8,40],[8,41],[8,41],[8,42],[8,42],[8,43],[8,43],[8,44],[8,44],[7,21],[7,21],[7,21],[7,21],[7,28],[7,28],[7,28],[7,28],[8,61],[8,61],[8,62],[8,62],[8,63],[8,63],[8,0],[8,0],[8,320],[8,320],[8,384],[8,384],[5,10],[5,10],[5,10],[5,10],[5,10],[5,10],[5,10],[5,10],[5,10],[5,10],[5,10],[5,10],[5,10],[5,10],[5,10],[5,10],[5,11],[5,11],[5,11],[5,11],[5,11],[5,11],[5,11],[5,11],[5,11],[5,11],[5,11],[5,11],[5,11],[5,11],[5,11],[5,11],[7,27],[7,27],[7,27],[7,27],[8,59],[8,59],[8,60],[8,60],[9,1472],[9,1536],[9,1600],[9,1728],[7,18],[7,18],[7,18],[7,18],[7,24],[7,24],[7,24],[7,24],[8,49],[8,49],[8,50],[8,50],[8,51],[8,51],[8,52],[8,52],[7,25],[7,25],[7,25],[7,25],[8,55],[8,55],[8,56],[8,56],[8,57],[8,57],[8,58],[8,58],[6,192],[6,192],[6,192],[6,192],[6,192],[6,192],[6,192],[6,192],[6,1664],[6,1664],[6,1664],[6,1664],[6,1664],[6,1664],[6,1664],[6,1664],[8,448],[8,448],[8,512],[8,512],[9,704],[9,768],[8,640],[8,640],[8,576],[8,576],[9,832],[9,896],[9,960],[9,1024],[9,1088],[9,1152],[9,1216],[9,1280],[9,1344],[9,1408],[7,256],[7,256],[7,256],[7,256],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[5,128],[5,128],[5,128],[5,128],[5,128],[5,128],[5,128],[5,128],[5,128],[5,128],[5,128],[5,128],[5,128],[5,128],[5,128],[5,128],[5,8],[5,8],[5,8],[5,8],[5,8],[5,8],[5,8],[5,8],[5,8],[5,8],[5,8],[5,8],[5,8],[5,8],[5,8],[5,8],[5,9],[5,9],[5,9],[5,9],[5,9],[5,9],[5,9],[5,9],[5,9],[5,9],[5,9],[5,9],[5,9],[5,9],[5,9],[5,9],[6,16],[6,16],[6,16],[6,16],[6,16],[6,16],[6,16],[6,16],[6,17],[6,17],[6,17],[6,17],[6,17],[6,17],[6,17],[6,17],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[6,14],[6,14],[6,14],[6,14],[6,14],[6,14],[6,14],[6,14],[6,15],[6,15],[6,15],[6,15],[6,15],[6,15],[6,15],[6,15],[5,64],[5,64],[5,64],[5,64],[5,64],[5,64],[5,64],[5,64],[5,64],[5,64],[5,64],[5,64],[5,64],[5,64],[5,64],[5,64],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7]],i=[[-1,-1],[-1,-1],[12,-2],[12,-2],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[11,1792],[11,1792],[11,1792],[11,1792],[12,1984],[12,1984],[12,2048],[12,2048],[12,2112],[12,2112],[12,2176],[12,2176],[12,2240],[12,2240],[12,2304],[12,2304],[11,1856],[11,1856],[11,1856],[11,1856],[11,1920],[11,1920],[11,1920],[11,1920],[12,2368],[12,2368],[12,2432],[12,2432],[12,2496],[12,2496],[12,2560],[12,2560],[10,18],[10,18],[10,18],[10,18],[10,18],[10,18],[10,18],[10,18],[12,52],[12,52],[13,640],[13,704],[13,768],[13,832],[12,55],[12,55],[12,56],[12,56],[13,1280],[13,1344],[13,1408],[13,1472],[12,59],[12,59],[12,60],[12,60],[13,1536],[13,1600],[11,24],[11,24],[11,24],[11,24],[11,25],[11,25],[11,25],[11,25],[13,1664],[13,1728],[12,320],[12,320],[12,384],[12,384],[12,448],[12,448],[13,512],[13,576],[12,53],[12,53],[12,54],[12,54],[13,896],[13,960],[13,1024],[13,1088],[13,1152],[13,1216],[10,64],[10,64],[10,64],[10,64],[10,64],[10,64],[10,64],[10,64]],n=[[8,13],[8,13],[8,13],[8,13],[8,13],[8,13],[8,13],[8,13],[8,13],[8,13],[8,13],[8,13],[8,13],[8,13],[8,13],[8,13],[11,23],[11,23],[12,50],[12,51],[12,44],[12,45],[12,46],[12,47],[12,57],[12,58],[12,61],[12,256],[10,16],[10,16],[10,16],[10,16],[10,17],[10,17],[10,17],[10,17],[12,48],[12,49],[12,62],[12,63],[12,30],[12,31],[12,32],[12,33],[12,40],[12,41],[11,22],[11,22],[8,14],[8,14],[8,14],[8,14],[8,14],[8,14],[8,14],[8,14],[8,14],[8,14],[8,14],[8,14],[8,14],[8,14],[8,14],[8,14],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[9,15],[9,15],[9,15],[9,15],[9,15],[9,15],[9,15],[9,15],[12,128],[12,192],[12,26],[12,27],[12,28],[12,29],[11,19],[11,19],[11,20],[11,20],[12,34],[12,35],[12,36],[12,37],[12,38],[12,39],[11,21],[11,21],[12,42],[12,43],[10,0],[10,0],[10,0],[10,0],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12]],s=[[-1,-1],[-1,-1],[-1,-1],[-1,-1],[6,9],[6,8],[5,7],[5,7],[4,6],[4,6],[4,6],[4,6],[4,5],[4,5],[4,5],[4,5],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[2,3],[2,3],[2,3],[2,3],[2,3],[2,3],[2,3],[2,3],[2,3],[2,3],[2,3],[2,3],[2,3],[2,3],[2,3],[2,3],[2,2],[2,2],[2,2],[2,2],[2,2],[2,2],[2,2],[2,2],[2,2],[2,2],[2,2],[2,2],[2,2],[2,2],[2,2],[2,2]];e.prototype=Object.create(S.prototype);e.prototype.readBlock=function(){for(;!this.eof;){var e=this.lookChar();this.ensureBuffer(this.bufferLength+1);this.buffer[this.bufferLength++]=e}};e.prototype.addPixels=function(e,t){var a=this.codingLine,r=this.codingPos;if(e>a[r]){if(e>this.columns){h("row is wrong length");this.err=!0;e=this.columns}1&r^t&&++r;a[r]=e}this.codingPos=r};e.prototype.addPixelsNeg=function(e,t){var a=this.codingLine,r=this.codingPos;if(e>a[r]){if(e>this.columns){h("row is wrong length");this.err=!0;e=this.columns}1&r^t&&++r;a[r]=e}else if(e<a[r]){if(e<0){h("invalid code");this.err=!0;e=0}for(;r>0&&e<a[r-1];)--r;a[r]=e}this.codingPos=r};e.prototype.lookChar=function(){var e,t,a,r,i=this.refLine,n=this.codingLine,s=this.columns;if(0===this.outputBits){if(this.eof)return null;this.err=!1;var o,c,l;if(this.nextLine2D){for(r=0;n[r]<s;++r)i[r]=n[r];i[r++]=s;i[r]=s;n[0]=0;this.codingPos=0;e=0;t=0;for(;n[this.codingPos]<s;){o=this.getTwoDimCode();switch(o){case 0:this.addPixels(i[e+1],t);i[e+1]<s&&(e+=2);break;case 1:o=c=0;if(t){do{o+=l=this.getBlackCode()}while(l>=64);do{c+=l=this.getWhiteCode()}while(l>=64)}else{do{o+=l=this.getWhiteCode()}while(l>=64);do{c+=l=this.getBlackCode()}while(l>=64)}this.addPixels(n[this.codingPos]+o,t);n[this.codingPos]<s&&this.addPixels(n[this.codingPos]+c,1^t);for(;i[e]<=n[this.codingPos]&&i[e]<s;)e+=2;break;case 7:this.addPixels(i[e]+3,t);t^=1;if(n[this.codingPos]<s){++e;for(;i[e]<=n[this.codingPos]&&i[e]<s;)e+=2}break;case 5:this.addPixels(i[e]+2,t);t^=1;if(n[this.codingPos]<s){++e;for(;i[e]<=n[this.codingPos]&&i[e]<s;)e+=2}break;case 3:this.addPixels(i[e]+1,t);t^=1;if(n[this.codingPos]<s){++e;for(;i[e]<=n[this.codingPos]&&i[e]<s;)e+=2}break;case 2:this.addPixels(i[e],t);t^=1;if(n[this.codingPos]<s){++e;for(;i[e]<=n[this.codingPos]&&i[e]<s;)e+=2}break;case 8:this.addPixelsNeg(i[e]-3,t);t^=1;if(n[this.codingPos]<s){e>0?--e:++e;for(;i[e]<=n[this.codingPos]&&i[e]<s;)e+=2}break;case 6:this.addPixelsNeg(i[e]-2,t);t^=1;if(n[this.codingPos]<s){e>0?--e:++e;for(;i[e]<=n[this.codingPos]&&i[e]<s;)e+=2}break;case 4:this.addPixelsNeg(i[e]-1,t);t^=1;if(n[this.codingPos]<s){e>0?--e:++e;for(;i[e]<=n[this.codingPos]&&i[e]<s;)e+=2}break;case-1:this.addPixels(s,0);this.eof=!0;break;default:h("bad 2d code");this.addPixels(s,0);this.err=!0}}}else{n[0]=0;this.codingPos=0;t=0;for(;n[this.codingPos]<s;){o=0;if(t)do{o+=l=this.getBlackCode()}while(l>=64);else do{o+=l=this.getWhiteCode()}while(l>=64);this.addPixels(n[this.codingPos]+o,t);t^=1}}var u=!1;this.byteAlign&&(this.inputBits&=-8);if(this.eoblock||this.row!==this.rows-1){o=this.lookBits(12);if(this.eoline)for(;-1!==o&&1!==o;){this.eatBits(1);o=this.lookBits(12)}else for(;0===o;){this.eatBits(1);o=this.lookBits(12)}if(1===o){this.eatBits(12);u=!0}else-1===o&&(this.eof=!0)}else this.eof=!0;if(!this.eof&&this.encoding>0){this.nextLine2D=!this.lookBits(1);this.eatBits(1)}if(this.eoblock&&u&&this.byteAlign){o=this.lookBits(12);if(1===o){this.eatBits(12);if(this.encoding>0){this.lookBits(1);this.eatBits(1)}if(this.encoding>=0)for(r=0;r<4;++r){o=this.lookBits(12);1!==o&&h("bad rtc code: "+o);this.eatBits(12);if(this.encoding>0){this.lookBits(1);this.eatBits(1)}}this.eof=!0}}else if(this.err&&this.eoline){for(;;){o=this.lookBits(13);if(-1===o){this.eof=!0;return null}if(o>>1==1)break;this.eatBits(1)}this.eatBits(12);if(this.encoding>0){this.eatBits(1);this.nextLine2D=!(1&o)}}n[0]>0?this.outputBits=n[this.codingPos=0]:this.outputBits=n[this.codingPos=1];this.row++}var f;if(this.outputBits>=8){f=1&this.codingPos?0:255;this.outputBits-=8;if(0===this.outputBits&&n[this.codingPos]<s){this.codingPos++;this.outputBits=n[this.codingPos]-n[this.codingPos-1]}}else{a=8;f=0;do{if(this.outputBits>a){f<<=a;1&this.codingPos||(f|=255>>8-a);this.outputBits-=a;a=0}else{f<<=this.outputBits;1&this.codingPos||(f|=255>>8-this.outputBits);a-=this.outputBits;this.outputBits=0;if(n[this.codingPos]<s){this.codingPos++;this.outputBits=n[this.codingPos]-n[this.codingPos-1]}else if(a>0){f<<=a;a=0}}}while(a)}this.black&&(f^=255);return f};e.prototype.findTableCode=function(e,t,a,r){for(var i=r||0,n=e;n<=t;++n){var s=this.lookBits(n);if(-1===s)return[!0,1,!1];n<t&&(s<<=t-n);if(!i||s>=i){var o=a[s-i];if(o[0]===n){this.eatBits(n);return[!0,o[1],!0]}}}return[!1,0,!1]};e.prototype.getTwoDimCode=function(){var e,a=0;if(this.eoblock){a=this.lookBits(7);e=t[a];if(e&&e[0]>0){this.eatBits(e[0]);return e[1]}}else{var r=this.findTableCode(1,7,t);if(r[0]&&r[2])return r[1]}h("Bad two dim code");return-1};e.prototype.getWhiteCode=function(){var e,t=0;if(this.eoblock){t=this.lookBits(12);if(-1===t)return 1;e=t>>5==0?a[t]:r[t>>3];if(e[0]>0){this.eatBits(e[0]);return e[1]}}else{var i=this.findTableCode(1,9,r);if(i[0])return i[1];i=this.findTableCode(11,12,a);if(i[0])return i[1]}h("bad white code");this.eatBits(1);return 1};e.prototype.getBlackCode=function(){var e,t;if(this.eoblock){e=this.lookBits(13);if(-1===e)return 1;t=e>>7==0?i[e]:e>>9==0&&e>>7!=0?n[(e>>1)-64]:s[e>>7];if(t[0]>0){this.eatBits(t[0]);return t[1]}}else{var a=this.findTableCode(2,6,s);if(a[0])return a[1];a=this.findTableCode(7,12,n,64);if(a[0])return a[1];a=this.findTableCode(10,13,i);if(a[0])return a[1]}h("bad black code");this.eatBits(1);return 1};e.prototype.lookBits=function(e){for(var t;this.inputBits<e;){if(-1===(t=this.str.getByte()))return 0===this.inputBits?-1:this.inputBuf<<e-this.inputBits&65535>>16-e;this.inputBuf=this.inputBuf<<8|t;this.inputBits+=8}return this.inputBuf>>this.inputBits-e&65535>>16-e};e.prototype.eatBits=function(e){(this.inputBits-=e)<0&&(this.inputBits=0)};return e}(),F=function(){function e(e,t,a){this.str=e;this.dict=e.dict;this.cachedData=0;this.bitsCached=0;for(var r={earlyChange:a,codeLength:9,nextCode:258,dictionaryValues:new Uint8Array(4096),dictionaryLengths:new Uint16Array(4096),dictionaryPrevCodes:new Uint16Array(4096),currentSequence:new Uint8Array(4096),currentSequenceLength:0},i=0;i<256;++i){r.dictionaryValues[i]=i;r.dictionaryLengths[i]=1}this.lzwState=r;S.call(this,t)}e.prototype=Object.create(S.prototype);e.prototype.readBits=function(e){for(var t=this.bitsCached,a=this.cachedData;t<e;){var r=this.str.getByte();if(-1===r){this.eof=!0;return null}a=a<<8|r;t+=8}this.bitsCached=t-=e;this.cachedData=a;this.lastCode=null;return a>>>t&(1<<e)-1};e.prototype.readBlock=function(){var e,t,a,r=1024,i=this.lzwState;if(i){var n=i.earlyChange,s=i.nextCode,o=i.dictionaryValues,c=i.dictionaryLengths,l=i.dictionaryPrevCodes,h=i.codeLength,u=i.prevCode,f=i.currentSequence,d=i.currentSequenceLength,g=0,p=this.bufferLength,m=this.ensureBuffer(this.bufferLength+r);for(e=0;e<512;e++){var b=this.readBits(h),v=d>0;if(b<256){f[0]=b;d=1}else{if(!(b>=258)){if(256===b){h=9;s=258;d=0;continue}this.eof=!0;delete this.lzwState;break}if(b<s){d=c[b];for(t=d-1,a=b;t>=0;t--){f[t]=o[a];a=l[a]}}else f[d++]=f[0]}if(v){l[s]=u;c[s]=c[u]+1;o[s]=f[0];s++;h=s+n&s+n-1?h:0|Math.min(Math.log(s+n)/.6931471805599453+1,12)}u=b;g+=d;if(r<g){do{r+=512}while(r<g);m=this.ensureBuffer(this.bufferLength+r)}for(t=0;t<d;t++)m[p++]=f[t]}i.nextCode=s;i.codeLength=h;i.prevCode=u;i.currentSequenceLength=d;this.bufferLength=p}};return e}(),q=function(){function e(){C.call(this,new Uint8Array(0))}e.prototype=C.prototype;return e}();t.Ascii85Stream=M;t.AsciiHexStream=E;t.CCITTFaxStream=D;t.DecryptStream=P;t.DecodeStream=S;t.FlateStream=I;t.Jbig2Stream=O;t.JpegStream=R;t.JpxStream=T;t.NullStream=q;t.PredictorStream=B;t.RunLengthStream=L;t.Stream=C;t.StreamsSequenceStream=A;t.StringStream=x;t.LZWStream=F},function(e,t,a){"use strict";var r=a(0),i=a(1),n=a(6),s=r.error,o=r.info,c=r.isArray,l=r.isString,h=r.shadow,u=r.warn,f=i.isDict,d=i.isName,g=i.isStream,p=n.PDFFunction,m=function(){function e(e,t,a,r,i,n,s,o){s=1!==s?0:s;var c,l,h,u,f=a/i,d=r/n,g=0,p=new Uint16Array(i),m=3*a;for(c=0;c<i;c++)p[c]=3*Math.floor(c*f);for(c=0;c<n;c++){h=Math.floor(c*d)*m;for(l=0;l<i;l++){u=h+p[l];o[g++]=e[u++];o[g++]=e[u++];o[g++]=e[u++];g+=s}}}function t(){s("should not call ColorSpace constructor")}t.prototype={getRgb:function(e,t){var a=new Uint8Array(3);this.getRgbItem(e,t,a,0);return a},getRgbItem:function(e,t,a,r){s("Should not call ColorSpace.getRgbItem")},getRgbBuffer:function(e,t,a,r,i,n,o){s("Should not call ColorSpace.getRgbBuffer")},getOutputLength:function(e,t){s("Should not call ColorSpace.getOutputLength")},isPassthrough:function(e){return!1},fillRgb:function(t,a,r,i,n,s,o,c,l){var h,u,f=a*r,d=null,g=1<<o,p=r!==n||a!==i;if(this.isPassthrough(o))d=c;else if(1===this.numComps&&f>g&&"DeviceGray"!==this.name&&"DeviceRGB"!==this.name){var m,b=o<=8?new Uint8Array(g):new Uint16Array(g);for(h=0;h<g;h++)b[h]=h;var v=new Uint8Array(3*g);this.getRgbBuffer(b,0,g,v,0,o,0);var y,k;if(p){d=new Uint8Array(3*f);k=0;for(h=0;h<f;++h){m=3*c[h];d[k++]=v[m];d[k++]=v[m+1];d[k++]=v[m+2]}}else{y=0;for(h=0;h<f;++h){m=3*c[h];t[y++]=v[m];t[y++]=v[m+1];t[y++]=v[m+2];y+=l}}}else if(p){d=new Uint8Array(3*f);this.getRgbBuffer(c,0,f,d,0,o,0)}else this.getRgbBuffer(c,0,i*s,t,0,o,l);if(d)if(p)e(d,o,a,r,i,n,l,t);else{k=0;y=0;for(h=0,u=i*s;h<u;h++){t[y++]=d[k++];t[y++]=d[k++];t[y++]=d[k++];y+=l}}},usesZeroToOneRange:!0};t.parse=function(e,a,r){var i=t.parseToIR(e,a,r);return i instanceof b?i:t.fromIR(i)};t.fromIR=function(e){var a,r,i,n=c(e)?e[0]:e;switch(n){case"DeviceGrayCS":return this.singletons.gray;case"DeviceRgbCS":return this.singletons.rgb;case"DeviceCmykCS":return this.singletons.cmyk;case"CalGrayCS":a=e[1];r=e[2];i=e[3];return new x(a,r,i);case"CalRGBCS":a=e[1];r=e[2];i=e[3];var o=e[4];return new S(a,r,i,o);case"PatternCS":var l=e[1];l&&(l=t.fromIR(l));return new v(l);case"IndexedCS":var h=e[1],u=e[2],f=e[3];return new y(t.fromIR(h),u,f);case"AlternateCS":var d=e[1],g=e[2],m=e[3];return new b(d,t.fromIR(g),p.fromIR(m));case"LabCS":a=e[1];r=e[2];var k=e[3];return new A(a,r,k);default:s("Unknown name "+n)}return null};t.parseToIR=function(e,a,r){if(d(e)){var i=r.get("ColorSpace");if(f(i)){var n=i.get(e.name);n&&(e=n)}}e=a.fetchIfRef(e);if(d(e))switch(e.name){case"DeviceGray":case"G":return"DeviceGrayCS";case"DeviceRGB":case"RGB":return"DeviceRgbCS";case"DeviceCMYK":case"CMYK":return"DeviceCmykCS";case"Pattern":return["PatternCS",null];default:s("unrecognized colorspace "+e.name)}else if(c(e)){var o,l,h,m,b,v,y=a.fetchIfRef(e[0]).name;switch(y){case"DeviceGray":case"G":return"DeviceGrayCS";case"DeviceRGB":case"RGB":return"DeviceRgbCS";case"DeviceCMYK":case"CMYK":return"DeviceCmykCS";case"CalGray":l=a.fetchIfRef(e[1]);m=l.getArray("WhitePoint");b=l.getArray("BlackPoint");v=l.get("Gamma");return["CalGrayCS",m,b,v];case"CalRGB":l=a.fetchIfRef(e[1]);m=l.getArray("WhitePoint");b=l.getArray("BlackPoint");v=l.getArray("Gamma");var k=l.getArray("Matrix");return["CalRGBCS",m,b,v,k];case"ICCBased":var w=a.fetchIfRef(e[1]),C=w.dict;o=C.get("N");h=C.get("Alternate");if(h){var x=t.parseToIR(h,a,r),S=t.fromIR(x);if(S.numComps===o)return x;u("ICCBased color space: Ignoring incorrect /Alternate entry.")}if(1===o)return"DeviceGrayCS";if(3===o)return"DeviceRgbCS";if(4===o)return"DeviceCmykCS";break;case"Pattern":var A=e[1]||null;A&&(A=t.parseToIR(A,a,r));return["PatternCS",A];case"Indexed":case"I":var I=t.parseToIR(e[1],a,r),B=a.fetchIfRef(e[2])+1,R=a.fetchIfRef(e[3]);g(R)&&(R=R.getBytes());return["IndexedCS",I,B,R];case"Separation":case"DeviceN":var T=a.fetchIfRef(e[1]);o=c(T)?T.length:1;h=t.parseToIR(e[2],a,r);var O=p.getIR(a,a.fetchIfRef(e[3]));return["AlternateCS",o,h,O];case"Lab":l=a.fetchIfRef(e[1]);m=l.getArray("WhitePoint");b=l.getArray("BlackPoint");var P=l.getArray("Range");return["LabCS",m,b,P];default:s('unimplemented color space object "'+y+'"')}}else s('unrecognized color space object: "'+e+'"');return null};t.isDefaultDecode=function(e,t){if(!c(e))return!0;if(2*t!==e.length){u("The decode map is not the correct length");return!0}for(var a=0,r=e.length;a<r;a+=2)if(0!==e[a]||1!==e[a+1])return!1;return!0};t.singletons={get gray(){return h(this,"gray",new k)},get rgb(){return h(this,"rgb",new w)},get cmyk(){return h(this,"cmyk",new C)}};return t}(),b=function(){function e(e,t,a){this.name="Alternate";this.numComps=e;this.defaultColor=new Float32Array(e);for(var r=0;r<e;++r)this.defaultColor[r]=1;this.base=t;this.tintFn=a;this.tmpBuf=new Float32Array(t.numComps)}e.prototype={getRgb:m.prototype.getRgb,getRgbItem:function(e,t,a,r){var i=this.tmpBuf;this.tintFn(e,t,i,0);this.base.getRgbItem(i,0,a,r)},getRgbBuffer:function(e,t,a,r,i,n,s){var o,c,l=this.tintFn,h=this.base,u=1/((1<<n)-1),f=h.numComps,d=h.usesZeroToOneRange,g=(h.isPassthrough(8)||!d)&&0===s,p=g?i:0,m=g?r:new Uint8Array(f*a),b=this.numComps,v=new Float32Array(b),y=new Float32Array(f);for(o=0;o<a;o++){for(c=0;c<b;c++)v[c]=e[t++]*u;l(v,0,y,0);if(d)for(c=0;c<f;c++)m[p++]=255*y[c];else{h.getRgbItem(y,0,m,p);p+=f}}g||h.getRgbBuffer(m,0,a,r,i,8,s)},getOutputLength:function(e,t){return this.base.getOutputLength(e*this.base.numComps/this.numComps,t)},isPassthrough:m.prototype.isPassthrough,fillRgb:m.prototype.fillRgb,isDefaultDecode:function(e){return m.isDefaultDecode(e,this.numComps)},usesZeroToOneRange:!0};return e}(),v=function(){function e(e){this.name="Pattern";this.base=e}e.prototype={};return e}(),y=function(){function e(e,t,a){this.name="Indexed";this.numComps=1;this.defaultColor=new Uint8Array(this.numComps);this.base=e;this.highVal=t;var r=e.numComps,i=r*t;if(g(a)){this.lookup=new Uint8Array(i);var n=a.getBytes(i);this.lookup.set(n)}else if(l(a)){this.lookup=new Uint8Array(i);for(var o=0;o<i;++o)this.lookup[o]=a.charCodeAt(o)}else a instanceof Uint8Array||a instanceof Array?this.lookup=a:s("Unrecognized lookup table: "+a)}e.prototype={getRgb:m.prototype.getRgb,getRgbItem:function(e,t,a,r){var i=this.base.numComps,n=e[t]*i;this.base.getRgbItem(this.lookup,n,a,r)},getRgbBuffer:function(e,t,a,r,i,n,s){for(var o=this.base,c=o.numComps,l=o.getOutputLength(c,s),h=this.lookup,u=0;u<a;++u){var f=e[t++]*c;o.getRgbBuffer(h,f,1,r,i,8,s);i+=l}},getOutputLength:function(e,t){return this.base.getOutputLength(e*this.base.numComps,t)},isPassthrough:m.prototype.isPassthrough,fillRgb:m.prototype.fillRgb,isDefaultDecode:function(e){return!0},usesZeroToOneRange:!0};return e}(),k=function(){function e(){this.name="DeviceGray";this.numComps=1;this.defaultColor=new Float32Array(this.numComps)}e.prototype={getRgb:m.prototype.getRgb,getRgbItem:function(e,t,a,r){var i=255*e[t]|0;i=i<0?0:i>255?255:i;a[r]=a[r+1]=a[r+2]=i},getRgbBuffer:function(e,t,a,r,i,n,s){for(var o=255/((1<<n)-1),c=t,l=i,h=0;h<a;++h){var u=o*e[c++]|0;r[l++]=u;r[l++]=u;r[l++]=u;l+=s}},getOutputLength:function(e,t){return e*(3+t)},isPassthrough:m.prototype.isPassthrough,fillRgb:m.prototype.fillRgb,isDefaultDecode:function(e){return m.isDefaultDecode(e,this.numComps)},usesZeroToOneRange:!0};return e}(),w=function(){function e(){this.name="DeviceRGB";this.numComps=3;this.defaultColor=new Float32Array(this.numComps)}e.prototype={getRgb:m.prototype.getRgb,getRgbItem:function(e,t,a,r){var i=255*e[t]|0,n=255*e[t+1]|0,s=255*e[t+2]|0;a[r]=i<0?0:i>255?255:i;a[r+1]=n<0?0:n>255?255:n;a[r+2]=s<0?0:s>255?255:s},getRgbBuffer:function(e,t,a,r,i,n,s){if(8!==n||0!==s)for(var o=255/((1<<n)-1),c=t,l=i,h=0;h<a;++h){r[l++]=o*e[c++]|0;r[l++]=o*e[c++]|0;r[l++]=o*e[c++]|0;l+=s}else r.set(e.subarray(t,t+3*a),i)},getOutputLength:function(e,t){return e*(3+t)/3|0},isPassthrough:function(e){return 8===e},fillRgb:m.prototype.fillRgb,isDefaultDecode:function(e){return m.isDefaultDecode(e,this.numComps)},usesZeroToOneRange:!0};return e}(),C=function(){function e(e,t,a,r,i){var n=e[t+0]*a,s=e[t+1]*a,o=e[t+2]*a,c=e[t+3]*a,l=n*(-4.387332384609988*n+54.48615194189176*s+18.82290502165302*o+212.25662451639585*c-285.2331026137004)+s*(1.7149763477362134*s-5.6096736904047315*o+-17.873870861415444*c-5.497006427196366)+o*(-2.5217340131683033*o-21.248923337353073*c+17.5119270841813)+c*(-21.86122147463605*c-189.48180835922747)+255|0,h=n*(8.841041422036149*n+60.118027045597366*s+6.871425592049007*o+31.159100130055922*c-79.2970844816548)+s*(-15.310361306967817*s+17.575251261109482*o+131.35250912493976*c-190.9453302588951)+o*(4.444339102852739*o+9.8632861493405*c-24.86741582555878)+c*(-20.737325471181034*c-187.80453709719578)+255|0,u=n*(.8842522430003296*n+8.078677503112928*s+30.89978309703729*o-.23883238689178934*c-14.183576799673286)+s*(10.49593273432072*s+63.02378494754052*o+50.606957656360734*c-112.23884253719248)+o*(.03296041114873217*o+115.60384449646641*c-193.58209356861505)+c*(-22.33816807309886*c-180.12613974708367)+255|0;r[i]=l>255?255:l<0?0:l;r[i+1]=h>255?255:h<0?0:h;r[i+2]=u>255?255:u<0?0:u}function t(){this.name="DeviceCMYK";this.numComps=4;this.defaultColor=new Float32Array(this.numComps);this.defaultColor[3]=1}t.prototype={getRgb:m.prototype.getRgb,getRgbItem:function(t,a,r,i){e(t,a,1,r,i)},getRgbBuffer:function(t,a,r,i,n,s,o){for(var c=1/((1<<s)-1),l=0;l<r;l++){e(t,a,c,i,n);a+=4;n+=3+o}},getOutputLength:function(e,t){return e/4*(3+t)|0},isPassthrough:m.prototype.isPassthrough,fillRgb:m.prototype.fillRgb,isDefaultDecode:function(e){return m.isDefaultDecode(e,this.numComps)},usesZeroToOneRange:!0};return t}(),x=function(){function e(e,t,a){this.name="CalGray";this.numComps=1;this.defaultColor=new Float32Array(this.numComps);e||s("WhitePoint missing - required for color space CalGray");t=t||[0,0,0];a=a||1;this.XW=e[0];this.YW=e[1];this.ZW=e[2];this.XB=t[0];this.YB=t[1];this.ZB=t[2];this.G=a;(this.XW<0||this.ZW<0||1!==this.YW)&&s("Invalid WhitePoint components for "+this.name+", no fallback available");if(this.XB<0||this.YB<0||this.ZB<0){
+o("Invalid BlackPoint for "+this.name+", falling back to default");this.XB=this.YB=this.ZB=0}0===this.XB&&0===this.YB&&0===this.ZB||u(this.name+", BlackPoint: XB: "+this.XB+", YB: "+this.YB+", ZB: "+this.ZB+", only default values are supported.");if(this.G<1){o("Invalid Gamma: "+this.G+" for "+this.name+", falling back to default");this.G=1}}function t(e,t,a,r,i,n){var s=t[a]*n,o=Math.pow(s,e.G),c=e.YW*o,l=0|Math.max(295.8*Math.pow(c,.3333333333333333)-40.8,0);r[i]=l;r[i+1]=l;r[i+2]=l}e.prototype={getRgb:m.prototype.getRgb,getRgbItem:function(e,a,r,i){t(this,e,a,r,i,1)},getRgbBuffer:function(e,a,r,i,n,s,o){for(var c=1/((1<<s)-1),l=0;l<r;++l){t(this,e,a,i,n,c);a+=1;n+=3+o}},getOutputLength:function(e,t){return e*(3+t)},isPassthrough:m.prototype.isPassthrough,fillRgb:m.prototype.fillRgb,isDefaultDecode:function(e){return m.isDefaultDecode(e,this.numComps)},usesZeroToOneRange:!0};return e}(),S=function(){function e(e,t,a,r){this.name="CalRGB";this.numComps=3;this.defaultColor=new Float32Array(this.numComps);e||s("WhitePoint missing - required for color space CalRGB");t=t||new Float32Array(3);a=a||new Float32Array([1,1,1]);r=r||new Float32Array([1,0,0,0,1,0,0,0,1]);var i=e[0],n=e[1],c=e[2];this.whitePoint=e;var l=t[0],h=t[1],u=t[2];this.blackPoint=t;this.GR=a[0];this.GG=a[1];this.GB=a[2];this.MXA=r[0];this.MYA=r[1];this.MZA=r[2];this.MXB=r[3];this.MYB=r[4];this.MZB=r[5];this.MXC=r[6];this.MYC=r[7];this.MZC=r[8];(i<0||c<0||1!==n)&&s("Invalid WhitePoint components for "+this.name+", no fallback available");if(l<0||h<0||u<0){o("Invalid BlackPoint for "+this.name+" ["+l+", "+h+", "+u+"], falling back to default");this.blackPoint=new Float32Array(3)}if(this.GR<0||this.GG<0||this.GB<0){o("Invalid Gamma ["+this.GR+", "+this.GG+", "+this.GB+"] for "+this.name+", falling back to default");this.GR=this.GG=this.GB=1}if(this.MXA<0||this.MYA<0||this.MZA<0||this.MXB<0||this.MYB<0||this.MZB<0||this.MXC<0||this.MYC<0||this.MZC<0){o("Invalid Matrix for "+this.name+" ["+this.MXA+", "+this.MYA+", "+this.MZA+this.MXB+", "+this.MYB+", "+this.MZB+this.MXC+", "+this.MYC+", "+this.MZC+"], falling back to default");this.MXA=this.MYB=this.MZC=1;this.MXB=this.MYA=this.MZA=this.MXC=this.MYC=this.MZB=0}}function t(e,t,a){a[0]=e[0]*t[0]+e[1]*t[1]+e[2]*t[2];a[1]=e[3]*t[0]+e[4]*t[1]+e[5]*t[2];a[2]=e[6]*t[0]+e[7]*t[1]+e[8]*t[2]}function a(e,t,a){a[0]=1*t[0]/e[0];a[1]=1*t[1]/e[1];a[2]=1*t[2]/e[2]}function r(e,t,a){a[0]=.95047*t[0]/e[0];a[1]=1*t[1]/e[1];a[2]=1.08883*t[2]/e[2]}function i(e){return e<=.0031308?n(0,1,12.92*e):n(0,1,1.055*Math.pow(e,1/2.4)-.055)}function n(e,t,a){return Math.max(e,Math.min(t,a))}function c(e){return e<0?-c(-e):e>8?Math.pow((e+16)/116,3):e*w}function l(e,t,a){if(0!==e[0]||0!==e[1]||0!==e[2]){var r=c(0),i=r,n=c(e[0]),s=r,o=c(e[1]),l=r,h=c(e[2]),u=(1-i)/(1-n),f=1-u,d=(1-s)/(1-o),g=1-d,p=(1-l)/(1-h),m=1-p;a[0]=t[0]*u+f;a[1]=t[1]*d+g;a[2]=t[2]*p+m}else{a[0]=t[0];a[1]=t[1];a[2]=t[2]}}function h(e,r,i){if(1!==e[0]||1!==e[2]){var n=i;t(d,r,n);var s=v;a(e,n,s);t(g,s,i)}else{i[0]=r[0];i[1]=r[1];i[2]=r[2]}}function u(e,a,i){var n=i;t(d,a,n);var s=v;r(e,n,s);t(g,s,i)}function f(e,a,r,s,o,c){var f=n(0,1,a[r]*c),d=n(0,1,a[r+1]*c),g=n(0,1,a[r+2]*c),m=Math.pow(f,e.GR),v=Math.pow(d,e.GG),w=Math.pow(g,e.GB),C=e.MXA*m+e.MXB*v+e.MXC*w,x=e.MYA*m+e.MYB*v+e.MYC*w,S=e.MZA*m+e.MZB*v+e.MZC*w,A=y;A[0]=C;A[1]=x;A[2]=S;var I=k;h(e.whitePoint,A,I);var B=y;l(e.blackPoint,I,B);var R=k;u(b,B,R);var T=y;t(p,R,T);var O=i(T[0]),P=i(T[1]),M=i(T[2]);s[o]=Math.round(255*O);s[o+1]=Math.round(255*P);s[o+2]=Math.round(255*M)}var d=new Float32Array([.8951,.2664,-.1614,-.7502,1.7135,.0367,.0389,-.0685,1.0296]),g=new Float32Array([.9869929,-.1470543,.1599627,.4323053,.5183603,.0492912,-.0085287,.0400428,.9684867]),p=new Float32Array([3.2404542,-1.5371385,-.4985314,-.969266,1.8760108,.041556,.0556434,-.2040259,1.0572252]),b=new Float32Array([1,1,1]),v=new Float32Array(3),y=new Float32Array(3),k=new Float32Array(3),w=Math.pow(24/116,3)/8;e.prototype={getRgb:function(e,t){var a=new Uint8Array(3);this.getRgbItem(e,t,a,0);return a},getRgbItem:function(e,t,a,r){f(this,e,t,a,r,1)},getRgbBuffer:function(e,t,a,r,i,n,s){for(var o=1/((1<<n)-1),c=0;c<a;++c){f(this,e,t,r,i,o);t+=3;i+=3+s}},getOutputLength:function(e,t){return e*(3+t)/3|0},isPassthrough:m.prototype.isPassthrough,fillRgb:m.prototype.fillRgb,isDefaultDecode:function(e){return m.isDefaultDecode(e,this.numComps)},usesZeroToOneRange:!0};return e}(),A=function(){function e(e,t,a){this.name="Lab";this.numComps=3;this.defaultColor=new Float32Array(this.numComps);e||s("WhitePoint missing - required for color space Lab");t=t||[0,0,0];a=a||[-100,100,-100,100];this.XW=e[0];this.YW=e[1];this.ZW=e[2];this.amin=a[0];this.amax=a[1];this.bmin=a[2];this.bmax=a[3];this.XB=t[0];this.YB=t[1];this.ZB=t[2];(this.XW<0||this.ZW<0||1!==this.YW)&&s("Invalid WhitePoint components, no fallback available");if(this.XB<0||this.YB<0||this.ZB<0){o("Invalid BlackPoint, falling back to default");this.XB=this.YB=this.ZB=0}if(this.amin>this.amax||this.bmin>this.bmax){o("Invalid Range, falling back to defaults");this.amin=-100;this.amax=100;this.bmin=-100;this.bmax=100}}function t(e){var t;t=e>=6/29?e*e*e:108/841*(e-4/29);return t}function a(e,t,a,r){return a+e*(r-a)/t}function r(e,r,i,n,s,o){var c=r[i],l=r[i+1],h=r[i+2];if(!1!==n){c=a(c,n,0,100);l=a(l,n,e.amin,e.amax);h=a(h,n,e.bmin,e.bmax)}l=l>e.amax?e.amax:l<e.amin?e.amin:l;h=h>e.bmax?e.bmax:h<e.bmin?e.bmin:h;var u,f,d,g=(c+16)/116,p=g+l/500,m=g-h/200,b=e.XW*t(p),v=e.YW*t(g),y=e.ZW*t(m);if(e.ZW<1){u=3.1339*b+-1.617*v+-.4906*y;f=-.9785*b+1.916*v+.0333*y;d=.072*b+-.229*v+1.4057*y}else{u=3.2406*b+-1.5372*v+-.4986*y;f=-.9689*b+1.8758*v+.0415*y;d=.0557*b+-.204*v+1.057*y}s[o]=u<=0?0:u>=1?255:255*Math.sqrt(u)|0;s[o+1]=f<=0?0:f>=1?255:255*Math.sqrt(f)|0;s[o+2]=d<=0?0:d>=1?255:255*Math.sqrt(d)|0}e.prototype={getRgb:m.prototype.getRgb,getRgbItem:function(e,t,a,i){r(this,e,t,!1,a,i)},getRgbBuffer:function(e,t,a,i,n,s,o){for(var c=(1<<s)-1,l=0;l<a;l++){r(this,e,t,c,i,n);t+=3;n+=3+o}},getOutputLength:function(e,t){return e*(3+t)/3|0},isPassthrough:m.prototype.isPassthrough,fillRgb:m.prototype.fillRgb,isDefaultDecode:function(e){return!0},usesZeroToOneRange:!1};return e}();t.ColorSpace=m},function(e,t,a){"use strict";function r(e){switch(e){case"WinAnsiEncoding":return c;case"StandardEncoding":return o;case"MacRomanEncoding":return s;case"SymbolSetEncoding":return l;case"ZapfDingbatsEncoding":return h;case"ExpertEncoding":return i;case"MacExpertEncoding":return n;default:return null}}var i=["","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","space","exclamsmall","Hungarumlautsmall","","dollaroldstyle","dollarsuperior","ampersandsmall","Acutesmall","parenleftsuperior","parenrightsuperior","twodotenleader","onedotenleader","comma","hyphen","period","fraction","zerooldstyle","oneoldstyle","twooldstyle","threeoldstyle","fouroldstyle","fiveoldstyle","sixoldstyle","sevenoldstyle","eightoldstyle","nineoldstyle","colon","semicolon","commasuperior","threequartersemdash","periodsuperior","questionsmall","","asuperior","bsuperior","centsuperior","dsuperior","esuperior","","","isuperior","","","lsuperior","msuperior","nsuperior","osuperior","","","rsuperior","ssuperior","tsuperior","","ff","fi","fl","ffi","ffl","parenleftinferior","","parenrightinferior","Circumflexsmall","hyphensuperior","Gravesmall","Asmall","Bsmall","Csmall","Dsmall","Esmall","Fsmall","Gsmall","Hsmall","Ismall","Jsmall","Ksmall","Lsmall","Msmall","Nsmall","Osmall","Psmall","Qsmall","Rsmall","Ssmall","Tsmall","Usmall","Vsmall","Wsmall","Xsmall","Ysmall","Zsmall","colonmonetary","onefitted","rupiah","Tildesmall","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","exclamdownsmall","centoldstyle","Lslashsmall","","","Scaronsmall","Zcaronsmall","Dieresissmall","Brevesmall","Caronsmall","","Dotaccentsmall","","","Macronsmall","","","figuredash","hypheninferior","","","Ogoneksmall","Ringsmall","Cedillasmall","","","","onequarter","onehalf","threequarters","questiondownsmall","oneeighth","threeeighths","fiveeighths","seveneighths","onethird","twothirds","","","zerosuperior","onesuperior","twosuperior","threesuperior","foursuperior","fivesuperior","sixsuperior","sevensuperior","eightsuperior","ninesuperior","zeroinferior","oneinferior","twoinferior","threeinferior","fourinferior","fiveinferior","sixinferior","seveninferior","eightinferior","nineinferior","centinferior","dollarinferior","periodinferior","commainferior","Agravesmall","Aacutesmall","Acircumflexsmall","Atildesmall","Adieresissmall","Aringsmall","AEsmall","Ccedillasmall","Egravesmall","Eacutesmall","Ecircumflexsmall","Edieresissmall","Igravesmall","Iacutesmall","Icircumflexsmall","Idieresissmall","Ethsmall","Ntildesmall","Ogravesmall","Oacutesmall","Ocircumflexsmall","Otildesmall","Odieresissmall","OEsmall","Oslashsmall","Ugravesmall","Uacutesmall","Ucircumflexsmall","Udieresissmall","Yacutesmall","Thornsmall","Ydieresissmall"],n=["","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","space","exclamsmall","Hungarumlautsmall","centoldstyle","dollaroldstyle","dollarsuperior","ampersandsmall","Acutesmall","parenleftsuperior","parenrightsuperior","twodotenleader","onedotenleader","comma","hyphen","period","fraction","zerooldstyle","oneoldstyle","twooldstyle","threeoldstyle","fouroldstyle","fiveoldstyle","sixoldstyle","sevenoldstyle","eightoldstyle","nineoldstyle","colon","semicolon","","threequartersemdash","","questionsmall","","","","","Ethsmall","","","onequarter","onehalf","threequarters","oneeighth","threeeighths","fiveeighths","seveneighths","onethird","twothirds","","","","","","","ff","fi","fl","ffi","ffl","parenleftinferior","","parenrightinferior","Circumflexsmall","hypheninferior","Gravesmall","Asmall","Bsmall","Csmall","Dsmall","Esmall","Fsmall","Gsmall","Hsmall","Ismall","Jsmall","Ksmall","Lsmall","Msmall","Nsmall","Osmall","Psmall","Qsmall","Rsmall","Ssmall","Tsmall","Usmall","Vsmall","Wsmall","Xsmall","Ysmall","Zsmall","colonmonetary","onefitted","rupiah","Tildesmall","","","asuperior","centsuperior","","","","","Aacutesmall","Agravesmall","Acircumflexsmall","Adieresissmall","Atildesmall","Aringsmall","Ccedillasmall","Eacutesmall","Egravesmall","Ecircumflexsmall","Edieresissmall","Iacutesmall","Igravesmall","Icircumflexsmall","Idieresissmall","Ntildesmall","Oacutesmall","Ogravesmall","Ocircumflexsmall","Odieresissmall","Otildesmall","Uacutesmall","Ugravesmall","Ucircumflexsmall","Udieresissmall","","eightsuperior","fourinferior","threeinferior","sixinferior","eightinferior","seveninferior","Scaronsmall","","centinferior","twoinferior","","Dieresissmall","","Caronsmall","osuperior","fiveinferior","","commainferior","periodinferior","Yacutesmall","","dollarinferior","","Thornsmall","","nineinferior","zeroinferior","Zcaronsmall","AEsmall","Oslashsmall","questiondownsmall","oneinferior","Lslashsmall","","","","","","","Cedillasmall","","","","","","OEsmall","figuredash","hyphensuperior","","","","","exclamdownsmall","","Ydieresissmall","","onesuperior","twosuperior","threesuperior","foursuperior","fivesuperior","sixsuperior","sevensuperior","ninesuperior","zerosuperior","","esuperior","rsuperior","tsuperior","","","isuperior","ssuperior","dsuperior","","","","","","lsuperior","Ogoneksmall","Brevesmall","Macronsmall","bsuperior","nsuperior","msuperior","commasuperior","periodsuperior","Dotaccentsmall","Ringsmall"],s=["","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","space","exclam","quotedbl","numbersign","dollar","percent","ampersand","quotesingle","parenleft","parenright","asterisk","plus","comma","hyphen","period","slash","zero","one","two","three","four","five","six","seven","eight","nine","colon","semicolon","less","equal","greater","question","at","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","bracketleft","backslash","bracketright","asciicircum","underscore","grave","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","braceleft","bar","braceright","asciitilde","","Adieresis","Aring","Ccedilla","Eacute","Ntilde","Odieresis","Udieresis","aacute","agrave","acircumflex","adieresis","atilde","aring","ccedilla","eacute","egrave","ecircumflex","edieresis","iacute","igrave","icircumflex","idieresis","ntilde","oacute","ograve","ocircumflex","odieresis","otilde","uacute","ugrave","ucircumflex","udieresis","dagger","degree","cent","sterling","section","bullet","paragraph","germandbls","registered","copyright","trademark","acute","dieresis","notequal","AE","Oslash","infinity","plusminus","lessequal","greaterequal","yen","mu","partialdiff","summation","product","pi","integral","ordfeminine","ordmasculine","Omega","ae","oslash","questiondown","exclamdown","logicalnot","radical","florin","approxequal","Delta","guillemotleft","guillemotright","ellipsis","space","Agrave","Atilde","Otilde","OE","oe","endash","emdash","quotedblleft","quotedblright","quoteleft","quoteright","divide","lozenge","ydieresis","Ydieresis","fraction","currency","guilsinglleft","guilsinglright","fi","fl","daggerdbl","periodcentered","quotesinglbase","quotedblbase","perthousand","Acircumflex","Ecircumflex","Aacute","Edieresis","Egrave","Iacute","Icircumflex","Idieresis","Igrave","Oacute","Ocircumflex","apple","Ograve","Uacute","Ucircumflex","Ugrave","dotlessi","circumflex","tilde","macron","breve","dotaccent","ring","cedilla","hungarumlaut","ogonek","caron"],o=["","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","space","exclam","quotedbl","numbersign","dollar","percent","ampersand","quoteright","parenleft","parenright","asterisk","plus","comma","hyphen","period","slash","zero","one","two","three","four","five","six","seven","eight","nine","colon","semicolon","less","equal","greater","question","at","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","bracketleft","backslash","bracketright","asciicircum","underscore","quoteleft","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","braceleft","bar","braceright","asciitilde","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","exclamdown","cent","sterling","fraction","yen","florin","section","currency","quotesingle","quotedblleft","guillemotleft","guilsinglleft","guilsinglright","fi","fl","","endash","dagger","daggerdbl","periodcentered","","paragraph","bullet","quotesinglbase","quotedblbase","quotedblright","guillemotright","ellipsis","perthousand","","questiondown","","grave","acute","circumflex","tilde","macron","breve","dotaccent","dieresis","","ring","cedilla","","hungarumlaut","ogonek","caron","emdash","","","","","","","","","","","","","","","","","AE","","ordfeminine","","","","","Lslash","Oslash","OE","ordmasculine","","","","","","ae","","","","dotlessi","","","lslash","oslash","oe","germandbls"],c=["","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","space","exclam","quotedbl","numbersign","dollar","percent","ampersand","quotesingle","parenleft","parenright","asterisk","plus","comma","hyphen","period","slash","zero","one","two","three","four","five","six","seven","eight","nine","colon","semicolon","less","equal","greater","question","at","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","bracketleft","backslash","bracketright","asciicircum","underscore","grave","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","braceleft","bar","braceright","asciitilde","bullet","Euro","bullet","quotesinglbase","florin","quotedblbase","ellipsis","dagger","daggerdbl","circumflex","perthousand","Scaron","guilsinglleft","OE","bullet","Zcaron","bullet","bullet","quoteleft","quoteright","quotedblleft","quotedblright","bullet","endash","emdash","tilde","trademark","scaron","guilsinglright","oe","bullet","zcaron","Ydieresis","space","exclamdown","cent","sterling","currency","yen","brokenbar","section","dieresis","copyright","ordfeminine","guillemotleft","logicalnot","hyphen","registered","macron","degree","plusminus","twosuperior","threesuperior","acute","mu","paragraph","periodcentered","cedilla","onesuperior","ordmasculine","guillemotright","onequarter","onehalf","threequarters","questiondown","Agrave","Aacute","Acircumflex","Atilde","Adieresis","Aring","AE","Ccedilla","Egrave","Eacute","Ecircumflex","Edieresis","Igrave","Iacute","Icircumflex","Idieresis","Eth","Ntilde","Ograve","Oacute","Ocircumflex","Otilde","Odieresis","multiply","Oslash","Ugrave","Uacute","Ucircumflex","Udieresis","Yacute","Thorn","germandbls","agrave","aacute","acircumflex","atilde","adieresis","aring","ae","ccedilla","egrave","eacute","ecircumflex","edieresis","igrave","iacute","icircumflex","idieresis","eth","ntilde","ograve","oacute","ocircumflex","otilde","odieresis","divide","oslash","ugrave","uacute","ucircumflex","udieresis","yacute","thorn","ydieresis"],l=["","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","space","exclam","universal","numbersign","existential","percent","ampersand","suchthat","parenleft","parenright","asteriskmath","plus","comma","minus","period","slash","zero","one","two","three","four","five","six","seven","eight","nine","colon","semicolon","less","equal","greater","question","congruent","Alpha","Beta","Chi","Delta","Epsilon","Phi","Gamma","Eta","Iota","theta1","Kappa","Lambda","Mu","Nu","Omicron","Pi","Theta","Rho","Sigma","Tau","Upsilon","sigma1","Omega","Xi","Psi","Zeta","bracketleft","therefore","bracketright","perpendicular","underscore","radicalex","alpha","beta","chi","delta","epsilon","phi","gamma","eta","iota","phi1","kappa","lambda","mu","nu","omicron","pi","theta","rho","sigma","tau","upsilon","omega1","omega","xi","psi","zeta","braceleft","bar","braceright","similar","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Euro","Upsilon1","minute","lessequal","fraction","infinity","florin","club","diamond","heart","spade","arrowboth","arrowleft","arrowup","arrowright","arrowdown","degree","plusminus","second","greaterequal","multiply","proportional","partialdiff","bullet","divide","notequal","equivalence","approxequal","ellipsis","arrowvertex","arrowhorizex","carriagereturn","aleph","Ifraktur","Rfraktur","weierstrass","circlemultiply","circleplus","emptyset","intersection","union","propersuperset","reflexsuperset","notsubset","propersubset","reflexsubset","element","notelement","angle","gradient","registerserif","copyrightserif","trademarkserif","product","radical","dotmath","logicalnot","logicaland","logicalor","arrowdblboth","arrowdblleft","arrowdblup","arrowdblright","arrowdbldown","lozenge","angleleft","registersans","copyrightsans","trademarksans","summation","parenlefttp","parenleftex","parenleftbt","bracketlefttp","bracketleftex","bracketleftbt","bracelefttp","braceleftmid","braceleftbt","braceex","","angleright","integral","integraltp","integralex","integralbt","parenrighttp","parenrightex","parenrightbt","bracketrighttp","bracketrightex","bracketrightbt","bracerighttp","bracerightmid","bracerightbt"],h=["","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","space","a1","a2","a202","a3","a4","a5","a119","a118","a117","a11","a12","a13","a14","a15","a16","a105","a17","a18","a19","a20","a21","a22","a23","a24","a25","a26","a27","a28","a6","a7","a8","a9","a10","a29","a30","a31","a32","a33","a34","a35","a36","a37","a38","a39","a40","a41","a42","a43","a44","a45","a46","a47","a48","a49","a50","a51","a52","a53","a54","a55","a56","a57","a58","a59","a60","a61","a62","a63","a64","a65","a66","a67","a68","a69","a70","a71","a72","a73","a74","a203","a75","a204","a76","a77","a78","a79","a81","a82","a83","a84","a97","a98","a99","a100","","a89","a90","a93","a94","a91","a92","a205","a85","a206","a86","a87","a88","a95","a96","","","","","","","","","","","","","","","","","","","","a101","a102","a103","a104","a106","a107","a108","a112","a111","a110","a109","a120","a121","a122","a123","a124","a125","a126","a127","a128","a129","a130","a131","a132","a133","a134","a135","a136","a137","a138","a139","a140","a141","a142","a143","a144","a145","a146","a147","a148","a149","a150","a151","a152","a153","a154","a155","a156","a157","a158","a159","a160","a161","a163","a164","a196","a165","a192","a166","a167","a168","a169","a170","a171","a172","a173","a162","a174","a175","a176","a177","a178","a179","a193","a180","a199","a181","a200","a182","","a201","a183","a184","a197","a185","a194","a198","a186","a195","a187","a188","a189","a190","a191"];t.WinAnsiEncoding=c;t.StandardEncoding=o;t.MacRomanEncoding=s;t.SymbolSetEncoding=l;t.ZapfDingbatsEncoding=h;t.ExpertEncoding=i;t.getEncoding=r},function(e,t,a){"use strict";var r=a(0),i=a(1),n=a(2),s=r.MissingDataException,o=r.StreamType,c=r.assert,l=r.error,h=r.info,u=r.isArray,f=r.isInt,d=r.isNum,g=r.isString,p=r.warn,m=i.EOF,b=i.Cmd,v=i.Dict,y=i.Name,k=i.Ref,w=i.isEOF,C=i.isCmd,x=i.isDict,S=i.isName,A=n.Ascii85Stream,I=n.AsciiHexStream,B=n.CCITTFaxStream,R=n.FlateStream,T=n.Jbig2Stream,O=n.JpegStream,P=n.JpxStream,M=n.LZWStream,E=n.NullStream,L=n.PredictorStream,D=n.RunLengthStream,F=function(){function e(e,t,a,r){this.lexer=e;this.allowStreams=t;this.xref=a;this.recoveryMode=r||!1;this.imageCache=Object.create(null);this.refill()}e.prototype={refill:function(){this.buf1=this.lexer.getObj();this.buf2=this.lexer.getObj()},shift:function(){if(C(this.buf2,"ID")){this.buf1=this.buf2;this.buf2=null}else{this.buf1=this.buf2;this.buf2=this.lexer.getObj()}},tryShift:function(){try{this.shift();return!0}catch(e){if(e instanceof s)throw e;return!1}},getObj:function(e){var t=this.buf1;this.shift();if(t instanceof b)switch(t.cmd){case"BI":return this.makeInlineImage(e);case"[":for(var a=[];!C(this.buf1,"]")&&!w(this.buf1);)a.push(this.getObj(e));if(w(this.buf1)){this.recoveryMode||l("End of file inside array");return a}this.shift();return a;case"<<":for(var r=new v(this.xref);!C(this.buf1,">>")&&!w(this.buf1);)if(S(this.buf1)){var i=this.buf1.name;this.shift();if(w(this.buf1))break;r.set(i,this.getObj(e))}else{h("Malformed dictionary: key must be a name object");this.shift()}if(w(this.buf1)){this.recoveryMode||l("End of file inside dictionary");return r}if(C(this.buf2,"stream"))return this.allowStreams?this.makeStream(r,e):r;this.shift();return r;default:return t}if(f(t)){var n=t;if(f(this.buf1)&&C(this.buf2,"R")){var s=new k(n,this.buf1);this.shift();this.shift();return s}return n}if(g(t)){var o=t;e&&(o=e.decryptString(o));return o}return t},findDefaultInlineStreamEnd:function(e){for(var t,a,r,i,n=e.pos,s=0;-1!==(t=e.getByte());)if(0===s)s=69===t?1:0;else if(1===s)s=73===t?2:0;else{c(2===s);if(32===t||10===t||13===t){r=5;i=e.peekBytes(r);for(a=0;a<r;a++){t=i[a];if(10!==t&&13!==t&&(t<32||t>127)){s=0;break}}if(2===s)break}else s=0}return e.pos-4-n},findDCTDecodeInlineStreamEnd:function(e){for(var t,a,r,i=e.pos,n=!1;-1!==(t=e.getByte());)if(255===t){switch(e.getByte()){case 0:break;case 255:e.skip(-1);break;case 217:n=!0;break;case 192:case 193:case 194:case 195:case 197:case 198:case 199:case 201:case 202:case 203:case 205:case 206:case 207:case 196:case 204:case 218:case 219:case 220:case 221:case 222:case 223:case 224:case 225:case 226:case 227:case 228:case 229:case 230:case 231:case 232:case 233:case 234:case 235:case 236:case 237:case 238:case 239:case 254:a=e.getUint16();a>2?e.skip(a-2):e.skip(-2)}if(n)break}r=e.pos-i;if(-1===t){p("Inline DCTDecode image stream: EOI marker not found, searching for /EI/ instead.");e.skip(-r);return this.findDefaultInlineStreamEnd(e)}this.inlineStreamSkipEI(e);return r},findASCII85DecodeInlineStreamEnd:function(e){for(var t,a,r=e.pos;-1!==(t=e.getByte());)if(126===t&&62===e.peekByte()){e.skip();break}a=e.pos-r;if(-1===t){p("Inline ASCII85Decode image stream: EOD marker not found, searching for /EI/ instead.");e.skip(-a);return this.findDefaultInlineStreamEnd(e)}this.inlineStreamSkipEI(e);return a},findASCIIHexDecodeInlineStreamEnd:function(e){for(var t,a,r=e.pos;-1!==(t=e.getByte())&&62!==t;);a=e.pos-r;if(-1===t){p("Inline ASCIIHexDecode image stream: EOD marker not found, searching for /EI/ instead.");e.skip(-a);return this.findDefaultInlineStreamEnd(e)}this.inlineStreamSkipEI(e);return a},inlineStreamSkipEI:function(e){for(var t,a=0;-1!==(t=e.getByte());)if(0===a)a=69===t?1:0;else if(1===a)a=73===t?2:0;else if(2===a)break},makeInlineImage:function(e){for(var t=this.lexer,a=t.stream,r=new v(this.xref);!C(this.buf1,"ID")&&!w(this.buf1);){S(this.buf1)||l("Dictionary key must be a name object");var i=this.buf1.name;this.shift();if(w(this.buf1))break;r.set(i,this.getObj(e))}var n,s=r.get("Filter","F");if(S(s))n=s.name;else if(u(s)){var o=this.xref.fetchIfRef(s[0]);S(o)&&(n=o.name)}var c,h,f,d=a.pos;c="DCTDecode"===n||"DCT"===n?this.findDCTDecodeInlineStreamEnd(a):"ASCII85Decide"===n||"A85"===n?this.findASCII85DecodeInlineStreamEnd(a):"ASCIIHexDecode"===n||"AHx"===n?this.findASCIIHexDecodeInlineStreamEnd(a):this.findDefaultInlineStreamEnd(a);var g,p=a.makeSubStream(d,c,r);if(c<1e3){var m=p.getBytes();p.reset();var y=1,k=0;for(h=0,f=m.length;h<f;++h){y+=255&m[h];k+=y}g=k%65521<<16|y%65521;if(this.imageCache.adler32===g){this.buf2=b.get("EI");this.shift();this.imageCache[g].reset();return this.imageCache[g]}}e&&(p=e.createStream(p,c));p=this.filter(p,r,c);p.dict=r;if(void 0!==g){p.cacheKey="inline_"+c+"_"+g;this.imageCache[g]=p}this.buf2=b.get("EI");this.shift();return p},makeStream:function(e,t){var a=this.lexer,r=a.stream;a.skipToNextLine();var i=r.pos-1,n=e.get("Length");if(!f(n)){h("Bad "+n+" attribute in stream");n=0}r.pos=i+n;a.nextChar();if(this.tryShift()&&C(this.buf2,"endstream"))this.shift();else{r.pos=i;for(var s,o,c=[101,110,100,115,116,114,101,97,109],u=0,d=!1;r.pos<r.end;){var g=r.peekBytes(2048),p=g.length-9;if(p<=0)break;d=!1;s=0;for(;s<p;){o=0;for(;o<9&&g[s+o]===c[o];)o++;if(o>=9){d=!0;break}s++}if(d){u+=s;r.pos+=s;break}u+=p;r.pos+=p}d||l("Missing endstream");n=u;a.nextChar();this.shift();this.shift()}this.shift();r=r.makeSubStream(i,n,e);t&&(r=t.createStream(r,n));r=this.filter(r,e,n);r.dict=e;return r},filter:function(e,t,a){var r=t.get("Filter","F"),i=t.get("DecodeParms","DP");if(S(r)){u(i)&&(i=this.xref.fetchIfRef(i[0]));return this.makeFilter(e,r.name,a,i)}var n=a;if(u(r))for(var s=r,o=i,c=0,h=s.length;c<h;++c){r=this.xref.fetchIfRef(s[c]);S(r)||l("Bad filter name: "+r);i=null;u(o)&&c in o&&(i=this.xref.fetchIfRef(o[c]));e=this.makeFilter(e,r.name,n,i);n=null}return e},makeFilter:function(e,t,a,r){if(0===a){p('Empty "'+t+'" stream.');return new E(e)}try{var i=this.xref.stats.streamTypes;if("FlateDecode"===t||"Fl"===t){i[o.FLATE]=!0;return r?new L(new R(e,a),a,r):new R(e,a)}if("LZWDecode"===t||"LZW"===t){i[o.LZW]=!0;var n=1;if(r){r.has("EarlyChange")&&(n=r.get("EarlyChange"));return new L(new M(e,a,n),a,r)}return new M(e,a,n)}if("DCTDecode"===t||"DCT"===t){i[o.DCT]=!0;return new O(e,a,e.dict,r)}if("JPXDecode"===t||"JPX"===t){i[o.JPX]=!0;return new P(e,a,e.dict,r)}if("ASCII85Decode"===t||"A85"===t){i[o.A85]=!0;return new A(e,a)}if("ASCIIHexDecode"===t||"AHx"===t){i[o.AHX]=!0;return new I(e,a)}if("CCITTFaxDecode"===t||"CCF"===t){i[o.CCF]=!0;return new B(e,a,r)}if("RunLengthDecode"===t||"RL"===t){i[o.RL]=!0;return new D(e,a)}if("JBIG2Decode"===t){i[o.JBIG]=!0;return new T(e,a,e.dict,r)}p('filter "'+t+'" not supported yet');return e}catch(t){if(t instanceof s)throw t;p('Invalid stream: "'+t+'"');return new E(e)}}};return e}(),q=function(){function e(e,t){this.stream=e;this.nextChar();this.strBuf=[];this.knownCommands=t}function t(e){return e>=48&&e<=57?15&e:e>=65&&e<=70||e>=97&&e<=102?9+(15&e):-1}var a=[1,0,0,0,0,0,0,0,0,1,1,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,2,0,0,2,2,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,2,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];e.prototype={nextChar:function(){return this.currentChar=this.stream.getByte()},peekChar:function(){return this.stream.peekByte()},getNumber:function(){var e=this.currentChar,t=!1,a=0,r=1;if(45===e){r=-1;e=this.nextChar();45===e&&(e=this.nextChar())}else 43===e&&(e=this.nextChar());if(46===e){a=10;e=this.nextChar()}if(e<48||e>57){l("Invalid number: "+String.fromCharCode(e));return 0}for(var i=e-48,n=0,s=1;(e=this.nextChar())>=0;)if(48<=e&&e<=57){var o=e-48;if(t)n=10*n+o;else{0!==a&&(a*=10);i=10*i+o}}else if(46===e){if(0!==a)break;a=1}else if(45===e)p("Badly formatted number");else{if(69!==e&&101!==e)break;e=this.peekChar();if(43===e||45===e){s=45===e?-1:1;this.nextChar()}else if(e<48||e>57)break;t=!0}0!==a&&(i/=a);t&&(i*=Math.pow(10,s*n));return r*i},getString:function(){var e=1,t=!1,a=this.strBuf;a.length=0;for(var r=this.nextChar();;){var i=!1;switch(0|r){case-1:p("Unterminated string");t=!0;break;case 40:++e;a.push("(");break;case 41:if(0==--e){this.nextChar();t=!0}else a.push(")");break;case 92:r=this.nextChar();switch(r){case-1:p("Unterminated string");t=!0;break;case 110:a.push("\n");break;case 114:a.push("\r");break;case 116:a.push("\t");break;case 98:a.push("\b");break;case 102:a.push("\f");break;case 92:case 40:case 41:a.push(String.fromCharCode(r));break;case 48:case 49:case 50:case 51:case 52:case 53:case 54:case 55:var n=15&r;r=this.nextChar();i=!0;if(r>=48&&r<=55){n=(n<<3)+(15&r);r=this.nextChar();if(r>=48&&r<=55){i=!1;n=(n<<3)+(15&r)}}a.push(String.fromCharCode(n));break;case 13:10===this.peekChar()&&this.nextChar();break;case 10:break;default:a.push(String.fromCharCode(r))}break;default:a.push(String.fromCharCode(r))}if(t)break;i||(r=this.nextChar())}return a.join("")},getName:function(){var e,r,i=this.strBuf;i.length=0;for(;(e=this.nextChar())>=0&&!a[e];)if(35===e){e=this.nextChar();if(a[e]){p("Lexer_getName: NUMBER SIGN (#) should be followed by a hexadecimal number.");i.push("#");break}var n=t(e);if(-1!==n){r=e;e=this.nextChar();var s=t(e);if(-1===s){p("Lexer_getName: Illegal digit ("+String.fromCharCode(e)+") in hexadecimal number.");i.push("#",String.fromCharCode(r));if(a[e])break;i.push(String.fromCharCode(e));continue}i.push(String.fromCharCode(n<<4|s))}else i.push("#",String.fromCharCode(e))}else i.push(String.fromCharCode(e));i.length>127&&p("name token is longer than allowed by the spec: "+i.length);return y.get(i.join(""))},getHexString:function(){var e=this.strBuf;e.length=0;for(var r,i,n=this.currentChar,s=!0;;){if(n<0){p("Unterminated hex string");break}if(62===n){this.nextChar();break}if(1!==a[n]){if(s){r=t(n);if(-1===r){p('Ignoring invalid character "'+n+'" in hex string');n=this.nextChar();continue}}else{i=t(n);if(-1===i){p('Ignoring invalid character "'+n+'" in hex string');n=this.nextChar();continue}e.push(String.fromCharCode(r<<4|i))}s=!s;n=this.nextChar()}else n=this.nextChar()}return e.join("")},getObj:function(){for(var e=!1,t=this.currentChar;;){if(t<0)return m;if(e)10!==t&&13!==t||(e=!1);else if(37===t)e=!0;else if(1!==a[t])break;t=this.nextChar()}switch(0|t){case 48:case 49:case 50:case 51:case 52:case 53:case 54:case 55:case 56:case 57:case 43:case 45:case 46:return this.getNumber();case 40:return this.getString();case 47:return this.getName();case 91:this.nextChar();return b.get("[");case 93:this.nextChar();return b.get("]");case 60:t=this.nextChar();if(60===t){this.nextChar();return b.get("<<")}return this.getHexString();case 62:t=this.nextChar();if(62===t){this.nextChar();return b.get(">>")}
+return b.get(">");case 123:this.nextChar();return b.get("{");case 125:this.nextChar();return b.get("}");case 41:this.nextChar();l("Illegal character: "+t)}for(var r=String.fromCharCode(t),i=this.knownCommands,n=i&&void 0!==i[r];(t=this.nextChar())>=0&&!a[t];){var s=r+String.fromCharCode(t);if(n&&void 0===i[s])break;128===r.length&&l("Command token too long: "+r.length);r=s;n=i&&void 0!==i[r]}return"true"===r||"false"!==r&&("null"===r?null:b.get(r))},skipToNextLine:function(){for(var e=this.currentChar;e>=0;){if(13===e){e=this.nextChar();10===e&&this.nextChar();break}if(10===e){this.nextChar();break}e=this.nextChar()}}};return e}(),U={create:function(e){function t(e,t){var a=c.get(e);if(f(a)&&(t?a>=0:a>0))return a;throw new Error('The "'+e+'" parameter in the linearization dictionary is invalid.')}var a,r,i=new F(new q(e),!1,null),n=i.getObj(),s=i.getObj(),o=i.getObj(),c=i.getObj();if(!(f(n)&&f(s)&&C(o,"obj")&&x(c)&&d(a=c.get("Linearized"))&&a>0))return null;if((r=t("L"))!==e.length)throw new Error('The "L" parameter in the linearization dictionary does not equal the stream length.');return{length:r,hints:function(){var e,t,a=c.get("H");if(u(a)&&(2===(e=a.length)||4===e)){for(var r=0;r<e;r++)if(!(f(t=a[r])&&t>0))throw new Error("Hint ("+r+") in the linearization dictionary is invalid.");return a}throw new Error("Hint array in the linearization dictionary is invalid.")}(),objectNumberFirst:t("O"),endFirst:t("E"),numPages:t("N"),mainXRefEntriesOffset:t("T"),pageFirst:c.has("P")?t("P",!0):0}}};t.Lexer=q;t.Linearization=U;t.Parser=F},function(e,t,a){"use strict";function r(e){var t;if("object"!=typeof e)return!1;if(u(e))t=e;else{if(!f(e))return!1;t=e.dict}return t.has("FunctionType")}var i=a(0),n=a(1),s=a(34),o=i.error,c=i.info,l=i.isArray,h=i.isBool,u=n.isDict,f=n.isStream,d=s.PostScriptLexer,g=s.PostScriptParser,p=function(){return{getSampleArray:function(e,t,a,r){var i,n,s=1;for(i=0,n=e.length;i<n;i++)s*=e[i];s*=t;var o=new Array(s),c=0,l=0,h=1/(Math.pow(2,a)-1),u=r.getBytes((s*a+7)/8),f=0;for(i=0;i<s;i++){for(;c<a;){l<<=8;l|=u[f++];c+=8}c-=a;o[i]=(l>>c)*h;l&=(1<<c)-1}return o},getIR:function(e,t){var a=t.dict;a||(a=t);var r=[this.constructSampled,null,this.constructInterpolated,this.constructStiched,this.constructPostScript],i=a.get("FunctionType"),n=r[i];n||o("Unknown type of function");return n.call(this,t,a,e)},fromIR:function(e){switch(e[0]){case 0:return this.constructSampledFromIR(e);case 2:return this.constructInterpolatedFromIR(e);case 3:return this.constructStichedFromIR(e);default:return this.constructPostScriptFromIR(e)}},parse:function(e,t){var a=this.getIR(e,t);return this.fromIR(a)},parseArray:function(e,t){if(!l(t))return this.parse(e,t);for(var a=[],r=0,i=t.length;r<i;r++){var n=e.fetchIfRef(t[r]);a.push(p.parse(e,n))}return function(e,t,r,i){for(var n=0,s=a.length;n<s;n++)a[n](e,t,r,i+n)}},constructSampled:function(e,t){function a(e){for(var t=e.length,a=[],r=0,i=0;i<t;i+=2){a[r]=[e[i],e[i+1]];++r}return a}var r=t.getArray("Domain"),i=t.getArray("Range");r&&i||o("No domain or range");var n=r.length/2,s=i.length/2;r=a(r);i=a(i);var l=t.get("Size"),h=t.get("BitsPerSample"),u=t.get("Order")||1;1!==u&&c("No support for cubic spline interpolation: "+u);var f=t.getArray("Encode");if(!f){f=[];for(var d=0;d<n;++d){f.push(0);f.push(l[d]-1)}}f=a(f);var g=t.getArray("Decode");g=g?a(g):i;return[0,n,r,f,g,this.getSampleArray(l,s,h,e),l,s,Math.pow(2,h)-1,i]},constructSampledFromIR:function(e){function t(e,t,a,r,i){return r+(i-r)/(a-t)*(e-t)}return function(a,r,i,n){var s,o,c=e[1],l=e[2],h=e[3],u=e[4],f=e[5],d=e[6],g=e[7],p=e[9],m=1<<c,b=new Float64Array(m),v=new Uint32Array(m);for(o=0;o<m;o++)b[o]=1;var y=g,k=1;for(s=0;s<c;++s){var w=l[s][0],C=l[s][1],x=Math.min(Math.max(a[r+s],w),C),S=t(x,w,C,h[s][0],h[s][1]),A=d[s];S=Math.min(Math.max(S,0),A-1);var I=S<A-1?Math.floor(S):S-1,B=I+1-S,R=S-I,T=I*y,O=T+y;for(o=0;o<m;o++)if(o&k){b[o]*=R;v[o]+=O}else{b[o]*=B;v[o]+=T}y*=A;k<<=1}for(o=0;o<g;++o){var P=0;for(s=0;s<m;s++)P+=f[v[s]+o]*b[s];P=t(P,0,1,u[o][0],u[o][1]);i[n+o]=Math.min(Math.max(P,p[o][0]),p[o][1])}}},constructInterpolated:function(e,t){var a=t.getArray("C0")||[0],r=t.getArray("C1")||[1],i=t.get("N");l(a)&&l(r)||o("Illegal dictionary for interpolated function");for(var n=a.length,s=[],c=0;c<n;++c)s.push(r[c]-a[c]);return[2,a,s,i]},constructInterpolatedFromIR:function(e){var t=e[1],a=e[2],r=e[3],i=a.length;return function(e,n,s,o){for(var c=1===r?e[n]:Math.pow(e[n],r),l=0;l<i;++l)s[o+l]=t[l]+c*a[l]}},constructStiched:function(e,t,a){var r=t.getArray("Domain");r||o("No domain");1!=r.length/2&&o("Bad domain for stiched function");for(var i=t.get("Functions"),n=[],s=0,c=i.length;s<c;++s)n.push(p.getIR(a,a.fetchIfRef(i[s])));return[3,r,t.getArray("Bounds"),t.getArray("Encode"),n]},constructStichedFromIR:function(e){for(var t=e[1],a=e[2],r=e[3],i=e[4],n=[],s=new Float32Array(1),o=0,c=i.length;o<c;o++)n.push(p.fromIR(i[o]));return function(e,i,o,c){for(var l=function(e,t,a){e>a?e=a:e<t&&(e=t);return e}(e[i],t[0],t[1]),h=0,u=a.length;h<u&&!(l<a[h]);++h);var f=t[0];h>0&&(f=a[h-1]);var d=t[1];h<a.length&&(d=a[h]);var g=r[2*h],p=r[2*h+1];s[0]=f===d?g:g+(l-f)*(p-g)/(d-f);n[h](s,0,o,c)}},constructPostScript:function(e,t,a){var r=t.getArray("Domain"),i=t.getArray("Range");r||o("No domain.");i||o("No range.");var n=new d(e);return[4,r,i,new g(n).parse()]},constructPostScriptFromIR:function(e){var t=e[1],a=e[2],r=e[3],i=(new v).compile(r,t,a);if(i)return new Function("src","srcOffset","dest","destOffset",i);c("Unable to compile PS function");var n=a.length>>1,s=t.length>>1,o=new b(r),l=Object.create(null),h=8192,u=new Float32Array(s);return function(e,t,r,i){var c,f,d="",g=u;for(c=0;c<s;c++){f=e[t+c];g[c]=f;d+=f+"_"}var p=l[d];if(void 0===p){var m=new Float32Array(n),b=o.execute(g),v=b.length-n;for(c=0;c<n;c++){f=b[v+c];var y=a[2*c];if(f<y)f=y;else{y=a[2*c+1];f>y&&(f=y)}m[c]=f}if(h>0){h--;l[d]=m}r.set(m,i)}else r.set(p,i)}}}}(),m=function(){function e(e){this.stack=e?Array.prototype.slice.call(e,0):[]}e.prototype={push:function(e){this.stack.length>=100&&o("PostScript function stack overflow.");this.stack.push(e)},pop:function(){this.stack.length<=0&&o("PostScript function stack underflow.");return this.stack.pop()},copy:function(e){this.stack.length+e>=100&&o("PostScript function stack overflow.");for(var t=this.stack,a=t.length-e,r=e-1;r>=0;r--,a++)t.push(t[a])},index:function(e){this.push(this.stack[this.stack.length-e-1])},roll:function(e,t){var a,r,i,n=this.stack,s=n.length-e,o=n.length-1,c=s+(t-Math.floor(t/e)*e);for(a=s,r=o;a<r;a++,r--){i=n[a];n[a]=n[r];n[r]=i}for(a=s,r=c-1;a<r;a++,r--){i=n[a];n[a]=n[r];n[r]=i}for(a=c,r=o;a<r;a++,r--){i=n[a];n[a]=n[r];n[r]=i}}};return e}(),b=function(){function e(e){this.operators=e}e.prototype={execute:function(e){for(var t,a,r,i=new m(e),n=0,s=this.operators,c=s.length;n<c;){t=s[n++];if("number"!=typeof t)switch(t){case"jz":r=i.pop();a=i.pop();a||(n=r);break;case"j":a=i.pop();n=a;break;case"abs":a=i.pop();i.push(Math.abs(a));break;case"add":r=i.pop();a=i.pop();i.push(a+r);break;case"and":r=i.pop();a=i.pop();h(a)&&h(r)?i.push(a&&r):i.push(a&r);break;case"atan":a=i.pop();i.push(Math.atan(a));break;case"bitshift":r=i.pop();a=i.pop();a>0?i.push(a<<r):i.push(a>>r);break;case"ceiling":a=i.pop();i.push(Math.ceil(a));break;case"copy":a=i.pop();i.copy(a);break;case"cos":a=i.pop();i.push(Math.cos(a));break;case"cvi":a=0|i.pop();i.push(a);break;case"cvr":break;case"div":r=i.pop();a=i.pop();i.push(a/r);break;case"dup":i.copy(1);break;case"eq":r=i.pop();a=i.pop();i.push(a===r);break;case"exch":i.roll(2,1);break;case"exp":r=i.pop();a=i.pop();i.push(Math.pow(a,r));break;case"false":i.push(!1);break;case"floor":a=i.pop();i.push(Math.floor(a));break;case"ge":r=i.pop();a=i.pop();i.push(a>=r);break;case"gt":r=i.pop();a=i.pop();i.push(a>r);break;case"idiv":r=i.pop();a=i.pop();i.push(a/r|0);break;case"index":a=i.pop();i.index(a);break;case"le":r=i.pop();a=i.pop();i.push(a<=r);break;case"ln":a=i.pop();i.push(Math.log(a));break;case"log":a=i.pop();i.push(Math.log(a)/Math.LN10);break;case"lt":r=i.pop();a=i.pop();i.push(a<r);break;case"mod":r=i.pop();a=i.pop();i.push(a%r);break;case"mul":r=i.pop();a=i.pop();i.push(a*r);break;case"ne":r=i.pop();a=i.pop();i.push(a!==r);break;case"neg":a=i.pop();i.push(-a);break;case"not":a=i.pop();h(a)?i.push(!a):i.push(~a);break;case"or":r=i.pop();a=i.pop();h(a)&&h(r)?i.push(a||r):i.push(a|r);break;case"pop":i.pop();break;case"roll":r=i.pop();a=i.pop();i.roll(a,r);break;case"round":a=i.pop();i.push(Math.round(a));break;case"sin":a=i.pop();i.push(Math.sin(a));break;case"sqrt":a=i.pop();i.push(Math.sqrt(a));break;case"sub":r=i.pop();a=i.pop();i.push(a-r);break;case"true":i.push(!0);break;case"truncate":a=i.pop();a=a<0?Math.ceil(a):Math.floor(a);i.push(a);break;case"xor":r=i.pop();a=i.pop();h(a)&&h(r)?i.push(a!==r):i.push(a^r);break;default:o("Unknown operator "+t)}else i.push(t)}return i.stack}};return e}(),v=function(){function e(e){this.type=e}function t(t,a,r){e.call(this,"args");this.index=t;this.min=a;this.max=r}function a(t){e.call(this,"literal");this.number=t;this.min=t;this.max=t}function r(t,a,r,i,n){e.call(this,"binary");this.op=t;this.arg1=a;this.arg2=r;this.min=i;this.max=n}function i(t,a){e.call(this,"max");this.arg=t;this.min=t.min;this.max=a}function n(t,a,r){e.call(this,"var");this.index=t;this.min=a;this.max=r}function s(t,a){e.call(this,"definition");this.variable=t;this.arg=a}function o(){this.parts=[]}function c(e,t){return"literal"===t.type&&0===t.number?e:"literal"===e.type&&0===e.number?t:"literal"===t.type&&"literal"===e.type?new a(e.number+t.number):new r("+",e,t,e.min+t.min,e.max+t.max)}function l(e,t){if("literal"===t.type){if(0===t.number)return new a(0);if(1===t.number)return e;if("literal"===e.type)return new a(e.number*t.number)}if("literal"===e.type){if(0===e.number)return new a(0);if(1===e.number)return t}return new r("*",e,t,Math.min(e.min*t.min,e.min*t.max,e.max*t.min,e.max*t.max),Math.max(e.min*t.min,e.min*t.max,e.max*t.min,e.max*t.max))}function h(e,t){if("literal"===t.type){if(0===t.number)return e;if("literal"===e.type)return new a(e.number-t.number)}return"binary"===t.type&&"-"===t.op&&"literal"===e.type&&1===e.number&&"literal"===t.arg1.type&&1===t.arg1.number?t.arg2:new r("-",e,t,e.min-t.max,e.max-t.min)}function u(e,t){return e.min>=t?new a(t):e.max<=t?e:new i(e,t)}function f(){}e.prototype.visit=function(e){throw new Error("abstract method")};t.prototype=Object.create(e.prototype);t.prototype.visit=function(e){e.visitArgument(this)};a.prototype=Object.create(e.prototype);a.prototype.visit=function(e){e.visitLiteral(this)};r.prototype=Object.create(e.prototype);r.prototype.visit=function(e){e.visitBinaryOperation(this)};i.prototype=Object.create(e.prototype);i.prototype.visit=function(e){e.visitMin(this)};n.prototype=Object.create(e.prototype);n.prototype.visit=function(e){e.visitVariable(this)};s.prototype=Object.create(e.prototype);s.prototype.visit=function(e){e.visitVariableDefinition(this)};o.prototype={visitArgument:function(e){this.parts.push("Math.max(",e.min,", Math.min(",e.max,", src[srcOffset + ",e.index,"]))")},visitVariable:function(e){this.parts.push("v",e.index)},visitLiteral:function(e){this.parts.push(e.number)},visitBinaryOperation:function(e){this.parts.push("(");e.arg1.visit(this);this.parts.push(" ",e.op," ");e.arg2.visit(this);this.parts.push(")")},visitVariableDefinition:function(e){this.parts.push("var ");e.variable.visit(this);this.parts.push(" = ");e.arg.visit(this);this.parts.push(";")},visitMin:function(e){this.parts.push("Math.min(");e.arg.visit(this);this.parts.push(", ",e.max,")")},toString:function(){return this.parts.join("")}};f.prototype={compile:function(e,r,i){var f,d,g,p,m,b,v,y,k,w,C=[],x=[],S=r.length>>1,A=i.length>>1,I=0;for(f=0;f<S;f++)C.push(new t(f,r[2*f],r[2*f+1]));for(f=0,d=e.length;f<d;f++){w=e[f];if("number"!=typeof w)switch(w){case"add":if(C.length<2)return null;b=C.pop();m=C.pop();C.push(c(m,b));break;case"cvr":if(C.length<1)return null;break;case"mul":if(C.length<2)return null;b=C.pop();m=C.pop();C.push(l(m,b));break;case"sub":if(C.length<2)return null;b=C.pop();m=C.pop();C.push(h(m,b));break;case"exch":if(C.length<2)return null;v=C.pop();y=C.pop();C.push(v,y);break;case"pop":if(C.length<1)return null;C.pop();break;case"index":if(C.length<1)return null;m=C.pop();if("literal"!==m.type)return null;g=m.number;if(g<0||(0|g)!==g||C.length<g)return null;v=C[C.length-g-1];if("literal"===v.type||"var"===v.type){C.push(v);break}k=new n(I++,v.min,v.max);C[C.length-g-1]=k;C.push(k);x.push(new s(k,v));break;case"dup":if(C.length<1)return null;if("number"==typeof e[f+1]&&"gt"===e[f+2]&&e[f+3]===f+7&&"jz"===e[f+4]&&"pop"===e[f+5]&&e[f+6]===e[f+1]){m=C.pop();C.push(u(m,e[f+1]));f+=6;break}v=C[C.length-1];if("literal"===v.type||"var"===v.type){C.push(v);break}k=new n(I++,v.min,v.max);C[C.length-1]=k;C.push(k);x.push(new s(k,v));break;case"roll":if(C.length<2)return null;b=C.pop();m=C.pop();if("literal"!==b.type||"literal"!==m.type)return null;p=b.number;g=m.number;if(g<=0||(0|g)!==g||(0|p)!==p||C.length<g)return null;p=(p%g+g)%g;if(0===p)break;Array.prototype.push.apply(C,C.splice(C.length-g,g-p));break;default:return null}else C.push(new a(w))}if(C.length!==A)return null;var B=[];x.forEach(function(e){var t=new o;e.visit(t);B.push(t.toString())});C.forEach(function(e,t){var a=new o;e.visit(a);var r=i[2*t],n=i[2*t+1],s=[a.toString()];if(r>e.min){s.unshift("Math.max(",r,", ");s.push(")")}if(n<e.max){s.unshift("Math.min(",n,", ");s.push(")")}s.unshift("dest[destOffset + ",t,"] = ");s.push(";");B.push(s.join(""))});return B.join("\n")}};return f}();t.isPDFFunction=r;t.PDFFunction=p;t.PostScriptEvaluator=b;t.PostScriptCompiler=v},function(e,t,a){"use strict";var r=a(0),i=r.getLookupTableFactory,n=i(function(e){e.A=65;e.AE=198;e.AEacute=508;e.AEmacron=482;e.AEsmall=63462;e.Aacute=193;e.Aacutesmall=63457;e.Abreve=258;e.Abreveacute=7854;e.Abrevecyrillic=1232;e.Abrevedotbelow=7862;e.Abrevegrave=7856;e.Abrevehookabove=7858;e.Abrevetilde=7860;e.Acaron=461;e.Acircle=9398;e.Acircumflex=194;e.Acircumflexacute=7844;e.Acircumflexdotbelow=7852;e.Acircumflexgrave=7846;e.Acircumflexhookabove=7848;e.Acircumflexsmall=63458;e.Acircumflextilde=7850;e.Acute=63177;e.Acutesmall=63412;e.Acyrillic=1040;e.Adblgrave=512;e.Adieresis=196;e.Adieresiscyrillic=1234;e.Adieresismacron=478;e.Adieresissmall=63460;e.Adotbelow=7840;e.Adotmacron=480;e.Agrave=192;e.Agravesmall=63456;e.Ahookabove=7842;e.Aiecyrillic=1236;e.Ainvertedbreve=514;e.Alpha=913;e.Alphatonos=902;e.Amacron=256;e.Amonospace=65313;e.Aogonek=260;e.Aring=197;e.Aringacute=506;e.Aringbelow=7680;e.Aringsmall=63461;e.Asmall=63329;e.Atilde=195;e.Atildesmall=63459;e.Aybarmenian=1329;e.B=66;e.Bcircle=9399;e.Bdotaccent=7682;e.Bdotbelow=7684;e.Becyrillic=1041;e.Benarmenian=1330;e.Beta=914;e.Bhook=385;e.Blinebelow=7686;e.Bmonospace=65314;e.Brevesmall=63220;e.Bsmall=63330;e.Btopbar=386;e.C=67;e.Caarmenian=1342;e.Cacute=262;e.Caron=63178;e.Caronsmall=63221;e.Ccaron=268;e.Ccedilla=199;e.Ccedillaacute=7688;e.Ccedillasmall=63463;e.Ccircle=9400;e.Ccircumflex=264;e.Cdot=266;e.Cdotaccent=266;e.Cedillasmall=63416;e.Chaarmenian=1353;e.Cheabkhasiancyrillic=1212;e.Checyrillic=1063;e.Chedescenderabkhasiancyrillic=1214;e.Chedescendercyrillic=1206;e.Chedieresiscyrillic=1268;e.Cheharmenian=1347;e.Chekhakassiancyrillic=1227;e.Cheverticalstrokecyrillic=1208;e.Chi=935;e.Chook=391;e.Circumflexsmall=63222;e.Cmonospace=65315;e.Coarmenian=1361;e.Csmall=63331;e.D=68;e.DZ=497;e.DZcaron=452;e.Daarmenian=1332;e.Dafrican=393;e.Dcaron=270;e.Dcedilla=7696;e.Dcircle=9401;e.Dcircumflexbelow=7698;e.Dcroat=272;e.Ddotaccent=7690;e.Ddotbelow=7692;e.Decyrillic=1044;e.Deicoptic=1006;e.Delta=8710;e.Deltagreek=916;e.Dhook=394;e.Dieresis=63179;e.DieresisAcute=63180;e.DieresisGrave=63181;e.Dieresissmall=63400;e.Digammagreek=988;e.Djecyrillic=1026;e.Dlinebelow=7694;e.Dmonospace=65316;e.Dotaccentsmall=63223;e.Dslash=272;e.Dsmall=63332;e.Dtopbar=395;e.Dz=498;e.Dzcaron=453;e.Dzeabkhasiancyrillic=1248;e.Dzecyrillic=1029;e.Dzhecyrillic=1039;e.E=69;e.Eacute=201;e.Eacutesmall=63465;e.Ebreve=276;e.Ecaron=282;e.Ecedillabreve=7708;e.Echarmenian=1333;e.Ecircle=9402;e.Ecircumflex=202;e.Ecircumflexacute=7870;e.Ecircumflexbelow=7704;e.Ecircumflexdotbelow=7878;e.Ecircumflexgrave=7872;e.Ecircumflexhookabove=7874;e.Ecircumflexsmall=63466;e.Ecircumflextilde=7876;e.Ecyrillic=1028;e.Edblgrave=516;e.Edieresis=203;e.Edieresissmall=63467;e.Edot=278;e.Edotaccent=278;e.Edotbelow=7864;e.Efcyrillic=1060;e.Egrave=200;e.Egravesmall=63464;e.Eharmenian=1335;e.Ehookabove=7866;e.Eightroman=8551;e.Einvertedbreve=518;e.Eiotifiedcyrillic=1124;e.Elcyrillic=1051;e.Elevenroman=8554;e.Emacron=274;e.Emacronacute=7702;e.Emacrongrave=7700;e.Emcyrillic=1052;e.Emonospace=65317;e.Encyrillic=1053;e.Endescendercyrillic=1186;e.Eng=330;e.Enghecyrillic=1188;e.Enhookcyrillic=1223;e.Eogonek=280;e.Eopen=400;e.Epsilon=917;e.Epsilontonos=904;e.Ercyrillic=1056;e.Ereversed=398;e.Ereversedcyrillic=1069;e.Escyrillic=1057;e.Esdescendercyrillic=1194;e.Esh=425;e.Esmall=63333;e.Eta=919;e.Etarmenian=1336;e.Etatonos=905;e.Eth=208;e.Ethsmall=63472;e.Etilde=7868;e.Etildebelow=7706;e.Euro=8364;e.Ezh=439;e.Ezhcaron=494;e.Ezhreversed=440;e.F=70;e.Fcircle=9403;e.Fdotaccent=7710;e.Feharmenian=1366;e.Feicoptic=996;e.Fhook=401;e.Fitacyrillic=1138;e.Fiveroman=8548;e.Fmonospace=65318;e.Fourroman=8547;e.Fsmall=63334;e.G=71;e.GBsquare=13191;e.Gacute=500;e.Gamma=915;e.Gammaafrican=404;e.Gangiacoptic=1002;e.Gbreve=286;e.Gcaron=486;e.Gcedilla=290;e.Gcircle=9404;e.Gcircumflex=284;e.Gcommaaccent=290;e.Gdot=288;e.Gdotaccent=288;e.Gecyrillic=1043;e.Ghadarmenian=1346;e.Ghemiddlehookcyrillic=1172;e.Ghestrokecyrillic=1170;e.Gheupturncyrillic=1168;e.Ghook=403;e.Gimarmenian=1331;e.Gjecyrillic=1027;e.Gmacron=7712;e.Gmonospace=65319;e.Grave=63182;e.Gravesmall=63328;e.Gsmall=63335;e.Gsmallhook=667;e.Gstroke=484;e.H=72;e.H18533=9679;e.H18543=9642;e.H18551=9643;e.H22073=9633;e.HPsquare=13259;e.Haabkhasiancyrillic=1192;e.Hadescendercyrillic=1202;e.Hardsigncyrillic=1066;e.Hbar=294;e.Hbrevebelow=7722;e.Hcedilla=7720;e.Hcircle=9405;e.Hcircumflex=292;e.Hdieresis=7718;e.Hdotaccent=7714;e.Hdotbelow=7716;e.Hmonospace=65320;e.Hoarmenian=1344;e.Horicoptic=1e3;e.Hsmall=63336;e.Hungarumlaut=63183;e.Hungarumlautsmall=63224;e.Hzsquare=13200;e.I=73;e.IAcyrillic=1071;e.IJ=306;e.IUcyrillic=1070;e.Iacute=205;e.Iacutesmall=63469;e.Ibreve=300;e.Icaron=463;e.Icircle=9406;e.Icircumflex=206;e.Icircumflexsmall=63470;e.Icyrillic=1030;e.Idblgrave=520;e.Idieresis=207;e.Idieresisacute=7726;e.Idieresiscyrillic=1252;e.Idieresissmall=63471;e.Idot=304;e.Idotaccent=304;e.Idotbelow=7882;e.Iebrevecyrillic=1238;e.Iecyrillic=1045;e.Ifraktur=8465;e.Igrave=204;e.Igravesmall=63468;e.Ihookabove=7880;e.Iicyrillic=1048;e.Iinvertedbreve=522;e.Iishortcyrillic=1049;e.Imacron=298;e.Imacroncyrillic=1250;e.Imonospace=65321;e.Iniarmenian=1339;e.Iocyrillic=1025;e.Iogonek=302;e.Iota=921;e.Iotaafrican=406;e.Iotadieresis=938;e.Iotatonos=906;e.Ismall=63337;e.Istroke=407;e.Itilde=296;e.Itildebelow=7724;e.Izhitsacyrillic=1140;e.Izhitsadblgravecyrillic=1142;e.J=74;e.Jaarmenian=1345;e.Jcircle=9407;e.Jcircumflex=308;e.Jecyrillic=1032;e.Jheharmenian=1355;e.Jmonospace=65322;e.Jsmall=63338;e.K=75;e.KBsquare=13189;e.KKsquare=13261;e.Kabashkircyrillic=1184;e.Kacute=7728;e.Kacyrillic=1050;e.Kadescendercyrillic=1178;e.Kahookcyrillic=1219;e.Kappa=922;e.Kastrokecyrillic=1182;e.Kaverticalstrokecyrillic=1180;e.Kcaron=488;e.Kcedilla=310;e.Kcircle=9408;e.Kcommaaccent=310;e.Kdotbelow=7730;e.Keharmenian=1364;e.Kenarmenian=1343;e.Khacyrillic=1061;e.Kheicoptic=998;e.Khook=408;e.Kjecyrillic=1036;e.Klinebelow=7732;e.Kmonospace=65323;e.Koppacyrillic=1152;e.Koppagreek=990;e.Ksicyrillic=1134;e.Ksmall=63339;e.L=76;e.LJ=455;e.LL=63167;e.Lacute=313;e.Lambda=923;e.Lcaron=317;e.Lcedilla=315;e.Lcircle=9409;e.Lcircumflexbelow=7740;e.Lcommaaccent=315;e.Ldot=319;e.Ldotaccent=319;e.Ldotbelow=7734;e.Ldotbelowmacron=7736;e.Liwnarmenian=1340;e.Lj=456;e.Ljecyrillic=1033;e.Llinebelow=7738;e.Lmonospace=65324;e.Lslash=321;e.Lslashsmall=63225;e.Lsmall=63340;e.M=77;e.MBsquare=13190;e.Macron=63184;e.Macronsmall=63407;e.Macute=7742;e.Mcircle=9410;e.Mdotaccent=7744;e.Mdotbelow=7746;e.Menarmenian=1348;e.Mmonospace=65325;e.Msmall=63341;e.Mturned=412;e.Mu=924;e.N=78;e.NJ=458;e.Nacute=323;e.Ncaron=327;e.Ncedilla=325;e.Ncircle=9411;e.Ncircumflexbelow=7754;e.Ncommaaccent=325;e.Ndotaccent=7748;e.Ndotbelow=7750;e.Nhookleft=413;e.Nineroman=8552;e.Nj=459;e.Njecyrillic=1034;e.Nlinebelow=7752;e.Nmonospace=65326;e.Nowarmenian=1350;e.Nsmall=63342;e.Ntilde=209;e.Ntildesmall=63473;e.Nu=925;e.O=79;e.OE=338;e.OEsmall=63226;e.Oacute=211;e.Oacutesmall=63475;e.Obarredcyrillic=1256;e.Obarreddieresiscyrillic=1258;e.Obreve=334;e.Ocaron=465;e.Ocenteredtilde=415;e.Ocircle=9412;e.Ocircumflex=212;e.Ocircumflexacute=7888;e.Ocircumflexdotbelow=7896;e.Ocircumflexgrave=7890;e.Ocircumflexhookabove=7892;e.Ocircumflexsmall=63476;e.Ocircumflextilde=7894;e.Ocyrillic=1054;e.Odblacute=336;e.Odblgrave=524;e.Odieresis=214;e.Odieresiscyrillic=1254;e.Odieresissmall=63478;e.Odotbelow=7884;e.Ogoneksmall=63227;e.Ograve=210;e.Ogravesmall=63474;e.Oharmenian=1365;e.Ohm=8486;e.Ohookabove=7886;e.Ohorn=416;e.Ohornacute=7898;e.Ohorndotbelow=7906;e.Ohorngrave=7900;e.Ohornhookabove=7902;e.Ohorntilde=7904;e.Ohungarumlaut=336;e.Oi=418;e.Oinvertedbreve=526;e.Omacron=332;e.Omacronacute=7762;e.Omacrongrave=7760;e.Omega=8486;e.Omegacyrillic=1120;e.Omegagreek=937;e.Omegaroundcyrillic=1146;e.Omegatitlocyrillic=1148;e.Omegatonos=911;e.Omicron=927;e.Omicrontonos=908;e.Omonospace=65327;e.Oneroman=8544;e.Oogonek=490;e.Oogonekmacron=492;e.Oopen=390;e.Oslash=216;e.Oslashacute=510;e.Oslashsmall=63480;e.Osmall=63343;e.Ostrokeacute=510;e.Otcyrillic=1150;e.Otilde=213;e.Otildeacute=7756;e.Otildedieresis=7758;e.Otildesmall=63477;e.P=80;e.Pacute=7764;e.Pcircle=9413;e.Pdotaccent=7766;e.Pecyrillic=1055;e.Peharmenian=1354;e.Pemiddlehookcyrillic=1190;e.Phi=934;e.Phook=420;e.Pi=928;e.Piwrarmenian=1363;e.Pmonospace=65328;e.Psi=936;e.Psicyrillic=1136;e.Psmall=63344;e.Q=81;e.Qcircle=9414;e.Qmonospace=65329;e.Qsmall=63345;e.R=82;e.Raarmenian=1356;e.Racute=340;e.Rcaron=344;e.Rcedilla=342;e.Rcircle=9415;e.Rcommaaccent=342;e.Rdblgrave=528;e.Rdotaccent=7768;e.Rdotbelow=7770;e.Rdotbelowmacron=7772;e.Reharmenian=1360;e.Rfraktur=8476;e.Rho=929;e.Ringsmall=63228;e.Rinvertedbreve=530;e.Rlinebelow=7774;e.Rmonospace=65330;e.Rsmall=63346;e.Rsmallinverted=641;e.Rsmallinvertedsuperior=694;e.S=83;e.SF010000=9484;e.SF020000=9492;e.SF030000=9488;e.SF040000=9496;e.SF050000=9532;e.SF060000=9516;e.SF070000=9524;e.SF080000=9500;e.SF090000=9508;e.SF100000=9472;e.SF110000=9474;e.SF190000=9569;e.SF200000=9570;e.SF210000=9558;e.SF220000=9557;e.SF230000=9571;e.SF240000=9553;e.SF250000=9559;e.SF260000=9565;e.SF270000=9564;e.SF280000=9563;e.SF360000=9566;e.SF370000=9567;e.SF380000=9562;e.SF390000=9556;e.SF400000=9577;e.SF410000=9574;e.SF420000=9568;e.SF430000=9552;e.SF440000=9580;e.SF450000=9575;e.SF460000=9576;e.SF470000=9572;e.SF480000=9573;e.SF490000=9561;e.SF500000=9560;e.SF510000=9554;e.SF520000=9555;e.SF530000=9579;e.SF540000=9578;e.Sacute=346;e.Sacutedotaccent=7780;e.Sampigreek=992;e.Scaron=352;e.Scarondotaccent=7782;e.Scaronsmall=63229;e.Scedilla=350;e.Schwa=399;e.Schwacyrillic=1240;e.Schwadieresiscyrillic=1242;e.Scircle=9416;e.Scircumflex=348;e.Scommaaccent=536;e.Sdotaccent=7776;e.Sdotbelow=7778;e.Sdotbelowdotaccent=7784;e.Seharmenian=1357;e.Sevenroman=8550;e.Shaarmenian=1351;e.Shacyrillic=1064;e.Shchacyrillic=1065;e.Sheicoptic=994;e.Shhacyrillic=1210;e.Shimacoptic=1004;e.Sigma=931;e.Sixroman=8549;e.Smonospace=65331;e.Softsigncyrillic=1068;e.Ssmall=63347;e.Stigmagreek=986;e.T=84;e.Tau=932;e.Tbar=358;e.Tcaron=356;e.Tcedilla=354;e.Tcircle=9417;e.Tcircumflexbelow=7792;e.Tcommaaccent=354;e.Tdotaccent=7786;e.Tdotbelow=7788;e.Tecyrillic=1058;e.Tedescendercyrillic=1196;e.Tenroman=8553;e.Tetsecyrillic=1204;e.Theta=920;e.Thook=428;e.Thorn=222;e.Thornsmall=63486;e.Threeroman=8546;e.Tildesmall=63230;e.Tiwnarmenian=1359;e.Tlinebelow=7790;e.Tmonospace=65332;e.Toarmenian=1337;e.Tonefive=444;e.Tonesix=388;e.Tonetwo=423;e.Tretroflexhook=430;e.Tsecyrillic=1062;e.Tshecyrillic=1035;e.Tsmall=63348;e.Twelveroman=8555;e.Tworoman=8545;e.U=85;e.Uacute=218;e.Uacutesmall=63482;e.Ubreve=364;e.Ucaron=467;e.Ucircle=9418;e.Ucircumflex=219;e.Ucircumflexbelow=7798;e.Ucircumflexsmall=63483;e.Ucyrillic=1059;e.Udblacute=368;e.Udblgrave=532;e.Udieresis=220;e.Udieresisacute=471;e.Udieresisbelow=7794;e.Udieresiscaron=473;e.Udieresiscyrillic=1264;e.Udieresisgrave=475;e.Udieresismacron=469;e.Udieresissmall=63484;e.Udotbelow=7908;e.Ugrave=217;e.Ugravesmall=63481;e.Uhookabove=7910;e.Uhorn=431;e.Uhornacute=7912;e.Uhorndotbelow=7920;e.Uhorngrave=7914;e.Uhornhookabove=7916;e.Uhorntilde=7918;e.Uhungarumlaut=368;e.Uhungarumlautcyrillic=1266;e.Uinvertedbreve=534;e.Ukcyrillic=1144;e.Umacron=362;e.Umacroncyrillic=1262;e.Umacrondieresis=7802;e.Umonospace=65333;e.Uogonek=370;e.Upsilon=933;e.Upsilon1=978;e.Upsilonacutehooksymbolgreek=979;e.Upsilonafrican=433;e.Upsilondieresis=939;e.Upsilondieresishooksymbolgreek=980;e.Upsilonhooksymbol=978;e.Upsilontonos=910;e.Uring=366;e.Ushortcyrillic=1038;e.Usmall=63349;e.Ustraightcyrillic=1198;e.Ustraightstrokecyrillic=1200;e.Utilde=360;e.Utildeacute=7800;e.Utildebelow=7796;e.V=86;e.Vcircle=9419;e.Vdotbelow=7806;e.Vecyrillic=1042;e.Vewarmenian=1358;e.Vhook=434;e.Vmonospace=65334;e.Voarmenian=1352;e.Vsmall=63350;e.Vtilde=7804;e.W=87;e.Wacute=7810;e.Wcircle=9420;e.Wcircumflex=372;e.Wdieresis=7812;e.Wdotaccent=7814;e.Wdotbelow=7816;e.Wgrave=7808;e.Wmonospace=65335;e.Wsmall=63351;e.X=88;e.Xcircle=9421;e.Xdieresis=7820;e.Xdotaccent=7818;e.Xeharmenian=1341;e.Xi=926;e.Xmonospace=65336;e.Xsmall=63352;e.Y=89;e.Yacute=221;e.Yacutesmall=63485;e.Yatcyrillic=1122;e.Ycircle=9422;e.Ycircumflex=374;e.Ydieresis=376;e.Ydieresissmall=63487;e.Ydotaccent=7822;e.Ydotbelow=7924;e.Yericyrillic=1067;e.Yerudieresiscyrillic=1272;e.Ygrave=7922;e.Yhook=435;e.Yhookabove=7926;e.Yiarmenian=1349;e.Yicyrillic=1031;e.Yiwnarmenian=1362;e.Ymonospace=65337;e.Ysmall=63353;e.Ytilde=7928;e.Yusbigcyrillic=1130;e.Yusbigiotifiedcyrillic=1132;e.Yuslittlecyrillic=1126;e.Yuslittleiotifiedcyrillic=1128;e.Z=90;e.Zaarmenian=1334;e.Zacute=377;e.Zcaron=381;e.Zcaronsmall=63231;e.Zcircle=9423;e.Zcircumflex=7824;e.Zdot=379;e.Zdotaccent=379;e.Zdotbelow=7826;e.Zecyrillic=1047;e.Zedescendercyrillic=1176;e.Zedieresiscyrillic=1246;e.Zeta=918;e.Zhearmenian=1338;e.Zhebrevecyrillic=1217;e.Zhecyrillic=1046;e.Zhedescendercyrillic=1174;e.Zhedieresiscyrillic=1244;e.Zlinebelow=7828;e.Zmonospace=65338;e.Zsmall=63354;e.Zstroke=437;e.a=97;e.aabengali=2438;e.aacute=225;e.aadeva=2310;e.aagujarati=2694;e.aagurmukhi=2566;e.aamatragurmukhi=2622;e.aarusquare=13059;e.aavowelsignbengali=2494;e.aavowelsigndeva=2366;e.aavowelsigngujarati=2750;e.abbreviationmarkarmenian=1375;e.abbreviationsigndeva=2416;e.abengali=2437;e.abopomofo=12570;e.abreve=259;e.abreveacute=7855;e.abrevecyrillic=1233;e.abrevedotbelow=7863;e.abrevegrave=7857;e.abrevehookabove=7859;e.abrevetilde=7861;e.acaron=462;e.acircle=9424;e.acircumflex=226;e.acircumflexacute=7845;e.acircumflexdotbelow=7853;e.acircumflexgrave=7847;e.acircumflexhookabove=7849;e.acircumflextilde=7851;e.acute=180;e.acutebelowcmb=791;e.acutecmb=769;e.acutecomb=769;e.acutedeva=2388;e.acutelowmod=719;e.acutetonecmb=833;e.acyrillic=1072;e.adblgrave=513;e.addakgurmukhi=2673;e.adeva=2309;e.adieresis=228;e.adieresiscyrillic=1235;e.adieresismacron=479;e.adotbelow=7841;e.adotmacron=481;e.ae=230;e.aeacute=509;e.aekorean=12624;e.aemacron=483;e.afii00208=8213;e.afii08941=8356;e.afii10017=1040;e.afii10018=1041;e.afii10019=1042;e.afii10020=1043;e.afii10021=1044;e.afii10022=1045;e.afii10023=1025;e.afii10024=1046;e.afii10025=1047;e.afii10026=1048;e.afii10027=1049;e.afii10028=1050;e.afii10029=1051;e.afii10030=1052;e.afii10031=1053;e.afii10032=1054;e.afii10033=1055;e.afii10034=1056;e.afii10035=1057;e.afii10036=1058;e.afii10037=1059;e.afii10038=1060;e.afii10039=1061;e.afii10040=1062;e.afii10041=1063;e.afii10042=1064;e.afii10043=1065;e.afii10044=1066;e.afii10045=1067;e.afii10046=1068;e.afii10047=1069;e.afii10048=1070;e.afii10049=1071;e.afii10050=1168;e.afii10051=1026;e.afii10052=1027;e.afii10053=1028;e.afii10054=1029;e.afii10055=1030;e.afii10056=1031;e.afii10057=1032;e.afii10058=1033;e.afii10059=1034;e.afii10060=1035;e.afii10061=1036;e.afii10062=1038;e.afii10063=63172;e.afii10064=63173;e.afii10065=1072;e.afii10066=1073;e.afii10067=1074;e.afii10068=1075;e.afii10069=1076;e.afii10070=1077;e.afii10071=1105;e.afii10072=1078;e.afii10073=1079;e.afii10074=1080;e.afii10075=1081;e.afii10076=1082;e.afii10077=1083;e.afii10078=1084;e.afii10079=1085;e.afii10080=1086;e.afii10081=1087;e.afii10082=1088;e.afii10083=1089;e.afii10084=1090;e.afii10085=1091;e.afii10086=1092;e.afii10087=1093;e.afii10088=1094;e.afii10089=1095;e.afii10090=1096;e.afii10091=1097;e.afii10092=1098;e.afii10093=1099;e.afii10094=1100;e.afii10095=1101;e.afii10096=1102;e.afii10097=1103;e.afii10098=1169;e.afii10099=1106;e.afii10100=1107;e.afii10101=1108;e.afii10102=1109;e.afii10103=1110;e.afii10104=1111;e.afii10105=1112;e.afii10106=1113;e.afii10107=1114;e.afii10108=1115;e.afii10109=1116;e.afii10110=1118;e.afii10145=1039;e.afii10146=1122;e.afii10147=1138;e.afii10148=1140;e.afii10192=63174;e.afii10193=1119;e.afii10194=1123;e.afii10195=1139;e.afii10196=1141;e.afii10831=63175;e.afii10832=63176;e.afii10846=1241;e.afii299=8206;e.afii300=8207;e.afii301=8205;e.afii57381=1642;e.afii57388=1548;e.afii57392=1632;e.afii57393=1633;e.afii57394=1634;e.afii57395=1635;e.afii57396=1636;e.afii57397=1637;e.afii57398=1638;e.afii57399=1639;e.afii57400=1640;e.afii57401=1641;e.afii57403=1563;e.afii57407=1567;e.afii57409=1569;e.afii57410=1570;e.afii57411=1571;e.afii57412=1572;e.afii57413=1573;e.afii57414=1574;e.afii57415=1575;e.afii57416=1576;e.afii57417=1577;e.afii57418=1578;e.afii57419=1579;e.afii57420=1580;e.afii57421=1581;e.afii57422=1582;e.afii57423=1583;e.afii57424=1584;e.afii57425=1585;e.afii57426=1586;e.afii57427=1587;e.afii57428=1588;e.afii57429=1589;e.afii57430=1590;e.afii57431=1591;e.afii57432=1592;e.afii57433=1593;e.afii57434=1594;e.afii57440=1600;e.afii57441=1601;e.afii57442=1602;e.afii57443=1603;e.afii57444=1604;e.afii57445=1605;e.afii57446=1606;e.afii57448=1608;e.afii57449=1609;e.afii57450=1610;e.afii57451=1611;e.afii57452=1612;e.afii57453=1613;e.afii57454=1614;e.afii57455=1615;e.afii57456=1616;e.afii57457=1617;e.afii57458=1618;e.afii57470=1607;e.afii57505=1700;e.afii57506=1662;e.afii57507=1670;e.afii57508=1688;e.afii57509=1711;e.afii57511=1657;e.afii57512=1672;e.afii57513=1681;e.afii57514=1722;e.afii57519=1746;e.afii57534=1749;e.afii57636=8362;e.afii57645=1470;e.afii57658=1475;e.afii57664=1488;e.afii57665=1489;e.afii57666=1490;e.afii57667=1491;e.afii57668=1492;e.afii57669=1493;e.afii57670=1494;e.afii57671=1495;e.afii57672=1496;e.afii57673=1497;e.afii57674=1498;e.afii57675=1499;e.afii57676=1500;e.afii57677=1501;e.afii57678=1502;e.afii57679=1503;e.afii57680=1504;e.afii57681=1505;e.afii57682=1506;e.afii57683=1507;e.afii57684=1508;e.afii57685=1509;e.afii57686=1510;e.afii57687=1511;e.afii57688=1512;e.afii57689=1513;e.afii57690=1514;e.afii57694=64298;e.afii57695=64299;e.afii57700=64331;e.afii57705=64287;e.afii57716=1520;e.afii57717=1521;e.afii57718=1522;e.afii57723=64309;e.afii57793=1460;e.afii57794=1461;e.afii57795=1462;e.afii57796=1467;e.afii57797=1464;e.afii57798=1463;e.afii57799=1456;e.afii57800=1458;e.afii57801=1457;e.afii57802=1459;e.afii57803=1474;e.afii57804=1473;e.afii57806=1465;e.afii57807=1468;e.afii57839=1469;e.afii57841=1471;e.afii57842=1472;e.afii57929=700;e.afii61248=8453;e.afii61289=8467;e.afii61352=8470;e.afii61573=8236;e.afii61574=8237;e.afii61575=8238;e.afii61664=8204;e.afii63167=1645;e.afii64937=701;e.agrave=224;e.agujarati=2693;e.agurmukhi=2565;e.ahiragana=12354;e.ahookabove=7843;e.aibengali=2448;e.aibopomofo=12574;e.aideva=2320;e.aiecyrillic=1237;e.aigujarati=2704;e.aigurmukhi=2576;e.aimatragurmukhi=2632;e.ainarabic=1593;e.ainfinalarabic=65226;e.aininitialarabic=65227;e.ainmedialarabic=65228
+;e.ainvertedbreve=515;e.aivowelsignbengali=2504;e.aivowelsigndeva=2376;e.aivowelsigngujarati=2760;e.akatakana=12450;e.akatakanahalfwidth=65393;e.akorean=12623;e.alef=1488;e.alefarabic=1575;e.alefdageshhebrew=64304;e.aleffinalarabic=65166;e.alefhamzaabovearabic=1571;e.alefhamzaabovefinalarabic=65156;e.alefhamzabelowarabic=1573;e.alefhamzabelowfinalarabic=65160;e.alefhebrew=1488;e.aleflamedhebrew=64335;e.alefmaddaabovearabic=1570;e.alefmaddaabovefinalarabic=65154;e.alefmaksuraarabic=1609;e.alefmaksurafinalarabic=65264;e.alefmaksurainitialarabic=65267;e.alefmaksuramedialarabic=65268;e.alefpatahhebrew=64302;e.alefqamatshebrew=64303;e.aleph=8501;e.allequal=8780;e.alpha=945;e.alphatonos=940;e.amacron=257;e.amonospace=65345;e.ampersand=38;e.ampersandmonospace=65286;e.ampersandsmall=63270;e.amsquare=13250;e.anbopomofo=12578;e.angbopomofo=12580;e.angbracketleft=12296;e.angbracketright=12297;e.angkhankhuthai=3674;e.angle=8736;e.anglebracketleft=12296;e.anglebracketleftvertical=65087;e.anglebracketright=12297;e.anglebracketrightvertical=65088;e.angleleft=9001;e.angleright=9002;e.angstrom=8491;e.anoteleia=903;e.anudattadeva=2386;e.anusvarabengali=2434;e.anusvaradeva=2306;e.anusvaragujarati=2690;e.aogonek=261;e.apaatosquare=13056;e.aparen=9372;e.apostrophearmenian=1370;e.apostrophemod=700;e.apple=63743;e.approaches=8784;e.approxequal=8776;e.approxequalorimage=8786;e.approximatelyequal=8773;e.araeaekorean=12686;e.araeakorean=12685;e.arc=8978;e.arighthalfring=7834;e.aring=229;e.aringacute=507;e.aringbelow=7681;e.arrowboth=8596;e.arrowdashdown=8675;e.arrowdashleft=8672;e.arrowdashright=8674;e.arrowdashup=8673;e.arrowdblboth=8660;e.arrowdbldown=8659;e.arrowdblleft=8656;e.arrowdblright=8658;e.arrowdblup=8657;e.arrowdown=8595;e.arrowdownleft=8601;e.arrowdownright=8600;e.arrowdownwhite=8681;e.arrowheaddownmod=709;e.arrowheadleftmod=706;e.arrowheadrightmod=707;e.arrowheadupmod=708;e.arrowhorizex=63719;e.arrowleft=8592;e.arrowleftdbl=8656;e.arrowleftdblstroke=8653;e.arrowleftoverright=8646;e.arrowleftwhite=8678;e.arrowright=8594;e.arrowrightdblstroke=8655;e.arrowrightheavy=10142;e.arrowrightoverleft=8644;e.arrowrightwhite=8680;e.arrowtableft=8676;e.arrowtabright=8677;e.arrowup=8593;e.arrowupdn=8597;e.arrowupdnbse=8616;e.arrowupdownbase=8616;e.arrowupleft=8598;e.arrowupleftofdown=8645;e.arrowupright=8599;e.arrowupwhite=8679;e.arrowvertex=63718;e.asciicircum=94;e.asciicircummonospace=65342;e.asciitilde=126;e.asciitildemonospace=65374;e.ascript=593;e.ascriptturned=594;e.asmallhiragana=12353;e.asmallkatakana=12449;e.asmallkatakanahalfwidth=65383;e.asterisk=42;e.asteriskaltonearabic=1645;e.asteriskarabic=1645;e.asteriskmath=8727;e.asteriskmonospace=65290;e.asterisksmall=65121;e.asterism=8258;e.asuperior=63209;e.asymptoticallyequal=8771;e.at=64;e.atilde=227;e.atmonospace=65312;e.atsmall=65131;e.aturned=592;e.aubengali=2452;e.aubopomofo=12576;e.audeva=2324;e.augujarati=2708;e.augurmukhi=2580;e.aulengthmarkbengali=2519;e.aumatragurmukhi=2636;e.auvowelsignbengali=2508;e.auvowelsigndeva=2380;e.auvowelsigngujarati=2764;e.avagrahadeva=2365;e.aybarmenian=1377;e.ayin=1506;e.ayinaltonehebrew=64288;e.ayinhebrew=1506;e.b=98;e.babengali=2476;e.backslash=92;e.backslashmonospace=65340;e.badeva=2348;e.bagujarati=2732;e.bagurmukhi=2604;e.bahiragana=12400;e.bahtthai=3647;e.bakatakana=12496;e.bar=124;e.barmonospace=65372;e.bbopomofo=12549;e.bcircle=9425;e.bdotaccent=7683;e.bdotbelow=7685;e.beamedsixteenthnotes=9836;e.because=8757;e.becyrillic=1073;e.beharabic=1576;e.behfinalarabic=65168;e.behinitialarabic=65169;e.behiragana=12409;e.behmedialarabic=65170;e.behmeeminitialarabic=64671;e.behmeemisolatedarabic=64520;e.behnoonfinalarabic=64621;e.bekatakana=12505;e.benarmenian=1378;e.bet=1489;e.beta=946;e.betasymbolgreek=976;e.betdagesh=64305;e.betdageshhebrew=64305;e.bethebrew=1489;e.betrafehebrew=64332;e.bhabengali=2477;e.bhadeva=2349;e.bhagujarati=2733;e.bhagurmukhi=2605;e.bhook=595;e.bihiragana=12403;e.bikatakana=12499;e.bilabialclick=664;e.bindigurmukhi=2562;e.birusquare=13105;e.blackcircle=9679;e.blackdiamond=9670;e.blackdownpointingtriangle=9660;e.blackleftpointingpointer=9668;e.blackleftpointingtriangle=9664;e.blacklenticularbracketleft=12304;e.blacklenticularbracketleftvertical=65083;e.blacklenticularbracketright=12305;e.blacklenticularbracketrightvertical=65084;e.blacklowerlefttriangle=9699;e.blacklowerrighttriangle=9698;e.blackrectangle=9644;e.blackrightpointingpointer=9658;e.blackrightpointingtriangle=9654;e.blacksmallsquare=9642;e.blacksmilingface=9787;e.blacksquare=9632;e.blackstar=9733;e.blackupperlefttriangle=9700;e.blackupperrighttriangle=9701;e.blackuppointingsmalltriangle=9652;e.blackuppointingtriangle=9650;e.blank=9251;e.blinebelow=7687;e.block=9608;e.bmonospace=65346;e.bobaimaithai=3610;e.bohiragana=12412;e.bokatakana=12508;e.bparen=9373;e.bqsquare=13251;e.braceex=63732;e.braceleft=123;e.braceleftbt=63731;e.braceleftmid=63730;e.braceleftmonospace=65371;e.braceleftsmall=65115;e.bracelefttp=63729;e.braceleftvertical=65079;e.braceright=125;e.bracerightbt=63742;e.bracerightmid=63741;e.bracerightmonospace=65373;e.bracerightsmall=65116;e.bracerighttp=63740;e.bracerightvertical=65080;e.bracketleft=91;e.bracketleftbt=63728;e.bracketleftex=63727;e.bracketleftmonospace=65339;e.bracketlefttp=63726;e.bracketright=93;e.bracketrightbt=63739;e.bracketrightex=63738;e.bracketrightmonospace=65341;e.bracketrighttp=63737;e.breve=728;e.brevebelowcmb=814;e.brevecmb=774;e.breveinvertedbelowcmb=815;e.breveinvertedcmb=785;e.breveinverteddoublecmb=865;e.bridgebelowcmb=810;e.bridgeinvertedbelowcmb=826;e.brokenbar=166;e.bstroke=384;e.bsuperior=63210;e.btopbar=387;e.buhiragana=12406;e.bukatakana=12502;e.bullet=8226;e.bulletinverse=9688;e.bulletoperator=8729;e.bullseye=9678;e.c=99;e.caarmenian=1390;e.cabengali=2458;e.cacute=263;e.cadeva=2330;e.cagujarati=2714;e.cagurmukhi=2586;e.calsquare=13192;e.candrabindubengali=2433;e.candrabinducmb=784;e.candrabindudeva=2305;e.candrabindugujarati=2689;e.capslock=8682;e.careof=8453;e.caron=711;e.caronbelowcmb=812;e.caroncmb=780;e.carriagereturn=8629;e.cbopomofo=12568;e.ccaron=269;e.ccedilla=231;e.ccedillaacute=7689;e.ccircle=9426;e.ccircumflex=265;e.ccurl=597;e.cdot=267;e.cdotaccent=267;e.cdsquare=13253;e.cedilla=184;e.cedillacmb=807;e.cent=162;e.centigrade=8451;e.centinferior=63199;e.centmonospace=65504;e.centoldstyle=63394;e.centsuperior=63200;e.chaarmenian=1401;e.chabengali=2459;e.chadeva=2331;e.chagujarati=2715;e.chagurmukhi=2587;e.chbopomofo=12564;e.cheabkhasiancyrillic=1213;e.checkmark=10003;e.checyrillic=1095;e.chedescenderabkhasiancyrillic=1215;e.chedescendercyrillic=1207;e.chedieresiscyrillic=1269;e.cheharmenian=1395;e.chekhakassiancyrillic=1228;e.cheverticalstrokecyrillic=1209;e.chi=967;e.chieuchacirclekorean=12919;e.chieuchaparenkorean=12823;e.chieuchcirclekorean=12905;e.chieuchkorean=12618;e.chieuchparenkorean=12809;e.chochangthai=3594;e.chochanthai=3592;e.chochingthai=3593;e.chochoethai=3596;e.chook=392;e.cieucacirclekorean=12918;e.cieucaparenkorean=12822;e.cieuccirclekorean=12904;e.cieuckorean=12616;e.cieucparenkorean=12808;e.cieucuparenkorean=12828;e.circle=9675;e.circlecopyrt=169;e.circlemultiply=8855;e.circleot=8857;e.circleplus=8853;e.circlepostalmark=12342;e.circlewithlefthalfblack=9680;e.circlewithrighthalfblack=9681;e.circumflex=710;e.circumflexbelowcmb=813;e.circumflexcmb=770;e.clear=8999;e.clickalveolar=450;e.clickdental=448;e.clicklateral=449;e.clickretroflex=451;e.club=9827;e.clubsuitblack=9827;e.clubsuitwhite=9831;e.cmcubedsquare=13220;e.cmonospace=65347;e.cmsquaredsquare=13216;e.coarmenian=1409;e.colon=58;e.colonmonetary=8353;e.colonmonospace=65306;e.colonsign=8353;e.colonsmall=65109;e.colontriangularhalfmod=721;e.colontriangularmod=720;e.comma=44;e.commaabovecmb=787;e.commaaboverightcmb=789;e.commaaccent=63171;e.commaarabic=1548;e.commaarmenian=1373;e.commainferior=63201;e.commamonospace=65292;e.commareversedabovecmb=788;e.commareversedmod=701;e.commasmall=65104;e.commasuperior=63202;e.commaturnedabovecmb=786;e.commaturnedmod=699;e.compass=9788;e.congruent=8773;e.contourintegral=8750;e.control=8963;e.controlACK=6;e.controlBEL=7;e.controlBS=8;e.controlCAN=24;e.controlCR=13;e.controlDC1=17;e.controlDC2=18;e.controlDC3=19;e.controlDC4=20;e.controlDEL=127;e.controlDLE=16;e.controlEM=25;e.controlENQ=5;e.controlEOT=4;e.controlESC=27;e.controlETB=23;e.controlETX=3;e.controlFF=12;e.controlFS=28;e.controlGS=29;e.controlHT=9;e.controlLF=10;e.controlNAK=21;e.controlNULL=0;e.controlRS=30;e.controlSI=15;e.controlSO=14;e.controlSOT=2;e.controlSTX=1;e.controlSUB=26;e.controlSYN=22;e.controlUS=31;e.controlVT=11;e.copyright=169;e.copyrightsans=63721;e.copyrightserif=63193;e.cornerbracketleft=12300;e.cornerbracketlefthalfwidth=65378;e.cornerbracketleftvertical=65089;e.cornerbracketright=12301;e.cornerbracketrighthalfwidth=65379;e.cornerbracketrightvertical=65090;e.corporationsquare=13183;e.cosquare=13255;e.coverkgsquare=13254;e.cparen=9374;e.cruzeiro=8354;e.cstretched=663;e.curlyand=8911;e.curlyor=8910;e.currency=164;e.cyrBreve=63185;e.cyrFlex=63186;e.cyrbreve=63188;e.cyrflex=63189;e.d=100;e.daarmenian=1380;e.dabengali=2470;e.dadarabic=1590;e.dadeva=2342;e.dadfinalarabic=65214;e.dadinitialarabic=65215;e.dadmedialarabic=65216;e.dagesh=1468;e.dageshhebrew=1468;e.dagger=8224;e.daggerdbl=8225;e.dagujarati=2726;e.dagurmukhi=2598;e.dahiragana=12384;e.dakatakana=12480;e.dalarabic=1583;e.dalet=1491;e.daletdagesh=64307;e.daletdageshhebrew=64307;e.dalethebrew=1491;e.dalfinalarabic=65194;e.dammaarabic=1615;e.dammalowarabic=1615;e.dammatanaltonearabic=1612;e.dammatanarabic=1612;e.danda=2404;e.dargahebrew=1447;e.dargalefthebrew=1447;e.dasiapneumatacyrilliccmb=1157;e.dblGrave=63187;e.dblanglebracketleft=12298;e.dblanglebracketleftvertical=65085;e.dblanglebracketright=12299;e.dblanglebracketrightvertical=65086;e.dblarchinvertedbelowcmb=811;e.dblarrowleft=8660;e.dblarrowright=8658;e.dbldanda=2405;e.dblgrave=63190;e.dblgravecmb=783;e.dblintegral=8748;e.dbllowline=8215;e.dbllowlinecmb=819;e.dbloverlinecmb=831;e.dblprimemod=698;e.dblverticalbar=8214;e.dblverticallineabovecmb=782;e.dbopomofo=12553;e.dbsquare=13256;e.dcaron=271;e.dcedilla=7697;e.dcircle=9427;e.dcircumflexbelow=7699;e.dcroat=273;e.ddabengali=2465;e.ddadeva=2337;e.ddagujarati=2721;e.ddagurmukhi=2593;e.ddalarabic=1672;e.ddalfinalarabic=64393;e.dddhadeva=2396;e.ddhabengali=2466;e.ddhadeva=2338;e.ddhagujarati=2722;e.ddhagurmukhi=2594;e.ddotaccent=7691;e.ddotbelow=7693;e.decimalseparatorarabic=1643;e.decimalseparatorpersian=1643;e.decyrillic=1076;e.degree=176;e.dehihebrew=1453;e.dehiragana=12391;e.deicoptic=1007;e.dekatakana=12487;e.deleteleft=9003;e.deleteright=8998;e.delta=948;e.deltaturned=397;e.denominatorminusonenumeratorbengali=2552;e.dezh=676;e.dhabengali=2471;e.dhadeva=2343;e.dhagujarati=2727;e.dhagurmukhi=2599;e.dhook=599;e.dialytikatonos=901;e.dialytikatonoscmb=836;e.diamond=9830;e.diamondsuitwhite=9826;e.dieresis=168;e.dieresisacute=63191;e.dieresisbelowcmb=804;e.dieresiscmb=776;e.dieresisgrave=63192;e.dieresistonos=901;e.dihiragana=12386;e.dikatakana=12482;e.dittomark=12291;e.divide=247;e.divides=8739;e.divisionslash=8725;e.djecyrillic=1106;e.dkshade=9619;e.dlinebelow=7695;e.dlsquare=13207;e.dmacron=273;e.dmonospace=65348;e.dnblock=9604;e.dochadathai=3598;e.dodekthai=3604;e.dohiragana=12393;e.dokatakana=12489;e.dollar=36;e.dollarinferior=63203;e.dollarmonospace=65284;e.dollaroldstyle=63268;e.dollarsmall=65129;e.dollarsuperior=63204;e.dong=8363;e.dorusquare=13094;e.dotaccent=729;e.dotaccentcmb=775;e.dotbelowcmb=803;e.dotbelowcomb=803;e.dotkatakana=12539;e.dotlessi=305;e.dotlessj=63166;e.dotlessjstrokehook=644;e.dotmath=8901;e.dottedcircle=9676;e.doubleyodpatah=64287;e.doubleyodpatahhebrew=64287;e.downtackbelowcmb=798;e.downtackmod=725;e.dparen=9375;e.dsuperior=63211;e.dtail=598;e.dtopbar=396;e.duhiragana=12389;e.dukatakana=12485;e.dz=499;e.dzaltone=675;e.dzcaron=454;e.dzcurl=677;e.dzeabkhasiancyrillic=1249;e.dzecyrillic=1109;e.dzhecyrillic=1119;e.e=101;e.eacute=233;e.earth=9793;e.ebengali=2447;e.ebopomofo=12572;e.ebreve=277;e.ecandradeva=2317;e.ecandragujarati=2701;e.ecandravowelsigndeva=2373;e.ecandravowelsigngujarati=2757;e.ecaron=283;e.ecedillabreve=7709;e.echarmenian=1381;e.echyiwnarmenian=1415;e.ecircle=9428;e.ecircumflex=234;e.ecircumflexacute=7871;e.ecircumflexbelow=7705;e.ecircumflexdotbelow=7879;e.ecircumflexgrave=7873;e.ecircumflexhookabove=7875;e.ecircumflextilde=7877;e.ecyrillic=1108;e.edblgrave=517;e.edeva=2319;e.edieresis=235;e.edot=279;e.edotaccent=279;e.edotbelow=7865;e.eegurmukhi=2575;e.eematragurmukhi=2631;e.efcyrillic=1092;e.egrave=232;e.egujarati=2703;e.eharmenian=1383;e.ehbopomofo=12573;e.ehiragana=12360;e.ehookabove=7867;e.eibopomofo=12575;e.eight=56;e.eightarabic=1640;e.eightbengali=2542;e.eightcircle=9319;e.eightcircleinversesansserif=10129;e.eightdeva=2414;e.eighteencircle=9329;e.eighteenparen=9349;e.eighteenperiod=9369;e.eightgujarati=2798;e.eightgurmukhi=2670;e.eighthackarabic=1640;e.eighthangzhou=12328;e.eighthnotebeamed=9835;e.eightideographicparen=12839;e.eightinferior=8328;e.eightmonospace=65304;e.eightoldstyle=63288;e.eightparen=9339;e.eightperiod=9359;e.eightpersian=1784;e.eightroman=8567;e.eightsuperior=8312;e.eightthai=3672;e.einvertedbreve=519;e.eiotifiedcyrillic=1125;e.ekatakana=12456;e.ekatakanahalfwidth=65396;e.ekonkargurmukhi=2676;e.ekorean=12628;e.elcyrillic=1083;e.element=8712;e.elevencircle=9322;e.elevenparen=9342;e.elevenperiod=9362;e.elevenroman=8570;e.ellipsis=8230;e.ellipsisvertical=8942;e.emacron=275;e.emacronacute=7703;e.emacrongrave=7701;e.emcyrillic=1084;e.emdash=8212;e.emdashvertical=65073;e.emonospace=65349;e.emphasismarkarmenian=1371;e.emptyset=8709;e.enbopomofo=12579;e.encyrillic=1085;e.endash=8211;e.endashvertical=65074;e.endescendercyrillic=1187;e.eng=331;e.engbopomofo=12581;e.enghecyrillic=1189;e.enhookcyrillic=1224;e.enspace=8194;e.eogonek=281;e.eokorean=12627;e.eopen=603;e.eopenclosed=666;e.eopenreversed=604;e.eopenreversedclosed=606;e.eopenreversedhook=605;e.eparen=9376;e.epsilon=949;e.epsilontonos=941;e.equal=61;e.equalmonospace=65309;e.equalsmall=65126;e.equalsuperior=8316;e.equivalence=8801;e.erbopomofo=12582;e.ercyrillic=1088;e.ereversed=600;e.ereversedcyrillic=1101;e.escyrillic=1089;e.esdescendercyrillic=1195;e.esh=643;e.eshcurl=646;e.eshortdeva=2318;e.eshortvowelsigndeva=2374;e.eshreversedloop=426;e.eshsquatreversed=645;e.esmallhiragana=12359;e.esmallkatakana=12455;e.esmallkatakanahalfwidth=65386;e.estimated=8494;e.esuperior=63212;e.eta=951;e.etarmenian=1384;e.etatonos=942;e.eth=240;e.etilde=7869;e.etildebelow=7707;e.etnahtafoukhhebrew=1425;e.etnahtafoukhlefthebrew=1425;e.etnahtahebrew=1425;e.etnahtalefthebrew=1425;e.eturned=477;e.eukorean=12641;e.euro=8364;e.evowelsignbengali=2503;e.evowelsigndeva=2375;e.evowelsigngujarati=2759;e.exclam=33;e.exclamarmenian=1372;e.exclamdbl=8252;e.exclamdown=161;e.exclamdownsmall=63393;e.exclammonospace=65281;e.exclamsmall=63265;e.existential=8707;e.ezh=658;e.ezhcaron=495;e.ezhcurl=659;e.ezhreversed=441;e.ezhtail=442;e.f=102;e.fadeva=2398;e.fagurmukhi=2654;e.fahrenheit=8457;e.fathaarabic=1614;e.fathalowarabic=1614;e.fathatanarabic=1611;e.fbopomofo=12552;e.fcircle=9429;e.fdotaccent=7711;e.feharabic=1601;e.feharmenian=1414;e.fehfinalarabic=65234;e.fehinitialarabic=65235;e.fehmedialarabic=65236;e.feicoptic=997;e.female=9792;e.ff=64256;e.ffi=64259;e.ffl=64260;e.fi=64257;e.fifteencircle=9326;e.fifteenparen=9346;e.fifteenperiod=9366;e.figuredash=8210;e.filledbox=9632;e.filledrect=9644;e.finalkaf=1498;e.finalkafdagesh=64314;e.finalkafdageshhebrew=64314;e.finalkafhebrew=1498;e.finalmem=1501;e.finalmemhebrew=1501;e.finalnun=1503;e.finalnunhebrew=1503;e.finalpe=1507;e.finalpehebrew=1507;e.finaltsadi=1509;e.finaltsadihebrew=1509;e.firsttonechinese=713;e.fisheye=9673;e.fitacyrillic=1139;e.five=53;e.fivearabic=1637;e.fivebengali=2539;e.fivecircle=9316;e.fivecircleinversesansserif=10126;e.fivedeva=2411;e.fiveeighths=8541;e.fivegujarati=2795;e.fivegurmukhi=2667;e.fivehackarabic=1637;e.fivehangzhou=12325;e.fiveideographicparen=12836;e.fiveinferior=8325;e.fivemonospace=65301;e.fiveoldstyle=63285;e.fiveparen=9336;e.fiveperiod=9356;e.fivepersian=1781;e.fiveroman=8564;e.fivesuperior=8309;e.fivethai=3669;e.fl=64258;e.florin=402;e.fmonospace=65350;e.fmsquare=13209;e.fofanthai=3615;e.fofathai=3613;e.fongmanthai=3663;e.forall=8704;e.four=52;e.fourarabic=1636;e.fourbengali=2538;e.fourcircle=9315;e.fourcircleinversesansserif=10125;e.fourdeva=2410;e.fourgujarati=2794;e.fourgurmukhi=2666;e.fourhackarabic=1636;e.fourhangzhou=12324;e.fourideographicparen=12835;e.fourinferior=8324;e.fourmonospace=65300;e.fournumeratorbengali=2551;e.fouroldstyle=63284;e.fourparen=9335;e.fourperiod=9355;e.fourpersian=1780;e.fourroman=8563;e.foursuperior=8308;e.fourteencircle=9325;e.fourteenparen=9345;e.fourteenperiod=9365;e.fourthai=3668;e.fourthtonechinese=715;e.fparen=9377;e.fraction=8260;e.franc=8355;e.g=103;e.gabengali=2455;e.gacute=501;e.gadeva=2327;e.gafarabic=1711;e.gaffinalarabic=64403;e.gafinitialarabic=64404;e.gafmedialarabic=64405;e.gagujarati=2711;e.gagurmukhi=2583;e.gahiragana=12364;e.gakatakana=12460;e.gamma=947;e.gammalatinsmall=611;e.gammasuperior=736;e.gangiacoptic=1003;e.gbopomofo=12557;e.gbreve=287;e.gcaron=487;e.gcedilla=291;e.gcircle=9430;e.gcircumflex=285;e.gcommaaccent=291;e.gdot=289;e.gdotaccent=289;e.gecyrillic=1075;e.gehiragana=12370;e.gekatakana=12466;e.geometricallyequal=8785;e.gereshaccenthebrew=1436;e.gereshhebrew=1523;e.gereshmuqdamhebrew=1437;e.germandbls=223;e.gershayimaccenthebrew=1438;e.gershayimhebrew=1524;e.getamark=12307;e.ghabengali=2456;e.ghadarmenian=1394;e.ghadeva=2328;e.ghagujarati=2712;e.ghagurmukhi=2584;e.ghainarabic=1594;e.ghainfinalarabic=65230;e.ghaininitialarabic=65231;e.ghainmedialarabic=65232;e.ghemiddlehookcyrillic=1173;e.ghestrokecyrillic=1171;e.gheupturncyrillic=1169;e.ghhadeva=2394;e.ghhagurmukhi=2650;e.ghook=608;e.ghzsquare=13203;e.gihiragana=12366;e.gikatakana=12462;e.gimarmenian=1379;e.gimel=1490;e.gimeldagesh=64306;e.gimeldageshhebrew=64306;e.gimelhebrew=1490;e.gjecyrillic=1107;e.glottalinvertedstroke=446;e.glottalstop=660;e.glottalstopinverted=662;e.glottalstopmod=704;e.glottalstopreversed=661;e.glottalstopreversedmod=705;e.glottalstopreversedsuperior=740;e.glottalstopstroke=673;e.glottalstopstrokereversed=674;e.gmacron=7713;e.gmonospace=65351;e.gohiragana=12372;e.gokatakana=12468;e.gparen=9378;e.gpasquare=13228;e.gradient=8711;e.grave=96;e.gravebelowcmb=790;e.gravecmb=768;e.gravecomb=768;e.gravedeva=2387;e.gravelowmod=718;e.gravemonospace=65344;e.gravetonecmb=832;e.greater=62;e.greaterequal=8805;e.greaterequalorless=8923;e.greatermonospace=65310;e.greaterorequivalent=8819;e.greaterorless=8823;e.greateroverequal=8807;e.greatersmall=65125;e.gscript=609;e.gstroke=485;e.guhiragana=12368;e.guillemotleft=171;e.guillemotright=187;e.guilsinglleft=8249;e.guilsinglright=8250;e.gukatakana=12464;e.guramusquare=13080;e.gysquare=13257;e.h=104;e.haabkhasiancyrillic=1193;e.haaltonearabic=1729;e.habengali=2489;e.hadescendercyrillic=1203;e.hadeva=2361;e.hagujarati=2745;e.hagurmukhi=2617;e.haharabic=1581;e.hahfinalarabic=65186;e.hahinitialarabic=65187;e.hahiragana=12399;e.hahmedialarabic=65188;e.haitusquare=13098;e.hakatakana=12495;e.hakatakanahalfwidth=65418;e.halantgurmukhi=2637;e.hamzaarabic=1569;e.hamzalowarabic=1569;e.hangulfiller=12644;e.hardsigncyrillic=1098;e.harpoonleftbarbup=8636;e.harpoonrightbarbup=8640;e.hasquare=13258;e.hatafpatah=1458;e.hatafpatah16=1458;e.hatafpatah23=1458;e.hatafpatah2f=1458;e.hatafpatahhebrew=1458;e.hatafpatahnarrowhebrew=1458;e.hatafpatahquarterhebrew=1458;e.hatafpatahwidehebrew=1458;e.hatafqamats=1459;e.hatafqamats1b=1459;e.hatafqamats28=1459;e.hatafqamats34=1459;e.hatafqamatshebrew=1459;e.hatafqamatsnarrowhebrew=1459;e.hatafqamatsquarterhebrew=1459;e.hatafqamatswidehebrew=1459;e.hatafsegol=1457;e.hatafsegol17=1457;e.hatafsegol24=1457;e.hatafsegol30=1457;e.hatafsegolhebrew=1457;e.hatafsegolnarrowhebrew=1457;e.hatafsegolquarterhebrew=1457;e.hatafsegolwidehebrew=1457;e.hbar=295;e.hbopomofo=12559;e.hbrevebelow=7723;e.hcedilla=7721;e.hcircle=9431;e.hcircumflex=293;e.hdieresis=7719;e.hdotaccent=7715;e.hdotbelow=7717;e.he=1492;e.heart=9829;e.heartsuitblack=9829;e.heartsuitwhite=9825;e.hedagesh=64308;e.hedageshhebrew=64308;e.hehaltonearabic=1729;e.heharabic=1607;e.hehebrew=1492;e.hehfinalaltonearabic=64423;e.hehfinalalttwoarabic=65258;e.hehfinalarabic=65258;e.hehhamzaabovefinalarabic=64421;e.hehhamzaaboveisolatedarabic=64420;e.hehinitialaltonearabic=64424;e.hehinitialarabic=65259;e.hehiragana=12408;e.hehmedialaltonearabic=64425;e.hehmedialarabic=65260;e.heiseierasquare=13179;e.hekatakana=12504;e.hekatakanahalfwidth=65421;e.hekutaarusquare=13110;e.henghook=615;e.herutusquare=13113;e.het=1495;e.hethebrew=1495;e.hhook=614;e.hhooksuperior=689;e.hieuhacirclekorean=12923;e.hieuhaparenkorean=12827;e.hieuhcirclekorean=12909;e.hieuhkorean=12622;e.hieuhparenkorean=12813;e.hihiragana=12402;e.hikatakana=12498;e.hikatakanahalfwidth=65419;e.hiriq=1460;e.hiriq14=1460;e.hiriq21=1460;e.hiriq2d=1460;e.hiriqhebrew=1460;e.hiriqnarrowhebrew=1460;e.hiriqquarterhebrew=1460;e.hiriqwidehebrew=1460;e.hlinebelow=7830;e.hmonospace=65352;e.hoarmenian=1392;e.hohipthai=3627;e.hohiragana=12411;e.hokatakana=12507;e.hokatakanahalfwidth=65422;e.holam=1465;e.holam19=1465;e.holam26=1465;e.holam32=1465;e.holamhebrew=1465;e.holamnarrowhebrew=1465;e.holamquarterhebrew=1465;e.holamwidehebrew=1465;e.honokhukthai=3630;e.hookabovecomb=777;e.hookcmb=777;e.hookpalatalizedbelowcmb=801;e.hookretroflexbelowcmb=802;e.hoonsquare=13122;e.horicoptic=1001;e.horizontalbar=8213;e.horncmb=795;e.hotsprings=9832;e.house=8962;e.hparen=9379;e.hsuperior=688;e.hturned=613;e.huhiragana=12405;e.huiitosquare=13107;e.hukatakana=12501;e.hukatakanahalfwidth=65420;e.hungarumlaut=733;e.hungarumlautcmb=779;e.hv=405;e.hyphen=45;e.hypheninferior=63205;e.hyphenmonospace=65293;e.hyphensmall=65123;e.hyphensuperior=63206;e.hyphentwo=8208;e.i=105;e.iacute=237;e.iacyrillic=1103;e.ibengali=2439;e.ibopomofo=12583;e.ibreve=301;e.icaron=464;e.icircle=9432;e.icircumflex=238;e.icyrillic=1110;e.idblgrave=521;e.ideographearthcircle=12943;e.ideographfirecircle=12939;e.ideographicallianceparen=12863;e.ideographiccallparen=12858;e.ideographiccentrecircle=12965;e.ideographicclose=12294;e.ideographiccomma=12289;e.ideographiccommaleft=65380;e.ideographiccongratulationparen=12855;e.ideographiccorrectcircle=12963;e.ideographicearthparen=12847;e.ideographicenterpriseparen=12861;e.ideographicexcellentcircle=12957;e.ideographicfestivalparen=12864;e.ideographicfinancialcircle=12950;e.ideographicfinancialparen=12854;e.ideographicfireparen=12843;e.ideographichaveparen=12850;e.ideographichighcircle=12964;e.ideographiciterationmark=12293;e.ideographiclaborcircle=12952;e.ideographiclaborparen=12856;e.ideographicleftcircle=12967;e.ideographiclowcircle=12966;e.ideographicmedicinecircle=12969;e.ideographicmetalparen=12846;e.ideographicmoonparen=12842;e.ideographicnameparen=12852;e.ideographicperiod=12290;e.ideographicprintcircle=12958;e.ideographicreachparen=12867;e.ideographicrepresentparen=12857;e.ideographicresourceparen=12862;e.ideographicrightcircle=12968;e.ideographicsecretcircle=12953;e.ideographicselfparen=12866;e.ideographicsocietyparen=12851;e.ideographicspace=12288;e.ideographicspecialparen=12853;e.ideographicstockparen=12849;e.ideographicstudyparen=12859;e.ideographicsunparen=12848;e.ideographicsuperviseparen=12860;e.ideographicwaterparen=12844;e.ideographicwoodparen=12845;e.ideographiczero=12295;e.ideographmetalcircle=12942;e.ideographmooncircle=12938;e.ideographnamecircle=12948;e.ideographsuncircle=12944;e.ideographwatercircle=12940;e.ideographwoodcircle=12941;e.ideva=2311;e.idieresis=239;e.idieresisacute=7727;e.idieresiscyrillic=1253;e.idotbelow=7883;e.iebrevecyrillic=1239;e.iecyrillic=1077;e.ieungacirclekorean=12917;e.ieungaparenkorean=12821;e.ieungcirclekorean=12903;e.ieungkorean=12615;e.ieungparenkorean=12807;e.igrave=236;e.igujarati=2695;e.igurmukhi=2567;e.ihiragana=12356;e.ihookabove=7881;e.iibengali=2440;e.iicyrillic=1080;e.iideva=2312;e.iigujarati=2696;e.iigurmukhi=2568;e.iimatragurmukhi=2624;e.iinvertedbreve=523;e.iishortcyrillic=1081;e.iivowelsignbengali=2496;e.iivowelsigndeva=2368;e.iivowelsigngujarati=2752;e.ij=307;e.ikatakana=12452;e.ikatakanahalfwidth=65394;e.ikorean=12643;e.ilde=732;e.iluyhebrew=1452;e.imacron=299;e.imacroncyrillic=1251;e.imageorapproximatelyequal=8787;e.imatragurmukhi=2623;e.imonospace=65353;e.increment=8710;e.infinity=8734;e.iniarmenian=1387;e.integral=8747;e.integralbottom=8993;e.integralbt=8993;e.integralex=63733;e.integraltop=8992;e.integraltp=8992;e.intersection=8745;e.intisquare=13061;e.invbullet=9688;e.invcircle=9689;e.invsmileface=9787;e.iocyrillic=1105;e.iogonek=303;e.iota=953;e.iotadieresis=970;e.iotadieresistonos=912;e.iotalatin=617;e.iotatonos=943;e.iparen=9380;e.irigurmukhi=2674;e.ismallhiragana=12355;e.ismallkatakana=12451;e.ismallkatakanahalfwidth=65384;e.issharbengali=2554;e.istroke=616;e.isuperior=63213;e.iterationhiragana=12445;e.iterationkatakana=12541;e.itilde=297;e.itildebelow=7725;e.iubopomofo=12585;e.iucyrillic=1102;e.ivowelsignbengali=2495;e.ivowelsigndeva=2367;e.ivowelsigngujarati=2751;e.izhitsacyrillic=1141;e.izhitsadblgravecyrillic=1143;e.j=106;e.jaarmenian=1393;e.jabengali=2460;e.jadeva=2332;e.jagujarati=2716;e.jagurmukhi=2588;e.jbopomofo=12560;e.jcaron=496;e.jcircle=9433;e.jcircumflex=309;e.jcrossedtail=669;e.jdotlessstroke=607;e.jecyrillic=1112;e.jeemarabic=1580;e.jeemfinalarabic=65182;e.jeeminitialarabic=65183;e.jeemmedialarabic=65184;e.jeharabic=1688;e.jehfinalarabic=64395;e.jhabengali=2461;e.jhadeva=2333;e.jhagujarati=2717;e.jhagurmukhi=2589;e.jheharmenian=1403;e.jis=12292;e.jmonospace=65354;e.jparen=9381;e.jsuperior=690;e.k=107;e.kabashkircyrillic=1185;e.kabengali=2453;e.kacute=7729;e.kacyrillic=1082;e.kadescendercyrillic=1179;e.kadeva=2325;e.kaf=1499;e.kafarabic=1603;e.kafdagesh=64315;e.kafdageshhebrew=64315;e.kaffinalarabic=65242;e.kafhebrew=1499;e.kafinitialarabic=65243;e.kafmedialarabic=65244;e.kafrafehebrew=64333;e.kagujarati=2709;e.kagurmukhi=2581;e.kahiragana=12363;e.kahookcyrillic=1220;e.kakatakana=12459;e.kakatakanahalfwidth=65398;e.kappa=954;e.kappasymbolgreek=1008;e.kapyeounmieumkorean=12657;e.kapyeounphieuphkorean=12676;e.kapyeounpieupkorean=12664;e.kapyeounssangpieupkorean=12665;e.karoriisquare=13069;e.kashidaautoarabic=1600;e.kashidaautonosidebearingarabic=1600;e.kasmallkatakana=12533;e.kasquare=13188;e.kasraarabic=1616;e.kasratanarabic=1613;e.kastrokecyrillic=1183;e.katahiraprolongmarkhalfwidth=65392;e.kaverticalstrokecyrillic=1181;e.kbopomofo=12558;e.kcalsquare=13193;e.kcaron=489;e.kcedilla=311;e.kcircle=9434;e.kcommaaccent=311;e.kdotbelow=7731;e.keharmenian=1412;e.kehiragana=12369;e.kekatakana=12465;e.kekatakanahalfwidth=65401;e.kenarmenian=1391;e.kesmallkatakana=12534;e.kgreenlandic=312;e.khabengali=2454;e.khacyrillic=1093;e.khadeva=2326;e.khagujarati=2710;e.khagurmukhi=2582;e.khaharabic=1582;e.khahfinalarabic=65190;e.khahinitialarabic=65191;e.khahmedialarabic=65192;e.kheicoptic=999;e.khhadeva=2393;e.khhagurmukhi=2649;e.khieukhacirclekorean=12920;e.khieukhaparenkorean=12824;e.khieukhcirclekorean=12906;e.khieukhkorean=12619;e.khieukhparenkorean=12810;e.khokhaithai=3586;e.khokhonthai=3589;e.khokhuatthai=3587;e.khokhwaithai=3588;e.khomutthai=3675;e.khook=409;e.khorakhangthai=3590;e.khzsquare=13201;e.kihiragana=12365;e.kikatakana=12461;e.kikatakanahalfwidth=65399;e.kiroguramusquare=13077;e.kiromeetorusquare=13078;e.kirosquare=13076;e.kiyeokacirclekorean=12910;e.kiyeokaparenkorean=12814;e.kiyeokcirclekorean=12896;e.kiyeokkorean=12593;e.kiyeokparenkorean=12800;e.kiyeoksioskorean=12595;e.kjecyrillic=1116;e.klinebelow=7733;e.klsquare=13208;e.kmcubedsquare=13222;e.kmonospace=65355;e.kmsquaredsquare=13218;e.kohiragana=12371;e.kohmsquare=13248;e.kokaithai=3585;e.kokatakana=12467;e.kokatakanahalfwidth=65402;e.kooposquare=13086;e.koppacyrillic=1153;e.koreanstandardsymbol=12927;e.koroniscmb=835;e.kparen=9382;e.kpasquare=13226;e.ksicyrillic=1135;e.ktsquare=13263;e.kturned=670;e.kuhiragana=12367;e.kukatakana=12463;e.kukatakanahalfwidth=65400;e.kvsquare=13240;e.kwsquare=13246;e.l=108;e.labengali=2482;e.lacute=314;e.ladeva=2354;e.lagujarati=2738;e.lagurmukhi=2610;e.lakkhangyaothai=3653;e.lamaleffinalarabic=65276;e.lamalefhamzaabovefinalarabic=65272;e.lamalefhamzaaboveisolatedarabic=65271;e.lamalefhamzabelowfinalarabic=65274;e.lamalefhamzabelowisolatedarabic=65273;e.lamalefisolatedarabic=65275;e.lamalefmaddaabovefinalarabic=65270;e.lamalefmaddaaboveisolatedarabic=65269;e.lamarabic=1604;e.lambda=955;e.lambdastroke=411;e.lamed=1500;e.lameddagesh=64316;e.lameddageshhebrew=64316;e.lamedhebrew=1500;e.lamfinalarabic=65246;e.lamhahinitialarabic=64714;e.laminitialarabic=65247;e.lamjeeminitialarabic=64713;e.lamkhahinitialarabic=64715;e.lamlamhehisolatedarabic=65010;e.lammedialarabic=65248;e.lammeemhahinitialarabic=64904;e.lammeeminitialarabic=64716;e.largecircle=9711;e.lbar=410;e.lbelt=620;e.lbopomofo=12556;e.lcaron=318;e.lcedilla=316;e.lcircle=9435;e.lcircumflexbelow=7741;e.lcommaaccent=316;e.ldot=320;e.ldotaccent=320;e.ldotbelow=7735;e.ldotbelowmacron=7737;e.leftangleabovecmb=794;e.lefttackbelowcmb=792;e.less=60;e.lessequal=8804;e.lessequalorgreater=8922;e.lessmonospace=65308;e.lessorequivalent=8818;e.lessorgreater=8822;e.lessoverequal=8806;e.lesssmall=65124;e.lezh=622;e.lfblock=9612;e.lhookretroflex=621;e.lira=8356;e.liwnarmenian=1388;e.lj=457;e.ljecyrillic=1113;e.ll=63168;e.lladeva=2355;e.llagujarati=2739;e.llinebelow=7739;e.llladeva=2356;e.llvocalicbengali=2529;e.llvocalicdeva=2401;e.llvocalicvowelsignbengali=2531;e.llvocalicvowelsigndeva=2403;e.lmiddletilde=619;e.lmonospace=65356;e.lmsquare=13264;e.lochulathai=3628;e.logicaland=8743;e.logicalnot=172;e.logicalnotreversed=8976;e.logicalor=8744;e.lolingthai=3621;e.longs=383;e.lowlinecenterline=65102;e.lowlinecmb=818;e.lowlinedashed=65101;e.lozenge=9674;e.lparen=9383;e.lslash=322;e.lsquare=8467;e.lsuperior=63214;e.ltshade=9617;e.luthai=3622;e.lvocalicbengali=2444;e.lvocalicdeva=2316;e.lvocalicvowelsignbengali=2530;e.lvocalicvowelsigndeva=2402;e.lxsquare=13267;e.m=109;e.mabengali=2478;e.macron=175;e.macronbelowcmb=817;e.macroncmb=772;e.macronlowmod=717;e.macronmonospace=65507;e.macute=7743;e.madeva=2350;e.magujarati=2734;e.magurmukhi=2606;e.mahapakhhebrew=1444;e.mahapakhlefthebrew=1444;e.mahiragana=12414;e.maichattawalowleftthai=63637;e.maichattawalowrightthai=63636;e.maichattawathai=3659;e.maichattawaupperleftthai=63635;e.maieklowleftthai=63628;e.maieklowrightthai=63627;e.maiekthai=3656;e.maiekupperleftthai=63626;e.maihanakatleftthai=63620;e.maihanakatthai=3633;e.maitaikhuleftthai=63625;e.maitaikhuthai=3655;e.maitholowleftthai=63631;e.maitholowrightthai=63630;e.maithothai=3657;e.maithoupperleftthai=63629;e.maitrilowleftthai=63634;e.maitrilowrightthai=63633;e.maitrithai=3658;e.maitriupperleftthai=63632;e.maiyamokthai=3654;e.makatakana=12510;e.makatakanahalfwidth=65423;e.male=9794;e.mansyonsquare=13127;e.maqafhebrew=1470;e.mars=9794;e.masoracirclehebrew=1455;e.masquare=13187;e.mbopomofo=12551;e.mbsquare=13268;e.mcircle=9436;e.mcubedsquare=13221;e.mdotaccent=7745;e.mdotbelow=7747;e.meemarabic=1605;e.meemfinalarabic=65250;e.meeminitialarabic=65251;e.meemmedialarabic=65252;e.meemmeeminitialarabic=64721;e.meemmeemisolatedarabic=64584;e.meetorusquare=13133;e.mehiragana=12417;e.meizierasquare=13182;e.mekatakana=12513;e.mekatakanahalfwidth=65426;e.mem=1502;e.memdagesh=64318;e.memdageshhebrew=64318;e.memhebrew=1502;e.menarmenian=1396;e.merkhahebrew=1445;e.merkhakefulahebrew=1446;e.merkhakefulalefthebrew=1446;e.merkhalefthebrew=1445;e.mhook=625;e.mhzsquare=13202;e.middledotkatakanahalfwidth=65381;e.middot=183;e.mieumacirclekorean=12914;e.mieumaparenkorean=12818;e.mieumcirclekorean=12900;e.mieumkorean=12609;e.mieumpansioskorean=12656;e.mieumparenkorean=12804;e.mieumpieupkorean=12654;e.mieumsioskorean=12655;e.mihiragana=12415;e.mikatakana=12511;e.mikatakanahalfwidth=65424;e.minus=8722;e.minusbelowcmb=800;e.minuscircle=8854;e.minusmod=727;e.minusplus=8723;e.minute=8242;e.miribaarusquare=13130;e.mirisquare=13129
+;e.mlonglegturned=624;e.mlsquare=13206;e.mmcubedsquare=13219;e.mmonospace=65357;e.mmsquaredsquare=13215;e.mohiragana=12418;e.mohmsquare=13249;e.mokatakana=12514;e.mokatakanahalfwidth=65427;e.molsquare=13270;e.momathai=3617;e.moverssquare=13223;e.moverssquaredsquare=13224;e.mparen=9384;e.mpasquare=13227;e.mssquare=13235;e.msuperior=63215;e.mturned=623;e.mu=181;e.mu1=181;e.muasquare=13186;e.muchgreater=8811;e.muchless=8810;e.mufsquare=13196;e.mugreek=956;e.mugsquare=13197;e.muhiragana=12416;e.mukatakana=12512;e.mukatakanahalfwidth=65425;e.mulsquare=13205;e.multiply=215;e.mumsquare=13211;e.munahhebrew=1443;e.munahlefthebrew=1443;e.musicalnote=9834;e.musicalnotedbl=9835;e.musicflatsign=9837;e.musicsharpsign=9839;e.mussquare=13234;e.muvsquare=13238;e.muwsquare=13244;e.mvmegasquare=13241;e.mvsquare=13239;e.mwmegasquare=13247;e.mwsquare=13245;e.n=110;e.nabengali=2472;e.nabla=8711;e.nacute=324;e.nadeva=2344;e.nagujarati=2728;e.nagurmukhi=2600;e.nahiragana=12394;e.nakatakana=12490;e.nakatakanahalfwidth=65413;e.napostrophe=329;e.nasquare=13185;e.nbopomofo=12555;e.nbspace=160;e.ncaron=328;e.ncedilla=326;e.ncircle=9437;e.ncircumflexbelow=7755;e.ncommaaccent=326;e.ndotaccent=7749;e.ndotbelow=7751;e.nehiragana=12397;e.nekatakana=12493;e.nekatakanahalfwidth=65416;e.newsheqelsign=8362;e.nfsquare=13195;e.ngabengali=2457;e.ngadeva=2329;e.ngagujarati=2713;e.ngagurmukhi=2585;e.ngonguthai=3591;e.nhiragana=12435;e.nhookleft=626;e.nhookretroflex=627;e.nieunacirclekorean=12911;e.nieunaparenkorean=12815;e.nieuncieuckorean=12597;e.nieuncirclekorean=12897;e.nieunhieuhkorean=12598;e.nieunkorean=12596;e.nieunpansioskorean=12648;e.nieunparenkorean=12801;e.nieunsioskorean=12647;e.nieuntikeutkorean=12646;e.nihiragana=12395;e.nikatakana=12491;e.nikatakanahalfwidth=65414;e.nikhahitleftthai=63641;e.nikhahitthai=3661;e.nine=57;e.ninearabic=1641;e.ninebengali=2543;e.ninecircle=9320;e.ninecircleinversesansserif=10130;e.ninedeva=2415;e.ninegujarati=2799;e.ninegurmukhi=2671;e.ninehackarabic=1641;e.ninehangzhou=12329;e.nineideographicparen=12840;e.nineinferior=8329;e.ninemonospace=65305;e.nineoldstyle=63289;e.nineparen=9340;e.nineperiod=9360;e.ninepersian=1785;e.nineroman=8568;e.ninesuperior=8313;e.nineteencircle=9330;e.nineteenparen=9350;e.nineteenperiod=9370;e.ninethai=3673;e.nj=460;e.njecyrillic=1114;e.nkatakana=12531;e.nkatakanahalfwidth=65437;e.nlegrightlong=414;e.nlinebelow=7753;e.nmonospace=65358;e.nmsquare=13210;e.nnabengali=2467;e.nnadeva=2339;e.nnagujarati=2723;e.nnagurmukhi=2595;e.nnnadeva=2345;e.nohiragana=12398;e.nokatakana=12494;e.nokatakanahalfwidth=65417;e.nonbreakingspace=160;e.nonenthai=3603;e.nonuthai=3609;e.noonarabic=1606;e.noonfinalarabic=65254;e.noonghunnaarabic=1722;e.noonghunnafinalarabic=64415;e.nooninitialarabic=65255;e.noonjeeminitialarabic=64722;e.noonjeemisolatedarabic=64587;e.noonmedialarabic=65256;e.noonmeeminitialarabic=64725;e.noonmeemisolatedarabic=64590;e.noonnoonfinalarabic=64653;e.notcontains=8716;e.notelement=8713;e.notelementof=8713;e.notequal=8800;e.notgreater=8815;e.notgreaternorequal=8817;e.notgreaternorless=8825;e.notidentical=8802;e.notless=8814;e.notlessnorequal=8816;e.notparallel=8742;e.notprecedes=8832;e.notsubset=8836;e.notsucceeds=8833;e.notsuperset=8837;e.nowarmenian=1398;e.nparen=9385;e.nssquare=13233;e.nsuperior=8319;e.ntilde=241;e.nu=957;e.nuhiragana=12396;e.nukatakana=12492;e.nukatakanahalfwidth=65415;e.nuktabengali=2492;e.nuktadeva=2364;e.nuktagujarati=2748;e.nuktagurmukhi=2620;e.numbersign=35;e.numbersignmonospace=65283;e.numbersignsmall=65119;e.numeralsigngreek=884;e.numeralsignlowergreek=885;e.numero=8470;e.nun=1504;e.nundagesh=64320;e.nundageshhebrew=64320;e.nunhebrew=1504;e.nvsquare=13237;e.nwsquare=13243;e.nyabengali=2462;e.nyadeva=2334;e.nyagujarati=2718;e.nyagurmukhi=2590;e.o=111;e.oacute=243;e.oangthai=3629;e.obarred=629;e.obarredcyrillic=1257;e.obarreddieresiscyrillic=1259;e.obengali=2451;e.obopomofo=12571;e.obreve=335;e.ocandradeva=2321;e.ocandragujarati=2705;e.ocandravowelsigndeva=2377;e.ocandravowelsigngujarati=2761;e.ocaron=466;e.ocircle=9438;e.ocircumflex=244;e.ocircumflexacute=7889;e.ocircumflexdotbelow=7897;e.ocircumflexgrave=7891;e.ocircumflexhookabove=7893;e.ocircumflextilde=7895;e.ocyrillic=1086;e.odblacute=337;e.odblgrave=525;e.odeva=2323;e.odieresis=246;e.odieresiscyrillic=1255;e.odotbelow=7885;e.oe=339;e.oekorean=12634;e.ogonek=731;e.ogonekcmb=808;e.ograve=242;e.ogujarati=2707;e.oharmenian=1413;e.ohiragana=12362;e.ohookabove=7887;e.ohorn=417;e.ohornacute=7899;e.ohorndotbelow=7907;e.ohorngrave=7901;e.ohornhookabove=7903;e.ohorntilde=7905;e.ohungarumlaut=337;e.oi=419;e.oinvertedbreve=527;e.okatakana=12458;e.okatakanahalfwidth=65397;e.okorean=12631;e.olehebrew=1451;e.omacron=333;e.omacronacute=7763;e.omacrongrave=7761;e.omdeva=2384;e.omega=969;e.omega1=982;e.omegacyrillic=1121;e.omegalatinclosed=631;e.omegaroundcyrillic=1147;e.omegatitlocyrillic=1149;e.omegatonos=974;e.omgujarati=2768;e.omicron=959;e.omicrontonos=972;e.omonospace=65359;e.one=49;e.onearabic=1633;e.onebengali=2535;e.onecircle=9312;e.onecircleinversesansserif=10122;e.onedeva=2407;e.onedotenleader=8228;e.oneeighth=8539;e.onefitted=63196;e.onegujarati=2791;e.onegurmukhi=2663;e.onehackarabic=1633;e.onehalf=189;e.onehangzhou=12321;e.oneideographicparen=12832;e.oneinferior=8321;e.onemonospace=65297;e.onenumeratorbengali=2548;e.oneoldstyle=63281;e.oneparen=9332;e.oneperiod=9352;e.onepersian=1777;e.onequarter=188;e.oneroman=8560;e.onesuperior=185;e.onethai=3665;e.onethird=8531;e.oogonek=491;e.oogonekmacron=493;e.oogurmukhi=2579;e.oomatragurmukhi=2635;e.oopen=596;e.oparen=9386;e.openbullet=9702;e.option=8997;e.ordfeminine=170;e.ordmasculine=186;e.orthogonal=8735;e.oshortdeva=2322;e.oshortvowelsigndeva=2378;e.oslash=248;e.oslashacute=511;e.osmallhiragana=12361;e.osmallkatakana=12457;e.osmallkatakanahalfwidth=65387;e.ostrokeacute=511;e.osuperior=63216;e.otcyrillic=1151;e.otilde=245;e.otildeacute=7757;e.otildedieresis=7759;e.oubopomofo=12577;e.overline=8254;e.overlinecenterline=65098;e.overlinecmb=773;e.overlinedashed=65097;e.overlinedblwavy=65100;e.overlinewavy=65099;e.overscore=175;e.ovowelsignbengali=2507;e.ovowelsigndeva=2379;e.ovowelsigngujarati=2763;e.p=112;e.paampssquare=13184;e.paasentosquare=13099;e.pabengali=2474;e.pacute=7765;e.padeva=2346;e.pagedown=8671;e.pageup=8670;e.pagujarati=2730;e.pagurmukhi=2602;e.pahiragana=12401;e.paiyannoithai=3631;e.pakatakana=12497;e.palatalizationcyrilliccmb=1156;e.palochkacyrillic=1216;e.pansioskorean=12671;e.paragraph=182;e.parallel=8741;e.parenleft=40;e.parenleftaltonearabic=64830;e.parenleftbt=63725;e.parenleftex=63724;e.parenleftinferior=8333;e.parenleftmonospace=65288;e.parenleftsmall=65113;e.parenleftsuperior=8317;e.parenlefttp=63723;e.parenleftvertical=65077;e.parenright=41;e.parenrightaltonearabic=64831;e.parenrightbt=63736;e.parenrightex=63735;e.parenrightinferior=8334;e.parenrightmonospace=65289;e.parenrightsmall=65114;e.parenrightsuperior=8318;e.parenrighttp=63734;e.parenrightvertical=65078;e.partialdiff=8706;e.paseqhebrew=1472;e.pashtahebrew=1433;e.pasquare=13225;e.patah=1463;e.patah11=1463;e.patah1d=1463;e.patah2a=1463;e.patahhebrew=1463;e.patahnarrowhebrew=1463;e.patahquarterhebrew=1463;e.patahwidehebrew=1463;e.pazerhebrew=1441;e.pbopomofo=12550;e.pcircle=9439;e.pdotaccent=7767;e.pe=1508;e.pecyrillic=1087;e.pedagesh=64324;e.pedageshhebrew=64324;e.peezisquare=13115;e.pefinaldageshhebrew=64323;e.peharabic=1662;e.peharmenian=1402;e.pehebrew=1508;e.pehfinalarabic=64343;e.pehinitialarabic=64344;e.pehiragana=12410;e.pehmedialarabic=64345;e.pekatakana=12506;e.pemiddlehookcyrillic=1191;e.perafehebrew=64334;e.percent=37;e.percentarabic=1642;e.percentmonospace=65285;e.percentsmall=65130;e.period=46;e.periodarmenian=1417;e.periodcentered=183;e.periodhalfwidth=65377;e.periodinferior=63207;e.periodmonospace=65294;e.periodsmall=65106;e.periodsuperior=63208;e.perispomenigreekcmb=834;e.perpendicular=8869;e.perthousand=8240;e.peseta=8359;e.pfsquare=13194;e.phabengali=2475;e.phadeva=2347;e.phagujarati=2731;e.phagurmukhi=2603;e.phi=966;e.phi1=981;e.phieuphacirclekorean=12922;e.phieuphaparenkorean=12826;e.phieuphcirclekorean=12908;e.phieuphkorean=12621;e.phieuphparenkorean=12812;e.philatin=632;e.phinthuthai=3642;e.phisymbolgreek=981;e.phook=421;e.phophanthai=3614;e.phophungthai=3612;e.phosamphaothai=3616;e.pi=960;e.pieupacirclekorean=12915;e.pieupaparenkorean=12819;e.pieupcieuckorean=12662;e.pieupcirclekorean=12901;e.pieupkiyeokkorean=12658;e.pieupkorean=12610;e.pieupparenkorean=12805;e.pieupsioskiyeokkorean=12660;e.pieupsioskorean=12612;e.pieupsiostikeutkorean=12661;e.pieupthieuthkorean=12663;e.pieuptikeutkorean=12659;e.pihiragana=12404;e.pikatakana=12500;e.pisymbolgreek=982;e.piwrarmenian=1411;e.plus=43;e.plusbelowcmb=799;e.pluscircle=8853;e.plusminus=177;e.plusmod=726;e.plusmonospace=65291;e.plussmall=65122;e.plussuperior=8314;e.pmonospace=65360;e.pmsquare=13272;e.pohiragana=12413;e.pointingindexdownwhite=9759;e.pointingindexleftwhite=9756;e.pointingindexrightwhite=9758;e.pointingindexupwhite=9757;e.pokatakana=12509;e.poplathai=3611;e.postalmark=12306;e.postalmarkface=12320;e.pparen=9387;e.precedes=8826;e.prescription=8478;e.primemod=697;e.primereversed=8245;e.product=8719;e.projective=8965;e.prolongedkana=12540;e.propellor=8984;e.propersubset=8834;e.propersuperset=8835;e.proportion=8759;e.proportional=8733;e.psi=968;e.psicyrillic=1137;e.psilipneumatacyrilliccmb=1158;e.pssquare=13232;e.puhiragana=12407;e.pukatakana=12503;e.pvsquare=13236;e.pwsquare=13242;e.q=113;e.qadeva=2392;e.qadmahebrew=1448;e.qafarabic=1602;e.qaffinalarabic=65238;e.qafinitialarabic=65239;e.qafmedialarabic=65240;e.qamats=1464;e.qamats10=1464;e.qamats1a=1464;e.qamats1c=1464;e.qamats27=1464;e.qamats29=1464;e.qamats33=1464;e.qamatsde=1464;e.qamatshebrew=1464;e.qamatsnarrowhebrew=1464;e.qamatsqatanhebrew=1464;e.qamatsqatannarrowhebrew=1464;e.qamatsqatanquarterhebrew=1464;e.qamatsqatanwidehebrew=1464;e.qamatsquarterhebrew=1464;e.qamatswidehebrew=1464;e.qarneyparahebrew=1439;e.qbopomofo=12561;e.qcircle=9440;e.qhook=672;e.qmonospace=65361;e.qof=1511;e.qofdagesh=64327;e.qofdageshhebrew=64327;e.qofhebrew=1511;e.qparen=9388;e.quarternote=9833;e.qubuts=1467;e.qubuts18=1467;e.qubuts25=1467;e.qubuts31=1467;e.qubutshebrew=1467;e.qubutsnarrowhebrew=1467;e.qubutsquarterhebrew=1467;e.qubutswidehebrew=1467;e.question=63;e.questionarabic=1567;e.questionarmenian=1374;e.questiondown=191;e.questiondownsmall=63423;e.questiongreek=894;e.questionmonospace=65311;e.questionsmall=63295;e.quotedbl=34;e.quotedblbase=8222;e.quotedblleft=8220;e.quotedblmonospace=65282;e.quotedblprime=12318;e.quotedblprimereversed=12317;e.quotedblright=8221;e.quoteleft=8216;e.quoteleftreversed=8219;e.quotereversed=8219;e.quoteright=8217;e.quoterightn=329;e.quotesinglbase=8218;e.quotesingle=39;e.quotesinglemonospace=65287;e.r=114;e.raarmenian=1404;e.rabengali=2480;e.racute=341;e.radeva=2352;e.radical=8730;e.radicalex=63717;e.radoverssquare=13230;e.radoverssquaredsquare=13231;e.radsquare=13229;e.rafe=1471;e.rafehebrew=1471;e.ragujarati=2736;e.ragurmukhi=2608;e.rahiragana=12425;e.rakatakana=12521;e.rakatakanahalfwidth=65431;e.ralowerdiagonalbengali=2545;e.ramiddlediagonalbengali=2544;e.ramshorn=612;e.ratio=8758;e.rbopomofo=12566;e.rcaron=345;e.rcedilla=343;e.rcircle=9441;e.rcommaaccent=343;e.rdblgrave=529;e.rdotaccent=7769;e.rdotbelow=7771;e.rdotbelowmacron=7773;e.referencemark=8251;e.reflexsubset=8838;e.reflexsuperset=8839;e.registered=174;e.registersans=63720;e.registerserif=63194;e.reharabic=1585;e.reharmenian=1408;e.rehfinalarabic=65198;e.rehiragana=12428;e.rekatakana=12524;e.rekatakanahalfwidth=65434;e.resh=1512;e.reshdageshhebrew=64328;e.reshhebrew=1512;e.reversedtilde=8765;e.reviahebrew=1431;e.reviamugrashhebrew=1431;e.revlogicalnot=8976;e.rfishhook=638;e.rfishhookreversed=639;e.rhabengali=2525;e.rhadeva=2397;e.rho=961;e.rhook=637;e.rhookturned=635;e.rhookturnedsuperior=693;e.rhosymbolgreek=1009;e.rhotichookmod=734;e.rieulacirclekorean=12913;e.rieulaparenkorean=12817;e.rieulcirclekorean=12899;e.rieulhieuhkorean=12608;e.rieulkiyeokkorean=12602;e.rieulkiyeoksioskorean=12649;e.rieulkorean=12601;e.rieulmieumkorean=12603;e.rieulpansioskorean=12652;e.rieulparenkorean=12803;e.rieulphieuphkorean=12607;e.rieulpieupkorean=12604;e.rieulpieupsioskorean=12651;e.rieulsioskorean=12605;e.rieulthieuthkorean=12606;e.rieultikeutkorean=12650;e.rieulyeorinhieuhkorean=12653;e.rightangle=8735;e.righttackbelowcmb=793;e.righttriangle=8895;e.rihiragana=12426;e.rikatakana=12522;e.rikatakanahalfwidth=65432;e.ring=730;e.ringbelowcmb=805;e.ringcmb=778;e.ringhalfleft=703;e.ringhalfleftarmenian=1369;e.ringhalfleftbelowcmb=796;e.ringhalfleftcentered=723;e.ringhalfright=702;e.ringhalfrightbelowcmb=825;e.ringhalfrightcentered=722;e.rinvertedbreve=531;e.rittorusquare=13137;e.rlinebelow=7775;e.rlongleg=636;e.rlonglegturned=634;e.rmonospace=65362;e.rohiragana=12429;e.rokatakana=12525;e.rokatakanahalfwidth=65435;e.roruathai=3619;e.rparen=9389;e.rrabengali=2524;e.rradeva=2353;e.rragurmukhi=2652;e.rreharabic=1681;e.rrehfinalarabic=64397;e.rrvocalicbengali=2528;e.rrvocalicdeva=2400;e.rrvocalicgujarati=2784;e.rrvocalicvowelsignbengali=2500;e.rrvocalicvowelsigndeva=2372;e.rrvocalicvowelsigngujarati=2756;e.rsuperior=63217;e.rtblock=9616;e.rturned=633;e.rturnedsuperior=692;e.ruhiragana=12427;e.rukatakana=12523;e.rukatakanahalfwidth=65433;e.rupeemarkbengali=2546;e.rupeesignbengali=2547;e.rupiah=63197;e.ruthai=3620;e.rvocalicbengali=2443;e.rvocalicdeva=2315;e.rvocalicgujarati=2699;e.rvocalicvowelsignbengali=2499;e.rvocalicvowelsigndeva=2371;e.rvocalicvowelsigngujarati=2755;e.s=115;e.sabengali=2488;e.sacute=347;e.sacutedotaccent=7781;e.sadarabic=1589;e.sadeva=2360;e.sadfinalarabic=65210;e.sadinitialarabic=65211;e.sadmedialarabic=65212;e.sagujarati=2744;e.sagurmukhi=2616;e.sahiragana=12373;e.sakatakana=12469;e.sakatakanahalfwidth=65403;e.sallallahoualayhewasallamarabic=65018;e.samekh=1505;e.samekhdagesh=64321;e.samekhdageshhebrew=64321;e.samekhhebrew=1505;e.saraaathai=3634;e.saraaethai=3649;e.saraaimaimalaithai=3652;e.saraaimaimuanthai=3651;e.saraamthai=3635;e.saraathai=3632;e.saraethai=3648;e.saraiileftthai=63622;e.saraiithai=3637;e.saraileftthai=63621;e.saraithai=3636;e.saraothai=3650;e.saraueeleftthai=63624;e.saraueethai=3639;e.saraueleftthai=63623;e.sarauethai=3638;e.sarauthai=3640;e.sarauuthai=3641;e.sbopomofo=12569;e.scaron=353;e.scarondotaccent=7783;e.scedilla=351;e.schwa=601;e.schwacyrillic=1241;e.schwadieresiscyrillic=1243;e.schwahook=602;e.scircle=9442;e.scircumflex=349;e.scommaaccent=537;e.sdotaccent=7777;e.sdotbelow=7779;e.sdotbelowdotaccent=7785;e.seagullbelowcmb=828;e.second=8243;e.secondtonechinese=714;e.section=167;e.seenarabic=1587;e.seenfinalarabic=65202;e.seeninitialarabic=65203;e.seenmedialarabic=65204;e.segol=1462;e.segol13=1462;e.segol1f=1462;e.segol2c=1462;e.segolhebrew=1462;e.segolnarrowhebrew=1462;e.segolquarterhebrew=1462;e.segoltahebrew=1426;e.segolwidehebrew=1462;e.seharmenian=1405;e.sehiragana=12379;e.sekatakana=12475;e.sekatakanahalfwidth=65406;e.semicolon=59;e.semicolonarabic=1563;e.semicolonmonospace=65307;e.semicolonsmall=65108;e.semivoicedmarkkana=12444;e.semivoicedmarkkanahalfwidth=65439;e.sentisquare=13090;e.sentosquare=13091;e.seven=55;e.sevenarabic=1639;e.sevenbengali=2541;e.sevencircle=9318;e.sevencircleinversesansserif=10128;e.sevendeva=2413;e.seveneighths=8542;e.sevengujarati=2797;e.sevengurmukhi=2669;e.sevenhackarabic=1639;e.sevenhangzhou=12327;e.sevenideographicparen=12838;e.seveninferior=8327;e.sevenmonospace=65303;e.sevenoldstyle=63287;e.sevenparen=9338;e.sevenperiod=9358;e.sevenpersian=1783;e.sevenroman=8566;e.sevensuperior=8311;e.seventeencircle=9328;e.seventeenparen=9348;e.seventeenperiod=9368;e.seventhai=3671;e.sfthyphen=173;e.shaarmenian=1399;e.shabengali=2486;e.shacyrillic=1096;e.shaddaarabic=1617;e.shaddadammaarabic=64609;e.shaddadammatanarabic=64606;e.shaddafathaarabic=64608;e.shaddakasraarabic=64610;e.shaddakasratanarabic=64607;e.shade=9618;e.shadedark=9619;e.shadelight=9617;e.shademedium=9618;e.shadeva=2358;e.shagujarati=2742;e.shagurmukhi=2614;e.shalshelethebrew=1427;e.shbopomofo=12565;e.shchacyrillic=1097;e.sheenarabic=1588;e.sheenfinalarabic=65206;e.sheeninitialarabic=65207;e.sheenmedialarabic=65208;e.sheicoptic=995;e.sheqel=8362;e.sheqelhebrew=8362;e.sheva=1456;e.sheva115=1456;e.sheva15=1456;e.sheva22=1456;e.sheva2e=1456;e.shevahebrew=1456;e.shevanarrowhebrew=1456;e.shevaquarterhebrew=1456;e.shevawidehebrew=1456;e.shhacyrillic=1211;e.shimacoptic=1005;e.shin=1513;e.shindagesh=64329;e.shindageshhebrew=64329;e.shindageshshindot=64300;e.shindageshshindothebrew=64300;e.shindageshsindot=64301;e.shindageshsindothebrew=64301;e.shindothebrew=1473;e.shinhebrew=1513;e.shinshindot=64298;e.shinshindothebrew=64298;e.shinsindot=64299;e.shinsindothebrew=64299;e.shook=642;e.sigma=963;e.sigma1=962;e.sigmafinal=962;e.sigmalunatesymbolgreek=1010;e.sihiragana=12375;e.sikatakana=12471;e.sikatakanahalfwidth=65404;e.siluqhebrew=1469;e.siluqlefthebrew=1469;e.similar=8764;e.sindothebrew=1474;e.siosacirclekorean=12916;e.siosaparenkorean=12820;e.sioscieuckorean=12670;e.sioscirclekorean=12902;e.sioskiyeokkorean=12666;e.sioskorean=12613;e.siosnieunkorean=12667;e.siosparenkorean=12806;e.siospieupkorean=12669;e.siostikeutkorean=12668;e.six=54;e.sixarabic=1638;e.sixbengali=2540;e.sixcircle=9317;e.sixcircleinversesansserif=10127;e.sixdeva=2412;e.sixgujarati=2796;e.sixgurmukhi=2668;e.sixhackarabic=1638;e.sixhangzhou=12326;e.sixideographicparen=12837;e.sixinferior=8326;e.sixmonospace=65302;e.sixoldstyle=63286;e.sixparen=9337;e.sixperiod=9357;e.sixpersian=1782;e.sixroman=8565;e.sixsuperior=8310;e.sixteencircle=9327;e.sixteencurrencydenominatorbengali=2553;e.sixteenparen=9347;e.sixteenperiod=9367;e.sixthai=3670;e.slash=47;e.slashmonospace=65295;e.slong=383;e.slongdotaccent=7835;e.smileface=9786;e.smonospace=65363;e.sofpasuqhebrew=1475;e.softhyphen=173;e.softsigncyrillic=1100;e.sohiragana=12381;e.sokatakana=12477;e.sokatakanahalfwidth=65407;e.soliduslongoverlaycmb=824;e.solidusshortoverlaycmb=823;e.sorusithai=3625;e.sosalathai=3624;e.sosothai=3595;e.sosuathai=3626;e.space=32;e.spacehackarabic=32;e.spade=9824;e.spadesuitblack=9824;e.spadesuitwhite=9828;e.sparen=9390;e.squarebelowcmb=827;e.squarecc=13252;e.squarecm=13213;e.squarediagonalcrosshatchfill=9641;e.squarehorizontalfill=9636;e.squarekg=13199;e.squarekm=13214;e.squarekmcapital=13262;e.squareln=13265;e.squarelog=13266;e.squaremg=13198;e.squaremil=13269;e.squaremm=13212;e.squaremsquared=13217;e.squareorthogonalcrosshatchfill=9638;e.squareupperlefttolowerrightfill=9639;e.squareupperrighttolowerleftfill=9640;e.squareverticalfill=9637;e.squarewhitewithsmallblack=9635;e.srsquare=13275;e.ssabengali=2487;e.ssadeva=2359;e.ssagujarati=2743;e.ssangcieuckorean=12617;e.ssanghieuhkorean=12677;e.ssangieungkorean=12672;e.ssangkiyeokkorean=12594;e.ssangnieunkorean=12645;e.ssangpieupkorean=12611;e.ssangsioskorean=12614;e.ssangtikeutkorean=12600;e.ssuperior=63218;e.sterling=163;e.sterlingmonospace=65505;e.strokelongoverlaycmb=822;e.strokeshortoverlaycmb=821;e.subset=8834;e.subsetnotequal=8842;e.subsetorequal=8838;e.succeeds=8827;e.suchthat=8715;e.suhiragana=12377;e.sukatakana=12473;e.sukatakanahalfwidth=65405;e.sukunarabic=1618;e.summation=8721;e.sun=9788;e.superset=8835;e.supersetnotequal=8843;e.supersetorequal=8839;e.svsquare=13276;e.syouwaerasquare=13180;e.t=116;e.tabengali=2468;e.tackdown=8868;e.tackleft=8867;e.tadeva=2340;e.tagujarati=2724;e.tagurmukhi=2596;e.taharabic=1591;e.tahfinalarabic=65218;e.tahinitialarabic=65219;e.tahiragana=12383;e.tahmedialarabic=65220;e.taisyouerasquare=13181;e.takatakana=12479;e.takatakanahalfwidth=65408;e.tatweelarabic=1600;e.tau=964;e.tav=1514;e.tavdages=64330;e.tavdagesh=64330;e.tavdageshhebrew=64330;e.tavhebrew=1514;e.tbar=359;e.tbopomofo=12554;e.tcaron=357;e.tccurl=680;e.tcedilla=355;e.tcheharabic=1670;e.tchehfinalarabic=64379;e.tchehinitialarabic=64380;e.tchehmedialarabic=64381;e.tcircle=9443;e.tcircumflexbelow=7793;e.tcommaaccent=355;e.tdieresis=7831;e.tdotaccent=7787;e.tdotbelow=7789;e.tecyrillic=1090;e.tedescendercyrillic=1197;e.teharabic=1578;e.tehfinalarabic=65174;e.tehhahinitialarabic=64674;e.tehhahisolatedarabic=64524;e.tehinitialarabic=65175;e.tehiragana=12390;e.tehjeeminitialarabic=64673;e.tehjeemisolatedarabic=64523;e.tehmarbutaarabic=1577;e.tehmarbutafinalarabic=65172;e.tehmedialarabic=65176;e.tehmeeminitialarabic=64676;e.tehmeemisolatedarabic=64526;e.tehnoonfinalarabic=64627;e.tekatakana=12486;e.tekatakanahalfwidth=65411;e.telephone=8481;e.telephoneblack=9742;e.telishagedolahebrew=1440;e.telishaqetanahebrew=1449;e.tencircle=9321;e.tenideographicparen=12841;e.tenparen=9341;e.tenperiod=9361;e.tenroman=8569;e.tesh=679;e.tet=1496;e.tetdagesh=64312;e.tetdageshhebrew=64312;e.tethebrew=1496;e.tetsecyrillic=1205;e.tevirhebrew=1435;e.tevirlefthebrew=1435;e.thabengali=2469;e.thadeva=2341;e.thagujarati=2725;e.thagurmukhi=2597;e.thalarabic=1584;e.thalfinalarabic=65196;e.thanthakhatlowleftthai=63640;e.thanthakhatlowrightthai=63639;e.thanthakhatthai=3660;e.thanthakhatupperleftthai=63638;e.theharabic=1579;e.thehfinalarabic=65178;e.thehinitialarabic=65179;e.thehmedialarabic=65180;e.thereexists=8707;e.therefore=8756;e.theta=952;e.theta1=977;e.thetasymbolgreek=977;e.thieuthacirclekorean=12921;e.thieuthaparenkorean=12825;e.thieuthcirclekorean=12907;e.thieuthkorean=12620;e.thieuthparenkorean=12811;e.thirteencircle=9324;e.thirteenparen=9344;e.thirteenperiod=9364;e.thonangmonthothai=3601;e.thook=429;e.thophuthaothai=3602;e.thorn=254;e.thothahanthai=3607;e.thothanthai=3600;e.thothongthai=3608;e.thothungthai=3606;e.thousandcyrillic=1154;e.thousandsseparatorarabic=1644;e.thousandsseparatorpersian=1644;e.three=51;e.threearabic=1635;e.threebengali=2537;e.threecircle=9314;e.threecircleinversesansserif=10124;e.threedeva=2409;e.threeeighths=8540;e.threegujarati=2793;e.threegurmukhi=2665;e.threehackarabic=1635;e.threehangzhou=12323;e.threeideographicparen=12834;e.threeinferior=8323;e.threemonospace=65299;e.threenumeratorbengali=2550;e.threeoldstyle=63283;e.threeparen=9334;e.threeperiod=9354;e.threepersian=1779;e.threequarters=190;e.threequartersemdash=63198;e.threeroman=8562;e.threesuperior=179;e.threethai=3667;e.thzsquare=13204;e.tihiragana=12385;e.tikatakana=12481;e.tikatakanahalfwidth=65409;e.tikeutacirclekorean=12912;e.tikeutaparenkorean=12816;e.tikeutcirclekorean=12898;e.tikeutkorean=12599;e.tikeutparenkorean=12802;e.tilde=732;e.tildebelowcmb=816;e.tildecmb=771;e.tildecomb=771;e.tildedoublecmb=864;e.tildeoperator=8764;e.tildeoverlaycmb=820;e.tildeverticalcmb=830;e.timescircle=8855;e.tipehahebrew=1430;e.tipehalefthebrew=1430;e.tippigurmukhi=2672;e.titlocyrilliccmb=1155;e.tiwnarmenian=1407;e.tlinebelow=7791;e.tmonospace=65364;e.toarmenian=1385;e.tohiragana=12392;e.tokatakana=12488;e.tokatakanahalfwidth=65412;e.tonebarextrahighmod=741;e.tonebarextralowmod=745;e.tonebarhighmod=742;e.tonebarlowmod=744;e.tonebarmidmod=743;e.tonefive=445;e.tonesix=389;e.tonetwo=424;e.tonos=900;e.tonsquare=13095;e.topatakthai=3599;e.tortoiseshellbracketleft=12308;e.tortoiseshellbracketleftsmall=65117;e.tortoiseshellbracketleftvertical=65081;e.tortoiseshellbracketright=12309;e.tortoiseshellbracketrightsmall=65118;e.tortoiseshellbracketrightvertical=65082;e.totaothai=3605;e.tpalatalhook=427;e.tparen=9391;e.trademark=8482;e.trademarksans=63722;e.trademarkserif=63195;e.tretroflexhook=648;e.triagdn=9660;e.triaglf=9668;e.triagrt=9658;e.triagup=9650;e.ts=678;e.tsadi=1510;e.tsadidagesh=64326;e.tsadidageshhebrew=64326;e.tsadihebrew=1510;e.tsecyrillic=1094;e.tsere=1461;e.tsere12=1461;e.tsere1e=1461;e.tsere2b=1461;e.tserehebrew=1461;e.tserenarrowhebrew=1461;e.tserequarterhebrew=1461;e.tserewidehebrew=1461;e.tshecyrillic=1115;e.tsuperior=63219;e.ttabengali=2463;e.ttadeva=2335;e.ttagujarati=2719;e.ttagurmukhi=2591;e.tteharabic=1657;e.ttehfinalarabic=64359;e.ttehinitialarabic=64360;e.ttehmedialarabic=64361;e.tthabengali=2464;e.tthadeva=2336;e.tthagujarati=2720;e.tthagurmukhi=2592;e.tturned=647;e.tuhiragana=12388;e.tukatakana=12484;e.tukatakanahalfwidth=65410;e.tusmallhiragana=12387;e.tusmallkatakana=12483;e.tusmallkatakanahalfwidth=65391;e.twelvecircle=9323;e.twelveparen=9343;e.twelveperiod=9363;e.twelveroman=8571;e.twentycircle=9331;e.twentyhangzhou=21316;e.twentyparen=9351;e.twentyperiod=9371;e.two=50;e.twoarabic=1634;e.twobengali=2536;e.twocircle=9313;e.twocircleinversesansserif=10123;e.twodeva=2408;e.twodotenleader=8229;e.twodotleader=8229;e.twodotleadervertical=65072;e.twogujarati=2792;e.twogurmukhi=2664;e.twohackarabic=1634;e.twohangzhou=12322;e.twoideographicparen=12833;e.twoinferior=8322;e.twomonospace=65298;e.twonumeratorbengali=2549;e.twooldstyle=63282;e.twoparen=9333;e.twoperiod=9353;e.twopersian=1778;e.tworoman=8561;e.twostroke=443;e.twosuperior=178;e.twothai=3666;e.twothirds=8532;e.u=117;e.uacute=250;e.ubar=649;e.ubengali=2441;e.ubopomofo=12584;e.ubreve=365;e.ucaron=468;e.ucircle=9444;e.ucircumflex=251;e.ucircumflexbelow=7799;e.ucyrillic=1091;e.udattadeva=2385;e.udblacute=369;e.udblgrave=533;e.udeva=2313;e.udieresis=252;e.udieresisacute=472;e.udieresisbelow=7795;e.udieresiscaron=474;e.udieresiscyrillic=1265;e.udieresisgrave=476;e.udieresismacron=470;e.udotbelow=7909;e.ugrave=249;e.ugujarati=2697;e.ugurmukhi=2569;e.uhiragana=12358;e.uhookabove=7911;e.uhorn=432;e.uhornacute=7913;e.uhorndotbelow=7921;e.uhorngrave=7915;e.uhornhookabove=7917;e.uhorntilde=7919;e.uhungarumlaut=369;e.uhungarumlautcyrillic=1267;e.uinvertedbreve=535;e.ukatakana=12454;e.ukatakanahalfwidth=65395;e.ukcyrillic=1145;e.ukorean=12636;e.umacron=363;e.umacroncyrillic=1263;e.umacrondieresis=7803;e.umatragurmukhi=2625;e.umonospace=65365;e.underscore=95;e.underscoredbl=8215;e.underscoremonospace=65343;e.underscorevertical=65075;e.underscorewavy=65103;e.union=8746;e.universal=8704;e.uogonek=371;e.uparen=9392;e.upblock=9600;e.upperdothebrew=1476;e.upsilon=965;e.upsilondieresis=971;e.upsilondieresistonos=944;e.upsilonlatin=650;e.upsilontonos=973;e.uptackbelowcmb=797;e.uptackmod=724;e.uragurmukhi=2675;e.uring=367;e.ushortcyrillic=1118;e.usmallhiragana=12357;e.usmallkatakana=12453;e.usmallkatakanahalfwidth=65385;e.ustraightcyrillic=1199;e.ustraightstrokecyrillic=1201;e.utilde=361;e.utildeacute=7801;e.utildebelow=7797;e.uubengali=2442;e.uudeva=2314;e.uugujarati=2698;e.uugurmukhi=2570;e.uumatragurmukhi=2626;e.uuvowelsignbengali=2498;e.uuvowelsigndeva=2370;e.uuvowelsigngujarati=2754;e.uvowelsignbengali=2497;e.uvowelsigndeva=2369;e.uvowelsigngujarati=2753;e.v=118;e.vadeva=2357;e.vagujarati=2741;e.vagurmukhi=2613;e.vakatakana=12535;e.vav=1493;e.vavdagesh=64309;e.vavdagesh65=64309;e.vavdageshhebrew=64309;e.vavhebrew=1493;e.vavholam=64331;e.vavholamhebrew=64331;e.vavvavhebrew=1520;e.vavyodhebrew=1521;e.vcircle=9445;e.vdotbelow=7807;e.vecyrillic=1074;e.veharabic=1700;e.vehfinalarabic=64363;e.vehinitialarabic=64364;e.vehmedialarabic=64365;e.vekatakana=12537;e.venus=9792;e.verticalbar=124;e.verticallineabovecmb=781;e.verticallinebelowcmb=809;e.verticallinelowmod=716;e.verticallinemod=712;e.vewarmenian=1406;e.vhook=651;e.vikatakana=12536;e.viramabengali=2509;e.viramadeva=2381;e.viramagujarati=2765;e.visargabengali=2435;e.visargadeva=2307;e.visargagujarati=2691;e.vmonospace=65366;e.voarmenian=1400;e.voicediterationhiragana=12446;e.voicediterationkatakana=12542;e.voicedmarkkana=12443;e.voicedmarkkanahalfwidth=65438;e.vokatakana=12538;e.vparen=9393;e.vtilde=7805;e.vturned=652;e.vuhiragana=12436;e.vukatakana=12532;e.w=119;e.wacute=7811;e.waekorean=12633;e.wahiragana=12431;e.wakatakana=12527;e.wakatakanahalfwidth=65436;e.wakorean=12632;e.wasmallhiragana=12430;e.wasmallkatakana=12526;e.wattosquare=13143;e.wavedash=12316;e.wavyunderscorevertical=65076;e.wawarabic=1608;e.wawfinalarabic=65262;e.wawhamzaabovearabic=1572;e.wawhamzaabovefinalarabic=65158;e.wbsquare=13277;e.wcircle=9446;e.wcircumflex=373;e.wdieresis=7813;e.wdotaccent=7815;e.wdotbelow=7817;e.wehiragana=12433;e.weierstrass=8472;e.wekatakana=12529;e.wekorean=12638;e.weokorean=12637;e.wgrave=7809;e.whitebullet=9702;e.whitecircle=9675;e.whitecircleinverse=9689;e.whitecornerbracketleft=12302;e.whitecornerbracketleftvertical=65091;e.whitecornerbracketright=12303;e.whitecornerbracketrightvertical=65092;e.whitediamond=9671;e.whitediamondcontainingblacksmalldiamond=9672;e.whitedownpointingsmalltriangle=9663;e.whitedownpointingtriangle=9661;e.whiteleftpointingsmalltriangle=9667;e.whiteleftpointingtriangle=9665;e.whitelenticularbracketleft=12310;e.whitelenticularbracketright=12311;e.whiterightpointingsmalltriangle=9657;e.whiterightpointingtriangle=9655;e.whitesmallsquare=9643;e.whitesmilingface=9786;e.whitesquare=9633;e.whitestar=9734;e.whitetelephone=9743;e.whitetortoiseshellbracketleft=12312;e.whitetortoiseshellbracketright=12313;e.whiteuppointingsmalltriangle=9653;e.whiteuppointingtriangle=9651;e.wihiragana=12432;e.wikatakana=12528;e.wikorean=12639;e.wmonospace=65367;e.wohiragana=12434;e.wokatakana=12530;e.wokatakanahalfwidth=65382;e.won=8361;e.wonmonospace=65510;e.wowaenthai=3623;e.wparen=9394;e.wring=7832;e.wsuperior=695;e.wturned=653;e.wynn=447;e.x=120;e.xabovecmb=829;e.xbopomofo=12562;e.xcircle=9447;e.xdieresis=7821;e.xdotaccent=7819;e.xeharmenian=1389;e.xi=958;e.xmonospace=65368;e.xparen=9395;e.xsuperior=739;e.y=121;e.yaadosquare=13134;e.yabengali=2479;e.yacute=253;e.yadeva=2351;e.yaekorean=12626;e.yagujarati=2735;e.yagurmukhi=2607;e.yahiragana=12420;e.yakatakana=12516;e.yakatakanahalfwidth=65428;e.yakorean=12625;e.yamakkanthai=3662;e.yasmallhiragana=12419;e.yasmallkatakana=12515;e.yasmallkatakanahalfwidth=65388;e.yatcyrillic=1123;e.ycircle=9448;e.ycircumflex=375;e.ydieresis=255;e.ydotaccent=7823;e.ydotbelow=7925;e.yeharabic=1610;e.yehbarreearabic=1746;e.yehbarreefinalarabic=64431;e.yehfinalarabic=65266;e.yehhamzaabovearabic=1574;e.yehhamzaabovefinalarabic=65162;e.yehhamzaaboveinitialarabic=65163;e.yehhamzaabovemedialarabic=65164;e.yehinitialarabic=65267;e.yehmedialarabic=65268;e.yehmeeminitialarabic=64733;e.yehmeemisolatedarabic=64600;e.yehnoonfinalarabic=64660;e.yehthreedotsbelowarabic=1745;e.yekorean=12630;e.yen=165;e.yenmonospace=65509;e.yeokorean=12629;e.yeorinhieuhkorean=12678;e.yerahbenyomohebrew=1450;e.yerahbenyomolefthebrew=1450;e.yericyrillic=1099;e.yerudieresiscyrillic=1273;e.yesieungkorean=12673;e.yesieungpansioskorean=12675;e.yesieungsioskorean=12674;e.yetivhebrew=1434;e.ygrave=7923;e.yhook=436;e.yhookabove=7927;e.yiarmenian=1397;e.yicyrillic=1111;e.yikorean=12642;e.yinyang=9775;e.yiwnarmenian=1410;e.ymonospace=65369;e.yod=1497;e.yoddagesh=64313;e.yoddageshhebrew=64313;e.yodhebrew=1497;e.yodyodhebrew=1522;e.yodyodpatahhebrew=64287;e.yohiragana=12424;e.yoikorean=12681;e.yokatakana=12520;e.yokatakanahalfwidth=65430;e.yokorean=12635;e.yosmallhiragana=12423;e.yosmallkatakana=12519;e.yosmallkatakanahalfwidth=65390;e.yotgreek=1011;e.yoyaekorean=12680;e.yoyakorean=12679;e.yoyakthai=3618;e.yoyingthai=3597;e.yparen=9396;e.ypogegrammeni=890;e.ypogegrammenigreekcmb=837;e.yr=422;e.yring=7833;e.ysuperior=696;e.ytilde=7929;e.yturned=654;e.yuhiragana=12422;e.yuikorean=12684;e.yukatakana=12518;e.yukatakanahalfwidth=65429;e.yukorean=12640;e.yusbigcyrillic=1131;e.yusbigiotifiedcyrillic=1133;e.yuslittlecyrillic=1127;e.yuslittleiotifiedcyrillic=1129;e.yusmallhiragana=12421;e.yusmallkatakana=12517;e.yusmallkatakanahalfwidth=65389;e.yuyekorean=12683;e.yuyeokorean=12682;e.yyabengali=2527;e.yyadeva=2399;e.z=122;e.zaarmenian=1382;e.zacute=378;e.zadeva=2395;e.zagurmukhi=2651;e.zaharabic=1592;e.zahfinalarabic=65222;e.zahinitialarabic=65223;e.zahiragana=12374;e.zahmedialarabic=65224;e.zainarabic=1586;e.zainfinalarabic=65200;e.zakatakana=12470;e.zaqefgadolhebrew=1429;e.zaqefqatanhebrew=1428;e.zarqahebrew=1432;e.zayin=1494;e.zayindagesh=64310;e.zayindageshhebrew=64310;e.zayinhebrew=1494;e.zbopomofo=12567;e.zcaron=382;e.zcircle=9449;e.zcircumflex=7825;e.zcurl=657;e.zdot=380;e.zdotaccent=380;e.zdotbelow=7827;e.zecyrillic=1079;e.zedescendercyrillic=1177;e.zedieresiscyrillic=1247;e.zehiragana=12380;e.zekatakana=12476;e.zero=48;e.zeroarabic=1632;e.zerobengali=2534;e.zerodeva=2406;e.zerogujarati=2790;e.zerogurmukhi=2662;e.zerohackarabic=1632
+;e.zeroinferior=8320;e.zeromonospace=65296;e.zerooldstyle=63280;e.zeropersian=1776;e.zerosuperior=8304;e.zerothai=3664;e.zerowidthjoiner=65279;e.zerowidthnonjoiner=8204;e.zerowidthspace=8203;e.zeta=950;e.zhbopomofo=12563;e.zhearmenian=1386;e.zhebrevecyrillic=1218;e.zhecyrillic=1078;e.zhedescendercyrillic=1175;e.zhedieresiscyrillic=1245;e.zihiragana=12376;e.zikatakana=12472;e.zinorhebrew=1454;e.zlinebelow=7829;e.zmonospace=65370;e.zohiragana=12382;e.zokatakana=12478;e.zparen=9397;e.zretroflexhook=656;e.zstroke=438;e.zuhiragana=12378;e.zukatakana=12474;e[".notdef"]=0;e.angbracketleftbig=9001;e.angbracketleftBig=9001;e.angbracketleftbigg=9001;e.angbracketleftBigg=9001;e.angbracketrightBig=9002;e.angbracketrightbig=9002;e.angbracketrightBigg=9002;e.angbracketrightbigg=9002;e.arrowhookleft=8618;e.arrowhookright=8617;e.arrowlefttophalf=8636;e.arrowleftbothalf=8637;e.arrownortheast=8599;e.arrownorthwest=8598;e.arrowrighttophalf=8640;e.arrowrightbothalf=8641;e.arrowsoutheast=8600;e.arrowsouthwest=8601;e.backslashbig=8726;e.backslashBig=8726;e.backslashBigg=8726;e.backslashbigg=8726;e.bardbl=8214;e.bracehtipdownleft=65079;e.bracehtipdownright=65079;e.bracehtipupleft=65080;e.bracehtipupright=65080;e.braceleftBig=123;e.braceleftbig=123;e.braceleftbigg=123;e.braceleftBigg=123;e.bracerightBig=125;e.bracerightbig=125;e.bracerightbigg=125;e.bracerightBigg=125;e.bracketleftbig=91;e.bracketleftBig=91;e.bracketleftbigg=91;e.bracketleftBigg=91;e.bracketrightBig=93;e.bracketrightbig=93;e.bracketrightbigg=93;e.bracketrightBigg=93;e.ceilingleftbig=8968;e.ceilingleftBig=8968;e.ceilingleftBigg=8968;e.ceilingleftbigg=8968;e.ceilingrightbig=8969;e.ceilingrightBig=8969;e.ceilingrightbigg=8969;e.ceilingrightBigg=8969;e.circledotdisplay=8857;e.circledottext=8857;e.circlemultiplydisplay=8855;e.circlemultiplytext=8855;e.circleplusdisplay=8853;e.circleplustext=8853;e.contintegraldisplay=8750;e.contintegraltext=8750;e.coproductdisplay=8720;e.coproducttext=8720;e.floorleftBig=8970;e.floorleftbig=8970;e.floorleftbigg=8970;e.floorleftBigg=8970;e.floorrightbig=8971;e.floorrightBig=8971;e.floorrightBigg=8971;e.floorrightbigg=8971;e.hatwide=770;e.hatwider=770;e.hatwidest=770;e.intercal=7488;e.integraldisplay=8747;e.integraltext=8747;e.intersectiondisplay=8898;e.intersectiontext=8898;e.logicalanddisplay=8743;e.logicalandtext=8743;e.logicalordisplay=8744;e.logicalortext=8744;e.parenleftBig=40;e.parenleftbig=40;e.parenleftBigg=40;e.parenleftbigg=40;e.parenrightBig=41;e.parenrightbig=41;e.parenrightBigg=41;e.parenrightbigg=41;e.prime=8242;e.productdisplay=8719;e.producttext=8719;e.radicalbig=8730;e.radicalBig=8730;e.radicalBigg=8730;e.radicalbigg=8730;e.radicalbt=8730;e.radicaltp=8730;e.radicalvertex=8730;e.slashbig=47;e.slashBig=47;e.slashBigg=47;e.slashbigg=47;e.summationdisplay=8721;e.summationtext=8721;e.tildewide=732;e.tildewider=732;e.tildewidest=732;e.uniondisplay=8899;e.unionmultidisplay=8846;e.unionmultitext=8846;e.unionsqdisplay=8852;e.unionsqtext=8852;e.uniontext=8899;e.vextenddouble=8741;e.vextendsingle=8739}),s=i(function(e){e.space=32;e.a1=9985;e.a2=9986;e.a202=9987;e.a3=9988;e.a4=9742;e.a5=9990;e.a119=9991;e.a118=9992;e.a117=9993;e.a11=9755;e.a12=9758;e.a13=9996;e.a14=9997;e.a15=9998;e.a16=9999;e.a105=1e4;e.a17=10001;e.a18=10002;e.a19=10003;e.a20=10004;e.a21=10005;e.a22=10006;e.a23=10007;e.a24=10008;e.a25=10009;e.a26=10010;e.a27=10011;e.a28=10012;e.a6=10013;e.a7=10014;e.a8=10015;e.a9=10016;e.a10=10017;e.a29=10018;e.a30=10019;e.a31=10020;e.a32=10021;e.a33=10022;e.a34=10023;e.a35=9733;e.a36=10025;e.a37=10026;e.a38=10027;e.a39=10028;e.a40=10029;e.a41=10030;e.a42=10031;e.a43=10032;e.a44=10033;e.a45=10034;e.a46=10035;e.a47=10036;e.a48=10037;e.a49=10038;e.a50=10039;e.a51=10040;e.a52=10041;e.a53=10042;e.a54=10043;e.a55=10044;e.a56=10045;e.a57=10046;e.a58=10047;e.a59=10048;e.a60=10049;e.a61=10050;e.a62=10051;e.a63=10052;e.a64=10053;e.a65=10054;e.a66=10055;e.a67=10056;e.a68=10057;e.a69=10058;e.a70=10059;e.a71=9679;e.a72=10061;e.a73=9632;e.a74=10063;e.a203=10064;e.a75=10065;e.a204=10066;e.a76=9650;e.a77=9660;e.a78=9670;e.a79=10070;e.a81=9687;e.a82=10072;e.a83=10073;e.a84=10074;e.a97=10075;e.a98=10076;e.a99=10077;e.a100=10078;e.a101=10081;e.a102=10082;e.a103=10083;e.a104=10084;e.a106=10085;e.a107=10086;e.a108=10087;e.a112=9827;e.a111=9830;e.a110=9829;e.a109=9824;e.a120=9312;e.a121=9313;e.a122=9314;e.a123=9315;e.a124=9316;e.a125=9317;e.a126=9318;e.a127=9319;e.a128=9320;e.a129=9321;e.a130=10102;e.a131=10103;e.a132=10104;e.a133=10105;e.a134=10106;e.a135=10107;e.a136=10108;e.a137=10109;e.a138=10110;e.a139=10111;e.a140=10112;e.a141=10113;e.a142=10114;e.a143=10115;e.a144=10116;e.a145=10117;e.a146=10118;e.a147=10119;e.a148=10120;e.a149=10121;e.a150=10122;e.a151=10123;e.a152=10124;e.a153=10125;e.a154=10126;e.a155=10127;e.a156=10128;e.a157=10129;e.a158=10130;e.a159=10131;e.a160=10132;e.a161=8594;e.a163=8596;e.a164=8597;e.a196=10136;e.a165=10137;e.a192=10138;e.a166=10139;e.a167=10140;e.a168=10141;e.a169=10142;e.a170=10143;e.a171=10144;e.a172=10145;e.a173=10146;e.a162=10147;e.a174=10148;e.a175=10149;e.a176=10150;e.a177=10151;e.a178=10152;e.a179=10153;e.a193=10154;e.a180=10155;e.a199=10156;e.a181=10157;e.a200=10158;e.a182=10159;e.a201=10161;e.a183=10162;e.a184=10163;e.a197=10164;e.a185=10165;e.a194=10166;e.a198=10167;e.a186=10168;e.a195=10169;e.a187=10170;e.a188=10171;e.a189=10172;e.a190=10173;e.a191=10174;e.a89=10088;e.a90=10089;e.a93=10090;e.a94=10091;e.a91=10092;e.a92=10093;e.a205=10094;e.a85=10095;e.a206=10096;e.a86=10097;e.a87=10098;e.a88=10099;e.a95=10100;e.a96=10101;e[".notdef"]=0});t.getGlyphsUnicode=n;t.getDingbatsGlyphsUnicode=s},function(e,t,a){"use strict";function r(e){i=e}var i,n=a(0),s=a(1),o=a(33),c=n.UNSUPPORTED_FEATURES,l=n.InvalidPDFException,h=n.MessageHandler,u=n.MissingPDFException,f=n.UnexpectedResponseException,d=n.PasswordException,g=n.UnknownErrorException,p=n.XRefParseException,m=n.arrayByteLength,b=n.arraysToBytes,v=n.assert,y=n.createPromiseCapability,k=n.info,w=n.warn,C=n.setVerbosityLevel,x=n.isNodeJS,S=s.Ref,A=o.LocalPdfManager,I=o.NetworkPdfManager,B=function(){function e(e){this.name=e;this.terminated=!1;this._capability=y()}e.prototype={get finished(){return this._capability.promise},finish:function(){this._capability.resolve()},terminate:function(){this.terminated=!0},ensureNotTerminated:function(){if(this.terminated)throw new Error("Worker task was terminated")}};return e}(),R=function(){function e(e,t){this._queuedChunks=[];var a=e.initialData;a&&a.length>0&&this._queuedChunks.push(a);this._msgHandler=t;this._isRangeSupported=!e.disableRange;this._isStreamingSupported=!e.disableStream;this._contentLength=e.length;this._fullRequestReader=null;this._rangeReaders=[];t.on("OnDataRange",this._onReceiveData.bind(this));t.on("OnDataProgress",this._onProgress.bind(this))}function t(e,t){this._stream=e;this._done=!1;this._queuedChunks=t||[];this._requests=[];this._headersReady=Promise.resolve();e._fullRequestReader=this;this.onProgress=null}function a(e,t,a){this._stream=e;this._begin=t;this._end=a;this._queuedChunk=null;this._requests=[];this._done=!1;this.onProgress=null}e.prototype={_onReceiveData:function(e){if(void 0===e.begin)this._fullRequestReader?this._fullRequestReader._enqueue(e.chunk):this._queuedChunks.push(e.chunk);else{var t=this._rangeReaders.some(function(t){if(t._begin!==e.begin)return!1;t._enqueue(e.chunk);return!0});v(t)}},_onProgress:function(e){if(this._rangeReaders.length>0){var t=this._rangeReaders[0];t.onProgress&&t.onProgress({loaded:e.loaded})}},_removeRangeReader:function(e){var t=this._rangeReaders.indexOf(e);t>=0&&this._rangeReaders.splice(t,1)},getFullReader:function(){v(!this._fullRequestReader);var e=this._queuedChunks;this._queuedChunks=null;return new t(this,e)},getRangeReader:function(e,t){var r=new a(this,e,t);this._msgHandler.send("RequestDataRange",{begin:e,end:t});this._rangeReaders.push(r);return r},cancelAllRequests:function(e){this._fullRequestReader&&this._fullRequestReader.cancel(e);this._rangeReaders.slice(0).forEach(function(t){t.cancel(e)})}};t.prototype={_enqueue:function(e){if(!this._done)if(this._requests.length>0){var t=this._requests.shift();t.resolve({value:e,done:!1})}else this._queuedChunks.push(e)},get headersReady(){return this._headersReady},get isRangeSupported(){return this._stream._isRangeSupported},get isStreamingSupported(){return this._stream._isStreamingSupported},get contentLength(){return this._stream._contentLength},read:function(){if(this._queuedChunks.length>0){var e=this._queuedChunks.shift();return Promise.resolve({value:e,done:!1})}if(this._done)return Promise.resolve({value:void 0,done:!0});var t=y();this._requests.push(t);return t.promise},cancel:function(e){this._done=!0;this._requests.forEach(function(e){e.resolve({value:void 0,done:!0})});this._requests=[]}};a.prototype={_enqueue:function(e){if(!this._done){if(0===this._requests.length)this._queuedChunk=e;else{this._requests.shift().resolve({value:e,done:!1});this._requests.forEach(function(e){e.resolve({value:void 0,done:!0})});this._requests=[]}this._done=!0;this._stream._removeRangeReader(this)}},get isStreamingSupported(){return!1},read:function(){if(this._queuedChunk)return Promise.resolve({value:this._queuedChunk,done:!1});if(this._done)return Promise.resolve({value:void 0,done:!0});var e=y();this._requests.push(e);return e.promise},cancel:function(e){this._done=!0;this._requests.forEach(function(e){e.resolve({value:void 0,done:!0})});this._requests=[];this._stream._removeRangeReader(this)}};return e}(),T={setup:function(e,t){var a=!1;e.on("test",function(t){if(!a){a=!0;if(t instanceof Uint8Array){var r=255===t[0];e.postMessageTransfers=r;var i=new XMLHttpRequest,n="response"in i;try{i.responseType}catch(e){n=!1}n?e.send("test",{supportTypedArray:!0,supportTransfers:r}):e.send("test",!1)}else e.send("test","main",!1)}});e.on("configure",function(e){C(e.verbosity)});e.on("GetDocRequest",function(e){return T.createDocumentHandler(e,t)})},createDocumentHandler:function(e,t){function a(){if(T)throw new Error("Worker was terminated")}function r(e){P.push(e)}function n(e){e.finish();var t=P.indexOf(e);P.splice(t,1)}function s(e){var t=y(),a=function(){var e=x.ensureDoc("numPages"),a=x.ensureDoc("fingerprint"),i=x.ensureXRef("encrypt");Promise.all([e,a,i]).then(function(e){var a={numPages:e[0],fingerprint:e[1],encrypted:!!e[2]};t.resolve(a)},r)},r=function(e){t.reject(e)};x.ensureDoc("checkHeader",[]).then(function(){x.ensureDoc("parseStartXRef",[]).then(function(){x.ensureDoc("parse",[e]).then(a,r)},r)},r);return t.promise}function o(e,t){var r,n=y(),s=e.source;if(s.data){try{r=new A(M,s.data,s.password,t,E);n.resolve(r)}catch(e){n.reject(e)}return n.promise}var o;try{if(s.chunkedViewerLoading)o=new R(s,D);else{v(i,"pdfjs/core/network module is not loaded");o=new i(e)}}catch(e){n.reject(e);return n.promise}var c=o.getFullReader();c.headersReady.then(function(){c.isStreamingSupported&&c.isRangeSupported||(c.onProgress=function(e){D.send("DocProgress",{loaded:e.loaded,total:e.total})});if(c.isRangeSupported){var e=s.disableAutoFetch||c.isStreamingSupported;r=new I(M,o,{msgHandler:D,url:s.url,password:s.password,length:c.contentLength,disableAutoFetch:e,rangeChunkSize:s.rangeChunkSize},t,E);n.resolve(r);O=null}}).catch(function(e){n.reject(e);O=null});var l=[],h=0,u=function(){var e=b(l);s.length&&e.length!==s.length&&w("reported HTTP length is different from actual");try{r=new A(M,e,s.password,t,E);n.resolve(r)}catch(e){n.reject(e)}l=[]};new Promise(function(e,t){var i=function(e){try{a();if(e.done){r||u();O=null;return}var n=e.value;h+=m(n);c.isStreamingSupported||D.send("DocProgress",{loaded:h,total:Math.max(h,c.contentLength||0)});r?r.sendProgressiveData(n):l.push(n);c.read().then(i,t)}catch(e){t(e)}};c.read().then(i,t)}).catch(function(e){n.reject(e);O=null});O=function(){o.cancelAllRequests("abort")};return n.promise}function C(e){function t(e){a();D.send("GetDoc",{pdfInfo:e})}function i(e){if(e instanceof d){var t=new B("PasswordException: response "+e.code);r(t);D.sendWithPromise("PasswordRequest",e).then(function(e){n(t);x.updatePassword(e.password);c()}).catch(function(e){n(t);D.send("PasswordException",e)}.bind(null,e))}else e instanceof l?D.send("InvalidPDF",e):e instanceof u?D.send("MissingPDF",e):e instanceof f?D.send("UnexpectedResponse",e):D.send("UnknownError",new g(e.message,e.toString()))}function c(){a();s(!1).then(t,function(e){a();if(e instanceof p){x.requestLoadedStream();x.onLoadedStream().then(function(){a();s(!0).then(t,i)})}else i(e)},i)}a();o(e,{forceDataSchema:e.disableCreateObjectURL,maxImageSize:void 0===e.maxImageSize?-1:e.maxImageSize,disableFontFace:e.disableFontFace,disableNativeImageDecoder:e.disableNativeImageDecoder}).then(function(e){if(T){e.terminate();throw new Error("Worker was terminated")}x=e;D.send("PDFManagerReady",null);x.onLoadedStream().then(function(e){D.send("DataLoaded",{length:e.bytes.byteLength})})}).then(c,i)}var x,T=!1,O=null,P=[],M=e.docId,E=e.docBaseUrl,L=e.docId+"_worker",D=new h(L,M,t);D.postMessageTransfers=e.postMessageTransfers;D.on("GetPage",function(e){return x.getPage(e.pageIndex).then(function(e){var t=x.ensure(e,"rotate"),a=x.ensure(e,"ref"),r=x.ensure(e,"userUnit"),i=x.ensure(e,"view");return Promise.all([t,a,r,i]).then(function(e){return{rotate:e[0],ref:e[1],userUnit:e[2],view:e[3]}})})});D.on("GetPageIndex",function(e){var t=new S(e.ref.num,e.ref.gen);return x.pdfDocument.catalog.getPageIndex(t)});D.on("GetDestinations",function(e){return x.ensureCatalog("destinations")});D.on("GetDestination",function(e){return x.ensureCatalog("getDestination",[e.id])});D.on("GetPageLabels",function(e){return x.ensureCatalog("pageLabels")});D.on("GetAttachments",function(e){return x.ensureCatalog("attachments")});D.on("GetJavaScript",function(e){return x.ensureCatalog("javaScript")});D.on("GetOutline",function(e){return x.ensureCatalog("documentOutline")});D.on("GetMetadata",function(e){return Promise.all([x.ensureDoc("documentInfo"),x.ensureCatalog("metadata")])});D.on("GetData",function(e){x.requestLoadedStream();return x.onLoadedStream().then(function(e){return e.bytes})});D.on("GetStats",function(e){return x.pdfDocument.xref.stats});D.on("GetAnnotations",function(e){return x.getPage(e.pageIndex).then(function(t){return x.ensure(t,"getAnnotationsData",[e.intent])})});D.on("RenderPageRequest",function(e){var t=e.pageIndex;x.getPage(t).then(function(a){var i=new B("RenderPageRequest: page "+t);r(i);var s=t+1,o=Date.now();a.getOperatorList(D,i,e.intent,e.renderInteractiveForms).then(function(e){n(i);k("page="+s+" - getOperatorList: time="+(Date.now()-o)+"ms, len="+e.totalLength)},function(t){n(i);if(!i.terminated){D.send("UnsupportedFeature",{featureId:c.unknown});var a,r="worker.js: while trying to getPage() and getOperatorList()";a="string"==typeof t?{message:t,stack:r}:"object"==typeof t?{message:t.message||t.toString(),stack:t.stack||r}:{message:"Unknown exception type: "+typeof t,stack:r};D.send("PageError",{pageNum:s,error:a,intent:e.intent})}})})},this);D.on("GetTextContent",function(e){var t=e.pageIndex,a=e.normalizeWhitespace,i=e.combineTextItems;return x.getPage(t).then(function(e){var s=new B("GetTextContent: page "+t);r(s);var o=t+1,c=Date.now();return e.extractTextContent(D,s,a,i).then(function(e){n(s);k("text indexing: page="+o+" - time="+(Date.now()-c)+"ms");return e},function(e){n(s);if(!s.terminated)throw e})})});D.on("Cleanup",function(e){return x.cleanup()});D.on("Terminate",function(e){T=!0;if(x){x.terminate();x=null}O&&O();var t=[];P.forEach(function(e){t.push(e.finished);e.terminate()});return Promise.all(t).then(function(){D.destroy();D=null})});D.on("Ready",function(t){C(e);e=null});return L}};"undefined"!=typeof window||x()||function(){var e=new h("worker","main",self);T.setup(e,self);e.send("ready",null)}();t.setPDFNetworkStreamClass=r;t.WorkerTask=B;t.WorkerMessageHandler=T},function(e,t,a){"use strict";var r;r=function(){return this}();try{r=r||Function("return this")()||(0,eval)("this")}catch(e){"object"==typeof window&&(r=window)}e.exports=r},function(e,t,a){"use strict";var r=function(){function e(e,t,a){this.data=e;this.bp=t;this.dataEnd=a;this.chigh=e[t];this.clow=0;this.byteIn();this.chigh=this.chigh<<7&65535|this.clow>>9&127;this.clow=this.clow<<7&65535;this.ct-=7;this.a=32768}var t=[{qe:22017,nmps:1,nlps:1,switchFlag:1},{qe:13313,nmps:2,nlps:6,switchFlag:0},{qe:6145,nmps:3,nlps:9,switchFlag:0},{qe:2753,nmps:4,nlps:12,switchFlag:0},{qe:1313,nmps:5,nlps:29,switchFlag:0},{qe:545,nmps:38,nlps:33,switchFlag:0},{qe:22017,nmps:7,nlps:6,switchFlag:1},{qe:21505,nmps:8,nlps:14,switchFlag:0},{qe:18433,nmps:9,nlps:14,switchFlag:0},{qe:14337,nmps:10,nlps:14,switchFlag:0},{qe:12289,nmps:11,nlps:17,switchFlag:0},{qe:9217,nmps:12,nlps:18,switchFlag:0},{qe:7169,nmps:13,nlps:20,switchFlag:0},{qe:5633,nmps:29,nlps:21,switchFlag:0},{qe:22017,nmps:15,nlps:14,switchFlag:1},{qe:21505,nmps:16,nlps:14,switchFlag:0},{qe:20737,nmps:17,nlps:15,switchFlag:0},{qe:18433,nmps:18,nlps:16,switchFlag:0},{qe:14337,nmps:19,nlps:17,switchFlag:0},{qe:13313,nmps:20,nlps:18,switchFlag:0},{qe:12289,nmps:21,nlps:19,switchFlag:0},{qe:10241,nmps:22,nlps:19,switchFlag:0},{qe:9217,nmps:23,nlps:20,switchFlag:0},{qe:8705,nmps:24,nlps:21,switchFlag:0},{qe:7169,nmps:25,nlps:22,switchFlag:0},{qe:6145,nmps:26,nlps:23,switchFlag:0},{qe:5633,nmps:27,nlps:24,switchFlag:0},{qe:5121,nmps:28,nlps:25,switchFlag:0},{qe:4609,nmps:29,nlps:26,switchFlag:0},{qe:4353,nmps:30,nlps:27,switchFlag:0},{qe:2753,nmps:31,nlps:28,switchFlag:0},{qe:2497,nmps:32,nlps:29,switchFlag:0},{qe:2209,nmps:33,nlps:30,switchFlag:0},{qe:1313,nmps:34,nlps:31,switchFlag:0},{qe:1089,nmps:35,nlps:32,switchFlag:0},{qe:673,nmps:36,nlps:33,switchFlag:0},{qe:545,nmps:37,nlps:34,switchFlag:0},{qe:321,nmps:38,nlps:35,switchFlag:0},{qe:273,nmps:39,nlps:36,switchFlag:0},{qe:133,nmps:40,nlps:37,switchFlag:0},{qe:73,nmps:41,nlps:38,switchFlag:0},{qe:37,nmps:42,nlps:39,switchFlag:0},{qe:21,nmps:43,nlps:40,switchFlag:0},{qe:9,nmps:44,nlps:41,switchFlag:0},{qe:5,nmps:45,nlps:42,switchFlag:0},{qe:1,nmps:45,nlps:43,switchFlag:0},{qe:22017,nmps:46,nlps:46,switchFlag:0}];e.prototype={byteIn:function(){var e=this.data,t=this.bp;if(255===e[t]){if(e[t+1]>143){this.clow+=65280;this.ct=8}else{t++;this.clow+=e[t]<<9;this.ct=7;this.bp=t}}else{t++;this.clow+=t<this.dataEnd?e[t]<<8:65280;this.ct=8;this.bp=t}if(this.clow>65535){this.chigh+=this.clow>>16;this.clow&=65535}},readBit:function(e,a){var r,i=e[a]>>1,n=1&e[a],s=t[i],o=s.qe,c=this.a-o;if(this.chigh<o)if(c<o){c=o;r=n;i=s.nmps}else{c=o;r=1^n;1===s.switchFlag&&(n=r);i=s.nlps}else{this.chigh-=o;if(0!=(32768&c)){this.a=c;return n}if(c<o){r=1^n;1===s.switchFlag&&(n=r);i=s.nlps}else{r=n;i=s.nmps}}do{0===this.ct&&this.byteIn();c<<=1;this.chigh=this.chigh<<1&65535|this.clow>>15&1;this.clow=this.clow<<1&65535;this.ct--}while(0==(32768&c));this.a=c;e[a]=i<<1|n;return r}};return e}();t.ArithmeticDecoder=r},function(e,t,a){"use strict";var r=a(0),i=a(22),n=a(4),s=r.error,o=r.info,c=r.bytesToString,l=r.warn,h=r.isArray,u=r.Util,f=r.stringToBytes,d=r.assert,g=i.ISOAdobeCharset,p=i.ExpertCharset,m=i.ExpertSubsetCharset,b=n.StandardEncoding,v=n.ExpertEncoding,y=[".notdef","space","exclam","quotedbl","numbersign","dollar","percent","ampersand","quoteright","parenleft","parenright","asterisk","plus","comma","hyphen","period","slash","zero","one","two","three","four","five","six","seven","eight","nine","colon","semicolon","less","equal","greater","question","at","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","bracketleft","backslash","bracketright","asciicircum","underscore","quoteleft","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","braceleft","bar","braceright","asciitilde","exclamdown","cent","sterling","fraction","yen","florin","section","currency","quotesingle","quotedblleft","guillemotleft","guilsinglleft","guilsinglright","fi","fl","endash","dagger","daggerdbl","periodcentered","paragraph","bullet","quotesinglbase","quotedblbase","quotedblright","guillemotright","ellipsis","perthousand","questiondown","grave","acute","circumflex","tilde","macron","breve","dotaccent","dieresis","ring","cedilla","hungarumlaut","ogonek","caron","emdash","AE","ordfeminine","Lslash","Oslash","OE","ordmasculine","ae","dotlessi","lslash","oslash","oe","germandbls","onesuperior","logicalnot","mu","trademark","Eth","onehalf","plusminus","Thorn","onequarter","divide","brokenbar","degree","thorn","threequarters","twosuperior","registered","minus","eth","multiply","threesuperior","copyright","Aacute","Acircumflex","Adieresis","Agrave","Aring","Atilde","Ccedilla","Eacute","Ecircumflex","Edieresis","Egrave","Iacute","Icircumflex","Idieresis","Igrave","Ntilde","Oacute","Ocircumflex","Odieresis","Ograve","Otilde","Scaron","Uacute","Ucircumflex","Udieresis","Ugrave","Yacute","Ydieresis","Zcaron","aacute","acircumflex","adieresis","agrave","aring","atilde","ccedilla","eacute","ecircumflex","edieresis","egrave","iacute","icircumflex","idieresis","igrave","ntilde","oacute","ocircumflex","odieresis","ograve","otilde","scaron","uacute","ucircumflex","udieresis","ugrave","yacute","ydieresis","zcaron","exclamsmall","Hungarumlautsmall","dollaroldstyle","dollarsuperior","ampersandsmall","Acutesmall","parenleftsuperior","parenrightsuperior","twodotenleader","onedotenleader","zerooldstyle","oneoldstyle","twooldstyle","threeoldstyle","fouroldstyle","fiveoldstyle","sixoldstyle","sevenoldstyle","eightoldstyle","nineoldstyle","commasuperior","threequartersemdash","periodsuperior","questionsmall","asuperior","bsuperior","centsuperior","dsuperior","esuperior","isuperior","lsuperior","msuperior","nsuperior","osuperior","rsuperior","ssuperior","tsuperior","ff","ffi","ffl","parenleftinferior","parenrightinferior","Circumflexsmall","hyphensuperior","Gravesmall","Asmall","Bsmall","Csmall","Dsmall","Esmall","Fsmall","Gsmall","Hsmall","Ismall","Jsmall","Ksmall","Lsmall","Msmall","Nsmall","Osmall","Psmall","Qsmall","Rsmall","Ssmall","Tsmall","Usmall","Vsmall","Wsmall","Xsmall","Ysmall","Zsmall","colonmonetary","onefitted","rupiah","Tildesmall","exclamdownsmall","centoldstyle","Lslashsmall","Scaronsmall","Zcaronsmall","Dieresissmall","Brevesmall","Caronsmall","Dotaccentsmall","Macronsmall","figuredash","hypheninferior","Ogoneksmall","Ringsmall","Cedillasmall","questiondownsmall","oneeighth","threeeighths","fiveeighths","seveneighths","onethird","twothirds","zerosuperior","foursuperior","fivesuperior","sixsuperior","sevensuperior","eightsuperior","ninesuperior","zeroinferior","oneinferior","twoinferior","threeinferior","fourinferior","fiveinferior","sixinferior","seveninferior","eightinferior","nineinferior","centinferior","dollarinferior","periodinferior","commainferior","Agravesmall","Aacutesmall","Acircumflexsmall","Atildesmall","Adieresissmall","Aringsmall","AEsmall","Ccedillasmall","Egravesmall","Eacutesmall","Ecircumflexsmall","Edieresissmall","Igravesmall","Iacutesmall","Icircumflexsmall","Idieresissmall","Ethsmall","Ntildesmall","Ogravesmall","Oacutesmall","Ocircumflexsmall","Otildesmall","Odieresissmall","OEsmall","Oslashsmall","Ugravesmall","Uacutesmall","Ucircumflexsmall","Udieresissmall","Yacutesmall","Thornsmall","Ydieresissmall","001.000","001.001","001.002","001.003","Black","Bold","Book","Light","Medium","Regular","Roman","Semibold"],k=function(){function e(e,t,a){this.bytes=e.getBytes();this.properties=t;this.seacAnalysisEnabled=!!a}var t=[null,{id:"hstem",min:2,stackClearing:!0,stem:!0},null,{id:"vstem",min:2,stackClearing:!0,stem:!0},{id:"vmoveto",min:1,stackClearing:!0},{id:"rlineto",min:2,resetStack:!0},{id:"hlineto",min:1,resetStack:!0},{id:"vlineto",min:1,resetStack:!0},{id:"rrcurveto",min:6,resetStack:!0},null,{id:"callsubr",min:1,undefStack:!0},{id:"return",min:0,undefStack:!0},null,null,{id:"endchar",min:0,stackClearing:!0},null,null,null,{id:"hstemhm",min:2,stackClearing:!0,stem:!0},{id:"hintmask",min:0,stackClearing:!0},{id:"cntrmask",min:0,stackClearing:!0},{id:"rmoveto",min:2,stackClearing:!0},{id:"hmoveto",min:1,stackClearing:!0},{id:"vstemhm",min:2,stackClearing:!0,stem:!0},{id:"rcurveline",min:8,resetStack:!0},{id:"rlinecurve",min:8,resetStack:!0},{id:"vvcurveto",min:4,resetStack:!0},{id:"hhcurveto",min:4,resetStack:!0},null,{id:"callgsubr",min:1,undefStack:!0},{id:"vhcurveto",min:4,resetStack:!0},{id:"hvcurveto",min:4,resetStack:!0}],a=[null,null,null,{id:"and",min:2,stackDelta:-1},{id:"or",min:2,stackDelta:-1},{id:"not",min:1,stackDelta:0},null,null,null,{id:"abs",min:1,stackDelta:0},{id:"add",min:2,stackDelta:-1,stackFn:function(e,t){e[t-2]=e[t-2]+e[t-1]}},{id:"sub",min:2,stackDelta:-1,stackFn:function(e,t){e[t-2]=e[t-2]-e[t-1]}},{id:"div",min:2,stackDelta:-1,stackFn:function(e,t){e[t-2]=e[t-2]/e[t-1]}},null,{id:"neg",min:1,stackDelta:0,stackFn:function(e,t){e[t-1]=-e[t-1]}},{id:"eq",min:2,stackDelta:-1},null,null,{id:"drop",min:1,stackDelta:-1},null,{id:"put",min:2,stackDelta:-2},{id:"get",min:1,stackDelta:0},{id:"ifelse",min:4,stackDelta:-3},{id:"random",min:0,stackDelta:1},{id:"mul",min:2,stackDelta:-1,stackFn:function(e,t){e[t-2]=e[t-2]*e[t-1]}},null,{id:"sqrt",min:1,stackDelta:0},{id:"dup",min:1,stackDelta:1},{id:"exch",min:2,stackDelta:0},{id:"index",min:2,stackDelta:0},{id:"roll",min:3,stackDelta:-2},null,null,null,{id:"hflex",min:7,resetStack:!0},{id:"flex",min:13,resetStack:!0},{id:"hflex1",min:9,resetStack:!0},{id:"flex1",min:11,resetStack:!0}];e.prototype={parse:function(){var e=this.properties,t=new w;this.cff=t;var a=this.parseHeader(),r=this.parseIndex(a.endPos),i=this.parseIndex(r.endPos),n=this.parseIndex(i.endPos),s=this.parseIndex(n.endPos),o=this.parseDict(i.obj.get(0)),c=this.createDict(I,o,t.strings);t.header=a.obj;t.names=this.parseNameIndex(r.obj);t.strings=this.parseStringIndex(n.obj);t.topDict=c;t.globalSubrIndex=s.obj;this.parsePrivateDict(t.topDict);t.isCIDFont=c.hasName("ROS");var l=c.getByName("CharStrings"),h=this.parseIndex(l).obj,u=c.getByName("FontMatrix");u&&(e.fontMatrix=u);var f=c.getByName("FontBBox");if(f){e.ascent=Math.max(f[3],f[1]);e.descent=Math.min(f[1],f[3]);e.ascentScaled=!0}var d,g;if(t.isCIDFont){for(var p=this.parseIndex(c.getByName("FDArray")).obj,m=0,b=p.count;m<b;++m){var v=p.get(m),y=this.createDict(I,this.parseDict(v),t.strings);this.parsePrivateDict(y);t.fdArray.push(y)}g=null;d=this.parseCharsets(c.getByName("charset"),h.count,t.strings,!0);t.fdSelect=this.parseFDSelect(c.getByName("FDSelect"),h.count)}else{d=this.parseCharsets(c.getByName("charset"),h.count,t.strings,!1);g=this.parseEncoding(c.getByName("Encoding"),e,t.strings,d.charset)}t.charset=d;t.encoding=g;var k=this.parseCharStrings(h,c.privateDict.subrsIndex,s.obj,t.fdSelect,t.fdArray);t.charStrings=k.charStrings;t.seacs=k.seacs;t.widths=k.widths;return t},parseHeader:function(){for(var e=this.bytes,t=e.length,a=0;a<t&&1!==e[a];)++a;if(a>=t)s("Invalid CFF header");else if(0!==a){o("cff data is shifted");e=e.subarray(a);this.bytes=e}var r=e[0],i=e[1],n=e[2],c=e[3];return{obj:new C(r,i,n,c),endPos:n}},parseDict:function(e){function t(){for(var t="",r=["0","1","2","3","4","5","6","7","8","9",".","E","E-",null,"-"],i=e.length;a<i;){var n=e[a++],s=n>>4,o=15&n;if(15===s)break;t+=r[s];if(15===o)break;t+=r[o]}return parseFloat(t)}var a=0,r=[],i=[];a=0;for(var n=e.length;a<n;){var s=e[a];if(s<=21){12===s&&(s=s<<8|e[++a]);i.push([s,r]);r=[];++a}else r.push(function(){var r=e[a++];if(30===r)return t();if(28===r){r=e[a++];r=(r<<24|e[a++]<<16)>>16;return r}if(29===r){r=e[a++];r=r<<8|e[a++];r=r<<8|e[a++];r=r<<8|e[a++];return r}if(r>=32&&r<=246)return r-139;if(r>=247&&r<=250)return 256*(r-247)+e[a++]+108;if(r>=251&&r<=254)return-256*(r-251)-e[a++]-108;l('CFFParser_parseDict: "'+r+'" is a reserved command.');return NaN}())}return i},parseIndex:function(e){var t,a,r=new S,i=this.bytes,n=i[e++]<<8|i[e++],s=[],o=e;if(0!==n){var c=i[e++],l=e+(n+1)*c-1;for(t=0,a=n+1;t<a;++t){for(var h=0,u=0;u<c;++u){h<<=8;h+=i[e++]}s.push(l+h)}o=s[n]}for(t=0,a=s.length-1;t<a;++t){var f=s[t],d=s[t+1];r.add(i.subarray(f,d))}return{obj:r,endPos:o}},parseNameIndex:function(e){for(var t=[],a=0,r=e.count;a<r;++a){for(var i=e.get(a),n=Math.min(i.length,127),s=[],o=0;o<n;++o){var l=i[o];0!==o||0!==l?s[o]=l<33||l>126||91===l||93===l||40===l||41===l||123===l||125===l||60===l||62===l||47===l||37===l||35===l?95:l:s[o]=l}t.push(c(s))}return t},parseStringIndex:function(e){for(var t=new x,a=0,r=e.count;a<r;++a){var i=e.get(a);t.add(c(i))}return t},createDict:function(e,t,a){for(var r=new e(a),i=0,n=t.length;i<n;++i){var s=t[i],o=s[0],c=s[1];r.setByKey(o,c)}return r},parseCharString:function(e,r,i,n){if(!r||e.callDepth>10)return!1;for(var s=e.stackSize,o=e.stack,c=r.length,h=0;h<c;){var u=r[h++],f=null;if(12===u){var d=r[h++];if(0===d){r[h-2]=139;r[h-1]=22;s=0}else f=a[d]}else if(28===u){o[s]=(r[h]<<24|r[h+1]<<16)>>16;h+=2;s++}else if(14===u){if(s>=4){s-=4;if(this.seacAnalysisEnabled){e.seac=o.slice(s,s+4);return!1}}f=t[u]}else if(u>=32&&u<=246){o[s]=u-139;s++}else if(u>=247&&u<=254){o[s]=u<251?(u-247<<8)+r[h]+108:-(u-251<<8)-r[h]-108;h++;s++}else if(255===u){o[s]=(r[h]<<24|r[h+1]<<16|r[h+2]<<8|r[h+3])/65536;h+=4;s++}else if(19===u||20===u){e.hints+=s>>1;h+=e.hints+7>>3;s%=2;f=t[u]}else{if(10===u||29===u){var g;g=10===u?i:n;if(!g){f=t[u];l("Missing subrsIndex for "+f.id);return!1}var p=32768;g.count<1240?p=107:g.count<33900&&(p=1131);var m=o[--s]+p;if(m<0||m>=g.count||isNaN(m)){f=t[u];l("Out of bounds subrIndex for "+f.id);return!1}e.stackSize=s;e.callDepth++;var b=this.parseCharString(e,g.get(m),i,n);if(!b)return!1;e.callDepth--;s=e.stackSize;continue}if(11===u){e.stackSize=s;return!0}f=t[u]}if(f){f.stem&&(e.hints+=s>>1);if("min"in f&&!e.undefStack&&s<f.min){l("Not enough parameters for "+f.id+"; actual: "+s+", expected: "+f.min);return!1}if(e.firstStackClearing&&f.stackClearing){e.firstStackClearing=!1;s-=f.min;s>=2&&f.stem?s%=2:s>1&&l("Found too many parameters for stack-clearing command");s>0&&o[s-1]>=0&&(e.width=o[s-1])}if("stackDelta"in f){"stackFn"in f&&f.stackFn(o,s);s+=f.stackDelta}else if(f.stackClearing)s=0;else if(f.resetStack){s=0;e.undefStack=!1}else if(f.undefStack){s=0;e.undefStack=!0;e.firstStackClearing=!1}}}e.stackSize=s;return!0},parseCharStrings:function(e,t,a,r,i){for(var n=[],s=[],o=e.count,c=0;c<o;c++){var h=e.get(c),u={callDepth:0,stackSize:0,stack:[],undefStack:!0,hints:0,firstStackClearing:!0,seac:null,width:null},f=!0,d=null;if(r&&i.length){var g=r.getFDIndex(c);if(-1===g){l("Glyph index is not in fd select.");f=!1}if(g>=i.length){l("Invalid fd index for glyph index.");f=!1}f&&(d=i[g].privateDict.subrsIndex)}else t&&(d=t);f&&(f=this.parseCharString(u,h,d,a));null!==u.width&&(s[c]=u.width);null!==u.seac&&(n[c]=u.seac);f||e.set(c,new Uint8Array([14]))}return{charStrings:e,seacs:n,widths:s}},emptyPrivateDictionary:function(e){var t=this.createDict(B,[],e.strings);e.setByKey(18,[0,0]);e.privateDict=t},parsePrivateDict:function(e){if(e.hasName("Private")){var t=e.getByName("Private");if(h(t)&&2===t.length){var a=t[0],r=t[1];if(0===a||r>=this.bytes.length)this.emptyPrivateDictionary(e);else{var i=r+a,n=this.bytes.subarray(r,i),s=this.parseDict(n),o=this.createDict(B,s,e.strings);e.privateDict=o;if(o.getByName("Subrs")){var c=o.getByName("Subrs"),l=r+c;if(0===c||l>=this.bytes.length)this.emptyPrivateDictionary(e);else{var u=this.parseIndex(l);o.subrsIndex=u.obj}}}}else e.removeByName("Private")}else this.emptyPrivateDictionary(e)},parseCharsets:function(e,t,a,r){if(0===e)return new T(!0,R.ISO_ADOBE,g);if(1===e)return new T(!0,R.EXPERT,p);if(2===e)return new T(!0,R.EXPERT_SUBSET,m);var i,n,o,c=this.bytes,l=e,h=c[e++],u=[".notdef"];t-=1;switch(h){case 0:for(o=0;o<t;o++){i=c[e++]<<8|c[e++];u.push(r?i:a.get(i))}break;case 1:for(;u.length<=t;){i=c[e++]<<8|c[e++];n=c[e++];for(o=0;o<=n;o++)u.push(r?i++:a.get(i++))}break;case 2:for(;u.length<=t;){i=c[e++]<<8|c[e++];n=c[e++]<<8|c[e++];for(o=0;o<=n;o++)u.push(r?i++:a.get(i++))}break;default:s("Unknown charset format")}var f=e,d=c.subarray(l,f)
+;return new T(!1,h,u,d)},parseEncoding:function(e,t,a,r){var i,n,o,c=Object.create(null),l=this.bytes,h=!1,u=null;if(0===e||1===e){h=!0;i=e;var f=e?v:b;for(n=0,o=r.length;n<o;n++){var d=f.indexOf(r[n]);-1!==d&&(c[d]=n)}}else{var g=e;i=l[e++];switch(127&i){case 0:var p=l[e++];for(n=1;n<=p;n++)c[l[e++]]=n;break;case 1:var m=l[e++],y=1;for(n=0;n<m;n++)for(var k=l[e++],w=l[e++],C=k;C<=k+w;C++)c[C]=y++;break;default:s("Unknown encoding format: "+i+" in CFF")}var x=e;if(128&i){l[g]&=127;!function(){var t=l[e++];for(n=0;n<t;n++){var i=l[e++],s=(l[e++]<<8)+(255&l[e++]);c[i]=r.indexOf(a.get(s))}}()}u=l.subarray(g,x)}i&=127;return new O(h,i,c,u)},parseFDSelect:function(e,t){var a,r,i=e,n=this.bytes,o=n[e++],c=[],h=!1;switch(o){case 0:for(r=0;r<t;++r){var u=n[e++];c.push(u)}a=n.subarray(i,e);break;case 3:var f=n[e++]<<8|n[e++];for(r=0;r<f;++r){var g=n[e++]<<8|n[e++];if(0===r&&0!==g){l("parseFDSelect: The first range must have a first GID of 0 -- trying to recover.");h=!0;g=0}for(var p=n[e++],m=n[e]<<8|n[e+1],b=g;b<m;++b)c.push(p)}e+=2;a=n.subarray(i,e);h&&(a[3]=a[4]=0);break;default:s('parseFDSelect: Unknown format "'+o+'".')}d(c.length===t,"parseFDSelect: Invalid font data.");return new P(c,a)}};return e}(),w=function(){function e(){this.header=null;this.names=[];this.topDict=null;this.strings=new x;this.globalSubrIndex=null;this.encoding=null;this.charset=null;this.charStrings=null;this.fdArray=[];this.fdSelect=null;this.isCIDFont=!1}return e}(),C=function(){function e(e,t,a,r){this.major=e;this.minor=t;this.hdrSize=a;this.offSize=r}return e}(),x=function(){function e(){this.strings=[]}e.prototype={get:function(e){return e>=0&&e<=390?y[e]:e-391<=this.strings.length?this.strings[e-391]:y[0]},add:function(e){this.strings.push(e)},get count(){return this.strings.length}};return e}(),S=function(){function e(){this.objects=[];this.length=0}e.prototype={add:function(e){this.length+=e.length;this.objects.push(e)},set:function(e,t){this.length+=t.length-this.objects[e].length;this.objects[e]=t},get:function(e){return this.objects[e]},get count(){return this.objects.length}};return e}(),A=function(){function e(e,t){this.keyToNameMap=e.keyToNameMap;this.nameToKeyMap=e.nameToKeyMap;this.defaults=e.defaults;this.types=e.types;this.opcodes=e.opcodes;this.order=e.order;this.strings=t;this.values=Object.create(null)}e.prototype={setByKey:function(e,t){if(!(e in this.keyToNameMap))return!1;var a=t.length;if(0===a)return!0;for(var r=0;r<a;r++)if(isNaN(t[r])){l('Invalid CFFDict value: "'+t+'" for key "'+e+'".');return!0}var i=this.types[e];"num"!==i&&"sid"!==i&&"offset"!==i||(t=t[0]);this.values[e]=t;return!0},setByName:function(e,t){e in this.nameToKeyMap||s('Invalid dictionary name "'+e+'"');this.values[this.nameToKeyMap[e]]=t},hasName:function(e){return this.nameToKeyMap[e]in this.values},getByName:function(e){e in this.nameToKeyMap||s('Invalid dictionary name "'+e+'"');var t=this.nameToKeyMap[e];return t in this.values?this.values[t]:this.defaults[t]},removeByName:function(e){delete this.values[this.nameToKeyMap[e]]}};e.createTables=function(e){for(var t={keyToNameMap:{},nameToKeyMap:{},defaults:{},types:{},opcodes:{},order:[]},a=0,r=e.length;a<r;++a){var i=e[a],n=h(i[0])?(i[0][0]<<8)+i[0][1]:i[0];t.keyToNameMap[n]=i[1];t.nameToKeyMap[i[1]]=n;t.types[n]=i[2];t.defaults[n]=i[3];t.opcodes[n]=h(i[0])?i[0]:[i[0]];t.order.push(n)}return t};return e}(),I=function(){function e(e){null===a&&(a=A.createTables(t));A.call(this,a,e);this.privateDict=null}var t=[[[12,30],"ROS",["sid","sid","num"],null],[[12,20],"SyntheticBase","num",null],[0,"version","sid",null],[1,"Notice","sid",null],[[12,0],"Copyright","sid",null],[2,"FullName","sid",null],[3,"FamilyName","sid",null],[4,"Weight","sid",null],[[12,1],"isFixedPitch","num",0],[[12,2],"ItalicAngle","num",0],[[12,3],"UnderlinePosition","num",-100],[[12,4],"UnderlineThickness","num",50],[[12,5],"PaintType","num",0],[[12,6],"CharstringType","num",2],[[12,7],"FontMatrix",["num","num","num","num","num","num"],[.001,0,0,.001,0,0]],[13,"UniqueID","num",null],[5,"FontBBox",["num","num","num","num"],[0,0,0,0]],[[12,8],"StrokeWidth","num",0],[14,"XUID","array",null],[15,"charset","offset",0],[16,"Encoding","offset",0],[17,"CharStrings","offset",0],[18,"Private",["offset","offset"],null],[[12,21],"PostScript","sid",null],[[12,22],"BaseFontName","sid",null],[[12,23],"BaseFontBlend","delta",null],[[12,31],"CIDFontVersion","num",0],[[12,32],"CIDFontRevision","num",0],[[12,33],"CIDFontType","num",0],[[12,34],"CIDCount","num",8720],[[12,35],"UIDBase","num",null],[[12,37],"FDSelect","offset",null],[[12,36],"FDArray","offset",null],[[12,38],"FontName","sid",null]],a=null;e.prototype=Object.create(A.prototype);return e}(),B=function(){function e(e){null===a&&(a=A.createTables(t));A.call(this,a,e);this.subrsIndex=null}var t=[[6,"BlueValues","delta",null],[7,"OtherBlues","delta",null],[8,"FamilyBlues","delta",null],[9,"FamilyOtherBlues","delta",null],[[12,9],"BlueScale","num",.039625],[[12,10],"BlueShift","num",7],[[12,11],"BlueFuzz","num",1],[10,"StdHW","num",null],[11,"StdVW","num",null],[[12,12],"StemSnapH","delta",null],[[12,13],"StemSnapV","delta",null],[[12,14],"ForceBold","num",0],[[12,17],"LanguageGroup","num",0],[[12,18],"ExpansionFactor","num",.06],[[12,19],"initialRandomSeed","num",0],[20,"defaultWidthX","num",0],[21,"nominalWidthX","num",0],[19,"Subrs","offset",null]],a=null;e.prototype=Object.create(A.prototype);return e}(),R={ISO_ADOBE:0,EXPERT:1,EXPERT_SUBSET:2},T=function(){function e(e,t,a,r){this.predefined=e;this.format=t;this.charset=a;this.raw=r}return e}(),O=function(){function e(e,t,a,r){this.predefined=e;this.format=t;this.encoding=a;this.raw=r}return e}(),P=function(){function e(e,t){this.fdSelect=e;this.raw=t}e.prototype={getFDIndex:function(e){return e<0||e>=this.fdSelect.length?-1:this.fdSelect[e]}};return e}(),M=function(){function e(){this.offsets=Object.create(null)}e.prototype={isTracking:function(e){return e in this.offsets},track:function(e,t){e in this.offsets&&s("Already tracking location of "+e);this.offsets[e]=t},offset:function(e){for(var t in this.offsets)this.offsets[t]+=e},setEntryLocation:function(e,t,a){e in this.offsets||s("Not tracking location of "+e);for(var r=a.data,i=this.offsets[e],n=0,o=t.length;n<o;++n){var c=5*n+i,l=c+1,h=c+2,u=c+3,f=c+4;29===r[c]&&0===r[l]&&0===r[h]&&0===r[u]&&0===r[f]||s("writing to an offset that is not empty");var d=t[n];r[c]=29;r[l]=d>>24&255;r[h]=d>>16&255;r[u]=d>>8&255;r[f]=255&d}}};return e}(),E=function(){function e(e){this.cff=e}e.prototype={compile:function(){var e=this.cff,t={data:[],length:0,add:function(e){this.data=this.data.concat(e);this.length=this.data.length}},a=this.compileHeader(e.header);t.add(a);var r=this.compileNameIndex(e.names);t.add(r);if(e.isCIDFont&&e.topDict.hasName("FontMatrix")){var i=e.topDict.getByName("FontMatrix");e.topDict.removeByName("FontMatrix");for(var n=0,s=e.fdArray.length;n<s;n++){var o=e.fdArray[n],c=i.slice(0);o.hasName("FontMatrix")&&(c=u.transform(c,o.getByName("FontMatrix")));o.setByName("FontMatrix",c)}}var l=this.compileTopDicts([e.topDict],t.length,e.isCIDFont);t.add(l.output);var h=l.trackers[0],f=this.compileStringIndex(e.strings.strings);t.add(f);var d=this.compileIndex(e.globalSubrIndex);t.add(d);if(e.encoding&&e.topDict.hasName("Encoding"))if(e.encoding.predefined)h.setEntryLocation("Encoding",[e.encoding.format],t);else{var g=this.compileEncoding(e.encoding);h.setEntryLocation("Encoding",[t.length],t);t.add(g)}if(e.charset&&e.topDict.hasName("charset"))if(e.charset.predefined)h.setEntryLocation("charset",[e.charset.format],t);else{var p=this.compileCharset(e.charset);h.setEntryLocation("charset",[t.length],t);t.add(p)}var m=this.compileCharStrings(e.charStrings);h.setEntryLocation("CharStrings",[t.length],t);t.add(m);if(e.isCIDFont){h.setEntryLocation("FDSelect",[t.length],t);var b=this.compileFDSelect(e.fdSelect.raw);t.add(b);l=this.compileTopDicts(e.fdArray,t.length,!0);h.setEntryLocation("FDArray",[t.length],t);t.add(l.output);var v=l.trackers;this.compilePrivateDicts(e.fdArray,v,t)}this.compilePrivateDicts([e.topDict],[h],t);t.add([0]);return t.data},encodeNumber:function(e){return parseFloat(e)!==parseInt(e,10)||isNaN(e)?this.encodeFloat(e):this.encodeInteger(e)},encodeFloat:function(e){var t=e.toString(),a=/\.(\d*?)(?:9{5,20}|0{5,20})\d{0,2}(?:e(.+)|$)/.exec(t);if(a){var r=parseFloat("1e"+((a[2]?+a[2]:0)+a[1].length));t=(Math.round(e*r)/r).toString()}var i,n,s="";for(i=0,n=t.length;i<n;++i){var o=t[i];s+="e"===o?"-"===t[++i]?"c":"b":"."===o?"a":"-"===o?"e":o}s+=1&s.length?"f":"ff";var c=[30];for(i=0,n=s.length;i<n;i+=2)c.push(parseInt(s.substr(i,2),16));return c},encodeInteger:function(e){var t;if(e>=-107&&e<=107)t=[e+139];else if(e>=108&&e<=1131){e-=108;t=[247+(e>>8),255&e]}else if(e>=-1131&&e<=-108){e=-e-108;t=[251+(e>>8),255&e]}else t=e>=-32768&&e<=32767?[28,e>>8&255,255&e]:[29,e>>24&255,e>>16&255,e>>8&255,255&e];return t},compileHeader:function(e){return[e.major,e.minor,e.hdrSize,e.offSize]},compileNameIndex:function(e){for(var t=new S,a=0,r=e.length;a<r;++a)t.add(f(e[a]));return this.compileIndex(t)},compileTopDicts:function(e,t,a){for(var r=[],i=new S,n=0,s=e.length;n<s;++n){var o=e[n];if(a){o.removeByName("CIDFontVersion");o.removeByName("CIDFontRevision");o.removeByName("CIDFontType");o.removeByName("CIDCount");o.removeByName("UIDBase")}var c=new M,l=this.compileDict(o,c);r.push(c);i.add(l);c.offset(t)}i=this.compileIndex(i,r);return{trackers:r,output:i}},compilePrivateDicts:function(e,t,a){for(var r=0,i=e.length;r<i;++r){var n=e[r];d(n.privateDict&&n.hasName("Private"),"There must be an private dictionary.");var s=n.privateDict,o=new M,c=this.compileDict(s,o),l=a.length;o.offset(l);c.length||(l=0);t[r].setEntryLocation("Private",[c.length,l],a);a.add(c);if(s.subrsIndex&&s.hasName("Subrs")){var h=this.compileIndex(s.subrsIndex);o.setEntryLocation("Subrs",[c.length],a);a.add(h)}}},compileDict:function(e,t){for(var a=[],r=e.order,i=0;i<r.length;++i){var n=r[i];if(n in e.values){var o=e.values[n],c=e.types[n];h(c)||(c=[c]);h(o)||(o=[o]);if(0!==o.length){for(var l=0,u=c.length;l<u;++l){var f=c[l],d=o[l];switch(f){case"num":case"sid":a=a.concat(this.encodeNumber(d));break;case"offset":var g=e.keyToNameMap[n];t.isTracking(g)||t.track(g,a.length);a=a.concat([29,0,0,0,0]);break;case"array":case"delta":a=a.concat(this.encodeNumber(d));for(var p=1,m=o.length;p<m;++p)a=a.concat(this.encodeNumber(o[p]));break;default:s("Unknown data type of "+f)}}a=a.concat(e.opcodes[n])}}}return a},compileStringIndex:function(e){for(var t=new S,a=0,r=e.length;a<r;++a)t.add(f(e[a]));return this.compileIndex(t)},compileGlobalSubrIndex:function(){var e=this.cff.globalSubrIndex;this.out.writeByteArray(this.compileIndex(e))},compileCharStrings:function(e){return this.compileIndex(e)},compileCharset:function(e){return this.compileTypedArray(e.raw)},compileEncoding:function(e){return this.compileTypedArray(e.raw)},compileFDSelect:function(e){return this.compileTypedArray(e)},compileTypedArray:function(e){for(var t=[],a=0,r=e.length;a<r;++a)t[a]=e[a];return t},compileIndex:function(e,t){t=t||[];var a=e.objects,r=a.length;if(0===r)return[0,0,0];var i,n=[r>>8&255,255&r],s=1;for(i=0;i<r;++i)s+=a[i].length;var o;o=s<256?1:s<65536?2:s<16777216?3:4;n.push(o);var c=1;for(i=0;i<r+1;i++){1===o?n.push(255&c):2===o?n.push(c>>8&255,255&c):3===o?n.push(c>>16&255,c>>8&255,255&c):n.push(c>>>24&255,c>>16&255,c>>8&255,255&c);a[i]&&(c+=a[i].length)}for(i=0;i<r;i++){t[i]&&t[i].offset(n.length);for(var l=0,h=a[i].length;l<h;l++)n.push(a[i][l])}return n}};return e}();t.CFFStandardStrings=y;t.CFFParser=k;t.CFF=w;t.CFFHeader=C;t.CFFStrings=x;t.CFFIndex=S;t.CFFCharset=T;t.CFFTopDict=I;t.CFFPrivateDict=B;t.CFFCompiler=E},function(e,t,a){"use strict";var r=a(0),i=r.MissingDataException,n=r.arrayByteLength,s=r.arraysToBytes,o=r.assert,c=r.createPromiseCapability,l=r.isInt,h=r.isEmptyObj,u=function(){function e(e,t,a){this.bytes=new Uint8Array(e);this.start=0;this.pos=0;this.end=e;this.chunkSize=t;this.loadedChunks=[];this.numChunksLoaded=0;this.numChunks=Math.ceil(e/t);this.manager=a;this.progressiveDataLength=0;this.lastSuccessfulEnsureByteChunk=-1}e.prototype={getMissingChunks:function(){for(var e=[],t=0,a=this.numChunks;t<a;++t)this.loadedChunks[t]||e.push(t);return e},getBaseStreams:function(){return[this]},allChunksLoaded:function(){return this.numChunksLoaded===this.numChunks},onReceiveData:function(e,t){var a=e+t.byteLength;o(e%this.chunkSize==0,"Bad begin offset: "+e);var r=this.bytes.length;o(a%this.chunkSize==0||a===r,"Bad end offset: "+a);this.bytes.set(new Uint8Array(t),e);var i,n=this.chunkSize,s=Math.floor(e/n),c=Math.floor((a-1)/n)+1;for(i=s;i<c;++i)if(!this.loadedChunks[i]){this.loadedChunks[i]=!0;++this.numChunksLoaded}},onReceiveProgressiveData:function(e){var t=this.progressiveDataLength,a=Math.floor(t/this.chunkSize);this.bytes.set(new Uint8Array(e),t);t+=e.byteLength;this.progressiveDataLength=t;var r,i=t>=this.end?this.numChunks:Math.floor(t/this.chunkSize);for(r=a;r<i;++r)if(!this.loadedChunks[r]){this.loadedChunks[r]=!0;++this.numChunksLoaded}},ensureByte:function(e){var t=Math.floor(e/this.chunkSize);if(t!==this.lastSuccessfulEnsureByteChunk){if(!this.loadedChunks[t])throw new i(e,e+1);this.lastSuccessfulEnsureByteChunk=t}},ensureRange:function(e,t){if(!(e>=t||t<=this.progressiveDataLength))for(var a=this.chunkSize,r=Math.floor(e/a),n=Math.floor((t-1)/a)+1,s=r;s<n;++s)if(!this.loadedChunks[s])throw new i(e,t)},nextEmptyChunk:function(e){for(var t,a=this.numChunks,r=0;r<a;++r){t=(e+r)%a;if(!this.loadedChunks[t])return t}return null},hasChunk:function(e){return!!this.loadedChunks[e]},get length(){return this.end-this.start},get isEmpty(){return 0===this.length},getByte:function(){var e=this.pos;if(e>=this.end)return-1;this.ensureByte(e);return this.bytes[this.pos++]},getUint16:function(){var e=this.getByte(),t=this.getByte();return-1===e||-1===t?-1:(e<<8)+t},getInt32:function(){return(this.getByte()<<24)+(this.getByte()<<16)+(this.getByte()<<8)+this.getByte()},getBytes:function(e){var t=this.bytes,a=this.pos,r=this.end;if(!e){this.ensureRange(a,r);return t.subarray(a,r)}var i=a+e;i>r&&(i=r);this.ensureRange(a,i);this.pos=i;return t.subarray(a,i)},peekByte:function(){var e=this.getByte();this.pos--;return e},peekBytes:function(e){var t=this.getBytes(e);this.pos-=t.length;return t},getByteRange:function(e,t){this.ensureRange(e,t);return this.bytes.subarray(e,t)},skip:function(e){e||(e=1);this.pos+=e},reset:function(){this.pos=this.start},moveStart:function(){this.start=this.pos},makeSubStream:function(e,t,a){function r(){}this.ensureRange(e,e+t);r.prototype=Object.create(this);r.prototype.getMissingChunks=function(){for(var e=this.chunkSize,t=Math.floor(this.start/e),a=Math.floor((this.end-1)/e)+1,r=[],i=t;i<a;++i)this.loadedChunks[i]||r.push(i);return r};var i=new r;i.pos=i.start=e;i.end=e+t||this.end;i.dict=a;return i}};return e}(),f=function(){function e(e,t){var a=t.rangeChunkSize,r=t.length;this.stream=new u(r,a,this);this.length=r;this.chunkSize=a;this.pdfNetworkStream=e;this.url=t.url;this.disableAutoFetch=t.disableAutoFetch;this.msgHandler=t.msgHandler;this.currRequestId=0;this.chunksNeededByRequest=Object.create(null);this.requestsByChunk=Object.create(null);this.promisesByRequest=Object.create(null);this.progressiveDataLength=0;this.aborted=!1;this._loadedStreamCapability=c()}e.prototype={onLoadedStream:function(){return this._loadedStreamCapability.promise},sendRequest:function(e,t){var a=this.pdfNetworkStream.getRangeReader(e,t);a.isStreamingSupported||(a.onProgress=this.onProgress.bind(this));var r=[],i=0,o=this;new Promise(function(e,t){var c=function(l){try{if(!l.done){var h=l.value;r.push(h);i+=n(h);a.isStreamingSupported&&o.onProgress({loaded:i});a.read().then(c,t);return}var u=s(r);r=null;e(u)}catch(e){t(e)}};a.read().then(c,t)}).then(function(t){this.aborted||this.onReceiveData({chunk:t,begin:e})}.bind(this))},requestAllChunks:function(){var e=this.stream.getMissingChunks();this._requestChunks(e);return this._loadedStreamCapability.promise},_requestChunks:function(e){var t,a,r=this.currRequestId++,i=Object.create(null);this.chunksNeededByRequest[r]=i;for(t=0,a=e.length;t<a;t++)this.stream.hasChunk(e[t])||(i[e[t]]=!0);if(h(i))return Promise.resolve();var n=c();this.promisesByRequest[r]=n;var s=[];for(var o in i){o|=0;if(!(o in this.requestsByChunk)){this.requestsByChunk[o]=[];s.push(o)}this.requestsByChunk[o].push(r)}if(!s.length)return n.promise;var l=this.groupChunks(s);for(t=0;t<l.length;++t){var u=l[t],f=u.beginChunk*this.chunkSize,d=Math.min(u.endChunk*this.chunkSize,this.length);this.sendRequest(f,d)}return n.promise},getStream:function(){return this.stream},requestRange:function(e,t){t=Math.min(t,this.length);for(var a=this.getBeginChunk(e),r=this.getEndChunk(t),i=[],n=a;n<r;++n)i.push(n);return this._requestChunks(i)},requestRanges:function(e){e=e||[];for(var t=[],a=0;a<e.length;a++)for(var r=this.getBeginChunk(e[a].begin),i=this.getEndChunk(e[a].end),n=r;n<i;++n)t.indexOf(n)<0&&t.push(n);t.sort(function(e,t){return e-t});return this._requestChunks(t)},groupChunks:function(e){for(var t=[],a=-1,r=-1,i=0;i<e.length;++i){var n=e[i];a<0&&(a=n);if(r>=0&&r+1!==n){t.push({beginChunk:a,endChunk:r+1});a=n}i+1===e.length&&t.push({beginChunk:a,endChunk:n+1});r=n}return t},onProgress:function(e){var t=this.stream.numChunksLoaded*this.chunkSize+e.loaded;this.msgHandler.send("DocProgress",{loaded:t,total:this.length})},onReceiveData:function(e){var t=e.chunk,a=void 0===e.begin,r=a?this.progressiveDataLength:e.begin,i=r+t.byteLength,n=Math.floor(r/this.chunkSize),s=i<this.length?Math.floor(i/this.chunkSize):Math.ceil(i/this.chunkSize);if(a){this.stream.onReceiveProgressiveData(t);this.progressiveDataLength=i}else this.stream.onReceiveData(r,t);this.stream.allChunksLoaded()&&this._loadedStreamCapability.resolve(this.stream);var o,c,u=[];for(t=n;t<s;++t){var f=this.requestsByChunk[t]||[];delete this.requestsByChunk[t];for(o=0;o<f.length;++o){c=f[o];var d=this.chunksNeededByRequest[c];t in d&&delete d[t];h(d)&&u.push(c)}}if(!this.disableAutoFetch&&h(this.requestsByChunk)){var g;if(1===this.stream.numChunksLoaded){var p=this.stream.numChunks-1;this.stream.hasChunk(p)||(g=p)}else g=this.stream.nextEmptyChunk(s);l(g)&&this._requestChunks([g])}for(o=0;o<u.length;++o){c=u[o];var m=this.promisesByRequest[c];delete this.promisesByRequest[c];m.resolve()}this.msgHandler.send("DocProgress",{loaded:this.stream.numChunksLoaded*this.chunkSize,total:this.length})},onError:function(e){this._loadedStreamCapability.reject(e)},getBeginChunk:function(e){return Math.floor(e/this.chunkSize)},getEndChunk:function(e){return Math.floor((e-1)/this.chunkSize)+1},abort:function(){this.aborted=!0;this.pdfNetworkStream&&this.pdfNetworkStream.cancelAllRequests("abort");for(var e in this.promisesByRequest){this.promisesByRequest[e].reject(new Error("Request was aborted"))}}};return e}();t.ChunkedStream=u;t.ChunkedStreamManager=f},function(e,t,a){"use strict";var r=a(0),i=a(1),n=a(2),s=r.PasswordException,o=r.PasswordResponses,c=r.bytesToString,l=r.warn,h=r.error,u=r.assert,f=r.isInt,d=r.stringToBytes,g=r.utf8StringToString,p=i.Name,m=i.isName,b=i.isDict,v=n.DecryptStream,y=function(){function e(e){this.a=0;this.b=0;var t,a,r=new Uint8Array(256),i=0,n=e.length;for(t=0;t<256;++t)r[t]=t;for(t=0;t<256;++t){a=r[t];i=i+a+e[t%n]&255;r[t]=r[i];r[i]=a}this.s=r}e.prototype={encryptBlock:function(e){var t,a,r,i=e.length,n=this.a,s=this.b,o=this.s,c=new Uint8Array(i);for(t=0;t<i;++t){n=n+1&255;a=o[n];s=s+a&255;r=o[s];o[n]=r;o[s]=a;c[t]=e[t]^o[a+r&255]}this.a=n;this.b=s;return c}};e.prototype.decryptBlock=e.prototype.encryptBlock;return e}(),k=function(){function e(e,r,i){var n,s,o,c=1732584193,l=-271733879,h=-1732584194,u=271733878,f=i+72&-64,d=new Uint8Array(f);for(n=0;n<i;++n)d[n]=e[r++];d[n++]=128;o=f-8;for(;n<o;)d[n++]=0;d[n++]=i<<3&255;d[n++]=i>>5&255;d[n++]=i>>13&255;d[n++]=i>>21&255;d[n++]=i>>>29&255;d[n++]=0;d[n++]=0;d[n++]=0;var g=new Int32Array(16);for(n=0;n<f;){for(s=0;s<16;++s,n+=4)g[s]=d[n]|d[n+1]<<8|d[n+2]<<16|d[n+3]<<24;var p,m,b=c,v=l,y=h,k=u;for(s=0;s<64;++s){if(s<16){p=v&y|~v&k;m=s}else if(s<32){p=k&v|~k&y;m=5*s+1&15}else if(s<48){p=v^y^k;m=3*s+5&15}else{p=y^(v|~k);m=7*s&15}var w=k,C=b+p+a[s]+g[m]|0,x=t[s];k=y;y=v;v=v+(C<<x|C>>>32-x)|0;b=w}c=c+b|0;l=l+v|0;h=h+y|0;u=u+k|0}return new Uint8Array([255&c,c>>8&255,c>>16&255,c>>>24&255,255&l,l>>8&255,l>>16&255,l>>>24&255,255&h,h>>8&255,h>>16&255,h>>>24&255,255&u,u>>8&255,u>>16&255,u>>>24&255])}var t=new Uint8Array([7,12,17,22,7,12,17,22,7,12,17,22,7,12,17,22,5,9,14,20,5,9,14,20,5,9,14,20,5,9,14,20,4,11,16,23,4,11,16,23,4,11,16,23,4,11,16,23,6,10,15,21,6,10,15,21,6,10,15,21,6,10,15,21]),a=new Int32Array([-680876936,-389564586,606105819,-1044525330,-176418897,1200080426,-1473231341,-45705983,1770035416,-1958414417,-42063,-1990404162,1804603682,-40341101,-1502002290,1236535329,-165796510,-1069501632,643717713,-373897302,-701558691,38016083,-660478335,-405537848,568446438,-1019803690,-187363961,1163531501,-1444681467,-51403784,1735328473,-1926607734,-378558,-2022574463,1839030562,-35309556,-1530992060,1272893353,-155497632,-1094730640,681279174,-358537222,-722521979,76029189,-640364487,-421815835,530742520,-995338651,-198630844,1126891415,-1416354905,-57434055,1700485571,-1894986606,-1051523,-2054922799,1873313359,-30611744,-1560198380,1309151649,-145523070,-1120210379,718787259,-343485551]);return e}(),w=function(){function e(e,t){this.high=0|e;this.low=0|t}e.prototype={and:function(e){this.high&=e.high;this.low&=e.low},xor:function(e){this.high^=e.high;this.low^=e.low},or:function(e){this.high|=e.high;this.low|=e.low},shiftRight:function(e){if(e>=32){this.low=this.high>>>e-32|0;this.high=0}else{this.low=this.low>>>e|this.high<<32-e;this.high=this.high>>>e|0}},shiftLeft:function(e){if(e>=32){this.high=this.low<<e-32;this.low=0}else{this.high=this.high<<e|this.low>>>32-e;this.low=this.low<<e}},rotateRight:function(e){var t,a;if(32&e){a=this.low;t=this.high}else{t=this.low;a=this.high}e&=31;this.low=t>>>e|a<<32-e;this.high=a>>>e|t<<32-e},not:function(){this.high=~this.high;this.low=~this.low},add:function(e){var t=(this.low>>>0)+(e.low>>>0),a=(this.high>>>0)+(e.high>>>0);t>4294967295&&(a+=1);this.low=0|t;this.high=0|a},copyTo:function(e,t){e[t]=this.high>>>24&255;e[t+1]=this.high>>16&255;e[t+2]=this.high>>8&255;e[t+3]=255&this.high;e[t+4]=this.low>>>24&255;e[t+5]=this.low>>16&255;e[t+6]=this.low>>8&255;e[t+7]=255&this.low},assign:function(e){this.high=e.high;this.low=e.low}};return e}(),C=function(){function e(e,t){return e>>>t|e<<32-t}function t(e,t,a){return e&t^~e&a}function a(e,t,a){return e&t^e&a^t&a}function r(t){return e(t,2)^e(t,13)^e(t,22)}function i(t){return e(t,6)^e(t,11)^e(t,25)}function n(t){return e(t,7)^e(t,18)^t>>>3}function s(t){return e(t,17)^e(t,19)^t>>>10}function o(e,o,l){var h,u,f,d=1779033703,g=3144134277,p=1013904242,m=2773480762,b=1359893119,v=2600822924,y=528734635,k=1541459225,w=64*Math.ceil((l+9)/64),C=new Uint8Array(w);for(h=0;h<l;++h)C[h]=e[o++];C[h++]=128;f=w-8;for(;h<f;)C[h++]=0;C[h++]=0;C[h++]=0;C[h++]=0;C[h++]=l>>>29&255;C[h++]=l>>21&255;C[h++]=l>>13&255;C[h++]=l>>5&255;C[h++]=l<<3&255;var x=new Uint32Array(64);for(h=0;h<w;){for(u=0;u<16;++u){x[u]=C[h]<<24|C[h+1]<<16|C[h+2]<<8|C[h+3];h+=4}for(u=16;u<64;++u)x[u]=s(x[u-2])+x[u-7]+n(x[u-15])+x[u-16]|0;var S,A,I=d,B=g,R=p,T=m,O=b,P=v,M=y,E=k;for(u=0;u<64;++u){S=E+i(O)+t(O,P,M)+c[u]+x[u];A=r(I)+a(I,B,R);E=M;M=P;P=O;O=T+S|0;T=R;R=B;B=I;I=S+A|0}d=d+I|0;g=g+B|0;p=p+R|0;m=m+T|0;b=b+O|0;v=v+P|0;y=y+M|0;k=k+E|0}return new Uint8Array([d>>24&255,d>>16&255,d>>8&255,255&d,g>>24&255,g>>16&255,g>>8&255,255&g,p>>24&255,p>>16&255,p>>8&255,255&p,m>>24&255,m>>16&255,m>>8&255,255&m,b>>24&255,b>>16&255,b>>8&255,255&b,v>>24&255,v>>16&255,v>>8&255,255&v,y>>24&255,y>>16&255,y>>8&255,255&y,k>>24&255,k>>16&255,k>>8&255,255&k])}var c=[1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298];return o}(),x=function(){function e(e,t,a,r,i){e.assign(t);e.and(a);i.assign(t);i.not();i.and(r);e.xor(i)}function t(e,t,a,r,i){e.assign(t);e.and(a);i.assign(t);i.and(r);e.xor(i);i.assign(a);i.and(r);e.xor(i)}function a(e,t,a){e.assign(t);e.rotateRight(28);a.assign(t);a.rotateRight(34);e.xor(a);a.assign(t);a.rotateRight(39);e.xor(a)}function r(e,t,a){e.assign(t);e.rotateRight(14);a.assign(t);a.rotateRight(18);e.xor(a);a.assign(t);a.rotateRight(41);e.xor(a)}function i(e,t,a){e.assign(t);e.rotateRight(1);a.assign(t);a.rotateRight(8);e.xor(a);a.assign(t);a.shiftRight(7);e.xor(a)}function n(e,t,a){e.assign(t);e.rotateRight(19);a.assign(t);a.rotateRight(61);e.xor(a);a.assign(t);a.shiftRight(6);e.xor(a)}function s(s,c,l,h){h=!!h;var u,f,d,g,p,m,b,v;if(h){u=new w(3418070365,3238371032);f=new w(1654270250,914150663);d=new w(2438529370,812702999);g=new w(355462360,4144912697);p=new w(1731405415,4290775857);m=new w(2394180231,1750603025);b=new w(3675008525,1694076839);v=new w(1203062813,3204075428)}else{u=new w(1779033703,4089235720);f=new w(3144134277,2227873595);d=new w(1013904242,4271175723);g=new w(2773480762,1595750129);p=new w(1359893119,2917565137);m=new w(2600822924,725511199);b=new w(528734635,4215389547);v=new w(1541459225,327033209)}var y,k,C,x=128*Math.ceil((l+17)/128),S=new Uint8Array(x);for(y=0;y<l;++y)S[y]=s[c++];S[y++]=128;C=x-16;for(;y<C;)S[y++]=0;S[y++]=0;S[y++]=0;S[y++]=0;S[y++]=0;S[y++]=0;S[y++]=0;S[y++]=0;S[y++]=0;S[y++]=0;S[y++]=0;S[y++]=0;S[y++]=l>>>29&255;S[y++]=l>>21&255;S[y++]=l>>13&255;S[y++]=l>>5&255;S[y++]=l<<3&255;var A=new Array(80);for(y=0;y<80;y++)A[y]=new w(0,0);var I,B=new w(0,0),R=new w(0,0),T=new w(0,0),O=new w(0,0),P=new w(0,0),M=new w(0,0),E=new w(0,0),L=new w(0,0),D=new w(0,0),F=new w(0,0),q=new w(0,0),U=new w(0,0);for(y=0;y<x;){for(k=0;k<16;++k){A[k].high=S[y]<<24|S[y+1]<<16|S[y+2]<<8|S[y+3];A[k].low=S[y+4]<<24|S[y+5]<<16|S[y+6]<<8|S[y+7];y+=8}for(k=16;k<80;++k){I=A[k];n(I,A[k-2],U);I.add(A[k-7]);i(q,A[k-15],U);I.add(q);I.add(A[k-16])}B.assign(u);R.assign(f);T.assign(d);O.assign(g);P.assign(p);M.assign(m);E.assign(b);L.assign(v);for(k=0;k<80;++k){D.assign(L);r(q,P,U);D.add(q);e(q,P,M,E,U);D.add(q);D.add(o[k]);D.add(A[k]);a(F,B,U);t(q,B,R,T,U);F.add(q);I=L;L=E;E=M;M=P;O.add(D);P=O;O=T;T=R;R=B;I.assign(D);I.add(F);B=I}u.add(B);f.add(R);d.add(T);g.add(O);p.add(P);m.add(M);b.add(E);v.add(L)}var N;if(h){N=new Uint8Array(48);u.copyTo(N,0);f.copyTo(N,8);d.copyTo(N,16);g.copyTo(N,24);p.copyTo(N,32);m.copyTo(N,40)}else{N=new Uint8Array(64);u.copyTo(N,0);f.copyTo(N,8);d.copyTo(N,16);g.copyTo(N,24);p.copyTo(N,32);m.copyTo(N,40);b.copyTo(N,48);v.copyTo(N,56)}return N}var o=[new w(1116352408,3609767458),new w(1899447441,602891725),new w(3049323471,3964484399),new w(3921009573,2173295548),new w(961987163,4081628472),new w(1508970993,3053834265),new w(2453635748,2937671579),new w(2870763221,3664609560),new w(3624381080,2734883394),new w(310598401,1164996542),new w(607225278,1323610764),new w(1426881987,3590304994),new w(1925078388,4068182383),new w(2162078206,991336113),new w(2614888103,633803317),new w(3248222580,3479774868),new w(3835390401,2666613458),new w(4022224774,944711139),new w(264347078,2341262773),new w(604807628,2007800933),new w(770255983,1495990901),new w(1249150122,1856431235),new w(1555081692,3175218132),new w(1996064986,2198950837),new w(2554220882,3999719339),new w(2821834349,766784016),new w(2952996808,2566594879),new w(3210313671,3203337956),new w(3336571891,1034457026),new w(3584528711,2466948901),new w(113926993,3758326383),new w(338241895,168717936),new w(666307205,1188179964),new w(773529912,1546045734),new w(1294757372,1522805485),new w(1396182291,2643833823),new w(1695183700,2343527390),new w(1986661051,1014477480),new w(2177026350,1206759142),new w(2456956037,344077627),new w(2730485921,1290863460),new w(2820302411,3158454273),new w(3259730800,3505952657),new w(3345764771,106217008),new w(3516065817,3606008344),new w(3600352804,1432725776),new w(4094571909,1467031594),new w(275423344,851169720),new w(430227734,3100823752),new w(506948616,1363258195),new w(659060556,3750685593),new w(883997877,3785050280),new w(958139571,3318307427),new w(1322822218,3812723403),new w(1537002063,2003034995),new w(1747873779,3602036899),new w(1955562222,1575990012),new w(2024104815,1125592928),new w(2227730452,2716904306),new w(2361852424,442776044),new w(2428436474,593698344),new w(2756734187,3733110249),new w(3204031479,2999351573),new w(3329325298,3815920427),new w(3391569614,3928383900),new w(3515267271,566280711),new w(3940187606,3454069534),new w(4118630271,4000239992),new w(116418474,1914138554),new w(174292421,2731055270),new w(289380356,3203993006),new w(460393269,320620315),new w(685471733,587496836),new w(852142971,1086792851),new w(1017036298,365543100),new w(1126000580,2618297676),new w(1288033470,3409855158),new w(1501505948,4234509866),new w(1607167915,987167468),new w(1816402316,1246189591)];return s}(),S=function(){function e(e,t,a){return x(e,t,a,!0)}return e}(),A=function(){function e(){}e.prototype={decryptBlock:function(e){return e}};return e}(),I=function(){function e(e){var t=new Uint8Array(176);t.set(e);for(var a=16,r=1;a<176;++r){var i=t[a-3],o=t[a-2],c=t[a-1],l=t[a-4];i=s[i];o=s[o];c=s[c];l=s[l];i^=n[r];for(var h=0;h<4;++h){t[a]=i^=t[a-16];a++;t[a]=o^=t[a-16];a++;t[a]=c^=t[a-16];a++;t[a]=l^=t[a-16];a++}}return t}function t(e,t){var a=new Uint8Array(16);a.set(e);var r,i,n,s,c,l;for(i=0,n=160;i<16;++i,++n)a[i]^=t[n];for(r=9;r>=1;--r){s=a[13];a[13]=a[9];a[9]=a[5];a[5]=a[1];a[1]=s;s=a[14];c=a[10];a[14]=a[6];a[10]=a[2];a[6]=s;a[2]=c;s=a[15];c=a[11];l=a[7];a[15]=a[3];a[11]=s;a[7]=c;a[3]=l;for(i=0;i<16;++i)a[i]=o[a[i]];for(i=0,n=16*r;i<16;++i,++n)a[i]^=t[n];for(i=0;i<16;i+=4){var u=h[a[i]],f=h[a[i+1]],d=h[a[i+2]],g=h[a[i+3]];s=u^f>>>8^f<<24^d>>>16^d<<16^g>>>24^g<<8;a[i]=s>>>24&255;a[i+1]=s>>16&255;a[i+2]=s>>8&255;a[i+3]=255&s}}s=a[13];a[13]=a[9];a[9]=a[5];a[5]=a[1];a[1]=s;s=a[14];c=a[10];a[14]=a[6];a[10]=a[2];a[6]=s;a[2]=c;s=a[15];c=a[11];l=a[7];a[15]=a[3];a[11]=s;a[7]=c;a[3]=l;for(i=0;i<16;++i){a[i]=o[a[i]];a[i]^=t[i]}return a}function a(e,t){var a,r,i,n,o=new Uint8Array(16);o.set(e);for(h=0;h<16;++h)o[h]^=t[h];for(l=1;l<10;l++){for(h=0;h<16;++h)o[h]=s[o[h]];i=o[1];o[1]=o[5];o[5]=o[9];o[9]=o[13];o[13]=i;i=o[2];r=o[6];o[2]=o[10];o[6]=o[14];o[10]=i;o[14]=r;i=o[3];r=o[7];a=o[11];o[3]=o[15];o[7]=i;o[11]=r;o[15]=a;for(var h=0;h<16;h+=4){var u=o[h+0],f=o[h+1],d=o[h+2],g=o[h+3];a=u^f^d^g;o[h+0]^=a^c[u^f];o[h+1]^=a^c[f^d];o[h+2]^=a^c[d^g];o[h+3]^=a^c[g^u]}for(h=0,n=16*l;h<16;++h,++n)o[h]^=t[n]}for(h=0;h<16;++h)o[h]=s[o[h]];i=o[1];o[1]=o[5];o[5]=o[9];o[9]=o[13];o[13]=i;i=o[2];r=o[6];o[2]=o[10];o[6]=o[14];o[10]=i;o[14]=r;i=o[3];r=o[7];a=o[11];o[3]=o[15];o[7]=i;o[11]=r;o[15]=a;for(h=0,n=160;h<16;++h,++n)o[h]^=t[n];return o}function r(t){this.key=e(t);this.buffer=new Uint8Array(16);this.bufferPosition=0}function i(e,a){var r,i,n,s=e.length,o=this.buffer,c=this.bufferPosition,l=[],h=this.iv;for(r=0;r<s;++r){o[c]=e[r];++c;if(!(c<16)){var u=t(o,this.key);for(i=0;i<16;++i)u[i]^=h[i];h=o;l.push(u);o=new Uint8Array(16);c=0}}this.buffer=o;this.bufferLength=c;this.iv=h;if(0===l.length)return new Uint8Array([]);var f=16*l.length;if(a){var d=l[l.length-1],g=d[15];if(g<=16){for(r=15,n=16-g;r>=n;--r)if(d[r]!==g){g=0;break}f-=g;l[l.length-1]=d.subarray(0,16-g)}}var p=new Uint8Array(f);for(r=0,i=0,n=l.length;r<n;++r,i+=16)p.set(l[r],i);return p}
+for(var n=new Uint8Array([141,1,2,4,8,16,32,64,128,27,54,108,216,171,77,154,47,94,188,99,198,151,53,106,212,179,125,250,239,197,145,57,114,228,211,189,97,194,159,37,74,148,51,102,204,131,29,58,116,232,203,141,1,2,4,8,16,32,64,128,27,54,108,216,171,77,154,47,94,188,99,198,151,53,106,212,179,125,250,239,197,145,57,114,228,211,189,97,194,159,37,74,148,51,102,204,131,29,58,116,232,203,141,1,2,4,8,16,32,64,128,27,54,108,216,171,77,154,47,94,188,99,198,151,53,106,212,179,125,250,239,197,145,57,114,228,211,189,97,194,159,37,74,148,51,102,204,131,29,58,116,232,203,141,1,2,4,8,16,32,64,128,27,54,108,216,171,77,154,47,94,188,99,198,151,53,106,212,179,125,250,239,197,145,57,114,228,211,189,97,194,159,37,74,148,51,102,204,131,29,58,116,232,203,141,1,2,4,8,16,32,64,128,27,54,108,216,171,77,154,47,94,188,99,198,151,53,106,212,179,125,250,239,197,145,57,114,228,211,189,97,194,159,37,74,148,51,102,204,131,29,58,116,232,203,141]),s=new Uint8Array([99,124,119,123,242,107,111,197,48,1,103,43,254,215,171,118,202,130,201,125,250,89,71,240,173,212,162,175,156,164,114,192,183,253,147,38,54,63,247,204,52,165,229,241,113,216,49,21,4,199,35,195,24,150,5,154,7,18,128,226,235,39,178,117,9,131,44,26,27,110,90,160,82,59,214,179,41,227,47,132,83,209,0,237,32,252,177,91,106,203,190,57,74,76,88,207,208,239,170,251,67,77,51,133,69,249,2,127,80,60,159,168,81,163,64,143,146,157,56,245,188,182,218,33,16,255,243,210,205,12,19,236,95,151,68,23,196,167,126,61,100,93,25,115,96,129,79,220,34,42,144,136,70,238,184,20,222,94,11,219,224,50,58,10,73,6,36,92,194,211,172,98,145,149,228,121,231,200,55,109,141,213,78,169,108,86,244,234,101,122,174,8,186,120,37,46,28,166,180,198,232,221,116,31,75,189,139,138,112,62,181,102,72,3,246,14,97,53,87,185,134,193,29,158,225,248,152,17,105,217,142,148,155,30,135,233,206,85,40,223,140,161,137,13,191,230,66,104,65,153,45,15,176,84,187,22]),o=new Uint8Array([82,9,106,213,48,54,165,56,191,64,163,158,129,243,215,251,124,227,57,130,155,47,255,135,52,142,67,68,196,222,233,203,84,123,148,50,166,194,35,61,238,76,149,11,66,250,195,78,8,46,161,102,40,217,36,178,118,91,162,73,109,139,209,37,114,248,246,100,134,104,152,22,212,164,92,204,93,101,182,146,108,112,72,80,253,237,185,218,94,21,70,87,167,141,157,132,144,216,171,0,140,188,211,10,247,228,88,5,184,179,69,6,208,44,30,143,202,63,15,2,193,175,189,3,1,19,138,107,58,145,17,65,79,103,220,234,151,242,207,206,240,180,230,115,150,172,116,34,231,173,53,133,226,249,55,232,28,117,223,110,71,241,26,113,29,41,197,137,111,183,98,14,170,24,190,27,252,86,62,75,198,210,121,32,154,219,192,254,120,205,90,244,31,221,168,51,136,7,199,49,177,18,16,89,39,128,236,95,96,81,127,169,25,181,74,13,45,229,122,159,147,201,156,239,160,224,59,77,174,42,245,176,200,235,187,60,131,83,153,97,23,43,4,126,186,119,214,38,225,105,20,99,85,33,12,125]),c=new Uint8Array(256),l=0;l<256;l++)c[l]=l<128?l<<1:l<<1^27;var h=new Uint32Array([0,235474187,470948374,303765277,941896748,908933415,607530554,708780849,1883793496,2118214995,1817866830,1649639237,1215061108,1181045119,1417561698,1517767529,3767586992,4003061179,4236429990,4069246893,3635733660,3602770327,3299278474,3400528769,2430122216,2664543715,2362090238,2193862645,2835123396,2801107407,3035535058,3135740889,3678124923,3576870512,3341394285,3374361702,3810496343,3977675356,4279080257,4043610186,2876494627,2776292904,3076639029,3110650942,2472011535,2640243204,2403728665,2169303058,1001089995,899835584,666464733,699432150,59727847,226906860,530400753,294930682,1273168787,1172967064,1475418501,1509430414,1942435775,2110667444,1876241833,1641816226,2910219766,2743034109,2976151520,3211623147,2505202138,2606453969,2302690252,2269728455,3711829422,3543599269,3240894392,3475313331,3843699074,3943906441,4178062228,4144047775,1306967366,1139781709,1374988112,1610459739,1975683434,2076935265,1775276924,1742315127,1034867998,866637845,566021896,800440835,92987698,193195065,429456164,395441711,1984812685,2017778566,1784663195,1683407248,1315562145,1080094634,1383856311,1551037884,101039829,135050206,437757123,337553864,1042385657,807962610,573804783,742039012,2531067453,2564033334,2328828971,2227573024,2935566865,2700099354,3001755655,3168937228,3868552805,3902563182,4203181171,4102977912,3736164937,3501741890,3265478751,3433712980,1106041591,1340463100,1576976609,1408749034,2043211483,2009195472,1708848333,1809054150,832877231,1068351396,766945465,599762354,159417987,126454664,361929877,463180190,2709260871,2943682380,3178106961,3009879386,2572697195,2538681184,2236228733,2336434550,3509871135,3745345300,3441850377,3274667266,3910161971,3877198648,4110568485,4211818798,2597806476,2497604743,2261089178,2295101073,2733856160,2902087851,3202437046,2968011453,3936291284,3835036895,4136440770,4169408201,3535486456,3702665459,3467192302,3231722213,2051518780,1951317047,1716890410,1750902305,1113818384,1282050075,1584504582,1350078989,168810852,67556463,371049330,404016761,841739592,1008918595,775550814,540080725,3969562369,3801332234,4035489047,4269907996,3569255213,3669462566,3366754619,3332740144,2631065433,2463879762,2160117071,2395588676,2767645557,2868897406,3102011747,3069049960,202008497,33778362,270040487,504459436,875451293,975658646,675039627,641025152,2084704233,1917518562,1615861247,1851332852,1147550661,1248802510,1484005843,1451044056,933301370,967311729,733156972,632953703,260388950,25965917,328671808,496906059,1206477858,1239443753,1543208500,1441952575,2144161806,1908694277,1675577880,1842759443,3610369226,3644379585,3408119516,3307916247,4011190502,3776767469,4077384432,4245618683,2809771154,2842737049,3144396420,3043140495,2673705150,2438237621,2203032232,2370213795]);r.prototype={decryptBlock:function(e,t){var a,r=e.length,n=this.buffer,s=this.bufferPosition;for(a=0;s<16&&a<r;++a,++s)n[s]=e[a];if(s<16){this.bufferLength=s;return new Uint8Array([])}this.iv=n;this.buffer=new Uint8Array(16);this.bufferLength=0;this.decryptBlock=i;return this.decryptBlock(e.subarray(16),t)},encrypt:function(e,t){var r,i,n,s=e.length,o=this.buffer,c=this.bufferPosition,l=[];t||(t=new Uint8Array(16));for(r=0;r<s;++r){o[c]=e[r];++c;if(!(c<16)){for(i=0;i<16;++i)o[i]^=t[i];var h=a(o,this.key);t=h;l.push(h);o=new Uint8Array(16);c=0}}this.buffer=o;this.bufferLength=c;this.iv=t;if(0===l.length)return new Uint8Array([]);var u=16*l.length,f=new Uint8Array(u);for(r=0,i=0,n=l.length;r<n;++r,i+=16)f.set(l[r],i);return f}};return r}(),B=function(){function e(e){var t=new Uint8Array(240),a=1;t.set(e);for(var r=32,i=1;r<240;++i){if(r%32==16){s=n[s];o=n[o];c=n[c];l=n[l]}else if(r%32==0){var s=t[r-3],o=t[r-2],c=t[r-1],l=t[r-4];s=n[s];o=n[o];c=n[c];l=n[l];s^=a;(a<<=1)>=256&&(a=255&(27^a))}for(var h=0;h<4;++h){t[r]=s^=t[r-32];r++;t[r]=o^=t[r-32];r++;t[r]=c^=t[r-32];r++;t[r]=l^=t[r-32];r++}}return t}function t(e,t){var a=new Uint8Array(16);a.set(e);var r,i,n,o,c,h;for(i=0,n=224;i<16;++i,++n)a[i]^=t[n];for(r=13;r>=1;--r){o=a[13];a[13]=a[9];a[9]=a[5];a[5]=a[1];a[1]=o;o=a[14];c=a[10];a[14]=a[6];a[10]=a[2];a[6]=o;a[2]=c;o=a[15];c=a[11];h=a[7];a[15]=a[3];a[11]=o;a[7]=c;a[3]=h;for(i=0;i<16;++i)a[i]=s[a[i]];for(i=0,n=16*r;i<16;++i,++n)a[i]^=t[n];for(i=0;i<16;i+=4){var u=l[a[i]],f=l[a[i+1]],d=l[a[i+2]],g=l[a[i+3]];o=u^f>>>8^f<<24^d>>>16^d<<16^g>>>24^g<<8;a[i]=o>>>24&255;a[i+1]=o>>16&255;a[i+2]=o>>8&255;a[i+3]=255&o}}o=a[13];a[13]=a[9];a[9]=a[5];a[5]=a[1];a[1]=o;o=a[14];c=a[10];a[14]=a[6];a[10]=a[2];a[6]=o;a[2]=c;o=a[15];c=a[11];h=a[7];a[15]=a[3];a[11]=o;a[7]=c;a[3]=h;for(i=0;i<16;++i){a[i]=s[a[i]];a[i]^=t[i]}return a}function a(e,t){var a,r,i,s,l=new Uint8Array(16);l.set(e);for(h=0;h<16;++h)l[h]^=t[h];for(c=1;c<14;c++){for(h=0;h<16;++h)l[h]=n[l[h]];i=l[1];l[1]=l[5];l[5]=l[9];l[9]=l[13];l[13]=i;i=l[2];r=l[6];l[2]=l[10];l[6]=l[14];l[10]=i;l[14]=r;i=l[3];r=l[7];a=l[11];l[3]=l[15];l[7]=i;l[11]=r;l[15]=a;for(var h=0;h<16;h+=4){var u=l[h+0],f=l[h+1],d=l[h+2],g=l[h+3];a=u^f^d^g;l[h+0]^=a^o[u^f];l[h+1]^=a^o[f^d];l[h+2]^=a^o[d^g];l[h+3]^=a^o[g^u]}for(h=0,s=16*c;h<16;++h,++s)l[h]^=t[s]}for(h=0;h<16;++h)l[h]=n[l[h]];i=l[1];l[1]=l[5];l[5]=l[9];l[9]=l[13];l[13]=i;i=l[2];r=l[6];l[2]=l[10];l[6]=l[14];l[10]=i;l[14]=r;i=l[3];r=l[7];a=l[11];l[3]=l[15];l[7]=i;l[11]=r;l[15]=a;for(h=0,s=224;h<16;++h,++s)l[h]^=t[s];return l}function r(t){this.key=e(t);this.buffer=new Uint8Array(16);this.bufferPosition=0}function i(e,a){var r,i,n,s=e.length,o=this.buffer,c=this.bufferPosition,l=[],h=this.iv;for(r=0;r<s;++r){o[c]=e[r];++c;if(!(c<16)){var u=t(o,this.key);for(i=0;i<16;++i)u[i]^=h[i];h=o;l.push(u);o=new Uint8Array(16);c=0}}this.buffer=o;this.bufferLength=c;this.iv=h;if(0===l.length)return new Uint8Array([]);var f=16*l.length;if(a){var d=l[l.length-1],g=d[15];if(g<=16){for(r=15,n=16-g;r>=n;--r)if(d[r]!==g){g=0;break}f-=g;l[l.length-1]=d.subarray(0,16-g)}}var p=new Uint8Array(f);for(r=0,i=0,n=l.length;r<n;++r,i+=16)p.set(l[r],i);return p}for(var n=new Uint8Array([99,124,119,123,242,107,111,197,48,1,103,43,254,215,171,118,202,130,201,125,250,89,71,240,173,212,162,175,156,164,114,192,183,253,147,38,54,63,247,204,52,165,229,241,113,216,49,21,4,199,35,195,24,150,5,154,7,18,128,226,235,39,178,117,9,131,44,26,27,110,90,160,82,59,214,179,41,227,47,132,83,209,0,237,32,252,177,91,106,203,190,57,74,76,88,207,208,239,170,251,67,77,51,133,69,249,2,127,80,60,159,168,81,163,64,143,146,157,56,245,188,182,218,33,16,255,243,210,205,12,19,236,95,151,68,23,196,167,126,61,100,93,25,115,96,129,79,220,34,42,144,136,70,238,184,20,222,94,11,219,224,50,58,10,73,6,36,92,194,211,172,98,145,149,228,121,231,200,55,109,141,213,78,169,108,86,244,234,101,122,174,8,186,120,37,46,28,166,180,198,232,221,116,31,75,189,139,138,112,62,181,102,72,3,246,14,97,53,87,185,134,193,29,158,225,248,152,17,105,217,142,148,155,30,135,233,206,85,40,223,140,161,137,13,191,230,66,104,65,153,45,15,176,84,187,22]),s=new Uint8Array([82,9,106,213,48,54,165,56,191,64,163,158,129,243,215,251,124,227,57,130,155,47,255,135,52,142,67,68,196,222,233,203,84,123,148,50,166,194,35,61,238,76,149,11,66,250,195,78,8,46,161,102,40,217,36,178,118,91,162,73,109,139,209,37,114,248,246,100,134,104,152,22,212,164,92,204,93,101,182,146,108,112,72,80,253,237,185,218,94,21,70,87,167,141,157,132,144,216,171,0,140,188,211,10,247,228,88,5,184,179,69,6,208,44,30,143,202,63,15,2,193,175,189,3,1,19,138,107,58,145,17,65,79,103,220,234,151,242,207,206,240,180,230,115,150,172,116,34,231,173,53,133,226,249,55,232,28,117,223,110,71,241,26,113,29,41,197,137,111,183,98,14,170,24,190,27,252,86,62,75,198,210,121,32,154,219,192,254,120,205,90,244,31,221,168,51,136,7,199,49,177,18,16,89,39,128,236,95,96,81,127,169,25,181,74,13,45,229,122,159,147,201,156,239,160,224,59,77,174,42,245,176,200,235,187,60,131,83,153,97,23,43,4,126,186,119,214,38,225,105,20,99,85,33,12,125]),o=new Uint8Array(256),c=0;c<256;c++)o[c]=c<128?c<<1:c<<1^27;var l=new Uint32Array([0,235474187,470948374,303765277,941896748,908933415,607530554,708780849,1883793496,2118214995,1817866830,1649639237,1215061108,1181045119,1417561698,1517767529,3767586992,4003061179,4236429990,4069246893,3635733660,3602770327,3299278474,3400528769,2430122216,2664543715,2362090238,2193862645,2835123396,2801107407,3035535058,3135740889,3678124923,3576870512,3341394285,3374361702,3810496343,3977675356,4279080257,4043610186,2876494627,2776292904,3076639029,3110650942,2472011535,2640243204,2403728665,2169303058,1001089995,899835584,666464733,699432150,59727847,226906860,530400753,294930682,1273168787,1172967064,1475418501,1509430414,1942435775,2110667444,1876241833,1641816226,2910219766,2743034109,2976151520,3211623147,2505202138,2606453969,2302690252,2269728455,3711829422,3543599269,3240894392,3475313331,3843699074,3943906441,4178062228,4144047775,1306967366,1139781709,1374988112,1610459739,1975683434,2076935265,1775276924,1742315127,1034867998,866637845,566021896,800440835,92987698,193195065,429456164,395441711,1984812685,2017778566,1784663195,1683407248,1315562145,1080094634,1383856311,1551037884,101039829,135050206,437757123,337553864,1042385657,807962610,573804783,742039012,2531067453,2564033334,2328828971,2227573024,2935566865,2700099354,3001755655,3168937228,3868552805,3902563182,4203181171,4102977912,3736164937,3501741890,3265478751,3433712980,1106041591,1340463100,1576976609,1408749034,2043211483,2009195472,1708848333,1809054150,832877231,1068351396,766945465,599762354,159417987,126454664,361929877,463180190,2709260871,2943682380,3178106961,3009879386,2572697195,2538681184,2236228733,2336434550,3509871135,3745345300,3441850377,3274667266,3910161971,3877198648,4110568485,4211818798,2597806476,2497604743,2261089178,2295101073,2733856160,2902087851,3202437046,2968011453,3936291284,3835036895,4136440770,4169408201,3535486456,3702665459,3467192302,3231722213,2051518780,1951317047,1716890410,1750902305,1113818384,1282050075,1584504582,1350078989,168810852,67556463,371049330,404016761,841739592,1008918595,775550814,540080725,3969562369,3801332234,4035489047,4269907996,3569255213,3669462566,3366754619,3332740144,2631065433,2463879762,2160117071,2395588676,2767645557,2868897406,3102011747,3069049960,202008497,33778362,270040487,504459436,875451293,975658646,675039627,641025152,2084704233,1917518562,1615861247,1851332852,1147550661,1248802510,1484005843,1451044056,933301370,967311729,733156972,632953703,260388950,25965917,328671808,496906059,1206477858,1239443753,1543208500,1441952575,2144161806,1908694277,1675577880,1842759443,3610369226,3644379585,3408119516,3307916247,4011190502,3776767469,4077384432,4245618683,2809771154,2842737049,3144396420,3043140495,2673705150,2438237621,2203032232,2370213795]);r.prototype={decryptBlock:function(e,t,a){var r,n=e.length,s=this.buffer,o=this.bufferPosition;if(a)this.iv=a;else{for(r=0;o<16&&r<n;++r,++o)s[o]=e[r];if(o<16){this.bufferLength=o;return new Uint8Array([])}this.iv=s;e=e.subarray(16)}this.buffer=new Uint8Array(16);this.bufferLength=0;this.decryptBlock=i;return this.decryptBlock(e,t)},encrypt:function(e,t){var r,i,n,s=e.length,o=this.buffer,c=this.bufferPosition,l=[];t||(t=new Uint8Array(16));for(r=0;r<s;++r){o[c]=e[r];++c;if(!(c<16)){for(i=0;i<16;++i)o[i]^=t[i];var h=a(o,this.key);this.iv=h;l.push(h);o=new Uint8Array(16);c=0}}this.buffer=o;this.bufferLength=c;this.iv=t;if(0===l.length)return new Uint8Array([]);var u=16*l.length,f=new Uint8Array(u);for(r=0,i=0,n=l.length;r<n;++r,i+=16)f.set(l[r],i);return f}};return r}(),R=function(){function e(e,t){if(e.length!==t.length)return!1;for(var a=0;a<e.length;a++)if(e[a]!==t[a])return!1;return!0}function t(){}t.prototype={checkOwnerPassword:function(t,a,r,i){var n=new Uint8Array(t.length+56);n.set(t,0);n.set(a,t.length);n.set(r,t.length+a.length);return e(C(n,0,n.length),i)},checkUserPassword:function(t,a,r){var i=new Uint8Array(t.length+8);i.set(t,0);i.set(a,t.length);return e(C(i,0,i.length),r)},getOwnerKey:function(e,t,a,r){var i=new Uint8Array(e.length+56);i.set(e,0);i.set(t,e.length);i.set(a,e.length+t.length);var n=C(i,0,i.length);return new B(n).decryptBlock(r,!1,new Uint8Array(16))},getUserKey:function(e,t,a){var r=new Uint8Array(e.length+8);r.set(e,0);r.set(t,e.length);var i=C(r,0,r.length);return new B(i).decryptBlock(a,!1,new Uint8Array(16))}};return t}(),T=function(){function e(e,t){var a=new Uint8Array(e.length+t.length);a.set(e,0);a.set(t,e.length);return a}function t(t,a,r){for(var i=C(a,0,a.length).subarray(0,32),n=[0],s=0;s<64||n[n.length-1]>s-32;){var o=t.length+i.length+r.length,c=new Uint8Array(64*o),l=e(t,i);l=e(l,r);for(var h=0,u=0;h<64;h++,u+=o)c.set(l,u);n=new I(i.subarray(0,16)).encrypt(c,i.subarray(16,32));for(var f=0,d=0;d<16;d++){f*=1;f%=3;f+=(n[d]>>>0)%3;f%=3}0===f?i=C(n,0,n.length):1===f?i=S(n,0,n.length):2===f&&(i=x(n,0,n.length));s++}return i.subarray(0,32)}function a(){}function r(e,t){if(e.length!==t.length)return!1;for(var a=0;a<e.length;a++)if(e[a]!==t[a])return!1;return!0}a.prototype={hash:function(e,a,r){return t(e,a,r)},checkOwnerPassword:function(e,a,i,n){var s=new Uint8Array(e.length+56);s.set(e,0);s.set(a,e.length);s.set(i,e.length+a.length);return r(t(e,s,i),n)},checkUserPassword:function(e,a,i){var n=new Uint8Array(e.length+8);n.set(e,0);n.set(a,e.length);return r(t(e,n,[]),i)},getOwnerKey:function(e,a,r,i){var n=new Uint8Array(e.length+56);n.set(e,0);n.set(a,e.length);n.set(r,e.length+a.length);var s=t(e,n,r);return new B(s).decryptBlock(i,!1,new Uint8Array(16))},getUserKey:function(e,a,r){var i=new Uint8Array(e.length+8);i.set(e,0);i.set(a,e.length);var n=t(e,i,[]);return new B(n).decryptBlock(r,!1,new Uint8Array(16))}};return a}(),O=function(){function e(e,t){this.StringCipherConstructor=e;this.StreamCipherConstructor=t}e.prototype={createStream:function(e,t){var a=new this.StreamCipherConstructor;return new v(e,t,function(e,t){return a.decryptBlock(e,t)})},decryptString:function(e){var t=new this.StringCipherConstructor,a=d(e);a=t.decryptBlock(a,!0);return c(a)}};return e}(),P=function(){function e(e,t,a,r,i,n,s,o,c,l,h,u){if(t){var f=Math.min(127,t.length);t=t.subarray(0,f)}else t=[];var d;d=6===e?new T:new R;return d.checkUserPassword(t,o,s)?d.getUserKey(t,c,h):t.length&&d.checkOwnerPassword(t,r,n,a)?d.getOwnerKey(t,i,n,l):null}function t(e,t,a,r,i,n,s,o){var l,h,u=40+a.length+e.length,f=new Uint8Array(u),d=0;if(t){h=Math.min(32,t.length);for(;d<h;++d)f[d]=t[d]}l=0;for(;d<32;)f[d++]=c[l++];for(l=0,h=a.length;l<h;++l)f[d++]=a[l];f[d++]=255&i;f[d++]=i>>8&255;f[d++]=i>>16&255;f[d++]=i>>>24&255;for(l=0,h=e.length;l<h;++l)f[d++]=e[l];if(n>=4&&!o){f[d++]=255;f[d++]=255;f[d++]=255;f[d++]=255}var g=k(f,0,d),p=s>>3;if(n>=3)for(l=0;l<50;++l)g=k(g,0,p);var m,b,v=g.subarray(0,p);if(n>=3){for(d=0;d<32;++d)f[d]=c[d];for(l=0,h=e.length;l<h;++l)f[d++]=e[l];m=new y(v);b=m.encryptBlock(k(f,0,d));h=v.length;var w,C=new Uint8Array(h);for(l=1;l<=19;++l){for(w=0;w<h;++w)C[w]=v[w]^l;m=new y(C);b=m.encryptBlock(b)}for(l=0,h=b.length;l<h;++l)if(r[l]!==b[l])return null}else{m=new y(v);b=m.encryptBlock(c);for(l=0,h=b.length;l<h;++l)if(r[l]!==b[l])return null}return v}function a(e,t,a,r){var i,n,s=new Uint8Array(32),o=0;n=Math.min(32,e.length);for(;o<n;++o)s[o]=e[o];i=0;for(;o<32;)s[o++]=c[i++];var l=k(s,0,o),h=r>>3;if(a>=3)for(i=0;i<50;++i)l=k(l,0,l.length);var u,f;if(a>=3){f=t;var d,g=new Uint8Array(h);for(i=19;i>=0;i--){for(d=0;d<h;++d)g[d]=l[d]^i;u=new y(g);f=u.encryptBlock(f)}}else{u=new y(l.subarray(0,h));f=u.encryptBlock(t)}return f}function r(r,i,n){var c=r.get("Filter");m(c,"Standard")||h("unknown encryption method");this.dict=r;var u=r.get("V");(!f(u)||1!==u&&2!==u&&4!==u&&5!==u)&&h("unsupported encryption algorithm");this.algorithm=u;var p=r.get("Length");if(!p)if(u<=3)p=40;else{var y=r.get("CF"),k=r.get("StmF");if(b(y)&&m(k)){y.suppressEncryption=!0;var w=y.get(k.name);p=w&&w.get("Length")||128;p<40&&(p<<=3)}}(!f(p)||p<40||p%8!=0)&&h("invalid key length");var C=d(r.get("O")).subarray(0,32),x=d(r.get("U")).subarray(0,32),S=r.get("P"),A=r.get("R"),I=(4===u||5===u)&&!1!==r.get("EncryptMetadata");this.encryptMetadata=I;var B,R=d(i);if(n){if(6===A)try{n=g(n)}catch(e){l("CipherTransformFactory: Unable to convert UTF8 encoded password.")}B=d(n)}var T;if(5!==u)T=t(R,B,C,x,S,A,p,I);else{T=e(A,B,C,d(r.get("O")).subarray(32,40),d(r.get("O")).subarray(40,48),d(r.get("U")).subarray(0,48),x,d(r.get("U")).subarray(32,40),d(r.get("U")).subarray(40,48),d(r.get("OE")),d(r.get("UE")),d(r.get("Perms")))}if(!T&&!n)throw new s("No password given",o.NEED_PASSWORD);if(!T&&n){T=t(R,a(B,C,A,p),C,x,S,A,p,I)}if(!T)throw new s("Incorrect Password",o.INCORRECT_PASSWORD);this.encryptionKey=T;if(u>=4){var O=r.get("CF");b(O)&&(O.suppressEncryption=!0);this.cf=O;this.stmf=r.get("StmF")||v;this.strf=r.get("StrF")||v;this.eff=r.get("EFF")||this.stmf}}function i(e,t,a,r){var i,n,s=new Uint8Array(a.length+9);for(i=0,n=a.length;i<n;++i)s[i]=a[i];s[i++]=255&e;s[i++]=e>>8&255;s[i++]=e>>16&255;s[i++]=255&t;s[i++]=t>>8&255;if(r){s[i++]=115;s[i++]=65;s[i++]=108;s[i++]=84}return k(s,0,i).subarray(0,Math.min(a.length+5,16))}function n(e,t,a,r,n){u(m(t),"Invalid crypt filter name.");var s,o=e.get(t.name);null!==o&&void 0!==o&&(s=o.get("CFM"));if(!s||"None"===s.name)return function(){return new A};if("V2"===s.name)return function(){return new y(i(a,r,n,!1))};if("AESV2"===s.name)return function(){return new I(i(a,r,n,!0))};if("AESV3"===s.name)return function(){return new B(n)};h("Unknown crypto method")}var c=new Uint8Array([40,191,78,94,78,117,138,65,100,0,78,86,255,250,1,8,46,46,0,182,208,104,62,128,47,12,169,254,100,83,105,122]),v=p.get("Identity");r.prototype={createCipherTransform:function(e,t){if(4===this.algorithm||5===this.algorithm)return new O(n(this.cf,this.stmf,e,t,this.encryptionKey),n(this.cf,this.strf,e,t,this.encryptionKey));var a=i(e,t,this.encryptionKey,!1),r=function(){return new y(a)};return new O(r,r)}};return r}();t.AES128Cipher=I;t.AES256Cipher=B;t.ARCFourCipher=y;t.CipherTransformFactory=P;t.PDF17=R;t.PDF20=T;t.calculateMD5=k;t.calculateSHA256=C;t.calculateSHA384=S;t.calculateSHA512=x},function(e,t,a){"use strict";var r=a(0),i=a(1),n=a(2),s=a(5),o=a(27),c=a(3),l=a(31),h=a(26),u=a(6),f=a(32),d=a(23),g=a(30),p=a(21),m=a(4),b=a(17),v=a(18),y=a(7),k=r.FONT_IDENTITY_MATRIX,w=r.IDENTITY_MATRIX,C=r.UNSUPPORTED_FEATURES,x=r.ImageKind,S=r.OPS,A=r.TextRenderingMode,I=r.CMapCompressionType,B=r.Util,R=r.assert,T=r.createPromiseCapability,O=r.error,P=r.info,M=r.isArray,E=r.isNum,L=r.isString,D=r.getLookupTableFactory,F=r.warn,q=i.Dict,U=i.Name,N=i.isEOF,j=i.isCmd,_=i.isDict,z=i.isName,H=i.isRef,G=i.isStream,X=n.DecodeStream,V=n.JpegStream,W=n.Stream,K=s.Lexer,Y=s.Parser,J=o.PDFImage,Z=c.ColorSpace,Q=l.MurmurHash3_64,$=h.ErrorFont,ee=h.FontFlags,te=h.Font,ae=h.IdentityToUnicodeMap,re=h.ToUnicodeMap,ie=h.getFontType,ne=u.isPDFFunction,se=u.PDFFunction,oe=f.Pattern,ce=f.getTilingPatternIR,le=d.CMapFactory,he=d.IdentityCMap,ue=g.getMetrics,fe=p.bidi,de=m.WinAnsiEncoding,ge=m.StandardEncoding,pe=m.MacRomanEncoding,me=m.SymbolSetEncoding,be=m.ZapfDingbatsEncoding,ve=m.getEncoding,ye=b.getStdFontMap,ke=b.getSerifFonts,we=b.getSymbolsFonts,Ce=v.getNormalizedUnicodes,xe=v.reverseIfRtl,Se=v.getUnicodeForGlyph,Ae=y.getGlyphsUnicode,Ie=function(){function e(e,t,a,r){this.xref=e;this.resources=t;this.handler=a;this.forceDataSchema=r}function t(e,t,a,i,n,s,o,c){this.pdfManager=e;this.xref=t;this.handler=a;this.pageIndex=i;this.idFactory=n;this.fontCache=s;this.builtInCMapCache=o;this.options=c||r;this.fetchBuiltInCMap=function(e){var t=o[e];return t?Promise.resolve(t):a.sendWithPromise("FetchBuiltInCMap",{name:e}).then(function(t){t.compressionType!==I.NONE&&(o[e]=t);return t})}}function a(){this.reset()}var r={forceDataSchema:!1,maxImageSize:-1,disableFontFace:!1,disableNativeImageDecoder:!1};e.prototype={canDecode:function(t){return t instanceof V&&e.isDecodable(t,this.xref,this.resources)},decode:function(e){var t=e.dict,a=t.get("ColorSpace","CS");a=Z.parse(a,this.xref,this.resources);var r=a.numComps;return this.handler.sendWithPromise("JpegDecode",[e.getIR(this.forceDataSchema),r]).then(function(t){var a=t.data;return new W(a,0,a.length,e.dict)})}};e.isSupported=function(e,t,a){var r=e.dict;if(r.has("DecodeParms")||r.has("DP"))return!1;var i=Z.parse(r.get("ColorSpace","CS"),t,a);return("DeviceGray"===i.name||"DeviceRGB"===i.name)&&i.isDefaultDecode(r.getArray("Decode","D"))};e.isDecodable=function(e,t,a){var r=e.dict;if(r.has("DecodeParms")||r.has("DP"))return!1;var i=Z.parse(r.get("ColorSpace","CS"),t,a);return(1===i.numComps||3===i.numComps)&&i.isDefaultDecode(r.getArray("Decode","D"))};a.prototype={check:function(){if(++this.checked<100)return!1;this.checked=0;return this.endTime<=Date.now()},reset:function(){this.endTime=Date.now()+20;this.checked=0}};var i=Promise.resolve();t.prototype={hasBlendModes:function(e){if(!_(e))return!1;var t=Object.create(null);e.objId&&(t[e.objId]=!0);for(var a=[e],r=this.xref;a.length;){var i,n,s,o=a.shift(),c=o.get("ExtGState");if(_(c)){var l=c.getKeys();for(n=0,s=l.length;n<s;n++){i=l[n];var h=c.get(i),u=h.get("BM");if(z(u)&&"Normal"!==u.name)return!0}}var f=o.get("XObject");if(_(f)){var d=f.getKeys();for(n=0,s=d.length;n<s;n++){i=d[n];var g=f.getRaw(i);if(H(g)){if(t[g.toString()])continue;g=r.fetch(g)}if(G(g)){if(g.dict.objId){if(t[g.dict.objId])continue;t[g.dict.objId]=!0}var p=g.dict.get("Resources");if(_(p)&&(!p.objId||!t[p.objId])){a.push(p);p.objId&&(t[p.objId]=!0)}}}}}return!1},buildFormXObject:function(e,t,a,r,i,n){var s=t.dict.getArray("Matrix"),o=t.dict.getArray("BBox"),c=t.dict.get("Group");if(c){var l,h={matrix:s,bbox:o,smask:a,isolated:!1,knockout:!1},u=c.get("S");if(z(u,"Transparency")){h.isolated=c.get("I")||!1;h.knockout=c.get("K")||!1;l=c.has("CS")?Z.parse(c.get("CS"),this.xref,e):null}if(a&&a.backdrop){l=l||Z.singletons.rgb;a.backdrop=l.getRgb(a.backdrop,0)}r.addOp(S.beginGroup,[h])}r.addOp(S.paintFormXObjectBegin,[s,o]);return this.getOperatorList(t,i,t.dict.get("Resources")||e,r,n).then(function(){r.addOp(S.paintFormXObjectEnd,[]);c&&r.addOp(S.endGroup,[h])})},buildPaintImageXObject:function(t,a,r,i,n,s){var o=this,c=a.dict,l=c.get("Width","W"),h=c.get("Height","H");if(l&&E(l)&&h&&E(h)){var u=this.options.maxImageSize;if(-1!==u&&l*h>u)F("Image exceeded maximum allowed size and was removed.");else{var f,d,g=c.get("ImageMask","IM")||!1;if(g){var p=c.get("Width","W"),m=c.get("Height","H"),b=p+7>>3,v=a.getBytes(b*m),y=c.getArray("Decode","D"),k=!!y&&y[0]>0;f=J.createMask(v,p,m,a instanceof X,k);f.cached=!0;d=[f];i.addOp(S.paintImageMaskXObject,d);n&&(s[n]={fn:S.paintImageMaskXObject,args:d})}else{var w=c.get("SMask","SM")||!1,C=c.get("Mask")||!1;if(!r||w||C||a instanceof V||!(l+h<200)){var x=!this.options.disableNativeImageDecoder,A="img_"+this.idFactory.createObjId();i.addDependency(A);d=[A,l,h];if(x&&!w&&!C&&a instanceof V&&e.isSupported(a,this.xref,t)){i.addOp(S.paintJpegXObject,d);this.handler.send("obj",[A,this.pageIndex,"JpegStream",a.getIR(this.options.forceDataSchema)])}else{var I=null;x&&(a instanceof V||C instanceof V||w instanceof V)&&(I=new e(o.xref,t,o.handler,o.options.forceDataSchema));J.buildImage(o.handler,o.xref,t,a,r,I).then(function(e){var t=e.createImageData(!1);o.handler.send("obj",[A,o.pageIndex,"Image",t],[t.data.buffer])}).then(void 0,function(e){F("Unable to decode image: "+e);o.handler.send("obj",[A,o.pageIndex,"Image",null])});i.addOp(S.paintImageXObject,d);n&&(s[n]={fn:S.paintImageXObject,args:d})}}else{f=new J(this.xref,t,a,r,null,null).createImageData(!0);i.addOp(S.paintInlineImageXObject,[f])}}}}else F("Image dimensions are missing, or not numbers.")},handleSMask:function(e,t,a,r,i){var n=e.get("G"),s={subtype:e.get("S").name,backdrop:e.get("BC")},o=e.get("TR");if(ne(o)){for(var c=se.parse(this.xref,o),l=new Uint8Array(256),h=new Float32Array(1),u=0;u<256;u++){h[0]=u/255;c(h,0,h,0);l[u]=255*h[0]|0}s.transferMap=l}return this.buildFormXObject(t,n,s,a,r,i.state.clone())},handleTilingType:function(e,t,a,r,i,n,s){var o=new Re,c=[i.get("Resources"),a],l=q.merge(this.xref,c);return this.getOperatorList(r,s,l,o).then(function(){n.addDependencies(o.dependencies);n.addOp(e,ce({fnArray:o.fnArray,argsArray:o.argsArray},i,t))})},handleSetFont:function(e,t,a,r,i,n){var s;if(t){t=t.slice();s=t[0].name}var o=this;return this.loadFont(s,a,e).then(function(t){return t.font.isType3Font?t.loadType3Data(o,e,r,i).then(function(){return t},function(e){o.handler.send("UnsupportedFeature",{featureId:C.font});return new Be("g_font_error",new $("Type3 font load error: "+e),t.font)}):t}).then(function(e){n.font=e.font;e.send(o.handler);return e.loadedName})},handleText:function(e,t){var a=t.font,r=a.charsToGlyphs(e),i=!!(t.textRenderingMode&A.ADD_TO_PATH_FLAG);if(a.data&&(i||this.options.disableFontFace))for(var n=function(e){if(!a.renderer.hasBuiltPath(e)){var t=a.renderer.getPathJs(e);this.handler.send("commonobj",[a.loadedName+"_path_"+e,"FontPath",t])}}.bind(this),s=0,o=r.length;s<o;s++){var c=r[s];n(c.fontChar);var l=c.accent;l&&l.fontChar&&n(l.fontChar)}return r},setGState:function(e,t,a,r,i){for(var n=[],s=t.getKeys(),o=this,c=Promise.resolve(),l=0,h=s.length;l<h;l++){var u=s[l],f=t.get(u);switch(u){case"Type":break;case"LW":case"LC":case"LJ":case"ML":case"D":case"RI":case"FL":case"CA":case"ca":n.push([u,f]);break;case"Font":c=c.then(function(){return o.handleSetFont(e,null,f[0],a,r,i.state).then(function(e){a.addDependency(e);n.push([u,[e,f[1]]])})});break;case"BM":n.push([u,f]);break;case"SMask":if(z(f,"None")){n.push([u,!1]);break}if(_(f)){c=c.then(function(t){return o.handleSMask(t,e,a,r,i)}.bind(this,f));n.push([u,!0])}else F("Unsupported SMask type");break;case"OP":case"op":case"OPM":case"BG":case"BG2":case"UCR":case"UCR2":case"TR":case"TR2":case"HT":case"SM":case"SA":case"AIS":case"TK":P("graphic state operator "+u);break;default:P("Unknown graphic state operator "+u)}}return c.then(function(){n.length>0&&a.addOp(S.setGState,[n])})},loadFont:function(e,t,a){function r(){return Promise.resolve(new Be("g_font_error",new $("Font "+e+" is not available"),t))}var i,n=this.xref;if(t){R(H(t));i=t}else{var s=a.get("Font");if(!s){F("fontRes not available");return r()}i=s.getRaw(e)}if(!i){F("fontRef not available");return r()}if(this.fontCache.has(i))return this.fontCache.get(i);t=n.fetchIfRef(i);if(!_(t))return r();if(t.translated)return t.translated;var o,c=T(),l=this.preEvaluateFont(t),h=l.descriptor,u=H(i);u&&(o=i.toString());if(_(h)){h.fontAliases||(h.fontAliases=Object.create(null));var f=h.fontAliases,d=l.hash;if(f[d]){var g=f[d].aliasRef;if(u&&g&&this.fontCache.has(g)){this.fontCache.putAlias(i,g);return this.fontCache.get(i)}}else f[d]={fontID:te.getFontID()};u&&(f[d].aliasRef=i);o=f[d].fontID}if(u)this.fontCache.put(i,c.promise);else{o||(o=this.idFactory.createObjId());this.fontCache.put("id_"+o,c.promise)}R(o,'The "fontID" must be defined.');t.loadedName="g_"+this.pdfManager.docId+"_f"+o;t.translated=c.promise;var p;try{p=this.translateFont(l)}catch(e){p=Promise.reject(e)}var m=this;p.then(function(e){if(void 0!==e.fontType){n.stats.fontTypes[e.fontType]=!0}c.resolve(new Be(t.loadedName,e,t))},function(e){m.handler.send("UnsupportedFeature",{featureId:C.font});try{var a=l.descriptor,r=a&&a.get("FontFile3"),i=r&&r.get("Subtype"),s=ie(l.type,i&&i.name);n.stats.fontTypes[s]=!0}catch(e){}c.resolve(new Be(t.loadedName,new $(e instanceof Error?e.message:e),t))});return c.promise},buildPath:function(e,t,a){var r=e.length-1;a||(a=[]);if(r<0||e.fnArray[r]!==S.constructPath)e.addOp(S.constructPath,[[t],a]);else{var i=e.argsArray[r];i[0].push(t);Array.prototype.push.apply(i[1],a)}},handleColorN:function(e,t,a,r,i,n,s){var o,c=a[a.length-1];if(z(c)&&(o=i.get(c.name))){var l=G(o)?o.dict:o,h=l.get("PatternType");if(1===h){var u=r.base?r.base.getRgb(a,0):null;return this.handleTilingType(t,u,n,o,l,e,s)}if(2===h){var f=l.get("Shading"),d=l.getArray("Matrix");o=oe.parseShading(f,d,this.xref,n,this.handler);e.addOp(t,o.getIR());return Promise.resolve()}return Promise.reject("Unknown PatternType: "+h)}e.addOp(t,a);return Promise.resolve()},getOperatorList:function(e,t,r,n,s){var o=this,c=this.xref,l=Object.create(null);R(n);r=r||q.empty;var h=r.get("XObject")||q.empty,u=r.get("Pattern")||q.empty,f=new Te(s||new Pe),d=new Me(e,c,f),g=new a;return new Promise(function e(a,s){var p=function(t){t.then(function(){try{e(a,s)}catch(e){s(e)}},s)};t.ensureNotTerminated();g.reset();for(var m,b,v,y,k={};!(m=g.check());){k.args=null;if(!d.read(k))break;var w=k.args,C=k.fn;switch(0|C){case S.paintXObject:if(w[0].code)break;var x=w[0].name;if(!x){F("XObject must be referred to by name.");continue}if(void 0!==l[x]){n.addOp(l[x].fn,l[x].args);w=null;continue}var A=h.get(x);if(A){R(G(A),"XObject should be a stream");var I=A.dict.get("Subtype");R(z(I),"XObject should have a Name subtype");if("Form"===I.name){f.save();p(o.buildFormXObject(r,A,null,n,t,f.state.clone()).then(function(){f.restore()}));return}if("Image"===I.name){o.buildPaintImageXObject(r,A,!1,n,x,l);w=null;continue}if("PS"===I.name){P("Ignored XObject subtype PS");continue}O("Unhandled XObject subtype "+I.name)}break;case S.setFont:var B=w[1];p(o.handleSetFont(r,w,null,n,t,f.state).then(function(e){n.addDependency(e);n.addOp(S.setFont,[e,B])}));return;case S.endInlineImage:
+var T=w[0].cacheKey;if(T){var M=l[T];if(void 0!==M){n.addOp(M.fn,M.args);w=null;continue}}o.buildPaintImageXObject(r,w[0],!0,n,T,l);w=null;continue;case S.showText:w[0]=o.handleText(w[0],f.state);break;case S.showSpacedText:var D=w[0],U=[],N=D.length,j=f.state;for(b=0;b<N;++b){var H=D[b];L(H)?Array.prototype.push.apply(U,o.handleText(H,j)):E(H)&&U.push(H)}w[0]=U;C=S.showText;break;case S.nextLineShowText:n.addOp(S.nextLine);w[0]=o.handleText(w[0],f.state);C=S.showText;break;case S.nextLineSetSpacingShowText:n.addOp(S.nextLine);n.addOp(S.setWordSpacing,[w.shift()]);n.addOp(S.setCharSpacing,[w.shift()]);w[0]=o.handleText(w[0],f.state);C=S.showText;break;case S.setTextRenderingMode:f.state.textRenderingMode=w[0];break;case S.setFillColorSpace:f.state.fillColorSpace=Z.parse(w[0],c,r);continue;case S.setStrokeColorSpace:f.state.strokeColorSpace=Z.parse(w[0],c,r);continue;case S.setFillColor:y=f.state.fillColorSpace;w=y.getRgb(w,0);C=S.setFillRGBColor;break;case S.setStrokeColor:y=f.state.strokeColorSpace;w=y.getRgb(w,0);C=S.setStrokeRGBColor;break;case S.setFillGray:f.state.fillColorSpace=Z.singletons.gray;w=Z.singletons.gray.getRgb(w,0);C=S.setFillRGBColor;break;case S.setStrokeGray:f.state.strokeColorSpace=Z.singletons.gray;w=Z.singletons.gray.getRgb(w,0);C=S.setStrokeRGBColor;break;case S.setFillCMYKColor:f.state.fillColorSpace=Z.singletons.cmyk;w=Z.singletons.cmyk.getRgb(w,0);C=S.setFillRGBColor;break;case S.setStrokeCMYKColor:f.state.strokeColorSpace=Z.singletons.cmyk;w=Z.singletons.cmyk.getRgb(w,0);C=S.setStrokeRGBColor;break;case S.setFillRGBColor:f.state.fillColorSpace=Z.singletons.rgb;w=Z.singletons.rgb.getRgb(w,0);break;case S.setStrokeRGBColor:f.state.strokeColorSpace=Z.singletons.rgb;w=Z.singletons.rgb.getRgb(w,0);break;case S.setFillColorN:y=f.state.fillColorSpace;if("Pattern"===y.name){p(o.handleColorN(n,S.setFillColorN,w,y,u,r,t));return}w=y.getRgb(w,0);C=S.setFillRGBColor;break;case S.setStrokeColorN:y=f.state.strokeColorSpace;if("Pattern"===y.name){p(o.handleColorN(n,S.setStrokeColorN,w,y,u,r,t));return}w=y.getRgb(w,0);C=S.setStrokeRGBColor;break;case S.shadingFill:var X=r.get("Shading");R(X,"No shading resource found");var V=X.get(w[0].name);R(V,"No shading object found");w=[oe.parseShading(V,null,c,r,o.handler).getIR()];C=S.shadingFill;break;case S.setGState:var W=w[0],K=r.get("ExtGState");if(!_(K)||!K.has(W.name))break;var Y=K.get(W.name);p(o.setGState(r,Y,n,t,f));return;case S.moveTo:case S.lineTo:case S.curveTo:case S.curveTo2:case S.curveTo3:case S.closePath:case S.rectangle:o.buildPath(n,C,w);continue;case S.markPoint:case S.markPointProps:case S.beginMarkedContent:case S.beginMarkedContentProps:case S.endMarkedContent:case S.beginCompat:case S.endCompat:continue;default:if(null!==w){for(b=0,v=w.length;b<v&&!(w[b]instanceof q);b++);if(b<v){F("getOperatorList - ignoring operator: "+C);continue}}}n.addOp(C,w)}if(m)p(i);else{for(b=0,v=d.savedStatesDepth;b<v;b++)n.addOp(S.restore,[]);a()}})},getTextContent:function(e,t,r,n,s,o){function c(){if(b.initialized)return b;var e=I.font;e.loadedName in m.styles||(m.styles[e.loadedName]={fontFamily:e.fallbackName,ascent:e.ascent,descent:e.descent,vertical:e.vertical});b.fontName=e.loadedName;var t=[I.fontSize*I.textHScale,0,0,I.fontSize,0,I.textRise];if(e.isType3Font&&I.fontMatrix!==k&&1===I.fontSize){var a=e.bbox[3]-e.bbox[1];if(a>0){a*=I.fontMatrix[3];t[3]*=a}}var r=B.transform(I.ctm,B.transform(I.textMatrix,t));b.transform=r;if(e.vertical){b.width=Math.sqrt(r[0]*r[0]+r[1]*r[1]);b.height=0;b.vertical=!0}else{b.width=0;b.height=Math.sqrt(r[2]*r[2]+r[3]*r[3]);b.vertical=!1}var i=I.textLineMatrix[0],n=I.textLineMatrix[1],s=Math.sqrt(i*i+n*n);i=I.ctm[0];n=I.ctm[1];var o=Math.sqrt(i*i+n*n);b.textAdvanceScale=o*s;b.lastAdvanceWidth=0;b.lastAdvanceHeight=0;var c=e.spaceWidth/1e3*I.fontSize;if(c){b.spaceWidth=c;b.fakeSpaceMin=c*v;b.fakeMultiSpaceMin=c*y;b.fakeMultiSpaceMax=c*C;b.textRunBreakAllowed=!e.isMonospace}else{b.spaceWidth=0;b.fakeSpaceMin=1/0;b.fakeMultiSpaceMin=1/0;b.fakeMultiSpaceMax=0;b.textRunBreakAllowed=!1}b.initialized=!0;return b}function l(e){for(var t,a=0,r=e.length;a<r&&(t=e.charCodeAt(a))>=32&&t<=127;)a++;return a<r?e.replace(p," "):e}function h(e){var t=e.str.join(""),a=fe(t,-1,e.vertical);return{str:s?l(a.str):a.str,dir:a.dir,width:e.width,height:e.height,transform:e.transform,fontName:e.fontName}}function u(e,t){return x.loadFont(e,t,r).then(function(e){I.font=e.font;I.fontMatrix=e.font.fontMatrix||k})}function f(e){for(var t=I.font,a=c(),r=0,i=0,n=t.charsToGlyphs(e),s=0;s<n.length;s++){var o=n[s],l=null;l=t.vertical&&o.vmetric?o.vmetric[0]:o.width;var h=o.unicode,u=Ce();void 0!==u[h]&&(h=u[h]);h=xe(h);var f=I.charSpacing;if(o.isSpace){var g=I.wordSpacing;f+=g;g>0&&d(g,a.str)}var p=0,m=0;if(t.vertical){m=l*I.fontMatrix[0]*I.fontSize+f;i+=m}else{p=(l*I.fontMatrix[0]*I.fontSize+f)*I.textHScale;r+=p}I.translateTextMatrix(p,m);a.str.push(h)}if(t.vertical){a.lastAdvanceHeight=i;a.height+=Math.abs(i)}else{a.lastAdvanceWidth=r;a.width+=r}return a}function d(e,t){if(!(e<b.fakeSpaceMin))if(e<b.fakeMultiSpaceMin)t.push(" ");else for(var a=Math.round(e/b.spaceWidth);a-- >0;)t.push(" ")}function g(){if(b.initialized){b.width*=b.textAdvanceScale;b.height*=b.textAdvanceScale;m.items.push(h(b));b.initialized=!1;b.str.length=0}}n=n||new Te(new Oe);var p=/\s/g,m={items:[],styles:Object.create(null)},b={initialized:!1,str:[],width:0,height:0,vertical:!1,lastAdvanceWidth:0,lastAdvanceHeight:0,textAdvanceScale:0,spaceWidth:0,fakeSpaceMin:1/0,fakeMultiSpaceMin:1/0,fakeMultiSpaceMax:-0,textRunBreakAllowed:!1,transform:null,fontName:null},v=.3,y=1.5,C=4,x=this,A=this.xref;r=A.fetchIfRef(r)||q.empty;var I,T=null,O=Object.create(null),P=new Me(e,A,n),L=new a;return new Promise(function e(a,l){var h=function(t){t.then(function(){try{e(a,l)}catch(e){l(e)}},l)};t.ensureNotTerminated();L.reset();for(var p,v={},y=[];!(p=L.check());){y.length=0;v.args=y;if(!P.read(v))break;I=n.state;var k=v.fn;y=v.args;var C,A;switch(0|k){case S.setFont:var D=y[0].name,F=y[1];if(I.font&&D===I.fontName&&F===I.fontSize)break;g();I.fontName=D;I.fontSize=F;h(u(D,null));return;case S.setTextRise:g();I.textRise=y[0];break;case S.setHScale:g();I.textHScale=y[0]/100;break;case S.setLeading:g();I.leading=y[0];break;case S.moveText:var U=!!I.font&&0===(I.font.vertical?y[0]:y[1]);C=y[0]-y[1];if(o&&U&&b.initialized&&C>0&&C<=b.fakeMultiSpaceMax){I.translateTextLineMatrix(y[0],y[1]);b.width+=y[0]-b.lastAdvanceWidth;b.height+=y[1]-b.lastAdvanceHeight;A=y[0]-b.lastAdvanceWidth-(y[1]-b.lastAdvanceHeight);d(A,b.str);break}g();I.translateTextLineMatrix(y[0],y[1]);I.textMatrix=I.textLineMatrix.slice();break;case S.setLeadingMoveText:g();I.leading=-y[1];I.translateTextLineMatrix(y[0],y[1]);I.textMatrix=I.textLineMatrix.slice();break;case S.nextLine:g();I.carriageReturn();break;case S.setTextMatrix:C=I.calcTextLineMatrixAdvance(y[0],y[1],y[2],y[3],y[4],y[5]);if(o&&null!==C&&b.initialized&&C.value>0&&C.value<=b.fakeMultiSpaceMax){I.translateTextLineMatrix(C.width,C.height);b.width+=C.width-b.lastAdvanceWidth;b.height+=C.height-b.lastAdvanceHeight;A=C.width-b.lastAdvanceWidth-(C.height-b.lastAdvanceHeight);d(A,b.str);break}g();I.setTextMatrix(y[0],y[1],y[2],y[3],y[4],y[5]);I.setTextLineMatrix(y[0],y[1],y[2],y[3],y[4],y[5]);break;case S.setCharSpacing:I.charSpacing=y[0];break;case S.setWordSpacing:I.wordSpacing=y[0];break;case S.beginText:g();I.textMatrix=w.slice();I.textLineMatrix=w.slice();break;case S.showSpacedText:for(var N,j=y[0],H=0,X=j.length;H<X;H++)if("string"==typeof j[H])f(j[H]);else if(E(j[H])){c();C=j[H]*I.fontSize/1e3;var V=!1;if(I.font.vertical){N=C;I.translateTextMatrix(0,N);V=b.textRunBreakAllowed&&C>b.fakeMultiSpaceMax;V||(b.height+=N)}else{C=-C;N=C*I.textHScale;I.translateTextMatrix(N,0);V=b.textRunBreakAllowed&&C>b.fakeMultiSpaceMax;V||(b.width+=N)}V?g():C>0&&d(C,b.str)}break;case S.showText:f(y[0]);break;case S.nextLineShowText:g();I.carriageReturn();f(y[0]);break;case S.nextLineSetSpacingShowText:g();I.wordSpacing=y[0];I.charSpacing=y[1];I.carriageReturn();f(y[2]);break;case S.paintXObject:g();if(y[0].code)break;T||(T=r.get("XObject")||q.empty);var W=y[0].name;if(O.key===W){if(O.texts){B.appendToArray(m.items,O.texts.items);B.extendObj(m.styles,O.texts.styles)}break}var K=T.get(W);if(!K)break;R(G(K),"XObject should be a stream");var Y=K.dict.get("Subtype");R(z(Y),"XObject should have a Name subtype");if("Form"!==Y.name){O.key=W;O.texts=null;break}n.save();var J=K.dict.getArray("Matrix");M(J)&&6===J.length&&n.transform(J);h(x.getTextContent(K,t,K.dict.get("Resources")||r,n,s,o).then(function(e){B.appendToArray(m.items,e.items);B.extendObj(m.styles,e.styles);n.restore();O.key=W;O.texts=e}));return;case S.setGState:g();var Z=y[0],Q=r.get("ExtGState");if(!_(Q)||!z(Z))break;var $=Q.get(Z.name);if(!_($))break;var ee=$.get("Font");if(ee){I.fontName=null;I.fontSize=ee[1];h(u(null,ee[0]));return}}}if(p)h(i);else{g();a(m)}})},extractDataStructures:function(e,t,a){var r=this.xref,i=e.get("ToUnicode")||t.get("ToUnicode"),n=i?this.readToUnicode(i):Promise.resolve(void 0);if(a.composite){var s=e.get("CIDSystemInfo");_(s)&&(a.cidSystemInfo={registry:s.get("Registry"),ordering:s.get("Ordering"),supplement:s.get("Supplement")});var o=e.get("CIDToGIDMap");G(o)&&(a.cidToGidMap=this.readCidToGidMap(o))}var c,l=[],h=null;if(e.has("Encoding")){c=e.get("Encoding");if(_(c)){h=c.get("BaseEncoding");h=z(h)?h.name:null;if(c.has("Differences"))for(var u=c.get("Differences"),f=0,d=0,g=u.length;d<g;d++){var p=r.fetchIfRef(u[d]);E(p)?f=p:z(p)?l[f++]=p.name:O("Invalid entry in 'Differences' array: "+p)}}else z(c)?h=c.name:O("Encoding is not a Name nor a Dict");"MacRomanEncoding"!==h&&"MacExpertEncoding"!==h&&"WinAnsiEncoding"!==h&&(h=null)}if(h)a.defaultEncoding=ve(h).slice();else{var m=!!(a.flags&ee.Symbolic),b=!!(a.flags&ee.Nonsymbolic);c=ge;"TrueType"!==a.type||b||(c=de);if(m){c=pe;a.file||(/Symbol/i.test(a.name)?c=me:/Dingbats/i.test(a.name)&&(c=be))}a.defaultEncoding=c}a.differences=l;a.baseEncodingName=h;a.hasEncoding=!!h||l.length>0;a.dict=e;return n.then(function(e){a.toUnicode=e;return this.buildToUnicode(a)}.bind(this)).then(function(e){a.toUnicode=e;return a})},buildToUnicode:function(e){e.hasIncludedToUnicodeMap=!!e.toUnicode&&e.toUnicode.length>0;if(e.hasIncludedToUnicodeMap)return Promise.resolve(e.toUnicode);var t,a,r;if(!e.composite){t=[];var i=e.defaultEncoding.slice(),n=e.baseEncodingName,s=e.differences;for(a in s){r=s[a];".notdef"!==r&&(i[a]=r)}var o=Ae();for(a in i){r=i[a];if(""!==r)if(void 0!==o[r])t[a]=String.fromCharCode(o[r]);else{var c=0;switch(r[0]){case"G":3===r.length&&(c=parseInt(r.substr(1),16));break;case"g":5===r.length&&(c=parseInt(r.substr(1),16));break;case"C":case"c":r.length>=3&&(c=+r.substr(1));break;default:var l=Se(r,o);-1!==l&&(c=l)}if(c){if(n&&c===+a){var h=ve(n);if(h&&(r=h[a])){t[a]=String.fromCharCode(o[r]);continue}}t[a]=String.fromCharCode(c)}}}return Promise.resolve(new re(t))}if(e.composite&&(e.cMap.builtInCMap&&!(e.cMap instanceof he)||"Adobe"===e.cidSystemInfo.registry&&("GB1"===e.cidSystemInfo.ordering||"CNS1"===e.cidSystemInfo.ordering||"Japan1"===e.cidSystemInfo.ordering||"Korea1"===e.cidSystemInfo.ordering))){var u=e.cidSystemInfo.registry,f=e.cidSystemInfo.ordering,d=U.get(u+"-"+f+"-UCS2");return le.create({encoding:d,fetchBuiltInCMap:this.fetchBuiltInCMap,useCMap:null}).then(function(a){var r=e.cMap;t=[];r.forEach(function(e,r){R(r<=65535,"Max size of CID is 65,535");var i=a.lookup(r);i&&(t[e]=String.fromCharCode((i.charCodeAt(0)<<8)+i.charCodeAt(1)))});return new re(t)})}return Promise.resolve(new ae(e.firstChar,e.lastChar))},readToUnicode:function(e){var t=e;return z(t)?le.create({encoding:t,fetchBuiltInCMap:this.fetchBuiltInCMap,useCMap:null}).then(function(e){return e instanceof he?new ae(0,65535):new re(e.getMap())}):G(t)?le.create({encoding:t,fetchBuiltInCMap:this.fetchBuiltInCMap,useCMap:null}).then(function(e){if(e instanceof he)return new ae(0,65535);var t=new Array(e.length);e.forEach(function(e,a){for(var r=[],i=0;i<a.length;i+=2){var n=a.charCodeAt(i)<<8|a.charCodeAt(i+1);if(55296==(63488&n)){i+=2;var s=a.charCodeAt(i)<<8|a.charCodeAt(i+1);r.push(((1023&n)<<10)+(1023&s)+65536)}else r.push(n)}t[e]=String.fromCharCode.apply(String,r)});return new re(t)}):Promise.resolve(null)},readCidToGidMap:function(e){for(var t=e.getBytes(),a=[],r=0,i=t.length;r<i;r++){var n=t[r++]<<8|t[r];if(0!==n){a[r>>1]=n}}return a},extractWidths:function(e,t,a){var r,i,n,s,o,c,l,h,u=this.xref,f=[],d=0,g=[];if(a.composite){d=e.get("DW")||1e3;h=e.get("W");if(h)for(i=0,n=h.length;i<n;i++){c=u.fetchIfRef(h[i++]);l=u.fetchIfRef(h[i]);if(M(l))for(s=0,o=l.length;s<o;s++)f[c++]=u.fetchIfRef(l[s]);else{var p=u.fetchIfRef(h[++i]);for(s=c;s<=l;s++)f[s]=p}}if(a.vertical){var m=e.getArray("DW2")||[880,-1e3];r=[m[1],.5*d,m[0]];m=e.get("W2");if(m)for(i=0,n=m.length;i<n;i++){c=u.fetchIfRef(m[i++]);l=u.fetchIfRef(m[i]);if(M(l))for(s=0,o=l.length;s<o;s++)g[c++]=[u.fetchIfRef(l[s++]),u.fetchIfRef(l[s++]),u.fetchIfRef(l[s])];else{var b=[u.fetchIfRef(m[++i]),u.fetchIfRef(m[++i]),u.fetchIfRef(m[++i])];for(s=c;s<=l;s++)g[s]=b}}}}else{var v=a.firstChar;h=e.get("Widths");if(h){s=v;for(i=0,n=h.length;i<n;i++)f[s++]=u.fetchIfRef(h[i]);d=parseFloat(t.get("MissingWidth"))||0}else{var y=e.get("BaseFont");if(z(y)){var k=this.getBaseFontMetrics(y.name);f=this.buildCharCodeToWidth(k.widths,a);d=k.defaultWidth}}}var w=!0,C=d;for(var x in f){var S=f[x];if(S)if(C){if(C!==S){w=!1;break}}else C=S}w&&(a.flags|=ee.FixedPitch);a.defaultWidth=d;a.widths=f;a.defaultVMetrics=r;a.vmetrics=g},isSerifFont:function(e){var t=e.split("-")[0];return t in ke()||-1!==t.search(/serif/gi)},getBaseFontMetrics:function(e){var t=0,a=[],r=!1,i=ye(),n=i[e]||e,s=ue();n in s||(n=this.isSerifFont(e)?"Times-Roman":"Helvetica");var o=s[n];if(E(o)){t=o;r=!0}else a=o();return{defaultWidth:t,monospace:r,widths:a}},buildCharCodeToWidth:function(e,t){for(var a=Object.create(null),r=t.differences,i=t.defaultEncoding,n=0;n<256;n++)n in r&&e[r[n]]?a[n]=e[r[n]]:n in i&&e[i[n]]&&(a[n]=e[i[n]]);return a},preEvaluateFont:function(e){var t=e,a=e.get("Subtype");R(z(a),"invalid font Subtype");var r,i=!1;if("Type0"===a.name){var n=e.get("DescendantFonts");R(n,"Descendant fonts are not specified");e=M(n)?this.xref.fetchIfRef(n[0]):n;a=e.get("Subtype");R(z(a),"invalid font Subtype");i=!0}var s=e.get("FontDescriptor");if(s){var o=new Q,c=t.getRaw("Encoding");if(z(c))o.update(c.name);else if(H(c))o.update(c.toString());else if(_(c))for(var l=c.getKeys(),h=0,u=l.length;h<u;h++){var f=c.getRaw(l[h]);if(z(f))o.update(f.name);else if(H(f))o.update(f.toString());else if(M(f)){for(var d=f.length,g=new Array(d),p=0;p<d;p++){var m=f[p];z(m)?g[p]=m.name:(E(m)||H(m))&&(g[p]=m.toString())}o.update(g.join())}}var b=e.get("ToUnicode")||t.get("ToUnicode");if(G(b)){var v=b.str||b;r=v.buffer?new Uint8Array(v.buffer.buffer,0,v.bufferLength):new Uint8Array(v.bytes.buffer,v.start,v.end-v.start);o.update(r)}else z(b)&&o.update(b.name);var y=e.get("Widths")||t.get("Widths");if(y){r=new Uint8Array(new Uint32Array(y).buffer);o.update(r)}}return{descriptor:s,dict:e,baseDict:t,composite:i,type:a.name,hash:o?o.hexdigest():""}},translateFont:function(e){var t,a=e.baseDict,r=e.dict,i=e.composite,n=e.descriptor,s=e.type,o=i?65535:255;if(!n){if("Type3"!==s){var c=r.get("BaseFont");R(z(c),"Base font is not specified");c=c.name.replace(/[,_]/g,"-");var l=this.getBaseFontMetrics(c),h=c.split("-")[0],u=(this.isSerifFont(h)?ee.Serif:0)|(l.monospace?ee.FixedPitch:0)|(we()[h]?ee.Symbolic:ee.Nonsymbolic);t={type:s,name:c,widths:l.widths,defaultWidth:l.defaultWidth,flags:u,firstChar:0,lastChar:o};return this.extractDataStructures(r,r,t).then(function(e){e.widths=this.buildCharCodeToWidth(l.widths,e);return new te(c,null,e)}.bind(this))}n=new q(null);n.set("FontName",U.get(s));n.set("FontBBox",r.getArray("FontBBox"))}var f=r.get("FirstChar")||0,d=r.get("LastChar")||o,g=n.get("FontName"),p=r.get("BaseFont");L(g)&&(g=U.get(g));L(p)&&(p=U.get(p));if("Type3"!==s){var m=g&&g.name,b=p&&p.name;if(m!==b){P("The FontDescriptor's FontName is \""+m+'" but should be the same as the Font\'s BaseFont "'+b+'"');m&&b&&0===b.indexOf(m)&&(g=p)}}g=g||p;R(z(g),"invalid font name");var v=n.get("FontFile","FontFile2","FontFile3");if(v&&v.dict){var y=v.dict.get("Subtype");y&&(y=y.name);var w=v.dict.get("Length1"),C=v.dict.get("Length2"),x=v.dict.get("Length3")}t={type:s,name:g.name,subtype:y,file:v,length1:w,length2:C,length3:x,loadedName:a.loadedName,composite:i,wideChars:i,fixedPitch:!1,fontMatrix:r.getArray("FontMatrix")||k,firstChar:f||0,lastChar:d||o,bbox:n.getArray("FontBBox"),ascent:n.get("Ascent"),descent:n.get("Descent"),xHeight:n.get("XHeight"),capHeight:n.get("CapHeight"),flags:n.get("Flags"),italicAngle:n.get("ItalicAngle"),coded:!1};var S;if(i){var A=a.get("Encoding");z(A)&&(t.cidEncoding=A.name);S=le.create({encoding:A,fetchBuiltInCMap:this.fetchBuiltInCMap,useCMap:null}).then(function(e){t.cMap=e;t.vertical=t.cMap.vertical})}else S=Promise.resolve(void 0);return S.then(function(){return this.extractDataStructures(r,a,t)}.bind(this)).then(function(e){this.extractWidths(r,n,e);"Type3"===s&&(e.isType3Font=!0);return new te(g.name,v,e)}.bind(this))}};return t}(),Be=function(){function e(e,t,a){this.loadedName=e;this.font=t;this.dict=a;this.type3Loaded=null;this.sent=!1}e.prototype={send:function(e){if(!this.sent){var t=this.font.exportData();e.send("commonobj",[this.loadedName,"Font",t]);this.sent=!0}},loadType3Data:function(e,t,a,r){R(this.font.isType3Font);if(this.type3Loaded)return this.type3Loaded;for(var i=this.font,n=Promise.resolve(),s=this.dict.get("CharProcs"),o=this.dict.get("Resources")||t,c=s.getKeys(),l=Object.create(null),h=0,u=c.length;h<u;++h)n=n.then(function(t){var i=s.get(t),n=new Re;return e.getOperatorList(i,r,o,n).then(function(){l[t]=n.getIR();a.addDependencies(n.dependencies)},function(e){F('Type3 font resource "'+t+'" is not available');var a=new Re;l[t]=a.getIR()})}.bind(this,c[h]));this.type3Loaded=n.then(function(){i.charProcOperatorList=l});return this.type3Loaded}};return e}(),Re=function(){function e(e){for(var t=[],a=e.fnArray,r=e.argsArray,i=0,n=e.length;i<n;i++)switch(a[i]){case S.paintInlineImageXObject:case S.paintInlineImageXObjectGroup:case S.paintImageMaskXObject:var s=r[i][0];s.cached||t.push(s.data.buffer)}return t}function t(e,t,a){this.messageHandler=t;this.fnArray=[];this.argsArray=[];this.dependencies=Object.create(null);this._totalLength=0;this.pageIndex=a;this.intent=e}t.prototype={get length(){return this.argsArray.length},get totalLength(){return this._totalLength+this.length},addOp:function(e,t){this.fnArray.push(e);this.argsArray.push(t);this.messageHandler&&(this.fnArray.length>=1e3?this.flush():this.fnArray.length>=995&&(e===S.restore||e===S.endText)&&this.flush())},addDependency:function(e){if(!(e in this.dependencies)){this.dependencies[e]=!0;this.addOp(S.dependency,[e])}},addDependencies:function(e){for(var t in e)this.addDependency(t)},addOpList:function(e){B.extendObj(this.dependencies,e.dependencies);for(var t=0,a=e.length;t<a;t++)this.addOp(e.fnArray[t],e.argsArray[t])},getIR:function(){return{fnArray:this.fnArray,argsArray:this.argsArray,length:this.length}},flush:function(t){"oplist"!==this.intent&&(new Ee).optimize(this);var a=e(this),r=this.length;this._totalLength+=r;this.messageHandler.send("RenderPageChunk",{operatorList:{fnArray:this.fnArray,argsArray:this.argsArray,lastChunk:t,length:r},pageIndex:this.pageIndex,intent:this.intent},a);this.dependencies=Object.create(null);this.fnArray.length=0;this.argsArray.length=0}};return t}(),Te=function(){function e(e){this.state=e;this.stateStack=[]}e.prototype={save:function(){var e=this.state;this.stateStack.push(this.state);this.state=e.clone()},restore:function(){var e=this.stateStack.pop();e&&(this.state=e)},transform:function(e){this.state.ctm=B.transform(this.state.ctm,e)}};return e}(),Oe=function(){function e(){this.ctm=new Float32Array(w);this.fontName=null;this.fontSize=0;this.font=null;this.fontMatrix=k;this.textMatrix=w.slice();this.textLineMatrix=w.slice();this.charSpacing=0;this.wordSpacing=0;this.leading=0;this.textHScale=1;this.textRise=0}e.prototype={setTextMatrix:function(e,t,a,r,i,n){var s=this.textMatrix;s[0]=e;s[1]=t;s[2]=a;s[3]=r;s[4]=i;s[5]=n},setTextLineMatrix:function(e,t,a,r,i,n){var s=this.textLineMatrix;s[0]=e;s[1]=t;s[2]=a;s[3]=r;s[4]=i;s[5]=n},translateTextMatrix:function(e,t){var a=this.textMatrix;a[4]=a[0]*e+a[2]*t+a[4];a[5]=a[1]*e+a[3]*t+a[5]},translateTextLineMatrix:function(e,t){var a=this.textLineMatrix;a[4]=a[0]*e+a[2]*t+a[4];a[5]=a[1]*e+a[3]*t+a[5]},calcTextLineMatrixAdvance:function(e,t,a,r,i,n){var s=this.font;if(!s)return null;var o=this.textLineMatrix;if(e!==o[0]||t!==o[1]||a!==o[2]||r!==o[3])return null;var c=i-o[4],l=n-o[5];if(s.vertical&&0!==c||!s.vertical&&0!==l)return null;var h,u,f=e*r-t*a;if(s.vertical){h=-l*a/f;u=l*e/f}else{h=c*r/f;u=-c*t/f}return{width:h,height:u,value:s.vertical?u:h}},calcRenderMatrix:function(e){var t=[this.fontSize*this.textHScale,0,0,this.fontSize,0,this.textRise];return B.transform(e,B.transform(this.textMatrix,t))},carriageReturn:function(){this.translateTextLineMatrix(0,-this.leading);this.textMatrix=this.textLineMatrix.slice()},clone:function(){var e=Object.create(this);e.textMatrix=this.textMatrix.slice();e.textLineMatrix=this.textLineMatrix.slice();e.fontMatrix=this.fontMatrix.slice();return e}};return e}(),Pe=function(){function e(){this.ctm=new Float32Array(w);this.font=null;this.textRenderingMode=A.FILL;this.fillColorSpace=Z.singletons.gray;this.strokeColorSpace=Z.singletons.gray}e.prototype={clone:function(){return Object.create(this)}};return e}(),Me=function(){function e(e,a,r){this.opMap=t();this.parser=new Y(new K(e,this.opMap),!1,a);this.stateManager=r;this.nonProcessedArgs=[]}var t=D(function(e){e.w={id:S.setLineWidth,numArgs:1,variableArgs:!1};e.J={id:S.setLineCap,numArgs:1,variableArgs:!1};e.j={id:S.setLineJoin,numArgs:1,variableArgs:!1};e.M={id:S.setMiterLimit,numArgs:1,variableArgs:!1};e.d={id:S.setDash,numArgs:2,variableArgs:!1};e.ri={id:S.setRenderingIntent,numArgs:1,variableArgs:!1};e.i={id:S.setFlatness,numArgs:1,variableArgs:!1};e.gs={id:S.setGState,numArgs:1,variableArgs:!1};e.q={id:S.save,numArgs:0,variableArgs:!1};e.Q={id:S.restore,numArgs:0,variableArgs:!1};e.cm={id:S.transform,numArgs:6,variableArgs:!1};e.m={id:S.moveTo,numArgs:2,variableArgs:!1};e.l={id:S.lineTo,numArgs:2,variableArgs:!1};e.c={id:S.curveTo,numArgs:6,variableArgs:!1};e.v={id:S.curveTo2,numArgs:4,variableArgs:!1};e.y={id:S.curveTo3,numArgs:4,variableArgs:!1};e.h={id:S.closePath,numArgs:0,variableArgs:!1};e.re={id:S.rectangle,numArgs:4,variableArgs:!1};e.S={id:S.stroke,numArgs:0,variableArgs:!1};e.s={id:S.closeStroke,numArgs:0,variableArgs:!1};e.f={id:S.fill,numArgs:0,variableArgs:!1};e.F={id:S.fill,numArgs:0,variableArgs:!1};e["f*"]={id:S.eoFill,numArgs:0,variableArgs:!1};e.B={id:S.fillStroke,numArgs:0,variableArgs:!1};e["B*"]={id:S.eoFillStroke,numArgs:0,variableArgs:!1};e.b={id:S.closeFillStroke,numArgs:0,variableArgs:!1};e["b*"]={id:S.closeEOFillStroke,numArgs:0,variableArgs:!1};e.n={id:S.endPath,numArgs:0,variableArgs:!1};e.W={id:S.clip,numArgs:0,variableArgs:!1};e["W*"]={id:S.eoClip,numArgs:0,variableArgs:!1};e.BT={id:S.beginText,numArgs:0,variableArgs:!1};e.ET={id:S.endText,numArgs:0,variableArgs:!1};e.Tc={id:S.setCharSpacing,numArgs:1,variableArgs:!1};e.Tw={id:S.setWordSpacing,numArgs:1,variableArgs:!1};e.Tz={id:S.setHScale,numArgs:1,variableArgs:!1};e.TL={id:S.setLeading,numArgs:1,variableArgs:!1};e.Tf={id:S.setFont,numArgs:2,variableArgs:!1};e.Tr={id:S.setTextRenderingMode,numArgs:1,variableArgs:!1};e.Ts={id:S.setTextRise,numArgs:1,variableArgs:!1};e.Td={id:S.moveText,numArgs:2,variableArgs:!1};e.TD={id:S.setLeadingMoveText,numArgs:2,variableArgs:!1};e.Tm={id:S.setTextMatrix,numArgs:6,variableArgs:!1};e["T*"]={id:S.nextLine,numArgs:0,variableArgs:!1};e.Tj={id:S.showText,numArgs:1,variableArgs:!1};e.TJ={id:S.showSpacedText,numArgs:1,variableArgs:!1};e["'"]={id:S.nextLineShowText,numArgs:1,variableArgs:!1};e['"']={id:S.nextLineSetSpacingShowText,numArgs:3,variableArgs:!1};e.d0={id:S.setCharWidth,numArgs:2,variableArgs:!1};e.d1={id:S.setCharWidthAndBounds,numArgs:6,variableArgs:!1};e.CS={id:S.setStrokeColorSpace,numArgs:1,variableArgs:!1};e.cs={id:S.setFillColorSpace,numArgs:1,variableArgs:!1};e.SC={id:S.setStrokeColor,numArgs:4,variableArgs:!0};e.SCN={id:S.setStrokeColorN,numArgs:33,variableArgs:!0};e.sc={id:S.setFillColor,numArgs:4,variableArgs:!0};e.scn={id:S.setFillColorN,numArgs:33,variableArgs:!0};e.G={id:S.setStrokeGray,numArgs:1,variableArgs:!1};e.g={id:S.setFillGray,numArgs:1,variableArgs:!1};e.RG={id:S.setStrokeRGBColor,numArgs:3,variableArgs:!1};e.rg={id:S.setFillRGBColor,numArgs:3,variableArgs:!1};e.K={id:S.setStrokeCMYKColor,numArgs:4,variableArgs:!1};e.k={id:S.setFillCMYKColor,numArgs:4,variableArgs:!1};e.sh={id:S.shadingFill,numArgs:1,variableArgs:!1};e.BI={id:S.beginInlineImage,numArgs:0,variableArgs:!1};e.ID={id:S.beginImageData,numArgs:0,variableArgs:!1};e.EI={id:S.endInlineImage,numArgs:1,variableArgs:!1};e.Do={id:S.paintXObject,numArgs:1,variableArgs:!1};e.MP={id:S.markPoint,numArgs:1,variableArgs:!1};e.DP={id:S.markPointProps,numArgs:2,variableArgs:!1};e.BMC={id:S.beginMarkedContent,numArgs:1,variableArgs:!1};e.BDC={id:S.beginMarkedContentProps,numArgs:2,variableArgs:!1};e.EMC={id:S.endMarkedContent,numArgs:0,variableArgs:!1};e.BX={id:S.beginCompat,numArgs:0,variableArgs:!1};e.EX={id:S.endCompat,numArgs:0,variableArgs:!1};e.BM=null;e.BD=null;e.true=null;e.fa=null;e.fal=null;e.fals=null;e.false=null;e.nu=null;e.nul=null;e.null=null});e.prototype={get savedStatesDepth(){return this.stateManager.stateStack.length},read:function(e){for(var t=e.args;;){var a=this.parser.getObj();if(j(a)){var r=a.cmd,i=this.opMap[r];if(!i){F('Unknown command "'+r+'"');continue}var n=i.id,s=i.numArgs,o=null!==t?t.length:0;if(i.variableArgs)o>s&&P("Command "+n+": expected [0,"+s+"] args, but received "+o+" args.");else{if(o!==s){for(var c=this.nonProcessedArgs;o>s;){c.push(t.shift());o--}for(;o<s&&0!==c.length;){null===t&&(t=[]);t.unshift(c.pop());o++}}if(o<s){F("Skipping command "+n+": expected "+s+" args, but received "+o+" args.");null!==t&&(t.length=0);continue}}this.preprocessCommand(n,t);e.fn=n;e.args=t;return!0}if(N(a))return!1;if(null!==a){null===t&&(t=[]);t.push(a);R(t.length<=33,"Too many arguments")}}},preprocessCommand:function(e,t){switch(0|e){case S.save:this.stateManager.save();break;case S.restore:this.stateManager.restore();break;case S.transform:this.stateManager.transform(t)}}};return e}(),Ee=function(){function e(e,t,a){for(var r=e,i=0,n=t.length-1;i<n;i++){var s=t[i];r=r[s]||(r[s]=[])}r[t[t.length-1]]=a}function t(e,t,a,r){for(var i=e+2,n=0;n<t;n++){var s=r[i+4*n],o=1===s.length&&s[0];if(!o||1!==o.width||1!==o.height||o.data.length&&(1!==o.data.length||0!==o.data[0]))break;a[i+4*n]=S.paintSolidColorImageMask}return t-n}function a(){}var r=[];e(r,[S.save,S.transform,S.paintInlineImageXObject,S.restore],function(e){for(var t=e.fnArray,a=e.argsArray,r=e.iCurr,i=r-3,n=r-2,s=r-1,o=i+4,c=t.length;o+3<c&&t[o]===S.save&&t[o+1]===S.transform&&t[o+2]===S.paintInlineImageXObject&&t[o+3]===S.restore;)o+=4;var l=Math.min((o-i)/4,200);if(l<10)return o;var h,u=0,f=[],d=0,g=1,p=1;for(h=0;h<l;h++){var m=a[n+(h<<2)],b=a[s+(h<<2)][0];if(g+b.width>1e3){u=Math.max(u,g);p+=d+2;g=0;d=0}f.push({transform:m,x:g,y:p,w:b.width,h:b.height});g+=b.width+2;d=Math.max(d,b.height)}var v=Math.max(u,g)+1,y=p+d+1,k=new Uint8Array(v*y*4),w=v<<2;for(h=0;h<l;h++){var C=a[s+(h<<2)][0].data,A=f[h].w<<2,I=0,B=f[h].x+f[h].y*v<<2;k.set(C.subarray(0,A),B-w);for(var R=0,T=f[h].h;R<T;R++){k.set(C.subarray(I,I+A),B);I+=A;B+=w}k.set(C.subarray(I-A,I),B);for(;B>=0;){C[B-4]=C[B];C[B-3]=C[B+1];C[B-2]=C[B+2];C[B-1]=C[B+3];C[B+A]=C[B+A-4];C[B+A+1]=C[B+A-3];C[B+A+2]=C[B+A-2];C[B+A+3]=C[B+A-1];B-=w}}t.splice(i,4*l,S.paintInlineImageXObjectGroup);a.splice(i,4*l,[{width:v,height:y,kind:x.RGBA_32BPP,data:k},f]);return i+1});e(r,[S.save,S.transform,S.paintImageMaskXObject,S.restore],function(e){for(var a=e.fnArray,r=e.argsArray,i=e.iCurr,n=i-3,s=i-2,o=i-1,c=n+4,l=a.length;c+3<l&&a[c]===S.save&&a[c+1]===S.transform&&a[c+2]===S.paintImageMaskXObject&&a[c+3]===S.restore;)c+=4;var h=(c-n)/4;h=t(n,h,a,r);if(h<10)return c;var u,f,d,g=!1,p=r[o][0];if(0===r[s][1]&&0===r[s][2]){g=!0;var m=r[s][0],b=r[s][3];f=s+4;var v=o+4;for(u=1;u<h;u++,f+=4,v+=4){d=r[f];if(r[v][0]!==p||d[0]!==m||0!==d[1]||0!==d[2]||d[3]!==b){u<10?g=!1:h=u;break}}}if(g){h=Math.min(h,1e3);var y=new Float32Array(2*h);f=s;for(u=0;u<h;u++,f+=4){d=r[f];y[u<<1]=d[4];y[1+(u<<1)]=d[5]}a.splice(n,4*h,S.paintImageMaskXObjectRepeat);r.splice(n,4*h,[p,m,b,y])}else{h=Math.min(h,100);var k=[];for(u=0;u<h;u++){d=r[s+(u<<2)];var w=r[o+(u<<2)][0];k.push({data:w.data,width:w.width,height:w.height,transform:d})}a.splice(n,4*h,S.paintImageMaskXObjectGroup);r.splice(n,4*h,[k])}return n+1});e(r,[S.save,S.transform,S.paintImageXObject,S.restore],function(e){var t=e.fnArray,a=e.argsArray,r=e.iCurr,i=r-3,n=r-2,s=r-1,o=r;if(0!==a[n][1]||0!==a[n][2])return o+1;for(var c=a[s][0],l=a[n][0],h=a[n][3],u=i+4,f=t.length;u+3<f&&t[u]===S.save&&t[u+1]===S.transform&&t[u+2]===S.paintImageXObject&&t[u+3]===S.restore&&a[u+1][0]===l&&0===a[u+1][1]&&0===a[u+1][2]&&a[u+1][3]===h&&a[u+2][0]===c;)u+=4;var d=Math.min((u-i)/4,1e3);if(d<3)return u;for(var g=new Float32Array(2*d),p=n,m=0;m<d;m++,p+=4){var b=a[p];g[m<<1]=b[4];g[1+(m<<1)]=b[5]}var v=[c,l,h,g];t.splice(i,4*d,S.paintImageXObjectRepeat);a.splice(i,4*d,v);return i+1});e(r,[S.beginText,S.setFont,S.setTextMatrix,S.showText,S.endText],function(e){for(var t=e.fnArray,a=e.argsArray,r=e.iCurr,i=r-4,n=r-3,s=r-2,o=r-1,c=r,l=a[n][0],h=a[n][1],u=i+5,f=t.length;u+4<f&&t[u]===S.beginText&&t[u+1]===S.setFont&&t[u+2]===S.setTextMatrix&&t[u+3]===S.showText&&t[u+4]===S.endText&&a[u+1][0]===l&&a[u+1][1]===h;)u+=5;var d=Math.min((u-i)/5,1e3);if(d<3)return u;var g=i;if(i>=4&&t[i-4]===t[n]&&t[i-3]===t[s]&&t[i-2]===t[o]&&t[i-1]===t[c]&&a[i-4][0]===l&&a[i-4][1]===h){d++;g-=5}for(var p=g+4,m=1;m<d;m++){t.splice(p,3);a.splice(p,3);p+=2}return p+1});a.prototype={optimize:function(e){for(var t,a=e.fnArray,i=e.argsArray,n={iCurr:0,fnArray:a,argsArray:i},s=0,o=a.length;s<o;){t=(t||r)[a[s]];if("function"==typeof t){n.iCurr=s;s=t(n);t=void 0;o=n.fnArray.length}else s++}}};return a}();t.OperatorList=Re;t.PartialEvaluator=Ie},function(e,t,a){"use strict";var r=a(0),i=a(10),n=r.info,s=r.warn,o=r.error,c=r.log2,l=r.readUint16,h=r.readUint32,u=i.ArithmeticDecoder,f=function(){function e(){this.failOnCorruptedImage=!1}function t(e,t){e.x0=Math.ceil(t.XOsiz/e.XRsiz);e.x1=Math.ceil(t.Xsiz/e.XRsiz);e.y0=Math.ceil(t.YOsiz/e.YRsiz);e.y1=Math.ceil(t.Ysiz/e.YRsiz);e.width=e.x1-e.x0;e.height=e.y1-e.y0}function a(e,t){for(var a,r=e.SIZ,i=[],n=Math.ceil((r.Xsiz-r.XTOsiz)/r.XTsiz),s=Math.ceil((r.Ysiz-r.YTOsiz)/r.YTsiz),o=0;o<s;o++)for(var c=0;c<n;c++){a={};a.tx0=Math.max(r.XTOsiz+c*r.XTsiz,r.XOsiz);a.ty0=Math.max(r.YTOsiz+o*r.YTsiz,r.YOsiz);a.tx1=Math.min(r.XTOsiz+(c+1)*r.XTsiz,r.Xsiz);a.ty1=Math.min(r.YTOsiz+(o+1)*r.YTsiz,r.Ysiz);a.width=a.tx1-a.tx0;a.height=a.ty1-a.ty0;a.components=[];i.push(a)}e.tiles=i;for(var l=r.Csiz,h=0,u=l;h<u;h++)for(var f=t[h],d=0,g=i.length;d<g;d++){var p={};a=i[d];p.tcx0=Math.ceil(a.tx0/f.XRsiz);p.tcy0=Math.ceil(a.ty0/f.YRsiz);p.tcx1=Math.ceil(a.tx1/f.XRsiz);p.tcy1=Math.ceil(a.ty1/f.YRsiz);p.width=p.tcx1-p.tcx0;p.height=p.tcy1-p.tcy0;a.components[h]=p}}function r(e,t,a){var r=t.codingStyleParameters,i={};if(r.entropyCoderWithCustomPrecincts){i.PPx=r.precinctsSizes[a].PPx;i.PPy=r.precinctsSizes[a].PPy}else{i.PPx=15;i.PPy=15}i.xcb_=a>0?Math.min(r.xcb,i.PPx-1):Math.min(r.xcb,i.PPx);i.ycb_=a>0?Math.min(r.ycb,i.PPy-1):Math.min(r.ycb,i.PPy);return i}function i(e,t,a){var r=1<<a.PPx,i=1<<a.PPy,n=0===t.resLevel,s=1<<a.PPx+(n?0:-1),o=1<<a.PPy+(n?0:-1),c=t.trx1>t.trx0?Math.ceil(t.trx1/r)-Math.floor(t.trx0/r):0,l=t.try1>t.try0?Math.ceil(t.try1/i)-Math.floor(t.try0/i):0,h=c*l;t.precinctParameters={
+precinctWidth:r,precinctHeight:i,numprecinctswide:c,numprecinctshigh:l,numprecincts:h,precinctWidthInSubband:s,precinctHeightInSubband:o}}function f(e,t,a){var r,i,n,s,o=a.xcb_,c=a.ycb_,l=1<<o,h=1<<c,u=t.tbx0>>o,f=t.tby0>>c,d=t.tbx1+l-1>>o,g=t.tby1+h-1>>c,p=t.resolution.precinctParameters,m=[],b=[];for(i=f;i<g;i++)for(r=u;r<d;r++){n={cbx:r,cby:i,tbx0:l*r,tby0:h*i,tbx1:l*(r+1),tby1:h*(i+1)};n.tbx0_=Math.max(t.tbx0,n.tbx0);n.tby0_=Math.max(t.tby0,n.tby0);n.tbx1_=Math.min(t.tbx1,n.tbx1);n.tby1_=Math.min(t.tby1,n.tby1);var v=Math.floor((n.tbx0_-t.tbx0)/p.precinctWidthInSubband),y=Math.floor((n.tby0_-t.tby0)/p.precinctHeightInSubband);s=v+y*p.numprecinctswide;n.precinctNumber=s;n.subbandType=t.type;n.Lblock=3;if(!(n.tbx1_<=n.tbx0_||n.tby1_<=n.tby0_)){m.push(n);var k=b[s];if(void 0!==k){r<k.cbxMin?k.cbxMin=r:r>k.cbxMax&&(k.cbxMax=r);i<k.cbyMin?k.cbxMin=i:i>k.cbyMax&&(k.cbyMax=i)}else b[s]=k={cbxMin:r,cbyMin:i,cbxMax:r,cbyMax:i};n.precinct=k}}t.codeblockParameters={codeblockWidth:o,codeblockHeight:c,numcodeblockwide:d-u+1,numcodeblockhigh:g-f+1};t.codeblocks=m;t.precincts=b}function d(e,t,a){for(var r=[],i=e.subbands,n=0,s=i.length;n<s;n++)for(var o=i[n],c=o.codeblocks,l=0,h=c.length;l<h;l++){var u=c[l];u.precinctNumber===t&&r.push(u)}return{layerNumber:a,codeblocks:r}}function g(e){for(var t=e.SIZ,a=e.currentTile.index,r=e.tiles[a],i=r.codingStyleDefaultParameters.layersCount,n=t.Csiz,s=0,c=0;c<n;c++)s=Math.max(s,r.components[c].codingStyleParameters.decompositionLevelsCount);var l=0,h=0,u=0,f=0;this.nextPacket=function(){for(;l<i;l++){for(;h<=s;h++){for(;u<n;u++){var e=r.components[u];if(!(h>e.codingStyleParameters.decompositionLevelsCount)){for(var t=e.resolutions[h],a=t.precinctParameters.numprecincts;f<a;){var c=d(t,f,l);f++;return c}f=0}}u=0}h=0}o("JPX Error: Out of packets")}}function p(e){for(var t=e.SIZ,a=e.currentTile.index,r=e.tiles[a],i=r.codingStyleDefaultParameters.layersCount,n=t.Csiz,s=0,c=0;c<n;c++)s=Math.max(s,r.components[c].codingStyleParameters.decompositionLevelsCount);var l=0,h=0,u=0,f=0;this.nextPacket=function(){for(;l<=s;l++){for(;h<i;h++){for(;u<n;u++){var e=r.components[u];if(!(l>e.codingStyleParameters.decompositionLevelsCount)){for(var t=e.resolutions[l],a=t.precinctParameters.numprecincts;f<a;){var c=d(t,f,h);f++;return c}f=0}}u=0}h=0}o("JPX Error: Out of packets")}}function m(e){var t,a,r,i,n=e.SIZ,s=e.currentTile.index,c=e.tiles[s],l=c.codingStyleDefaultParameters.layersCount,h=n.Csiz,u=0;for(r=0;r<h;r++){var f=c.components[r];u=Math.max(u,f.codingStyleParameters.decompositionLevelsCount)}var g=new Int32Array(u+1);for(a=0;a<=u;++a){var p=0;for(r=0;r<h;++r){var m=c.components[r].resolutions;a<m.length&&(p=Math.max(p,m[a].precinctParameters.numprecincts))}g[a]=p}t=0;a=0;r=0;i=0;this.nextPacket=function(){for(;a<=u;a++){for(;i<g[a];i++){for(;r<h;r++){var e=c.components[r];if(!(a>e.codingStyleParameters.decompositionLevelsCount)){var n=e.resolutions[a],s=n.precinctParameters.numprecincts;if(!(i>=s)){for(;t<l;){var f=d(n,i,t);t++;return f}t=0}}}r=0}i=0}o("JPX Error: Out of packets")}}function b(e){var t=e.SIZ,a=e.currentTile.index,r=e.tiles[a],i=r.codingStyleDefaultParameters.layersCount,n=t.Csiz,s=k(r),c=s,l=0,h=0,u=0,f=0,g=0;this.nextPacket=function(){for(;g<c.maxNumHigh;g++){for(;f<c.maxNumWide;f++){for(;u<n;u++){for(var e=r.components[u],t=e.codingStyleParameters.decompositionLevelsCount;h<=t;h++){var a=e.resolutions[h],p=s.components[u].resolutions[h],m=y(f,g,p,c,a);if(null!==m){for(;l<i;){var b=d(a,m,l);l++;return b}l=0}}h=0}u=0}f=0}o("JPX Error: Out of packets")}}function v(e){var t=e.SIZ,a=e.currentTile.index,r=e.tiles[a],i=r.codingStyleDefaultParameters.layersCount,n=t.Csiz,s=k(r),c=0,l=0,h=0,u=0,f=0;this.nextPacket=function(){for(;h<n;++h){for(var e=r.components[h],t=s.components[h],a=e.codingStyleParameters.decompositionLevelsCount;f<t.maxNumHigh;f++){for(;u<t.maxNumWide;u++){for(;l<=a;l++){var g=e.resolutions[l],p=t.resolutions[l],m=y(u,f,p,t,g);if(null!==m){for(;c<i;){var b=d(g,m,c);c++;return b}c=0}}l=0}u=0}f=0}o("JPX Error: Out of packets")}}function y(e,t,a,r,i){var n=e*r.minWidth,s=t*r.minHeight;if(n%a.width!=0||s%a.height!=0)return null;var o=s/a.width*i.precinctParameters.numprecinctswide;return n/a.height+o}function k(e){for(var t=e.components.length,a=Number.MAX_VALUE,r=Number.MAX_VALUE,i=0,n=0,s=new Array(t),o=0;o<t;o++){for(var c=e.components[o],l=c.codingStyleParameters.decompositionLevelsCount,h=new Array(l+1),u=Number.MAX_VALUE,f=Number.MAX_VALUE,d=0,g=0,p=1,m=l;m>=0;--m){var b=c.resolutions[m],v=p*b.precinctParameters.precinctWidth,y=p*b.precinctParameters.precinctHeight;u=Math.min(u,v);f=Math.min(f,y);d=Math.max(d,b.precinctParameters.numprecinctswide);g=Math.max(g,b.precinctParameters.numprecinctshigh);h[m]={width:v,height:y};p<<=1}a=Math.min(a,u);r=Math.min(r,f);i=Math.max(i,d);n=Math.max(n,g);s[o]={resolutions:h,minWidth:u,minHeight:f,maxNumWide:d,maxNumHigh:g}}return{components:s,minWidth:a,minHeight:r,maxNumWide:i,maxNumHigh:n}}function w(e){for(var t=e.SIZ,a=e.currentTile.index,n=e.tiles[a],s=t.Csiz,c=0;c<s;c++){for(var l=n.components[c],h=l.codingStyleParameters.decompositionLevelsCount,u=[],d=[],y=0;y<=h;y++){var k=r(e,l,y),w={},C=1<<h-y;w.trx0=Math.ceil(l.tcx0/C);w.try0=Math.ceil(l.tcy0/C);w.trx1=Math.ceil(l.tcx1/C);w.try1=Math.ceil(l.tcy1/C);w.resLevel=y;i(e,w,k);u.push(w);var x;if(0===y){x={};x.type="LL";x.tbx0=Math.ceil(l.tcx0/C);x.tby0=Math.ceil(l.tcy0/C);x.tbx1=Math.ceil(l.tcx1/C);x.tby1=Math.ceil(l.tcy1/C);x.resolution=w;f(e,x,k);d.push(x);w.subbands=[x]}else{var S=1<<h-y+1,A=[];x={};x.type="HL";x.tbx0=Math.ceil(l.tcx0/S-.5);x.tby0=Math.ceil(l.tcy0/S);x.tbx1=Math.ceil(l.tcx1/S-.5);x.tby1=Math.ceil(l.tcy1/S);x.resolution=w;f(e,x,k);d.push(x);A.push(x);x={};x.type="LH";x.tbx0=Math.ceil(l.tcx0/S);x.tby0=Math.ceil(l.tcy0/S-.5);x.tbx1=Math.ceil(l.tcx1/S);x.tby1=Math.ceil(l.tcy1/S-.5);x.resolution=w;f(e,x,k);d.push(x);A.push(x);x={};x.type="HH";x.tbx0=Math.ceil(l.tcx0/S-.5);x.tby0=Math.ceil(l.tcy0/S-.5);x.tbx1=Math.ceil(l.tcx1/S-.5);x.tby1=Math.ceil(l.tcy1/S-.5);x.resolution=w;f(e,x,k);d.push(x);A.push(x);w.subbands=A}}l.resolutions=u;l.subbands=d}var I=n.codingStyleDefaultParameters.progressionOrder;switch(I){case 0:n.packetsIterator=new g(e);break;case 1:n.packetsIterator=new p(e);break;case 2:n.packetsIterator=new m(e);break;case 3:n.packetsIterator=new b(e);break;case 4:n.packetsIterator=new v(e);break;default:o("JPX Error: Unsupported progression order "+I)}}function C(e,t,a,r){function i(e){for(;u<e;){var r=t[a+h];h++;if(f){l=l<<7|r;u+=7;f=!1}else{l=l<<8|r;u+=8}255===r&&(f=!0)}u-=e;return l>>>u&(1<<e)-1}function n(e){if(255===t[a+h-1]&&t[a+h]===e){s(1);return!0}if(255===t[a+h]&&t[a+h+1]===e){s(2);return!0}return!1}function s(e){h+=e}function o(){u=0;if(f){h++;f=!1}}for(var l,h=0,u=0,f=!1,d=e.currentTile.index,g=e.tiles[d],p=e.COD.sopMarkerUsed,m=e.COD.ephMarkerUsed,b=g.packetsIterator;h<r;){o();p&&n(145)&&s(4);var v=b.nextPacket();if(i(1)){for(var y,k=v.layerNumber,w=[],C=0,x=v.codeblocks.length;C<x;C++){y=v.codeblocks[C];var S,A=y.precinct,I=y.cbx-A.cbxMin,B=y.cby-A.cbyMin,O=!1,P=!1;if(void 0!==y.included)O=!!i(1);else{A=y.precinct;var M,E;if(void 0!==A.inclusionTree)M=A.inclusionTree;else{var L=A.cbxMax-A.cbxMin+1,D=A.cbyMax-A.cbyMin+1;M=new T(L,D,k);E=new R(L,D);A.inclusionTree=M;A.zeroBitPlanesTree=E}if(M.reset(I,B,k))for(;;){if(!i(1)){M.incrementValue(k);break}S=!M.nextLevel();if(S){y.included=!0;O=P=!0;break}}}if(O){if(P){E=A.zeroBitPlanesTree;E.reset(I,B);for(;;)if(i(1)){S=!E.nextLevel();if(S)break}else E.incrementValue();y.zeroBitPlanes=E.value}for(var F=function(){if(0===i(1))return 1;if(0===i(1))return 2;var e=i(2);if(e<3)return e+3;e=i(5);if(e<31)return e+6;e=i(7);return e+37}();i(1);)y.Lblock++;var q=c(F),U=(F<1<<q?q-1:q)+y.Lblock,N=i(U);w.push({codeblock:y,codingpasses:F,dataLength:N})}}o();m&&n(146);for(;w.length>0;){var j=w.shift();y=j.codeblock;void 0===y.data&&(y.data=[]);y.data.push({data:t,start:a+h,end:a+h+j.dataLength,codingpasses:j.codingpasses});h+=j.dataLength}}}return h}function x(e,t,a,r,i,n,s,o){for(var c=r.tbx0,l=r.tby0,h=r.tbx1-r.tbx0,f=r.codeblocks,d="H"===r.type.charAt(0)?1:0,g="H"===r.type.charAt(1)?t:0,p=0,m=f.length;p<m;++p){var b=f[p],v=b.tbx1_-b.tbx0_,y=b.tby1_-b.tby0_;if(0!==v&&0!==y&&void 0!==b.data){var k,w;k=new O(v,y,b.subbandType,b.zeroBitPlanes,n);w=2;var C,x,S,A=b.data,I=0,B=0;for(C=0,x=A.length;C<x;C++){S=A[C];I+=S.end-S.start;B+=S.codingpasses}var R=new Uint8Array(I),T=0;for(C=0,x=A.length;C<x;C++){S=A[C];var P=S.data.subarray(S.start,S.end);R.set(P,T);T+=P.length}var M=new u(R,0,I);k.setDecoder(M);for(C=0;C<B;C++){switch(w){case 0:k.runSignificancePropagationPass();break;case 1:k.runMagnitudeRefinementPass();break;case 2:k.runCleanupPass();o&&k.checkSegmentationSymbol()}w=(w+1)%3}var E,L,D,F=b.tbx0_-c+(b.tby0_-l)*h,q=k.coefficentsSign,U=k.coefficentsMagnitude,N=k.bitsDecoded,j=s?0:.5;T=0;var _="LL"!==r.type;for(C=0;C<y;C++){var z=F/h|0,H=2*z*(t-h)+d+g;for(E=0;E<v;E++){L=U[T];if(0!==L){L=(L+j)*i;0!==q[T]&&(L=-L);D=N[T];var G=_?H+(F<<1):F;e[G]=s&&D>=n?L:L*(1<<n-D)}F++;T++}F+=h-v}}}}function S(e,t,a){for(var r=t.components[a],i=r.codingStyleParameters,n=r.quantizationParameters,s=i.decompositionLevelsCount,o=n.SPqcds,c=n.scalarExpounded,l=n.guardBits,h=i.segmentationSymbolUsed,u=e.components[a].precision,f=i.reversibleTransformation,d=f?new E:new M,g=[],p=0,m=0;m<=s;m++){for(var b=r.resolutions[m],v=b.trx1-b.trx0,y=b.try1-b.try0,k=new Float32Array(v*y),w=0,C=b.subbands.length;w<C;w++){var S,A;if(c){S=o[p].mu;A=o[p].epsilon;p++}else{S=o[0].mu;A=o[0].epsilon+(m>0?1-m:0)}var I=b.subbands[w],R=B[I.type];x(k,v,y,I,f?1:Math.pow(2,u+R-A)*(1+S/2048),l+A-1,f,h)}g.push({width:v,height:y,items:k})}var T=d.calculate(g,r.tcx0,r.tcy0);return{left:r.tcx0,top:r.tcy0,width:T.width,height:T.height,items:T.items}}function A(e){for(var t=e.SIZ,a=e.components,r=t.Csiz,i=[],n=0,s=e.tiles.length;n<s;n++){var o,c=e.tiles[n],l=[];for(o=0;o<r;o++)l[o]=S(e,c,o);var h,u,f,d,g,p,m,b,v,y,k,w,C,x,A,I=l[0],B=new Uint8Array(I.items.length*r),R={left:I.left,top:I.top,width:I.width,height:I.height,items:B},T=0;if(c.codingStyleDefaultParameters.multipleComponentTransform){var O=4===r,P=l[0].items,M=l[1].items,E=l[2].items,L=O?l[3].items:null;h=a[0].precision-8;u=.5+(128<<h);f=255*(1<<h);g=.5*f;d=-g;var D=c.components[0],F=r-3;m=P.length;if(D.codingStyleParameters.reversibleTransformation)for(p=0;p<m;p++,T+=F){b=P[p]+u;v=M[p];y=E[p];w=b-(y+v>>2);k=w+y;C=w+v;B[T++]=k<=0?0:k>=f?255:k>>h;B[T++]=w<=0?0:w>=f?255:w>>h;B[T++]=C<=0?0:C>=f?255:C>>h}else for(p=0;p<m;p++,T+=F){b=P[p]+u;v=M[p];y=E[p];k=b+1.402*y;w=b-.34413*v-.71414*y;C=b+1.772*v;B[T++]=k<=0?0:k>=f?255:k>>h;B[T++]=w<=0?0:w>=f?255:w>>h;B[T++]=C<=0?0:C>=f?255:C>>h}if(O)for(p=0,T=3;p<m;p++,T+=4){x=L[p];B[T]=x<=d?0:x>=g?255:x+u>>h}}else for(o=0;o<r;o++){var q=l[o].items;h=a[o].precision-8;u=.5+(128<<h);f=127.5*(1<<h);d=-f;for(T=o,p=0,m=q.length;p<m;p++){A=q[p];B[T]=A<=d?0:A>=f?255:A+u>>h;T+=r}}i.push(R)}return i}function I(e,t){for(var a=e.SIZ,r=a.Csiz,i=e.tiles[t],n=0;n<r;n++){var s=i.components[n],o=void 0!==e.currentTile.QCC[n]?e.currentTile.QCC[n]:e.currentTile.QCD;s.quantizationParameters=o;var c=void 0!==e.currentTile.COC[n]?e.currentTile.COC[n]:e.currentTile.COD;s.codingStyleParameters=c}i.codingStyleDefaultParameters=e.currentTile.COD}var B={LL:0,LH:1,HL:1,HH:2};e.prototype={parse:function(e){if(65359!==l(e,0))for(var t=0,a=e.length;t<a;){var r=8,i=h(e,t),c=h(e,t+4);t+=r;if(1===i){i=4294967296*h(e,t)+h(e,t+4);t+=8;r+=8}0===i&&(i=a-t+r);i<r&&o("JPX Error: Invalid box field size");var u=i-r,f=!0;switch(c){case 1785737832:f=!1;break;case 1668246642:var d=e[t];if(1===d){var g=h(e,t+3);switch(g){case 16:case 17:case 18:break;default:s("Unknown colorspace "+g)}}else 2===d&&n("ICC profile not supported");break;case 1785737827:this.parseCodestream(e,t,t+u);break;case 1783636e3:218793738!==h(e,t)&&s("Invalid JP2 signature");break;case 1783634458:case 1718909296:case 1920099697:case 1919251232:case 1768449138:break;default:var p=String.fromCharCode(c>>24&255,c>>16&255,c>>8&255,255&c);s("Unsupported header type "+c+" ("+p+")")}f&&(t+=u)}else this.parseCodestream(e,0,e.length)},parseImageProperties:function(e){for(var t=e.getByte();t>=0;){var a=t;t=e.getByte();if(65361===(a<<8|t)){e.skip(4);var r=e.getInt32()>>>0,i=e.getInt32()>>>0,n=e.getInt32()>>>0,s=e.getInt32()>>>0;e.skip(16);var c=e.getUint16();this.width=r-n;this.height=i-s;this.componentsCount=c;this.bitsPerComponent=8;return}}o("JPX Error: No size marker found in JPX stream")},parseCodestream:function(e,r,i){var n={},c=!1;try{for(var u=r;u+1<i;){var f=l(e,u);u+=2;var d,g,p,m,b,v,y=0;switch(f){case 65359:n.mainHeader=!0;break;case 65497:break;case 65361:y=l(e,u);var k={};k.Xsiz=h(e,u+4);k.Ysiz=h(e,u+8);k.XOsiz=h(e,u+12);k.YOsiz=h(e,u+16);k.XTsiz=h(e,u+20);k.YTsiz=h(e,u+24);k.XTOsiz=h(e,u+28);k.YTOsiz=h(e,u+32);var x=l(e,u+36);k.Csiz=x;var S=[];d=u+38;for(var B=0;B<x;B++){var R={precision:1+(127&e[d]),isSigned:!!(128&e[d]),XRsiz:e[d+1],YRsiz:e[d+1]};t(R,k);S.push(R)}n.SIZ=k;n.components=S;a(n,S);n.QCC=[];n.COC=[];break;case 65372:y=l(e,u);var T={};d=u+2;g=e[d++];switch(31&g){case 0:m=8;b=!0;break;case 1:m=16;b=!1;break;case 2:m=16;b=!0;break;default:throw new Error("Invalid SQcd value "+g)}T.noQuantization=8===m;T.scalarExpounded=b;T.guardBits=g>>5;p=[];for(;d<y+u;){var O={};if(8===m){O.epsilon=e[d++]>>3;O.mu=0}else{O.epsilon=e[d]>>3;O.mu=(7&e[d])<<8|e[d+1];d+=2}p.push(O)}T.SPqcds=p;if(n.mainHeader)n.QCD=T;else{n.currentTile.QCD=T;n.currentTile.QCC=[]}break;case 65373:y=l(e,u);var P={};d=u+2;var M;if(n.SIZ.Csiz<257)M=e[d++];else{M=l(e,d);d+=2}g=e[d++];switch(31&g){case 0:m=8;b=!0;break;case 1:m=16;b=!1;break;case 2:m=16;b=!0;break;default:throw new Error("Invalid SQcd value "+g)}P.noQuantization=8===m;P.scalarExpounded=b;P.guardBits=g>>5;p=[];for(;d<y+u;){O={};if(8===m){O.epsilon=e[d++]>>3;O.mu=0}else{O.epsilon=e[d]>>3;O.mu=(7&e[d])<<8|e[d+1];d+=2}p.push(O)}P.SPqcds=p;n.mainHeader?n.QCC[M]=P:n.currentTile.QCC[M]=P;break;case 65362:y=l(e,u);var E={};d=u+2;var L=e[d++];E.entropyCoderWithCustomPrecincts=!!(1&L);E.sopMarkerUsed=!!(2&L);E.ephMarkerUsed=!!(4&L);E.progressionOrder=e[d++];E.layersCount=l(e,d);d+=2;E.multipleComponentTransform=e[d++];E.decompositionLevelsCount=e[d++];E.xcb=2+(15&e[d++]);E.ycb=2+(15&e[d++]);var D=e[d++];E.selectiveArithmeticCodingBypass=!!(1&D);E.resetContextProbabilities=!!(2&D);E.terminationOnEachCodingPass=!!(4&D);E.verticalyStripe=!!(8&D);E.predictableTermination=!!(16&D);E.segmentationSymbolUsed=!!(32&D);E.reversibleTransformation=e[d++];if(E.entropyCoderWithCustomPrecincts){for(var F=[];d<y+u;){var q=e[d++];F.push({PPx:15&q,PPy:q>>4})}E.precinctsSizes=F}var U=[];E.selectiveArithmeticCodingBypass&&U.push("selectiveArithmeticCodingBypass");E.resetContextProbabilities&&U.push("resetContextProbabilities");E.terminationOnEachCodingPass&&U.push("terminationOnEachCodingPass");E.verticalyStripe&&U.push("verticalyStripe");E.predictableTermination&&U.push("predictableTermination");if(U.length>0){c=!0;throw new Error("Unsupported COD options ("+U.join(", ")+")")}if(n.mainHeader)n.COD=E;else{n.currentTile.COD=E;n.currentTile.COC=[]}break;case 65424:y=l(e,u);v={};v.index=l(e,u+2);v.length=h(e,u+4);v.dataEnd=v.length+u-2;v.partIndex=e[u+8];v.partsCount=e[u+9];n.mainHeader=!1;if(0===v.partIndex){v.COD=n.COD;v.COC=n.COC.slice(0);v.QCD=n.QCD;v.QCC=n.QCC.slice(0)}n.currentTile=v;break;case 65427:v=n.currentTile;if(0===v.partIndex){I(n,v.index);w(n)}y=v.dataEnd-u;C(n,e,u,y);break;case 65365:case 65367:case 65368:case 65380:y=l(e,u);break;case 65363:throw new Error("Codestream code 0xFF53 (COC) is not implemented");default:throw new Error("Unknown codestream code: "+f.toString(16))}u+=y}}catch(e){c||this.failOnCorruptedImage?o("JPX Error: "+e.message):s("JPX: Trying to recover from: "+e.message)}this.tiles=A(n);this.width=n.SIZ.Xsiz-n.SIZ.XOsiz;this.height=n.SIZ.Ysiz-n.SIZ.YOsiz;this.componentsCount=n.SIZ.Csiz}};var R=function(){function e(e,t){var a=c(Math.max(e,t))+1;this.levels=[];for(var r=0;r<a;r++){var i={width:e,height:t,items:[]};this.levels.push(i);e=Math.ceil(e/2);t=Math.ceil(t/2)}}e.prototype={reset:function(e,t){for(var a,r=0,i=0;r<this.levels.length;){a=this.levels[r];var n=e+t*a.width;if(void 0!==a.items[n]){i=a.items[n];break}a.index=n;e>>=1;t>>=1;r++}r--;a=this.levels[r];a.items[a.index]=i;this.currentLevel=r;delete this.value},incrementValue:function(){var e=this.levels[this.currentLevel];e.items[e.index]++},nextLevel:function(){var e=this.currentLevel,t=this.levels[e],a=t.items[t.index];e--;if(e<0){this.value=a;return!1}this.currentLevel=e;t=this.levels[e];t.items[t.index]=a;return!0}};return e}(),T=function(){function e(e,t,a){var r=c(Math.max(e,t))+1;this.levels=[];for(var i=0;i<r;i++){for(var n=new Uint8Array(e*t),s=0,o=n.length;s<o;s++)n[s]=a;var l={width:e,height:t,items:n};this.levels.push(l);e=Math.ceil(e/2);t=Math.ceil(t/2)}}e.prototype={reset:function(e,t,a){for(var r=0;r<this.levels.length;){var i=this.levels[r],n=e+t*i.width;i.index=n;var s=i.items[n];if(255===s)break;if(s>a){this.currentLevel=r;this.propagateValues();return!1}e>>=1;t>>=1;r++}this.currentLevel=r-1;return!0},incrementValue:function(e){var t=this.levels[this.currentLevel];t.items[t.index]=e+1;this.propagateValues()},propagateValues:function(){for(var e=this.currentLevel,t=this.levels[e],a=t.items[t.index];--e>=0;){t=this.levels[e];t.items[t.index]=a}},nextLevel:function(){var e=this.currentLevel,t=this.levels[e],a=t.items[t.index];t.items[t.index]=255;e--;if(e<0)return!1;this.currentLevel=e;t=this.levels[e];t.items[t.index]=a;return!0}};return e}(),O=function(){function e(e,i,n,s,o){this.width=e;this.height=i;this.contextLabelTable="HH"===n?r:"HL"===n?a:t;var c=e*i;this.neighborsSignificance=new Uint8Array(c);this.coefficentsSign=new Uint8Array(c);this.coefficentsMagnitude=o>14?new Uint32Array(c):o>6?new Uint16Array(c):new Uint8Array(c);this.processingFlags=new Uint8Array(c);var l=new Uint8Array(c);if(0!==s)for(var h=0;h<c;h++)l[h]=s;this.bitsDecoded=l;this.reset()}var t=new Uint8Array([0,5,8,0,3,7,8,0,4,7,8,0,0,0,0,0,1,6,8,0,3,7,8,0,4,7,8,0,0,0,0,0,2,6,8,0,3,7,8,0,4,7,8,0,0,0,0,0,2,6,8,0,3,7,8,0,4,7,8,0,0,0,0,0,2,6,8,0,3,7,8,0,4,7,8]),a=new Uint8Array([0,3,4,0,5,7,7,0,8,8,8,0,0,0,0,0,1,3,4,0,6,7,7,0,8,8,8,0,0,0,0,0,2,3,4,0,6,7,7,0,8,8,8,0,0,0,0,0,2,3,4,0,6,7,7,0,8,8,8,0,0,0,0,0,2,3,4,0,6,7,7,0,8,8,8]),r=new Uint8Array([0,1,2,0,1,2,2,0,2,2,2,0,0,0,0,0,3,4,5,0,4,5,5,0,5,5,5,0,0,0,0,0,6,7,7,0,7,7,7,0,7,7,7,0,0,0,0,0,8,8,8,0,8,8,8,0,8,8,8,0,0,0,0,0,8,8,8,0,8,8,8,0,8,8,8]);e.prototype={setDecoder:function(e){this.decoder=e},reset:function(){this.contexts=new Int8Array(19);this.contexts[0]=8;this.contexts[17]=92;this.contexts[18]=6},setNeighborsSignificance:function(e,t,a){var r,i=this.neighborsSignificance,n=this.width,s=this.height,o=t>0,c=t+1<n;if(e>0){r=a-n;o&&(i[r-1]+=16);c&&(i[r+1]+=16);i[r]+=4}if(e+1<s){r=a+n;o&&(i[r-1]+=16);c&&(i[r+1]+=16);i[r]+=4}o&&(i[a-1]+=1);c&&(i[a+1]+=1);i[a]|=128},runSignificancePropagationPass:function(){for(var e=this.decoder,t=this.width,a=this.height,r=this.coefficentsMagnitude,i=this.coefficentsSign,n=this.neighborsSignificance,s=this.processingFlags,o=this.contexts,c=this.contextLabelTable,l=this.bitsDecoded,h=0;h<a;h+=4)for(var u=0;u<t;u++)for(var f=h*t+u,d=0;d<4;d++,f+=t){var g=h+d;if(g>=a)break;s[f]&=-2;if(!r[f]&&n[f]){var p=c[n[f]],m=e.readBit(o,p);if(m){var b=this.decodeSignBit(g,u,f);i[f]=b;r[f]=1;this.setNeighborsSignificance(g,u,f);s[f]|=2}l[f]++;s[f]|=1}}},decodeSignBit:function(e,t,a){var r,i,n,s,o,c,l=this.width,h=this.height,u=this.coefficentsMagnitude,f=this.coefficentsSign;s=t>0&&0!==u[a-1];if(t+1<l&&0!==u[a+1]){n=f[a+1];if(s){i=f[a-1];r=1-n-i}else r=1-n-n}else if(s){i=f[a-1];r=1-i-i}else r=0;var d=3*r;s=e>0&&0!==u[a-l];if(e+1<h&&0!==u[a+l]){n=f[a+l];if(s){i=f[a-l];r=1-n-i+d}else r=1-n-n+d}else if(s){i=f[a-l];r=1-i-i+d}else r=d;if(r>=0){o=9+r;c=this.decoder.readBit(this.contexts,o)}else{o=9-r;c=1^this.decoder.readBit(this.contexts,o)}return c},runMagnitudeRefinementPass:function(){for(var e,t=this.decoder,a=this.width,r=this.height,i=this.coefficentsMagnitude,n=this.neighborsSignificance,s=this.contexts,o=this.bitsDecoded,c=this.processingFlags,l=a*r,h=4*a,u=0;u<l;u=e){e=Math.min(l,u+h);for(var f=0;f<a;f++)for(var d=u+f;d<e;d+=a)if(i[d]&&0==(1&c[d])){var g=16;if(0!=(2&c[d])){c[d]^=2;var p=127&n[d];g=0===p?15:14}var m=t.readBit(s,g);i[d]=i[d]<<1|m;o[d]++;c[d]|=1}}},runCleanupPass:function(){for(var e,t=this.decoder,a=this.width,r=this.height,i=this.neighborsSignificance,n=this.coefficentsMagnitude,s=this.coefficentsSign,o=this.contexts,c=this.contextLabelTable,l=this.bitsDecoded,h=this.processingFlags,u=a,f=2*a,d=3*a,g=0;g<r;g=e){e=Math.min(g+4,r);for(var p=g*a,m=g+3<r,b=0;b<a;b++){var v,y=p+b,k=m&&0===h[y]&&0===h[y+u]&&0===h[y+f]&&0===h[y+d]&&0===i[y]&&0===i[y+u]&&0===i[y+f]&&0===i[y+d],w=0,C=y,x=g;if(k){if(!t.readBit(o,18)){l[y]++;l[y+u]++;l[y+f]++;l[y+d]++;continue}w=t.readBit(o,17)<<1|t.readBit(o,17);if(0!==w){x=g+w;C+=w*a}v=this.decodeSignBit(x,b,C);s[C]=v;n[C]=1;this.setNeighborsSignificance(x,b,C);h[C]|=2;C=y;for(var S=g;S<=x;S++,C+=a)l[C]++;w++}for(x=g+w;x<e;x++,C+=a)if(!n[C]&&0==(1&h[C])){var A=c[i[C]],I=t.readBit(o,A);if(1===I){v=this.decodeSignBit(x,b,C);s[C]=v;n[C]=1;this.setNeighborsSignificance(x,b,C);h[C]|=2}l[C]++}}}},checkSegmentationSymbol:function(){var e=this.decoder,t=this.contexts;10!=(e.readBit(t,17)<<3|e.readBit(t,17)<<2|e.readBit(t,17)<<1|e.readBit(t,17))&&o("JPX Error: Invalid segmentation symbol")}};return e}(),P=function(){function e(){}e.prototype.calculate=function(e,t,a){for(var r=e[0],i=1,n=e.length;i<n;i++)r=this.iterate(r,e[i],t,a);return r};e.prototype.extend=function(e,t,a){var r=t-1,i=t+1,n=t+a-2,s=t+a;e[r--]=e[i++];e[s++]=e[n--];e[r--]=e[i++];e[s++]=e[n--];e[r--]=e[i++];e[s++]=e[n--];e[r]=e[i];e[s]=e[n]};e.prototype.iterate=function(e,t,a,r){var i,n,s,o,c,l,h=e.width,u=e.height,f=e.items,d=t.width,g=t.height,p=t.items;for(s=0,i=0;i<u;i++){o=2*i*d;for(n=0;n<h;n++,s++,o+=2)p[o]=f[s]}f=e.items=null;var m=new Float32Array(d+8);if(1===d){if(0!=(1&a))for(l=0,s=0;l<g;l++,s+=d)p[s]*=.5}else for(l=0,s=0;l<g;l++,s+=d){m.set(p.subarray(s,s+d),4);this.extend(m,4,d);this.filter(m,4,d);p.set(m.subarray(4,4+d),s)}var b=16,v=[];for(i=0;i<b;i++)v.push(new Float32Array(g+8));var y,k=0;e=4+g;if(1===g){if(0!=(1&r))for(c=0;c<d;c++)p[c]*=.5}else for(c=0;c<d;c++){if(0===k){b=Math.min(d-c,b);for(s=c,o=4;o<e;s+=d,o++)for(y=0;y<b;y++)v[y][o]=p[s+y];k=b}k--;var w=v[k];this.extend(w,4,g);this.filter(w,4,g);if(0===k){s=c-b+1;for(o=4;o<e;s+=d,o++)for(y=0;y<b;y++)p[s+y]=v[y][o]}}return{width:d,height:g,items:p}};return e}(),M=function(){function e(){P.call(this)}e.prototype=Object.create(P.prototype);e.prototype.filter=function(e,t,a){var r=a>>1;t|=0;var i,n,s,o,c=-1.586134342059924,l=-.052980118572961,h=.882911075530934,u=.443506852043971,f=1.230174104914001;i=t-3;for(n=r+4;n--;i+=2)e[i]*=1/f;i=t-2;s=u*e[i-1];for(n=r+3;n--;i+=2){o=u*e[i+1];e[i]=f*e[i]-s-o;if(!n--)break;i+=2;s=u*e[i+1];e[i]=f*e[i]-s-o}i=t-1;s=h*e[i-1];for(n=r+2;n--;i+=2){o=h*e[i+1];e[i]-=s+o;if(!n--)break;i+=2;s=h*e[i+1];e[i]-=s+o}i=t;s=l*e[i-1];for(n=r+1;n--;i+=2){o=l*e[i+1];e[i]-=s+o;if(!n--)break;i+=2;s=l*e[i+1];e[i]-=s+o}if(0!==r){i=t+1;s=c*e[i-1];for(n=r;n--;i+=2){o=c*e[i+1];e[i]-=s+o;if(!n--)break;i+=2;s=c*e[i+1];e[i]-=s+o}}};return e}(),E=function(){function e(){P.call(this)}e.prototype=Object.create(P.prototype);e.prototype.filter=function(e,t,a){var r=a>>1;t|=0;var i,n;for(i=t,n=r+1;n--;i+=2)e[i]-=e[i-1]+e[i+1]+2>>2;for(i=t+1,n=r;n--;i+=2)e[i]+=e[i-1]+e[i+1]>>1};return e}();return e}();t.JpxImage=f},function(e,t,a){"use strict";var r=a(0),i=a(1),n=a(13),s=a(5),o=a(12),c=a(3),l=r.InvalidPDFException,h=r.MissingDataException,u=r.XRefParseException,f=r.assert,d=r.bytesToString,g=r.createPromiseCapability,p=r.error,m=r.info,b=r.isArray,v=r.isBool,y=r.isInt,k=r.isString,w=r.shadow,C=r.stringToPDFString,x=r.stringToUTF8String,S=r.warn,A=r.createValidAbsoluteUrl,I=r.Util,B=i.Dict,R=i.Ref,T=i.RefSet,O=i.RefSetCache,P=i.isName,M=i.isCmd,E=i.isDict,L=i.isRef,D=i.isRefsEqual,F=i.isStream,q=n.CipherTransformFactory,U=s.Lexer,N=s.Parser,j=o.ChunkedStream,_=c.ColorSpace,z=function(){function e(e,t,a){this.pdfManager=e;this.xref=t;this.catDict=t.getCatalogObj();f(E(this.catDict),"catalog object is not a dictionary");this.fontCache=new O;this.builtInCMapCache=Object.create(null);this.pageKidsCountCache=new O;this.pageFactory=a;this.pagePromises=[]}e.prototype={get metadata(){var e=this.catDict.getRaw("Metadata");if(!L(e))return w(this,"metadata",null);var t,a=!!this.xref.encrypt&&this.xref.encrypt.encryptMetadata,r=this.xref.fetch(e,!a);if(r&&E(r.dict)){var i=r.dict.get("Type"),n=r.dict.get("Subtype");if(P(i,"Metadata")&&P(n,"XML"))try{t=x(d(r.getBytes()))}catch(e){if(e instanceof h)throw e;m("Skipping invalid metadata.")}}return w(this,"metadata",t)},get toplevelPagesDict(){var e=this.catDict.get("Pages");f(E(e),"invalid top-level pages dictionary");return w(this,"toplevelPagesDict",e)},get documentOutline(){var e=null;try{e=this.readDocumentOutline()}catch(e){if(e instanceof h)throw e;S("Unable to read document outline")}return w(this,"documentOutline",e)},readDocumentOutline:function(){var t=this.catDict.get("Outlines");if(!E(t))return null;t=t.getRaw("First");if(!L(t))return null;var a={items:[]},r=[{obj:t,parent:a}],i=new T;i.put(t);for(var n=this.xref,s=new Uint8Array(3);r.length>0;){var o=r.shift(),c=n.fetchIfRef(o.obj);if(null!==c){f(c.has("Title"),"Invalid outline item");var l={url:null,dest:null};e.parseDestDictionary({destDict:c,resultObj:l,docBaseUrl:this.pdfManager.docBaseUrl});var h=c.get("Title"),u=c.get("F")||0,d=c.getArray("C"),g=s;!b(d)||3!==d.length||0===d[0]&&0===d[1]&&0===d[2]||(g=_.singletons.rgb.getRgb(d,0));var p={dest:l.dest,url:l.url,unsafeUrl:l.unsafeUrl,newWindow:l.newWindow,title:C(h),color:g,count:c.get("Count"),bold:!!(2&u),italic:!!(1&u),items:[]};o.parent.items.push(p);t=c.getRaw("First");if(L(t)&&!i.has(t)){r.push({obj:t,parent:p});i.put(t)}t=c.getRaw("Next");if(L(t)&&!i.has(t)){r.push({obj:t,parent:o.parent});i.put(t)}}}return a.items.length>0?a.items:null},get numPages(){var e=this.toplevelPagesDict.get("Count");f(y(e),"page count in top level pages object is not an integer");return w(this,"num",e)},get destinations(){function e(e){return E(e)?e.get("D"):e}var t,a,r=this.xref,i={},n=this.catDict.get("Names");n&&n.has("Dests")?t=n.getRaw("Dests"):this.catDict.has("Dests")&&(a=this.catDict.get("Dests"));if(a){n=a;n.forEach(function(t,a){a&&(i[t]=e(a))})}if(t){var s=new X(t,r),o=s.getAll();for(var c in o)i[c]=e(o[c])}return w(this,"destinations",i)},getDestination:function(e){function t(e){return E(e)?e.get("D"):e}var a,r,i=this.xref,n=null,s=this.catDict.get("Names");s&&s.has("Dests")?a=s.getRaw("Dests"):this.catDict.has("Dests")&&(r=this.catDict.get("Dests"));if(r){var o=r.get(e);o&&(n=t(o))}if(a){n=t(new X(a,i).get(e))}return n},get pageLabels(){var e=null;try{e=this.readPageLabels()}catch(e){if(e instanceof h)throw e;S("Unable to read page labels.")}return w(this,"pageLabels",e)},readPageLabels:function(){var e=this.catDict.getRaw("PageLabels");if(!e)return null;for(var t=new Array(this.numPages),a=null,r="",i=new V(e,this.xref),n=i.getAll(),s="",o=1,c=0,l=this.numPages;c<l;c++){if(c in n){var h=n[c];f(E(h),"The PageLabel is not a dictionary.");var u=h.get("Type");f(!u||P(u,"PageLabel"),"Invalid type in PageLabel dictionary.");var d=h.get("S");f(!d||P(d),"Invalid style in PageLabel dictionary.");a=d?d.name:null;var g=h.get("P");f(!g||k(g),"Invalid prefix in PageLabel dictionary.");r=g?C(g):"";var p=h.get("St");f(!p||y(p)&&p>=1,"Invalid start in PageLabel dictionary.");o=p||1}switch(a){case"D":s=o;break;case"R":case"r":s=I.toRoman(o,"r"===a);break;case"A":case"a":for(var m="a"===a?97:65,b=o-1,v=String.fromCharCode(m+b%26),w=[],x=0,S=b/26|0;x<=S;x++)w.push(v);s=w.join("");break;default:f(!a,'Invalid style "'+a+'" in PageLabel dictionary.')}t[c]=r+s;s="";o++}return t},get attachments(){var e,t=this.xref,a=null,r=this.catDict.get("Names");r&&(e=r.getRaw("EmbeddedFiles"));if(e){var i=new X(e,t),n=i.getAll();for(var s in n){var o=new W(n[s],t);a||(a=Object.create(null));a[C(s)]=o.serializable}}return w(this,"attachments",a)},get javaScript(){function e(e){var t=e.get("S");if(P(t,"JavaScript")){var a=e.get("JS");if(F(a))a=d(a.getBytes());else if(!k(a))return;r.push(C(a))}}var t=this.xref,a=this.catDict.get("Names"),r=[];if(a&&a.has("JavaScript")){var i=new X(a.getRaw("JavaScript"),t),n=i.getAll();for(var s in n){var o=n[s];E(o)&&e(o)}}var c=this.catDict.get("OpenAction");if(E(c,"Action")){var l=c.get("S");if(P(l,"Named")){var h=c.get("N");P(h,"Print")&&r.push("print({});")}else e(c)}return w(this,"javaScript",r)},cleanup:function(){this.pageKidsCountCache.clear();var e=[];this.fontCache.forEach(function(t){e.push(t)});return Promise.all(e).then(function(e){for(var t=0,a=e.length;t<a;t++){delete e[t].dict.translated}this.fontCache.clear();this.builtInCMapCache=Object.create(null)}.bind(this))},getPage:function(e){e in this.pagePromises||(this.pagePromises[e]=this.getPageDict(e).then(function(t){var a=t[0],r=t[1];return this.pageFactory.createPage(e,a,r,this.fontCache,this.builtInCMapCache)}.bind(this)));return this.pagePromises[e]},getPageDict:function(e){function t(){for(;i.length;){var c=i.pop();if(L(c)){a=o.get(c);if(a>0&&n+a<e){n+=a;continue}s.fetchAsync(c).then(function(a){if(E(a,"Page")||E(a)&&!a.has("Kids"))if(e===n){c&&!o.has(c)&&o.put(c,1);r.resolve([a,c])}else{n++;t()}else{i.push(a);t()}},r.reject);return}f(E(c),"page dictionary kid reference points to wrong type of object");a=c.get("Count");var l=c.objId;l&&!o.has(l)&&o.put(l,a);if(n+a<=e)n+=a;else{var h=c.get("Kids");f(b(h),"page dictionary kids object is not an array");for(var u=h.length-1;u>=0;u--)i.push(h[u])}}r.reject("Page index "+e+" not found.")}var a,r=g(),i=[this.catDict.getRaw("Pages")],n=0,s=this.xref,o=this.pageKidsCountCache;t();return r.promise},getPageIndex:function(e){function t(t){var a,i=0;return r.fetchAsync(t).then(function(r){if(D(t,e)&&!E(r,"Page")&&(!E(r)||r.has("Type")||!r.has("Contents")))throw new Error("The reference does not point to a /Page Dict.");if(!r)return null;f(E(r),"node must be a Dict.");a=r.getRaw("Parent");return r.getAsync("Parent")}).then(function(e){if(!e)return null;f(E(e),"parent must be a Dict.");return e.getAsync("Kids")}).then(function(e){if(!e)return null;for(var n=[],s=!1,o=0;o<e.length;o++){var c=e[o];f(L(c),"kid must be a Ref.");if(c.num===t.num){s=!0;break}n.push(r.fetchAsync(c).then(function(e){if(e.has("Count")){var t=e.get("Count");i+=t}else i++}))}s||p("kid ref not found in parents kids");return Promise.all(n).then(function(){return[i,a]})})}function a(e){return t(e).then(function(e){if(!e)return i;var t=e[0],r=e[1];i+=t;return a(r)})}var r=this.xref,i=0;return a(e)}};e.parseDestDictionary=function(e){var t=e.destDict;if(E(t)){var a=e.resultObj;if("object"==typeof a){var r,i,n=e.docBaseUrl||null,s=t.get("A");if(E(s)){var o=s.get("S").name;switch(o){case"URI":r=s.get("URI");P(r)?r="/"+r.name:k(r)&&(r=function(e){return 0===e.indexOf("www.")?"http://"+e:e}(r));break;case"GoTo":i=s.get("D");break;case"Launch":case"GoToR":var c=s.get("F");E(c)?r=c.get("F")||null:k(c)&&(r=c);var l=s.get("D");if(l){P(l)&&(l=l.name);if(k(r)){var h=r.split("#")[0];k(l)?r=h+"#"+(/^\d+$/.test(l)?"nameddest=":"")+l:b(l)&&(r=h+"#"+JSON.stringify(l))}}var u=s.get("NewWindow");v(u)&&(a.newWindow=u);break;case"Named":var f=s.get("N");P(f)&&(a.action=f.name);break;case"JavaScript":var g,p=s.get("JS");F(p)?g=d(p.getBytes()):k(p)&&(g=p);if(g){var m=["app.launchURL","window.open"],y=new RegExp("^\\s*("+m.join("|").split(".").join("\\.")+")\\((?:'|\")([^'\"]*)(?:'|\")(?:,\\s*(\\w+)\\)|\\))","i"),w=y.exec(C(g));if(w&&w[2]){r=w[2];"true"===w[3]&&"app.launchURL"===w[1]&&(a.newWindow=!0);break}}default:S('Catalog_parseDestDictionary: Unrecognized link type "'+o+'".')}}else t.has("Dest")&&(i=t.get("Dest"));if(k(r)){r=function(e){try{return x(e)}catch(t){return e}}(r);var I=A(r,n)
+;I&&(a.url=I.href);a.unsafeUrl=r}if(i){P(i)&&(i=i.name);(k(i)||b(i))&&(a.dest=i)}}else S('Catalog_parseDestDictionary: "resultObj" must be an object.')}else S('Catalog_parseDestDictionary: "destDict" must be a dictionary.')};return e}(),H=function(){function e(e,t){this.stream=e;this.pdfManager=t;this.entries=[];this.xrefstms=Object.create(null);this.cache=[];this.stats={streamTypes:[],fontTypes:[]}}e.prototype={setStartXRef:function(e){this.startXRefQueue=[e]},parse:function(e){var t;if(e){S("Indexing all PDF objects");t=this.indexObjects()}else t=this.readXRef();t.assignXref(this);this.trailer=t;var a=t.get("Encrypt");if(E(a)){var r=t.get("ID"),i=r&&r.length?r[0]:"";a.suppressEncryption=!0;this.encrypt=new q(a,i,this.pdfManager.password)}(this.root=t.get("Root"))||p("Invalid root reference")},processXRefTable:function(e){"tableState"in this||(this.tableState={entryNum:0,streamPos:e.lexer.stream.pos,parserBuf1:e.buf1,parserBuf2:e.buf2});var t=this.readXRefTable(e);M(t,"trailer")||p("Invalid XRef table: could not find trailer dictionary");var a=e.getObj();!E(a)&&a.dict&&(a=a.dict);E(a)||p("Invalid XRef table: could not parse trailer dictionary");delete this.tableState;return a},readXRefTable:function(e){var t=e.lexer.stream,a=this.tableState;t.pos=a.streamPos;e.buf1=a.parserBuf1;e.buf2=a.parserBuf2;for(var r;;){if(!("firstEntryNum"in a&&"entryCount"in a)){if(M(r=e.getObj(),"trailer"))break;a.firstEntryNum=r;a.entryCount=e.getObj()}var i=a.firstEntryNum,n=a.entryCount;y(i)&&y(n)||p("Invalid XRef table: wrong types in subsection header");for(var s=a.entryNum;s<n;s++){a.streamPos=t.pos;a.entryNum=s;a.parserBuf1=e.buf1;a.parserBuf2=e.buf2;var o={};o.offset=e.getObj();o.gen=e.getObj();var c=e.getObj();M(c,"f")?o.free=!0:M(c,"n")&&(o.uncompressed=!0);y(o.offset)&&y(o.gen)&&(o.free||o.uncompressed)||p("Invalid entry in XRef subsection: "+i+", "+n);0===s&&o.free&&1===i&&(i=0);this.entries[s+i]||(this.entries[s+i]=o)}a.entryNum=0;a.streamPos=t.pos;a.parserBuf1=e.buf1;a.parserBuf2=e.buf2;delete a.firstEntryNum;delete a.entryCount}this.entries[0]&&!this.entries[0].free&&p("Invalid XRef table: unexpected first object");return r},processXRefStream:function(e){if(!("streamState"in this)){var t=e.dict,a=t.get("W"),r=t.get("Index");r||(r=[0,t.get("Size")]);this.streamState={entryRanges:r,byteWidths:a,entryNum:0,streamPos:e.pos}}this.readXRefStream(e);delete this.streamState;return e.dict},readXRefStream:function(e){var t,a,r=this.streamState;e.pos=r.streamPos;for(var i=r.byteWidths,n=i[0],s=i[1],o=i[2],c=r.entryRanges;c.length>0;){var l=c[0],h=c[1];y(l)&&y(h)||p("Invalid XRef range fields: "+l+", "+h);y(n)&&y(s)&&y(o)||p("Invalid XRef entry fields length: "+l+", "+h);for(t=r.entryNum;t<h;++t){r.entryNum=t;r.streamPos=e.pos;var u=0,f=0,d=0;for(a=0;a<n;++a)u=u<<8|e.getByte();0===n&&(u=1);for(a=0;a<s;++a)f=f<<8|e.getByte();for(a=0;a<o;++a)d=d<<8|e.getByte();var g={};g.offset=f;g.gen=d;switch(u){case 0:g.free=!0;break;case 1:g.uncompressed=!0;break;case 2:break;default:p("Invalid XRef entry type: "+u)}this.entries[l+t]||(this.entries[l+t]=g)}r.entryNum=0;r.streamPos=e.pos;c.splice(0,2)}},indexObjects:function(){function e(e,t,a){for(var r=a.length,i=e.length,n=0;t<i;){for(var s=0;s<r&&e[t+s]===a[s];)++s;if(s>=r)break;t++;n++}return n}var t=10,a=13,r=60,i=/^(\d+)\s+(\d+)\s+obj\b/,n=new Uint8Array([116,114,97,105,108,101,114]),s=new Uint8Array([115,116,97,114,116,120,114,101,102]),o=new Uint8Array([101,110,100,111,98,106]),c=new Uint8Array([47,88,82,101,102]);this.entries.length=0;var h=this.stream;h.pos=0;for(var u=h.getBytes(),f=h.start,d=u.length,g=[],p=[];f<d;){var m=u[f];if(9!==m&&m!==t&&m!==a&&32!==m)if(37!==m){var b,v=function(e,i){for(var n="",s=e[i];s!==t&&s!==a&&s!==r&&!(++i>=e.length);){n+=String.fromCharCode(s);s=e[i]}return n}(u,f);if(0!==v.indexOf("xref")||4!==v.length&&!/\s/.test(v[4]))if(b=i.exec(v)){void 0===this.entries[b[1]]&&(this.entries[b[1]]={offset:f-h.start,gen:0|b[2],uncompressed:!0});var y=e(u,f,o)+7,k=u.subarray(f,f+y),w=e(k,0,c);if(w<y&&k[w+5]<64){p.push(f-h.start);this.xrefstms[f-h.start]=1}f+=y}else if(0!==v.indexOf("trailer")||7!==v.length&&!/\s/.test(v[7]))f+=v.length+1;else{g.push(f);f+=e(u,f,s)}else{f+=e(u,f,n);g.push(f);f+=e(u,f,s)}}else do{++f;if(f>=d)break;m=u[f]}while(m!==t&&m!==a);else++f}var C,x;for(C=0,x=p.length;C<x;++C){this.startXRefQueue.push(p[C]);this.readXRef(!0)}var S;for(C=0,x=g.length;C<x;++C){h.pos=g[C];var A=new N(new U(h),!0,this,!0),I=A.getObj();if(M(I,"trailer")){S=A.getObj();if(E(S)&&S.has("ID"))return S}}if(S)return S;throw new l("Invalid PDF structure")},readXRef:function(e){var t=this.stream;try{for(;this.startXRefQueue.length;){var a=this.startXRefQueue[0];t.pos=a+t.start;var r,i=new N(new U(t),!0,this),n=i.getObj();if(M(n,"xref")){r=this.processXRefTable(i);this.topDict||(this.topDict=r);n=r.get("XRefStm");if(y(n)){var s=n;if(!(s in this.xrefstms)){this.xrefstms[s]=1;this.startXRefQueue.push(s)}}}else if(y(n)){y(i.getObj())&&M(i.getObj(),"obj")&&F(n=i.getObj())||p("Invalid XRef stream");r=this.processXRefStream(n);this.topDict||(this.topDict=r);r||p("Failed to read XRef stream")}else p("Invalid XRef stream header");n=r.get("Prev");y(n)?this.startXRefQueue.push(n):L(n)&&this.startXRefQueue.push(n.num);this.startXRefQueue.shift()}return this.topDict}catch(e){if(e instanceof h)throw e;m("(while reading XRef): "+e)}if(!e)throw new u},getEntry:function(e){var t=this.entries[e];return t&&!t.free&&t.offset?t:null},fetchIfRef:function(e,t){return L(e)?this.fetch(e,t):e},fetch:function(e,t){f(L(e),"ref object is not a reference");var a=e.num;if(a in this.cache){var r=this.cache[a];r instanceof B&&!r.objId&&(r.objId=e.toString());return r}var i=this.getEntry(a);if(null===i)return this.cache[a]=null;i=i.uncompressed?this.fetchUncompressed(e,i,t):this.fetchCompressed(i,t);E(i)?i.objId=e.toString():F(i)&&(i.dict.objId=e.toString());return i},fetchUncompressed:function(e,t,a){var r=e.gen,i=e.num;t.gen!==r&&p("inconsistent generation in XRef");var n=this.stream.makeSubStream(t.offset+this.stream.start),s=new N(new U(n),!0,this),o=s.getObj(),c=s.getObj(),l=s.getObj();y(o)&&parseInt(o,10)===i&&y(c)&&parseInt(c,10)===r&&M(l)||p("bad XRef entry");if(!M(l,"obj")){if(0===l.cmd.indexOf("obj")){i=parseInt(l.cmd.substring(3),10);if(!isNaN(i))return i}p("bad XRef entry")}t=this.encrypt&&!a?s.getObj(this.encrypt.createCipherTransform(i,r)):s.getObj();F(t)||(this.cache[i]=t);return t},fetchCompressed:function(e,t){var a=e.offset,r=this.fetch(new R(a,0));F(r)||p("bad ObjStm stream");var i=r.dict.get("First"),n=r.dict.get("N");y(i)&&y(n)||p("invalid first and n parameters for ObjStm stream");var s=new N(new U(r),!1,this);s.allowStreams=!0;var o,c,l=[],h=[];for(o=0;o<n;++o){c=s.getObj();y(c)||p("invalid object number in the ObjStm stream: "+c);h.push(c);var u=s.getObj();y(u)||p("invalid object offset in the ObjStm stream: "+u)}for(o=0;o<n;++o){l.push(s.getObj());M(s.buf1,"endobj")&&s.shift();c=h[o];var f=this.entries[c];f&&f.offset===a&&f.gen===o&&(this.cache[c]=l[o])}e=l[e.gen];void 0===e&&p("bad XRef entry for compressed object");return e},fetchIfRefAsync:function(e,t){return L(e)?this.fetchAsync(e,t):Promise.resolve(e)},fetchAsync:function(e,t){var a=this.stream.manager,r=this;return new Promise(function i(n,s){try{n(r.fetch(e,t))}catch(e){if(e instanceof h){a.requestRange(e.begin,e.end).then(function(){i(n,s)},s);return}s(e)}})},getCatalogObj:function(){return this.root}};return e}(),G=function(){function e(e,t){throw new Error("Cannot initialize NameOrNumberTree.")}e.prototype={getAll:function(){var e=Object.create(null);if(!this.root)return e;var t=this.xref,a=new T;a.put(this.root);for(var r=[this.root];r.length>0;){var i,n,s=t.fetchIfRef(r.shift());if(E(s))if(s.has("Kids")){var o=s.get("Kids");for(i=0,n=o.length;i<n;i++){var c=o[i];f(!a.has(c),'Duplicate entry in "'+this._type+'" tree.');r.push(c);a.put(c)}}else{var l=s.get(this._type);if(b(l))for(i=0,n=l.length;i<n;i+=2)e[t.fetchIfRef(l[i])]=t.fetchIfRef(l[i+1])}}return e},get:function(e){if(!this.root)return null;for(var t,a,r,i=this.xref,n=i.fetchIfRef(this.root),s=0;n.has("Kids");){if(++s>10){S('Search depth limit reached for "'+this._type+'" tree.');return null}var o=n.get("Kids");if(!b(o))return null;t=0;a=o.length-1;for(;t<=a;){r=t+a>>1;var c=i.fetchIfRef(o[r]),l=c.get("Limits");if(e<i.fetchIfRef(l[0]))a=r-1;else{if(!(e>i.fetchIfRef(l[1]))){n=i.fetchIfRef(o[r]);break}t=r+1}}if(t>a)return null}var h=n.get(this._type);if(b(h)){t=0;a=h.length-2;for(;t<=a;){r=t+a&-2;var u=i.fetchIfRef(h[r]);if(e<u)a=r-2;else{if(!(e>u))return i.fetchIfRef(h[r+1]);t=r+2}}}return null}};return e}(),X=function(){function e(e,t){this.root=e;this.xref=t;this._type="Names"}I.inherit(e,G,{});return e}(),V=function(){function e(e,t){this.root=e;this.xref=t;this._type="Nums"}I.inherit(e,G,{});return e}(),W=function(){function e(e,t){if(e&&E(e)){this.xref=t;this.root=e;e.has("FS")&&(this.fs=e.get("FS"));this.description=e.has("Desc")?C(e.get("Desc")):"";e.has("RF")&&S("Related file specifications are not supported");this.contentAvailable=!0;if(!e.has("EF")){this.contentAvailable=!1;S("Non-embedded file specifications are not supported")}}}function t(e){return e.has("UF")?e.get("UF"):e.has("F")?e.get("F"):e.has("Unix")?e.get("Unix"):e.has("Mac")?e.get("Mac"):e.has("DOS")?e.get("DOS"):null}e.prototype={get filename(){if(!this._filename&&this.root){var e=t(this.root)||"unnamed";this._filename=C(e).replace(/\\\\/g,"\\").replace(/\\\//g,"/").replace(/\\/g,"/")}return this._filename},get content(){if(!this.contentAvailable)return null;!this.contentRef&&this.root&&(this.contentRef=t(this.root.get("EF")));var e=null;if(this.contentRef){var a=this.xref,r=a.fetchIfRef(this.contentRef);r&&F(r)?e=r.getBytes():S("Embedded file specification points to non-existing/invalid content")}else S("Embedded file specification does not have a content");return e},get serializable(){return{filename:this.filename,content:this.content}}};return e}(),K=function(){function e(e){return L(e)||E(e)||b(e)||F(e)}function t(t,a){var r;if(E(t)||F(t)){var i;i=E(t)?t.map:t.dict.map;for(var n in i){r=i[n];e(r)&&a.push(r)}}else if(b(t))for(var s=0,o=t.length;s<o;s++){r=t[s];e(r)&&a.push(r)}}function a(e,t,a){this.obj=e;this.keys=t;this.xref=a;this.refSet=null;this.capability=null}a.prototype={load:function(){var e=this.keys;this.capability=g();if(!(this.xref.stream instanceof j)||0===this.xref.stream.getMissingChunks().length){this.capability.resolve();return this.capability.promise}this.refSet=new T;for(var t=[],a=0;a<e.length;a++)t.push(this.obj[e[a]]);this._walk(t);return this.capability.promise},_walk:function(e){for(var a=[],r=[];e.length;){var i=e.pop();if(L(i)){if(this.refSet.has(i))continue;try{var n=i;this.refSet.put(n);i=this.xref.fetch(i)}catch(e){if(!(e instanceof h))throw e;a.push(i);r.push({begin:e.begin,end:e.end})}}if(i&&i.getBaseStreams){for(var s=i.getBaseStreams(),o=!1,c=0;c<s.length;c++){var l=s[c];if(l.getMissingChunks&&l.getMissingChunks().length){o=!0;r.push({begin:l.start,end:l.end})}}o&&a.push(i)}t(i,e)}if(r.length)this.xref.stream.manager.requestRanges(r).then(function(){e=a;for(var t=0;t<a.length;t++){var r=a[t];L(r)&&this.refSet.remove(r)}this._walk(e)}.bind(this),this.capability.reject);else{this.refSet=null;this.capability.resolve()}}};return a}();t.Catalog=z;t.ObjectLoader=K;t.XRef=H;t.FileSpec=W},function(e,t,a){"use strict";var r=a(0),i=r.getLookupTableFactory,n=i(function(e){e.ArialNarrow="Helvetica";e["ArialNarrow-Bold"]="Helvetica-Bold";e["ArialNarrow-BoldItalic"]="Helvetica-BoldOblique";e["ArialNarrow-Italic"]="Helvetica-Oblique";e.ArialBlack="Helvetica";e["ArialBlack-Bold"]="Helvetica-Bold";e["ArialBlack-BoldItalic"]="Helvetica-BoldOblique";e["ArialBlack-Italic"]="Helvetica-Oblique";e["Arial-Black"]="Helvetica";e["Arial-Black-Bold"]="Helvetica-Bold";e["Arial-Black-BoldItalic"]="Helvetica-BoldOblique";e["Arial-Black-Italic"]="Helvetica-Oblique";e.Arial="Helvetica";e["Arial-Bold"]="Helvetica-Bold";e["Arial-BoldItalic"]="Helvetica-BoldOblique";e["Arial-Italic"]="Helvetica-Oblique";e["Arial-BoldItalicMT"]="Helvetica-BoldOblique";e["Arial-BoldMT"]="Helvetica-Bold";e["Arial-ItalicMT"]="Helvetica-Oblique";e.ArialMT="Helvetica";e["Courier-Bold"]="Courier-Bold";e["Courier-BoldItalic"]="Courier-BoldOblique";e["Courier-Italic"]="Courier-Oblique";e.CourierNew="Courier";e["CourierNew-Bold"]="Courier-Bold";e["CourierNew-BoldItalic"]="Courier-BoldOblique";e["CourierNew-Italic"]="Courier-Oblique";e["CourierNewPS-BoldItalicMT"]="Courier-BoldOblique";e["CourierNewPS-BoldMT"]="Courier-Bold";e["CourierNewPS-ItalicMT"]="Courier-Oblique";e.CourierNewPSMT="Courier";e.Helvetica="Helvetica";e["Helvetica-Bold"]="Helvetica-Bold";e["Helvetica-BoldItalic"]="Helvetica-BoldOblique";e["Helvetica-BoldOblique"]="Helvetica-BoldOblique";e["Helvetica-Italic"]="Helvetica-Oblique";e["Helvetica-Oblique"]="Helvetica-Oblique";e["Symbol-Bold"]="Symbol";e["Symbol-BoldItalic"]="Symbol";e["Symbol-Italic"]="Symbol";e.TimesNewRoman="Times-Roman";e["TimesNewRoman-Bold"]="Times-Bold";e["TimesNewRoman-BoldItalic"]="Times-BoldItalic";e["TimesNewRoman-Italic"]="Times-Italic";e.TimesNewRomanPS="Times-Roman";e["TimesNewRomanPS-Bold"]="Times-Bold";e["TimesNewRomanPS-BoldItalic"]="Times-BoldItalic";e["TimesNewRomanPS-BoldItalicMT"]="Times-BoldItalic";e["TimesNewRomanPS-BoldMT"]="Times-Bold";e["TimesNewRomanPS-Italic"]="Times-Italic";e["TimesNewRomanPS-ItalicMT"]="Times-Italic";e.TimesNewRomanPSMT="Times-Roman";e["TimesNewRomanPSMT-Bold"]="Times-Bold";e["TimesNewRomanPSMT-BoldItalic"]="Times-BoldItalic";e["TimesNewRomanPSMT-Italic"]="Times-Italic"}),s=i(function(e){e.CenturyGothic="Helvetica";e["CenturyGothic-Bold"]="Helvetica-Bold";e["CenturyGothic-BoldItalic"]="Helvetica-BoldOblique";e["CenturyGothic-Italic"]="Helvetica-Oblique";e.ComicSansMS="Comic Sans MS";e["ComicSansMS-Bold"]="Comic Sans MS-Bold";e["ComicSansMS-BoldItalic"]="Comic Sans MS-BoldItalic";e["ComicSansMS-Italic"]="Comic Sans MS-Italic";e.LucidaConsole="Courier";e["LucidaConsole-Bold"]="Courier-Bold";e["LucidaConsole-BoldItalic"]="Courier-BoldOblique";e["LucidaConsole-Italic"]="Courier-Oblique";e["MS-Gothic"]="MS Gothic";e["MS-Gothic-Bold"]="MS Gothic-Bold";e["MS-Gothic-BoldItalic"]="MS Gothic-BoldItalic";e["MS-Gothic-Italic"]="MS Gothic-Italic";e["MS-Mincho"]="MS Mincho";e["MS-Mincho-Bold"]="MS Mincho-Bold";e["MS-Mincho-BoldItalic"]="MS Mincho-BoldItalic";e["MS-Mincho-Italic"]="MS Mincho-Italic";e["MS-PGothic"]="MS PGothic";e["MS-PGothic-Bold"]="MS PGothic-Bold";e["MS-PGothic-BoldItalic"]="MS PGothic-BoldItalic";e["MS-PGothic-Italic"]="MS PGothic-Italic";e["MS-PMincho"]="MS PMincho";e["MS-PMincho-Bold"]="MS PMincho-Bold";e["MS-PMincho-BoldItalic"]="MS PMincho-BoldItalic";e["MS-PMincho-Italic"]="MS PMincho-Italic";e.NuptialScript="Times-Italic";e.Wingdings="ZapfDingbats"}),o=i(function(e){e["Adobe Jenson"]=!0;e["Adobe Text"]=!0;e.Albertus=!0;e.Aldus=!0;e.Alexandria=!0;e.Algerian=!0;e["American Typewriter"]=!0;e.Antiqua=!0;e.Apex=!0;e.Arno=!0;e.Aster=!0;e.Aurora=!0;e.Baskerville=!0;e.Bell=!0;e.Bembo=!0;e["Bembo Schoolbook"]=!0;e.Benguiat=!0;e["Berkeley Old Style"]=!0;e["Bernhard Modern"]=!0;e["Berthold City"]=!0;e.Bodoni=!0;e["Bauer Bodoni"]=!0;e["Book Antiqua"]=!0;e.Bookman=!0;e["Bordeaux Roman"]=!0;e["Californian FB"]=!0;e.Calisto=!0;e.Calvert=!0;e.Capitals=!0;e.Cambria=!0;e.Cartier=!0;e.Caslon=!0;e.Catull=!0;e.Centaur=!0;e["Century Old Style"]=!0;e["Century Schoolbook"]=!0;e.Chaparral=!0;e["Charis SIL"]=!0;e.Cheltenham=!0;e["Cholla Slab"]=!0;e.Clarendon=!0;e.Clearface=!0;e.Cochin=!0;e.Colonna=!0;e["Computer Modern"]=!0;e["Concrete Roman"]=!0;e.Constantia=!0;e["Cooper Black"]=!0;e.Corona=!0;e.Ecotype=!0;e.Egyptienne=!0;e.Elephant=!0;e.Excelsior=!0;e.Fairfield=!0;e["FF Scala"]=!0;e.Folkard=!0;e.Footlight=!0;e.FreeSerif=!0;e["Friz Quadrata"]=!0;e.Garamond=!0;e.Gentium=!0;e.Georgia=!0;e.Gloucester=!0;e["Goudy Old Style"]=!0;e["Goudy Schoolbook"]=!0;e["Goudy Pro Font"]=!0;e.Granjon=!0;e["Guardian Egyptian"]=!0;e.Heather=!0;e.Hercules=!0;e["High Tower Text"]=!0;e.Hiroshige=!0;e["Hoefler Text"]=!0;e["Humana Serif"]=!0;e.Imprint=!0;e["Ionic No. 5"]=!0;e.Janson=!0;e.Joanna=!0;e.Korinna=!0;e.Lexicon=!0;e["Liberation Serif"]=!0;e["Linux Libertine"]=!0;e.Literaturnaya=!0;e.Lucida=!0;e["Lucida Bright"]=!0;e.Melior=!0;e.Memphis=!0;e.Miller=!0;e.Minion=!0;e.Modern=!0;e["Mona Lisa"]=!0;e["Mrs Eaves"]=!0;e["MS Serif"]=!0;e["Museo Slab"]=!0;e["New York"]=!0;e["Nimbus Roman"]=!0;e["NPS Rawlinson Roadway"]=!0;e.NuptialScript=!0;e.Palatino=!0;e.Perpetua=!0;e.Plantin=!0;e["Plantin Schoolbook"]=!0;e.Playbill=!0;e["Poor Richard"]=!0;e["Rawlinson Roadway"]=!0;e.Renault=!0;e.Requiem=!0;e.Rockwell=!0;e.Roman=!0;e["Rotis Serif"]=!0;e.Sabon=!0;e.Scala=!0;e.Seagull=!0;e.Sistina=!0;e.Souvenir=!0;e.STIX=!0;e["Stone Informal"]=!0;e["Stone Serif"]=!0;e.Sylfaen=!0;e.Times=!0;e.Trajan=!0;e["Trinité"]=!0;e["Trump Mediaeval"]=!0;e.Utopia=!0;e["Vale Type"]=!0;e["Bitstream Vera"]=!0;e["Vera Serif"]=!0;e.Versailles=!0;e.Wanted=!0;e.Weiss=!0;e["Wide Latin"]=!0;e.Windsor=!0;e.XITS=!0}),c=i(function(e){e.Dingbats=!0;e.Symbol=!0;e.ZapfDingbats=!0}),l=i(function(e){e[2]=10;e[3]=32;e[4]=33;e[5]=34;e[6]=35;e[7]=36;e[8]=37;e[9]=38;e[10]=39;e[11]=40;e[12]=41;e[13]=42;e[14]=43;e[15]=44;e[16]=45;e[17]=46;e[18]=47;e[19]=48;e[20]=49;e[21]=50;e[22]=51;e[23]=52;e[24]=53;e[25]=54;e[26]=55;e[27]=56;e[28]=57;e[29]=58;e[30]=894;e[31]=60;e[32]=61;e[33]=62;e[34]=63;e[35]=64;e[36]=65;e[37]=66;e[38]=67;e[39]=68;e[40]=69;e[41]=70;e[42]=71;e[43]=72;e[44]=73;e[45]=74;e[46]=75;e[47]=76;e[48]=77;e[49]=78;e[50]=79;e[51]=80;e[52]=81;e[53]=82;e[54]=83;e[55]=84;e[56]=85;e[57]=86;e[58]=87;e[59]=88;e[60]=89;e[61]=90;e[62]=91;e[63]=92;e[64]=93;e[65]=94;e[66]=95;e[67]=96;e[68]=97;e[69]=98;e[70]=99;e[71]=100;e[72]=101;e[73]=102;e[74]=103;e[75]=104;e[76]=105;e[77]=106;e[78]=107;e[79]=108;e[80]=109;e[81]=110;e[82]=111;e[83]=112;e[84]=113;e[85]=114;e[86]=115;e[87]=116;e[88]=117;e[89]=118;e[90]=119;e[91]=120;e[92]=121;e[93]=122;e[94]=123;e[95]=124;e[96]=125;e[97]=126;e[98]=196;e[99]=197;e[100]=199;e[101]=201;e[102]=209;e[103]=214;e[104]=220;e[105]=225;e[106]=224;e[107]=226;e[108]=228;e[109]=227;e[110]=229;e[111]=231;e[112]=233;e[113]=232;e[114]=234;e[115]=235;e[116]=237;e[117]=236;e[118]=238;e[119]=239;e[120]=241;e[121]=243;e[122]=242;e[123]=244;e[124]=246;e[125]=245;e[126]=250;e[127]=249;e[128]=251;e[129]=252;e[130]=8224;e[131]=176;e[132]=162;e[133]=163;e[134]=167;e[135]=8226;e[136]=182;e[137]=223;e[138]=174;e[139]=169;e[140]=8482;e[141]=180;e[142]=168;e[143]=8800;e[144]=198;e[145]=216;e[146]=8734;e[147]=177;e[148]=8804;e[149]=8805;e[150]=165;e[151]=181;e[152]=8706;e[153]=8721;e[154]=8719;e[156]=8747;e[157]=170;e[158]=186;e[159]=8486;e[160]=230;e[161]=248;e[162]=191;e[163]=161;e[164]=172;e[165]=8730;e[166]=402;e[167]=8776;e[168]=8710;e[169]=171;e[170]=187;e[171]=8230;e[210]=218;e[223]=711;e[224]=321;e[225]=322;e[227]=353;e[229]=382;e[234]=253;e[252]=263;e[253]=268;e[254]=269;e[258]=258;e[260]=260;e[261]=261;e[265]=280;e[266]=281;e[268]=283;e[269]=313;e[275]=323;e[276]=324;e[278]=328;e[284]=345;e[285]=346;e[286]=347;e[292]=367;e[295]=377;e[296]=378;e[298]=380;e[305]=963;e[306]=964;e[307]=966;e[308]=8215;e[309]=8252;e[310]=8319;e[311]=8359;e[312]=8592;e[313]=8593;e[337]=9552;e[493]=1039;e[494]=1040;e[705]=1524;e[706]=8362;e[710]=64288;e[711]=64298;e[759]=1617;e[761]=1776;e[763]=1778;e[775]=1652;e[777]=1764;e[778]=1780;e[779]=1781;e[780]=1782;e[782]=771;e[783]=64726;e[786]=8363;e[788]=8532;e[790]=768;e[791]=769;e[792]=768;e[795]=803;e[797]=64336;e[798]=64337;e[799]=64342;e[800]=64343;e[801]=64344;e[802]=64345;e[803]=64362;e[804]=64363;e[805]=64364;e[2424]=7821;e[2425]=7822;e[2426]=7823;e[2427]=7824;e[2428]=7825;e[2429]=7826;e[2430]=7827;e[2433]=7682;e[2678]=8045;e[2679]=8046;e[2830]=1552;e[2838]=686;e[2840]=751;e[2842]=753;e[2843]=754;e[2844]=755;e[2846]=757;e[2856]=767;e[2857]=848;e[2858]=849;e[2862]=853;e[2863]=854;e[2864]=855;e[2865]=861;e[2866]=862;e[2906]=7460;e[2908]=7462;e[2909]=7463;e[2910]=7464;e[2912]=7466;e[2913]=7467;e[2914]=7468;e[2916]=7470;e[2917]=7471;e[2918]=7472;e[2920]=7474;e[2921]=7475;e[2922]=7476;e[2924]=7478;e[2925]=7479;e[2926]=7480;e[2928]=7482;e[2929]=7483;e[2930]=7484;e[2932]=7486;e[2933]=7487;e[2934]=7488;e[2936]=7490;e[2937]=7491;e[2938]=7492;e[2940]=7494;e[2941]=7495;e[2942]=7496;e[2944]=7498;e[2946]=7500;e[2948]=7502;e[2950]=7504;e[2951]=7505;e[2952]=7506;e[2954]=7508;e[2955]=7509;e[2956]=7510;e[2958]=7512;e[2959]=7513;e[2960]=7514;e[2962]=7516;e[2963]=7517;e[2964]=7518;e[2966]=7520;e[2967]=7521;e[2968]=7522;e[2970]=7524;e[2971]=7525;e[2972]=7526;e[2974]=7528;e[2975]=7529;e[2976]=7530;e[2978]=1537;e[2979]=1538;e[2980]=1539;e[2982]=1549;e[2983]=1551;e[2984]=1552;e[2986]=1554;e[2987]=1555;e[2988]=1556;e[2990]=1623;e[2991]=1624;e[2995]=1775;e[2999]=1791;e[3002]=64290;e[3003]=64291;e[3004]=64292;e[3006]=64294;e[3007]=64295;e[3008]=64296;e[3011]=1900;e[3014]=8223;e[3015]=8244;e[3017]=7532;e[3018]=7533;e[3019]=7534;e[3075]=7590;e[3076]=7591;e[3079]=7594;e[3080]=7595;e[3083]=7598;e[3084]=7599;e[3087]=7602;e[3088]=7603;e[3091]=7606;e[3092]=7607;e[3095]=7610;e[3096]=7611;e[3099]=7614;e[3100]=7615;e[3103]=7618;e[3104]=7619;e[3107]=8337;e[3108]=8338;e[3116]=1884;e[3119]=1885;e[3120]=1885;e[3123]=1886;e[3124]=1886;e[3127]=1887;e[3128]=1887;e[3131]=1888;e[3132]=1888;e[3135]=1889;e[3136]=1889;e[3139]=1890;e[3140]=1890;e[3143]=1891;e[3144]=1891;e[3147]=1892;e[3148]=1892;e[3153]=580;e[3154]=581;e[3157]=584;e[3158]=585;e[3161]=588;e[3162]=589;e[3165]=891;e[3166]=892;e[3169]=1274;e[3170]=1275;e[3173]=1278;e[3174]=1279;e[3181]=7622;e[3182]=7623;e[3282]=11799;e[3316]=578;e[3379]=42785;e[3393]=1159;e[3416]=8377}),h=i(function(e){e[227]=322;e[264]=261;e[291]=346});t.getStdFontMap=n;t.getNonStdFontMap=s;t.getSerifFonts=o;t.getSymbolsFonts=c;t.getGlyphMapForStandardFonts=l;t.getSupplementalGlyphMapForArialBlack=h},function(e,t,a){"use strict";function r(e){return e>=65520&&e<=65535?0:e>=62976&&e<=63743?h()[e]||e:e}function i(e,t){var a=t[e];if(void 0!==a)return a;if(!e)return-1;if("u"===e[0]){var r,i=e.length;if(7===i&&"n"===e[1]&&"i"===e[2])r=e.substr(3);else{if(!(i>=5&&i<=7))return-1;r=e.substr(1)}if(r===r.toUpperCase()){a=parseInt(r,16);if(a>=0)return a}}return-1}function n(e){for(var t=0,a=u.length;t<a;t++){var r=u[t];if(e>=r.begin&&e<r.end)return t}return-1}function s(e){var t=u[13];if(e>=t.begin&&e<t.end)return!0;t=u[11];return e>=t.begin&&e<t.end}function o(e){var t=e.length;if(t<=1||!s(e.charCodeAt(0)))return e;for(var a="",r=t-1;r>=0;r--)a+=e[r];return a}var c=a(0),l=c.getLookupTableFactory,h=l(function(e){e[63721]=169;e[63193]=169;e[63720]=174;e[63194]=174;e[63722]=8482;e[63195]=8482;e[63729]=9127;e[63730]=9128;e[63731]=9129;e[63740]=9131;e[63741]=9132;e[63742]=9133;e[63726]=9121;e[63727]=9122;e[63728]=9123;e[63737]=9124;e[63738]=9125;e[63739]=9126;e[63723]=9115;e[63724]=9116;e[63725]=9117;e[63734]=9118;e[63735]=9119;e[63736]=9120}),u=[{begin:0,end:127},{begin:128,end:255},{begin:256,end:383},{begin:384,end:591},{begin:592,end:687},{begin:688,end:767},{begin:768,end:879},{begin:880,end:1023},{begin:11392,end:11519},{begin:1024,end:1279},{begin:1328,end:1423},{begin:1424,end:1535},{begin:42240,end:42559},{begin:1536,end:1791},{begin:1984,end:2047},{begin:2304,end:2431},{begin:2432,end:2559},{begin:2560,end:2687},{begin:2688,end:2815},{begin:2816,end:2943},{begin:2944,end:3071},{begin:3072,end:3199},{begin:3200,end:3327},{begin:3328,end:3455},{begin:3584,end:3711},{begin:3712,end:3839},{begin:4256,end:4351},{begin:6912,end:7039},{begin:4352,end:4607},{begin:7680,end:7935},{begin:7936,end:8191},{begin:8192,end:8303},{begin:8304,end:8351},{begin:8352,end:8399},{begin:8400,end:8447},{begin:8448,end:8527},{begin:8528,end:8591},{begin:8592,end:8703},{begin:8704,end:8959},{begin:8960,end:9215},{begin:9216,end:9279},{begin:9280,end:9311},{begin:9312,end:9471},{begin:9472,end:9599},{begin:9600,end:9631},{begin:9632,end:9727},{begin:9728,end:9983},{begin:9984,end:10175},{begin:12288,end:12351},{begin:12352,end:12447},{begin:12448,end:12543},{begin:12544,end:12591},{begin:12592,end:12687},{begin:43072,end:43135},{begin:12800,end:13055},{begin:13056,end:13311},{begin:44032,end:55215},{begin:55296,end:57343},{begin:67840,end:67871},{begin:19968,end:40959},{begin:57344,end:63743},{begin:12736,end:12783},{begin:64256,end:64335},{begin:64336,end:65023},{begin:65056,end:65071},{begin:65040,end:65055},{begin:65104,end:65135},{begin:65136,end:65279},{begin:65280,end:65519},{begin:65520,end:65535},{begin:3840,end:4095},{begin:1792,end:1871},{begin:1920,end:1983},{begin:3456,end:3583},{begin:4096,end:4255},{begin:4608,end:4991},{begin:5024,end:5119},{begin:5120,end:5759},{begin:5760,end:5791},{begin:5792,end:5887},{begin:6016,end:6143},{begin:6144,end:6319},{begin:10240,end:10495},{begin:40960,end:42127},{begin:5888,end:5919},{begin:66304,end:66351},{begin:66352,end:66383},{begin:66560,end:66639},{begin:118784,end:119039},{begin:119808,end:120831},{begin:1044480,end:1048573},{begin:65024,end:65039},{begin:917504,end:917631},{begin:6400,end:6479},{begin:6480,end:6527},{begin:6528,end:6623},{begin:6656,end:6687},{begin:11264,end:11359},{begin:11568,end:11647},{begin:19904,end:19967},{begin:43008,end:43055},{begin:65536,end:65663},{begin:65856,end:65935},{begin:66432,end:66463},{begin:66464,end:66527},{begin:66640,end:66687},{begin:66688,end:66735},{begin:67584,end:67647},{begin:68096,end:68191},{begin:119552,end:119647},{begin:73728,end:74751},{begin:119648,end:119679},{begin:7040,end:7103},{begin:7168,end:7247},{begin:7248,end:7295},{begin:43136,end:43231},{begin:43264,end:43311},{begin:43312,end:43359},{begin:43520,end:43615},{begin:65936,end:65999},{begin:66e3,end:66047},{begin:66208,end:66271},{begin:127024,end:127135}],f=l(function(e){e["¨"]=" ̈";e["¯"]=" Ì„";e["´"]=" Ì";e["µ"]="μ";e["¸"]=" ̧";e["IJ"]="IJ";e["ij"]="ij";e["Ä¿"]="L·";e["Å€"]="l·";e["ʼn"]="ʼn";e["Å¿"]="s";e["Ç„"]="DŽ";e["Ç…"]="Dž";e["dž"]="dž";e["LJ"]="LJ";e["Lj"]="Lj";e["lj"]="lj";e["ÇŠ"]="NJ";e["Ç‹"]="Nj";e["ÇŒ"]="nj";e["DZ"]="DZ";e["Dz"]="Dz";e["dz"]="dz";e["˘"]=" ̆";e["Ë™"]=" ̇";e["Ëš"]=" ÌŠ";e["Ë›"]=" ̨";e["Ëœ"]=" ̃";e["Ë"]=" Ì‹";e["ͺ"]=" Í…";e["΄"]=" Ì";e["Ï"]="β";e["Ï‘"]="θ";e["Ï’"]="Î¥";e["Ï•"]="φ";e["Ï–"]="Ï€";e["Ï°"]="κ";e["ϱ"]="Ï";e["ϲ"]="Ï‚";e["Ï´"]="Θ";e["ϵ"]="ε";e["Ϲ"]="Σ";e["Ö‡"]="Õ¥Ö‚";e["Ùµ"]="اٴ";e["Ù¶"]="وٴ";e["Ù·"]="Û‡Ù´";e["Ù¸"]="يٴ";e["ำ"]="à¹à¸²";e["ຳ"]="à»àº²";e["ໜ"]="ຫນ";e["à»"]="ຫມ";e["ཷ"]="ྲà¾";e["ཹ"]="ླà¾";e["ẚ"]="aʾ";e["á¾½"]=" Ì“";e["᾿"]=" Ì“";e["á¿€"]=" Í‚";e["῾"]=" Ì”";e[" "]=" ";e[" "]=" ";e[" "]=" ";e[" "]=" ";e[" "]=" ";e[" "]=" ";e[" "]=" ";e[" "]=" ";e["‗"]=" ̳";e["․"]=".";e["‥"]="..";e["…"]="...";e["″"]="′′";e["‴"]="′′′";e["‶"]="‵‵";e["‷"]="‵‵‵";e["‼"]="!!";e["‾"]=" Ì…";e["â‡"]="??";e["âˆ"]="?!";e["â‰"]="!?";e["â—"]="′′′′";e["âŸ"]=" ";e["₨"]="Rs";e["â„€"]="a/c";e["â„"]="a/s";e["℃"]="°C";e["â„…"]="c/o";e["℆"]="c/u";e["ℇ"]="Æ";e["℉"]="°F";e["â„–"]="No";e["â„¡"]="TEL";e["ℵ"]="×";e["ℶ"]="ב";e["â„·"]="×’";e["ℸ"]="ד";e["â„»"]="FAX";e["â… "]="I";e["â…¡"]="II";e["â…¢"]="III";e["â…£"]="IV";e["â…¤"]="V";e["â…¥"]="VI";e["â…¦"]="VII";e["â…§"]="VIII";e["â…¨"]="IX";e["â…©"]="X";e["â…ª"]="XI";e["â…«"]="XII";e["â…¬"]="L";e["â…­"]="C";e["â…®"]="D";e["â…¯"]="M";e["â…°"]="i";e["â…±"]="ii";e["â…²"]="iii";e["â…³"]="iv";e["â…´"]="v";e["â…µ"]="vi";e["â…¶"]="vii";e["â…·"]="viii";e["â…¸"]="ix";e["â…¹"]="x";e["â…º"]="xi";e["â…»"]="xii";e["â…¼"]="l";e["â…½"]="c";e["â…¾"]="d";e["â…¿"]="m";e["∬"]="∫∫";e["∭"]="∫∫∫";e["∯"]="∮∮";e["∰"]="∮∮∮";e["â‘´"]="(1)";e["⑵"]="(2)";e["⑶"]="(3)";e["â‘·"]="(4)";e["⑸"]="(5)";e["⑹"]="(6)";e["⑺"]="(7)";e["â‘»"]="(8)";e["⑼"]="(9)";e["⑽"]="(10)";e["⑾"]="(11)";e["â‘¿"]="(12)";e["â’€"]="(13)";e["â’"]="(14)";e["â’‚"]="(15)";e["â’ƒ"]="(16)";e["â’„"]="(17)";e["â’…"]="(18)";e["â’†"]="(19)";e["â’‡"]="(20)";e["â’ˆ"]="1.";e["â’‰"]="2.";e["â’Š"]="3.";e["â’‹"]="4.";e["â’Œ"]="5.";e["â’"]="6.";e["â’Ž"]="7.";e["â’"]="8.";e["â’"]="9.";e["â’‘"]="10.";e["â’’"]="11.";e["â’“"]="12.";e["â’”"]="13.";e["â’•"]="14.";e["â’–"]="15.";e["â’—"]="16.";e["â’˜"]="17.";e["â’™"]="18.";e["â’š"]="19.";e["â’›"]="20.";e["â’œ"]="(a)";e["â’"]="(b)";e["â’ž"]="(c)";e["â’Ÿ"]="(d)";e["â’ "]="(e)";e["â’¡"]="(f)";e["â’¢"]="(g)";e["â’£"]="(h)";e["â’¤"]="(i)";e["â’¥"]="(j)";e["â’¦"]="(k)";e["â’§"]="(l)";e["â’¨"]="(m)";e["â’©"]="(n)";e["â’ª"]="(o)";e["â’«"]="(p)";e["â’¬"]="(q)";e["â’­"]="(r)";e["â’®"]="(s)";e["â’¯"]="(t)";e["â’°"]="(u)";e["â’±"]="(v)";e["â’²"]="(w)";e["â’³"]="(x)";e["â’´"]="(y)";e["â’µ"]="(z)";e["⨌"]="∫∫∫∫";e["â©´"]="::=";e["⩵"]="==";e["⩶"]="===";e["⺟"]="æ¯";e["⻳"]="龟";e["â¼€"]="一";e["â¼"]="丨";e["⼂"]="丶";e["⼃"]="丿";e["⼄"]="ä¹™";e["â¼…"]="亅";e["⼆"]="二";e["⼇"]="亠";e["⼈"]="人";e["⼉"]="å„¿";e["⼊"]="å…¥";e["⼋"]="å…«";e["⼌"]="冂";e["â¼"]="冖";e["⼎"]="冫";e["â¼"]="几";e["â¼"]="凵";e["⼑"]="刀";e["â¼’"]="力";e["⼓"]="勹";e["â¼”"]="匕";e["⼕"]="匚";e["â¼–"]="匸";e["â¼—"]="å";e["⼘"]="åœ";e["â¼™"]="å©";e["⼚"]="厂";e["â¼›"]="厶";e["⼜"]="åˆ";e["â¼"]="å£";e["⼞"]="å›—";e["⼟"]="土";e["â¼ "]="士";e["⼡"]="夂";e["â¼¢"]="夊";e["â¼£"]="夕";e["⼤"]="大";e["â¼¥"]="女";e["⼦"]="å­";e["⼧"]="宀";e["⼨"]="寸";e["⼩"]="å°";e["⼪"]="å°¢";e["⼫"]="å°¸";e["⼬"]="å±®";e["â¼­"]="å±±";e["â¼®"]="å·›";e["⼯"]="å·¥";e["â¼°"]="å·±";e["â¼±"]="å·¾";e["â¼²"]="å¹²";e["â¼³"]="幺";e["â¼´"]="广";e["â¼µ"]="å»´";e["⼶"]="廾";e["â¼·"]="弋";e["⼸"]="弓";e["â¼¹"]="å½";e["⼺"]="彡";e["â¼»"]="å½³";e["â¼¼"]="心";e["â¼½"]="戈";e["â¼¾"]="戶";e["⼿"]="手";e["â½€"]="支";e["â½"]="æ”´";e["⽂"]="æ–‡";e["⽃"]="æ–—";e["⽄"]="æ–¤";e["â½…"]="æ–¹";e["⽆"]="æ— ";e["⽇"]="æ—¥";e["⽈"]="æ›°";e["⽉"]="月";e["⽊"]="木";e["⽋"]="欠";e["⽌"]="æ­¢";e["â½"]="æ­¹";e["⽎"]="殳";e["â½"]="毋";e["â½"]="比";e["⽑"]="毛";e["â½’"]="æ°";e["⽓"]="æ°”";e["â½”"]="æ°´";e["⽕"]="ç«";e["â½–"]="爪";e["â½—"]="父";e["⽘"]="爻";e["â½™"]="爿";e["⽚"]="片";e["â½›"]="牙";e["⽜"]="牛";e["â½"]="犬";e["⽞"]="玄";e["⽟"]="玉";e["â½ "]="ç“œ";e["⽡"]="瓦";e["â½¢"]="甘";e["â½£"]="生";e["⽤"]="用";e["â½¥"]="ç”°";e["⽦"]="ç–‹";e["⽧"]="ç–’";e["⽨"]="癶";e["⽩"]="白";e["⽪"]="çš®";e["⽫"]="çš¿";e["⽬"]="ç›®";e["â½­"]="矛";e["â½®"]="矢";e["⽯"]="石";e["â½°"]="示";e["â½±"]="禸";e["â½²"]="禾";e["â½³"]="ç©´";e["â½´"]="ç«‹";e["â½µ"]="竹";e["⽶"]="ç±³";e["â½·"]="糸";e["⽸"]="缶";e["â½¹"]="网";e["⽺"]="羊";e["â½»"]="ç¾½";e["â½¼"]="è€";e["â½½"]="而";e["â½¾"]="耒";e["⽿"]="耳";e["â¾€"]="è¿";e["â¾"]="肉";e["⾂"]="臣";e["⾃"]="自";e["⾄"]="至";e["â¾…"]="臼";e["⾆"]="舌";e["⾇"]="舛";e["⾈"]="舟";e["⾉"]="艮";e["⾊"]="色";e["⾋"]="艸";e["⾌"]="è™";e["â¾"]="虫";e["⾎"]="è¡€";e["â¾"]="è¡Œ";e["â¾"]="è¡£";e["⾑"]="襾";e["â¾’"]="見";e["⾓"]="角";e["â¾”"]="言";e["⾕"]="è°·";e["â¾–"]="豆";e["â¾—"]="豕";e["⾘"]="豸";e["â¾™"]="è²";e["⾚"]="赤";e["â¾›"]="èµ°";e["⾜"]="足";e["â¾"]="身";e["⾞"]="車";e["⾟"]="è¾›";e["â¾ "]="è¾°";e["⾡"]="è¾µ";e["â¾¢"]="é‚‘";e["â¾£"]="é…‰";e["⾤"]="釆";e["â¾¥"]="里";e["⾦"]="金";e["⾧"]="é•·";e["⾨"]="é–€";e["⾩"]="阜";e["⾪"]="隶";e["⾫"]="éš¹";e["⾬"]="雨";e["â¾­"]="é‘";e["â¾®"]="éž";e["⾯"]="é¢";e["â¾°"]="é©";e["â¾±"]="韋";e["â¾²"]="韭";e["â¾³"]="音";e["â¾´"]="é ";e["â¾µ"]="風";e["⾶"]="飛";e["â¾·"]="食";e["⾸"]="首";e["â¾¹"]="香";e["⾺"]="馬";e["â¾»"]="骨";e["â¾¼"]="高";e["â¾½"]="é«Ÿ";e["â¾¾"]="鬥";e["⾿"]="鬯";e["â¿€"]="鬲";e["â¿"]="鬼";e["â¿‚"]="é­š";e["⿃"]="é³¥";e["â¿„"]="é¹µ";e["â¿…"]="鹿";e["⿆"]="麥";e["⿇"]="麻";e["⿈"]="黃";e["⿉"]="é»";e["â¿Š"]="黑";e["â¿‹"]="黹";e["â¿Œ"]="黽";e["â¿"]="鼎";e["â¿Ž"]="鼓";e["â¿"]="é¼ ";e["â¿"]="é¼»";e["â¿‘"]="齊";e["â¿’"]="é½’";e["â¿“"]="é¾";e["â¿”"]="龜";e["â¿•"]="é¾ ";e["〶"]="〒";e["〸"]="å";e["〹"]="å„";e["〺"]="å…";e["ã‚›"]=" ã‚™";e["ã‚œ"]=" ã‚š";e["ㄱ"]="á„€";e["ㄲ"]="á„";e["ㄳ"]="ᆪ";e["ã„´"]="á„‚";e["ㄵ"]="ᆬ";e["ㄶ"]="ᆭ";e["ã„·"]="ᄃ";e["ㄸ"]="á„„";e["ㄹ"]="á„…";e["ㄺ"]="ᆰ";e["ã„»"]="ᆱ";e["ㄼ"]="ᆲ";e["ㄽ"]="ᆳ";e["ㄾ"]="ᆴ";e["ã„¿"]="ᆵ";e["ã…€"]="á„š";e["ã…"]="ᄆ";e["ã…‚"]="ᄇ";e["ã…ƒ"]="ᄈ";e["ã…„"]="á„¡";e["ã……"]="ᄉ";e["ã…†"]="á„Š";e["ã…‡"]="á„‹";e["ã…ˆ"]="á„Œ";e["ã…‰"]="á„";e["ã…Š"]="á„Ž";e["ã…‹"]="á„";e["ã…Œ"]="á„";e["ã…"]="á„‘";e["ã…Ž"]="á„’";e["ã…"]="á…¡";e["ã…"]="á…¢";e["ã…‘"]="á…£";e["ã…’"]="á…¤";e["ã…“"]="á…¥";e["ã…”"]="á…¦";e["ã…•"]="á…§";e["ã…–"]="á…¨";e["ã…—"]="á…©";e["ã…˜"]="á…ª";e["ã…™"]="á…«";e["ã…š"]="á…¬";e["ã…›"]="á…­";e["ã…œ"]="á…®";e["ã…"]="á…¯";e["ã…ž"]="á…°";e["ã…Ÿ"]="á…±";e["ã… "]="á…²";e["ã…¡"]="á…³";e["ã…¢"]="á…´";e["ã…£"]="á…µ";e["ã…¤"]="á… ";e["ã…¥"]="á„”";e["ã…¦"]="á„•";e["ã…§"]="ᇇ";e["ã…¨"]="ᇈ";e["ã…©"]="ᇌ";e["ã…ª"]="ᇎ";e["ã…«"]="ᇓ";e["ã…¬"]="ᇗ";e["ã…­"]="ᇙ";e["ã…®"]="á„œ";e["ã…¯"]="á‡";e["ã…°"]="ᇟ";e["ã…±"]="á„";e["ã…²"]="á„ž";e["ã…³"]="á„ ";e["ã…´"]="á„¢";e["ã…µ"]="á„£";e["ã…¶"]="ᄧ";e["ã…·"]="á„©";e["ã…¸"]="á„«";e["ã…¹"]="ᄬ";e["ã…º"]="á„­";e["ã…»"]="á„®";e["ã…¼"]="ᄯ";e["ã…½"]="ᄲ";e["ã…¾"]="ᄶ";e["ã…¿"]="á…€";e["ㆀ"]="á…‡";e["ã†"]="á…Œ";e["ㆂ"]="ᇱ";e["ㆃ"]="ᇲ";e["ㆄ"]="á…—";e["ㆅ"]="á…˜";e["ㆆ"]="á…™";e["ㆇ"]="ᆄ";e["ㆈ"]="ᆅ";e["ㆉ"]="ᆈ";e["ㆊ"]="ᆑ";e["ㆋ"]="ᆒ";e["ㆌ"]="ᆔ";e["ã†"]="ᆞ";e["ㆎ"]="ᆡ";e["㈀"]="(á„€)";e["ãˆ"]="(á„‚)";e["㈂"]="(ᄃ)";e["㈃"]="(á„…)";e["㈄"]="(ᄆ)";e["㈅"]="(ᄇ)";e["㈆"]="(ᄉ)";e["㈇"]="(á„‹)";e["㈈"]="(á„Œ)";e["㈉"]="(á„Ž)";e["㈊"]="(á„)";e["㈋"]="(á„)";e["㈌"]="(á„‘)";e["ãˆ"]="(á„’)";e["㈎"]="(가)";e["ãˆ"]="(á„‚á…¡)";e["ãˆ"]="(다)";e["㈑"]="(á„…á…¡)";e["㈒"]="(마)";e["㈓"]="(바)";e["㈔"]="(사)";e["㈕"]="(á„‹á…¡)";e["㈖"]="(자)";e["㈗"]="(á„Žá…¡)";e["㈘"]="(á„á…¡)"
+;e["㈙"]="(á„á…¡)";e["㈚"]="(á„‘á…¡)";e["㈛"]="(á„’á…¡)";e["㈜"]="(주)";e["ãˆ"]="(오전)";e["㈞"]="(á„‹á…©á„’á…®)";e["㈠"]="(一)";e["㈡"]="(二)";e["㈢"]="(三)";e["㈣"]="(å››)";e["㈤"]="(五)";e["㈥"]="(å…­)";e["㈦"]="(七)";e["㈧"]="(å…«)";e["㈨"]="(ä¹)";e["㈩"]="(å)";e["㈪"]="(月)";e["㈫"]="(ç«)";e["㈬"]="(æ°´)";e["㈭"]="(木)";e["㈮"]="(金)";e["㈯"]="(土)";e["㈰"]="(æ—¥)";e["㈱"]="(æ ª)";e["㈲"]="(有)";e["㈳"]="(社)";e["㈴"]="(å)";e["㈵"]="(特)";e["㈶"]="(財)";e["㈷"]="(ç¥)";e["㈸"]="(労)";e["㈹"]="(代)";e["㈺"]="(呼)";e["㈻"]="(å­¦)";e["㈼"]="(監)";e["㈽"]="(ä¼)";e["㈾"]="(資)";e["㈿"]="(å”)";e["㉀"]="(祭)";e["ã‰"]="(休)";e["㉂"]="(自)";e["㉃"]="(至)";e["ã‹€"]="1月";e["ã‹"]="2月";e["ã‹‚"]="3月";e["㋃"]="4月";e["ã‹„"]="5月";e["ã‹…"]="6月";e["㋆"]="7月";e["㋇"]="8月";e["㋈"]="9月";e["㋉"]="10月";e["ã‹Š"]="11月";e["ã‹‹"]="12月";e["ã˜"]="0点";e["ã™"]="1点";e["ãš"]="2点";e["ã›"]="3点";e["ãœ"]="4点";e["ã"]="5点";e["ãž"]="6点";e["ãŸ"]="7点";e["ã "]="8点";e["ã¡"]="9点";e["ã¢"]="10点";e["ã£"]="11点";e["ã¤"]="12点";e["ã¥"]="13点";e["ã¦"]="14点";e["ã§"]="15点";e["ã¨"]="16点";e["ã©"]="17点";e["ãª"]="18点";e["ã«"]="19点";e["ã¬"]="20点";e["ã­"]="21点";e["ã®"]="22点";e["ã¯"]="23点";e["ã°"]="24点";e["ã "]="1æ—¥";e["ã¡"]="2æ—¥";e["ã¢"]="3æ—¥";e["ã£"]="4æ—¥";e["ã¤"]="5æ—¥";e["ã¥"]="6æ—¥";e["ã¦"]="7æ—¥";e["ã§"]="8æ—¥";e["ã¨"]="9æ—¥";e["ã©"]="10æ—¥";e["ãª"]="11æ—¥";e["ã«"]="12æ—¥";e["ã¬"]="13æ—¥";e["ã­"]="14æ—¥";e["ã®"]="15æ—¥";e["ã¯"]="16æ—¥";e["ã°"]="17æ—¥";e["ã±"]="18æ—¥";e["ã²"]="19æ—¥";e["ã³"]="20æ—¥";e["ã´"]="21æ—¥";e["ãµ"]="22æ—¥";e["ã¶"]="23æ—¥";e["ã·"]="24æ—¥";e["ã¸"]="25æ—¥";e["ã¹"]="26æ—¥";e["ãº"]="27æ—¥";e["ã»"]="28æ—¥";e["ã¼"]="29æ—¥";e["ã½"]="30æ—¥";e["ã¾"]="31æ—¥";e["ff"]="ff";e["ï¬"]="fi";e["fl"]="fl";e["ffi"]="ffi";e["ffl"]="ffl";e["ſt"]="Å¿t";e["st"]="st";e["ﬓ"]="Õ´Õ¶";e["ﬔ"]="Õ´Õ¥";e["ﬕ"]="Õ´Õ«";e["ﬖ"]="Õ¾Õ¶";e["ﬗ"]="Õ´Õ­";e["ï­"]="×ל";e["ï­"]="Ù±";e["ï­‘"]="Ù±";e["ï­’"]="Ù»";e["ï­“"]="Ù»";e["ï­”"]="Ù»";e["ï­•"]="Ù»";e["ï­–"]="Ù¾";e["ï­—"]="Ù¾";e["ï­˜"]="Ù¾";e["ï­™"]="Ù¾";e["ï­š"]="Ú€";e["ï­›"]="Ú€";e["ï­œ"]="Ú€";e["ï­"]="Ú€";e["ï­ž"]="Ùº";e["ï­Ÿ"]="Ùº";e["ï­ "]="Ùº";e["ï­¡"]="Ùº";e["ï­¢"]="Ù¿";e["ï­£"]="Ù¿";e["ï­¤"]="Ù¿";e["ï­¥"]="Ù¿";e["ï­¦"]="Ù¹";e["ï­§"]="Ù¹";e["ï­¨"]="Ù¹";e["ï­©"]="Ù¹";e["ï­ª"]="Ú¤";e["ï­«"]="Ú¤";e["ï­¬"]="Ú¤";e["ï­­"]="Ú¤";e["ï­®"]="Ú¦";e["ï­¯"]="Ú¦";e["ï­°"]="Ú¦";e["ï­±"]="Ú¦";e["ï­²"]="Ú„";e["ï­³"]="Ú„";e["ï­´"]="Ú„";e["ï­µ"]="Ú„";e["ï­¶"]="Úƒ";e["ï­·"]="Úƒ";e["ï­¸"]="Úƒ";e["ï­¹"]="Úƒ";e["ï­º"]="Ú†";e["ï­»"]="Ú†";e["ï­¼"]="Ú†";e["ï­½"]="Ú†";e["ï­¾"]="Ú‡";e["ï­¿"]="Ú‡";e["ﮀ"]="Ú‡";e["ï®"]="Ú‡";e["ﮂ"]="Ú";e["ﮃ"]="Ú";e["ﮄ"]="ÚŒ";e["ï®…"]="ÚŒ";e["ﮆ"]="ÚŽ";e["ﮇ"]="ÚŽ";e["ﮈ"]="Úˆ";e["ﮉ"]="Úˆ";e["ﮊ"]="Ú˜";e["ﮋ"]="Ú˜";e["ﮌ"]="Ú‘";e["ï®"]="Ú‘";e["ﮎ"]="Ú©";e["ï®"]="Ú©";e["ï®"]="Ú©";e["ﮑ"]="Ú©";e["ï®’"]="Ú¯";e["ﮓ"]="Ú¯";e["ï®”"]="Ú¯";e["ﮕ"]="Ú¯";e["ï®–"]="Ú³";e["ï®—"]="Ú³";e["ﮘ"]="Ú³";e["ï®™"]="Ú³";e["ﮚ"]="Ú±";e["ï®›"]="Ú±";e["ﮜ"]="Ú±";e["ï®"]="Ú±";e["ﮞ"]="Úº";e["ﮟ"]="Úº";e["ï® "]="Ú»";e["ﮡ"]="Ú»";e["ﮢ"]="Ú»";e["ﮣ"]="Ú»";e["ﮤ"]="Û€";e["ﮥ"]="Û€";e["ﮦ"]="Û";e["ﮧ"]="Û";e["ﮨ"]="Û";e["ﮩ"]="Û";e["ﮪ"]="Ú¾";e["ﮫ"]="Ú¾";e["ﮬ"]="Ú¾";e["ï®­"]="Ú¾";e["ï®®"]="Û’";e["ﮯ"]="Û’";e["ï®°"]="Û“";e["ï®±"]="Û“";e["ﯓ"]="Ú­";e["ﯔ"]="Ú­";e["ﯕ"]="Ú­";e["ﯖ"]="Ú­";e["ﯗ"]="Û‡";e["ﯘ"]="Û‡";e["ﯙ"]="Û†";e["ﯚ"]="Û†";e["ﯛ"]="Ûˆ";e["ﯜ"]="Ûˆ";e["ï¯"]="Ù·";e["ﯞ"]="Û‹";e["ﯟ"]="Û‹";e["ﯠ"]="Û…";e["ﯡ"]="Û…";e["ﯢ"]="Û‰";e["ﯣ"]="Û‰";e["ﯤ"]="Û";e["ﯥ"]="Û";e["ﯦ"]="Û";e["ﯧ"]="Û";e["ﯨ"]="Ù‰";e["ﯩ"]="Ù‰";e["ﯪ"]="ئا";e["ﯫ"]="ئا";e["ﯬ"]="ئە";e["ﯭ"]="ئە";e["ﯮ"]="ئو";e["ﯯ"]="ئو";e["ﯰ"]="ئۇ";e["ﯱ"]="ئۇ";e["ﯲ"]="ئۆ";e["ﯳ"]="ئۆ";e["ﯴ"]="ئۈ";e["ﯵ"]="ئۈ";e["ﯶ"]="ئÛ";e["ﯷ"]="ئÛ";e["ﯸ"]="ئÛ";e["ﯹ"]="ئى";e["ﯺ"]="ئى";e["ﯻ"]="ئى";e["ﯼ"]="ÛŒ";e["ﯽ"]="ÛŒ";e["ﯾ"]="ÛŒ";e["ﯿ"]="ÛŒ";e["ï°€"]="ئج";e["ï°"]="ئح";e["ï°‚"]="ئم";e["ï°ƒ"]="ئى";e["ï°„"]="ئي";e["ï°…"]="بج";e["ï°†"]="بح";e["ï°‡"]="بخ";e["ï°ˆ"]="بم";e["ï°‰"]="بى";e["ï°Š"]="بي";e["ï°‹"]="تج";e["ï°Œ"]="تح";e["ï°"]="تخ";e["ï°Ž"]="تم";e["ï°"]="تى";e["ï°"]="تي";e["ï°‘"]="ثج";e["ï°’"]="ثم";e["ï°“"]="ثى";e["ï°”"]="ثي";e["ï°•"]="جح";e["ï°–"]="جم";e["ï°—"]="حج";e["ï°˜"]="حم";e["ï°™"]="خج";e["ï°š"]="خح";e["ï°›"]="خم";e["ï°œ"]="سج";e["ï°"]="سح";e["ï°ž"]="سخ";e["ï°Ÿ"]="سم";e["ï° "]="صح";e["ï°¡"]="صم";e["ï°¢"]="ضج";e["ï°£"]="ضح";e["ï°¤"]="ضخ";e["ï°¥"]="ضم";e["ï°¦"]="طح";e["ï°§"]="طم";e["ï°¨"]="ظم";e["ï°©"]="عج";e["ï°ª"]="عم";e["ï°«"]="غج";e["ï°¬"]="غم";e["ï°­"]="Ùج";e["ï°®"]="ÙØ­";e["ï°¯"]="ÙØ®";e["ï°°"]="ÙÙ…";e["ï°±"]="ÙÙ‰";e["ï°²"]="ÙÙŠ";e["ï°³"]="قح";e["ï°´"]="قم";e["ï°µ"]="قى";e["ï°¶"]="قي";e["ï°·"]="كا";e["ï°¸"]="كج";e["ï°¹"]="كح";e["ï°º"]="كخ";e["ï°»"]="كل";e["ï°¼"]="كم";e["ï°½"]="كى";e["ï°¾"]="كي";e["ï°¿"]="لج";e["ï±€"]="لح";e["ï±"]="لخ";e["ﱂ"]="لم";e["ﱃ"]="لى";e["ﱄ"]="لي";e["ï±…"]="مج";e["ﱆ"]="مح";e["ﱇ"]="مخ";e["ﱈ"]="مم";e["ﱉ"]="مى";e["ﱊ"]="مي";e["ﱋ"]="نج";e["ﱌ"]="نح";e["ï±"]="نخ";e["ﱎ"]="نم";e["ï±"]="نى";e["ï±"]="ني";e["ﱑ"]="هج";e["ï±’"]="هم";e["ﱓ"]="هى";e["ï±”"]="هي";e["ﱕ"]="يج";e["ï±–"]="يح";e["ï±—"]="يخ";e["ﱘ"]="يم";e["ï±™"]="يى";e["ﱚ"]="يي";e["ï±›"]="ذٰ";e["ﱜ"]="رٰ";e["ï±"]="ىٰ";e["ﱞ"]=" ٌّ";e["ﱟ"]=" ÙÙ‘";e["ï± "]=" ÙŽÙ‘";e["ﱡ"]=" ÙÙ‘";e["ï±¢"]=" ÙÙ‘";e["ï±£"]=" ّٰ";e["ﱤ"]="ئر";e["ï±¥"]="ئز";e["ﱦ"]="ئم";e["ﱧ"]="ئن";e["ﱨ"]="ئى";e["ﱩ"]="ئي";e["ﱪ"]="بر";e["ﱫ"]="بز";e["ﱬ"]="بم";e["ï±­"]="بن";e["ï±®"]="بى";e["ﱯ"]="بي";e["ï±°"]="تر";e["ï±±"]="تز";e["ï±²"]="تم";e["ï±³"]="تن";e["ï±´"]="تى";e["ï±µ"]="تي";e["ﱶ"]="ثر";e["ï±·"]="ثز";e["ﱸ"]="ثم";e["ï±¹"]="ثن";e["ﱺ"]="ثى";e["ï±»"]="ثي";e["ï±¼"]="ÙÙ‰";e["ï±½"]="ÙÙŠ";e["ï±¾"]="قى";e["ﱿ"]="قي";e["ï²€"]="كا";e["ï²"]="كل";e["ﲂ"]="كم";e["ﲃ"]="كى";e["ﲄ"]="كي";e["ï²…"]="لم";e["ﲆ"]="لى";e["ﲇ"]="لي";e["ﲈ"]="ما";e["ﲉ"]="مم";e["ﲊ"]="نر";e["ﲋ"]="نز";e["ﲌ"]="نم";e["ï²"]="نن";e["ﲎ"]="نى";e["ï²"]="ني";e["ï²"]="ىٰ";e["ﲑ"]="ير";e["ï²’"]="يز";e["ﲓ"]="يم";e["ï²”"]="ين";e["ﲕ"]="يى";e["ï²–"]="يي";e["ï²—"]="ئج";e["ﲘ"]="ئح";e["ï²™"]="ئخ";e["ﲚ"]="ئم";e["ï²›"]="ئه";e["ﲜ"]="بج";e["ï²"]="بح";e["ﲞ"]="بخ";e["ﲟ"]="بم";e["ï² "]="به";e["ﲡ"]="تج";e["ï²¢"]="تح";e["ï²£"]="تخ";e["ﲤ"]="تم";e["ï²¥"]="ته";e["ﲦ"]="ثم";e["ﲧ"]="جح";e["ﲨ"]="جم";e["ﲩ"]="حج";e["ﲪ"]="حم";e["ﲫ"]="خج";e["ﲬ"]="خم";e["ï²­"]="سج";e["ï²®"]="سح";e["ﲯ"]="سخ";e["ï²°"]="سم";e["ï²±"]="صح";e["ï²²"]="صخ";e["ï²³"]="صم";e["ï²´"]="ضج";e["ï²µ"]="ضح";e["ﲶ"]="ضخ";e["ï²·"]="ضم";e["ﲸ"]="طح";e["ï²¹"]="ظم";e["ﲺ"]="عج";e["ï²»"]="عم";e["ï²¼"]="غج";e["ï²½"]="غم";e["ï²¾"]="Ùج";e["ﲿ"]="ÙØ­";e["ï³€"]="ÙØ®";e["ï³"]="ÙÙ…";e["ﳂ"]="قح";e["ﳃ"]="قم";e["ﳄ"]="كج";e["ï³…"]="كح";e["ﳆ"]="كخ";e["ﳇ"]="كل";e["ﳈ"]="كم";e["ﳉ"]="لج";e["ﳊ"]="لح";e["ﳋ"]="لخ";e["ﳌ"]="لم";e["ï³"]="له";e["ﳎ"]="مج";e["ï³"]="مح";e["ï³"]="مخ";e["ﳑ"]="مم";e["ï³’"]="نج";e["ﳓ"]="نح";e["ï³”"]="نخ";e["ﳕ"]="نم";e["ï³–"]="نه";e["ï³—"]="هج";e["ﳘ"]="هم";e["ï³™"]="هٰ";e["ﳚ"]="يج";e["ï³›"]="يح";e["ﳜ"]="يخ";e["ï³"]="يم";e["ﳞ"]="يه";e["ﳟ"]="ئم";e["ï³ "]="ئه";e["ﳡ"]="بم";e["ï³¢"]="به";e["ï³£"]="تم";e["ﳤ"]="ته";e["ï³¥"]="ثم";e["ﳦ"]="ثه";e["ﳧ"]="سم";e["ﳨ"]="سه";e["ﳩ"]="شم";e["ﳪ"]="شه";e["ﳫ"]="كل";e["ﳬ"]="كم";e["ï³­"]="لم";e["ï³®"]="نم";e["ﳯ"]="نه";e["ï³°"]="يم";e["ï³±"]="يه";e["ï³²"]="Ù€ÙŽÙ‘";e["ï³³"]="Ù€ÙÙ‘";e["ï³´"]="Ù€ÙÙ‘";e["ï³µ"]="طى";e["ﳶ"]="طي";e["ï³·"]="عى";e["ﳸ"]="عي";e["ï³¹"]="غى";e["ﳺ"]="غي";e["ï³»"]="سى";e["ï³¼"]="سي";e["ï³½"]="شى";e["ï³¾"]="شي";e["ﳿ"]="حى";e["ï´€"]="حي";e["ï´"]="جى";e["ï´‚"]="جي";e["ï´ƒ"]="خى";e["ï´„"]="خي";e["ï´…"]="صى";e["ï´†"]="صي";e["ï´‡"]="ضى";e["ï´ˆ"]="ضي";e["ï´‰"]="شج";e["ï´Š"]="شح";e["ï´‹"]="شخ";e["ï´Œ"]="شم";e["ï´"]="شر";e["ï´Ž"]="سر";e["ï´"]="صر";e["ï´"]="ضر";e["ï´‘"]="طى";e["ï´’"]="طي";e["ï´“"]="عى";e["ï´”"]="عي";e["ï´•"]="غى";e["ï´–"]="غي";e["ï´—"]="سى";e["ï´˜"]="سي";e["ï´™"]="شى";e["ï´š"]="شي";e["ï´›"]="حى";e["ï´œ"]="حي";e["ï´"]="جى";e["ï´ž"]="جي";e["ï´Ÿ"]="خى";e["ï´ "]="خي";e["ï´¡"]="صى";e["ï´¢"]="صي";e["ï´£"]="ضى";e["ï´¤"]="ضي";e["ï´¥"]="شج";e["ï´¦"]="شح";e["ï´§"]="شخ";e["ï´¨"]="شم";e["ï´©"]="شر";e["ï´ª"]="سر";e["ï´«"]="صر";e["ï´¬"]="ضر";e["ï´­"]="شج";e["ï´®"]="شح";e["ï´¯"]="شخ";e["ï´°"]="شم";e["ï´±"]="سه";e["ï´²"]="شه";e["ï´³"]="طم";e["ï´´"]="سج";e["ï´µ"]="سح";e["ï´¶"]="سخ";e["ï´·"]="شج";e["ï´¸"]="شح";e["ï´¹"]="شخ";e["ï´º"]="طم";e["ï´»"]="ظم";e["ï´¼"]="اً";e["ï´½"]="اً";e["ïµ"]="تجم";e["ﵑ"]="تحج";e["ïµ’"]="تحج";e["ﵓ"]="تحم";e["ïµ”"]="تخم";e["ﵕ"]="تمج";e["ïµ–"]="تمح";e["ïµ—"]="تمخ";e["ﵘ"]="جمح";e["ïµ™"]="جمح";e["ﵚ"]="حمي";e["ïµ›"]="حمى";e["ﵜ"]="سحج";e["ïµ"]="سجح";e["ﵞ"]="سجى";e["ﵟ"]="سمح";e["ïµ "]="سمح";e["ﵡ"]="سمج";e["ïµ¢"]="سمم";e["ïµ£"]="سمم";e["ﵤ"]="صحح";e["ïµ¥"]="صحح";e["ﵦ"]="صمم";e["ﵧ"]="شحم";e["ﵨ"]="شحم";e["ﵩ"]="شجي";e["ﵪ"]="شمخ";e["ﵫ"]="شمخ";e["ﵬ"]="شمم";e["ïµ­"]="شمم";e["ïµ®"]="ضحى";e["ﵯ"]="ضخم";e["ïµ°"]="ضخم";e["ïµ±"]="طمح";e["ïµ²"]="طمح";e["ïµ³"]="طمم";e["ïµ´"]="طمي";e["ïµµ"]="عجم";e["ﵶ"]="عمم";e["ïµ·"]="عمم";e["ﵸ"]="عمى";e["ïµ¹"]="غمم";e["ﵺ"]="غمي";e["ïµ»"]="غمى";e["ïµ¼"]="Ùخم";e["ïµ½"]="Ùخم";e["ïµ¾"]="قمح";e["ﵿ"]="قمم";e["ﶀ"]="لحم";e["ï¶"]="لحي";e["ﶂ"]="لحى";e["ﶃ"]="لجج";e["ﶄ"]="لجج";e["ﶅ"]="لخم";e["ﶆ"]="لخم";e["ﶇ"]="لمح";e["ﶈ"]="لمح";e["ﶉ"]="محج";e["ﶊ"]="محم";e["ﶋ"]="محي";e["ﶌ"]="مجح";e["ï¶"]="مجم";e["ﶎ"]="مخج";e["ï¶"]="مخم";e["ﶒ"]="مجخ";e["ﶓ"]="همج";e["ﶔ"]="همم";e["ﶕ"]="نحم";e["ﶖ"]="نحى";e["ﶗ"]="نجم";e["ﶘ"]="نجم";e["ﶙ"]="نجى";e["ﶚ"]="نمي";e["ﶛ"]="نمى";e["ﶜ"]="يمم";e["ï¶"]="يمم";e["ﶞ"]="بخي";e["ﶟ"]="تجي";e["ﶠ"]="تجى";e["ﶡ"]="تخي";e["ﶢ"]="تخى";e["ﶣ"]="تمي";e["ﶤ"]="تمى";e["ﶥ"]="جمي";e["ﶦ"]="جحى";e["ﶧ"]="جمى";e["ﶨ"]="سخى";e["ﶩ"]="صحي";e["ﶪ"]="شحي";e["ﶫ"]="ضحي";e["ﶬ"]="لجي";e["ﶭ"]="لمي";e["ﶮ"]="يحي";e["ﶯ"]="يجي";e["ﶰ"]="يمي";e["ﶱ"]="ممي";e["ﶲ"]="قمي";e["ﶳ"]="نحي";e["ﶴ"]="قمح";e["ﶵ"]="لحم";e["ﶶ"]="عمي";e["ﶷ"]="كمي";e["ﶸ"]="نجح";e["ﶹ"]="مخي";e["ﶺ"]="لجم";e["ﶻ"]="كمم";e["ﶼ"]="لجم";e["ﶽ"]="نجح";e["ﶾ"]="جحي";e["ﶿ"]="حجي";e["ï·€"]="مجي";e["ï·"]="Ùمي";e["ï·‚"]="بحي";e["ï·ƒ"]="كمم";e["ï·„"]="عجم";e["ï·…"]="صمم";e["ï·†"]="سخي";e["ï·‡"]="نجي";e["﹉"]="‾";e["﹊"]="‾";e["﹋"]="‾";e["﹌"]="‾";e["ï¹"]="_";e["﹎"]="_";e["ï¹"]="_";e["ﺀ"]="Ø¡";e["ïº"]="Ø¢";e["ﺂ"]="Ø¢";e["ﺃ"]="Ø£";e["ﺄ"]="Ø£";e["ﺅ"]="ؤ";e["ﺆ"]="ؤ";e["ﺇ"]="Ø¥";e["ﺈ"]="Ø¥";e["ﺉ"]="ئ";e["ﺊ"]="ئ";e["ﺋ"]="ئ";e["ﺌ"]="ئ";e["ïº"]="ا";e["ﺎ"]="ا";e["ïº"]="ب";e["ïº"]="ب";e["ﺑ"]="ب";e["ﺒ"]="ب";e["ﺓ"]="Ø©";e["ﺔ"]="Ø©";e["ﺕ"]="ت";e["ﺖ"]="ت";e["ﺗ"]="ت";e["ﺘ"]="ت";e["ﺙ"]="Ø«";e["ﺚ"]="Ø«";e["ﺛ"]="Ø«";e["ﺜ"]="Ø«";e["ïº"]="ج";e["ﺞ"]="ج";e["ﺟ"]="ج";e["ﺠ"]="ج";e["ﺡ"]="Ø­";e["ﺢ"]="Ø­";e["ﺣ"]="Ø­";e["ﺤ"]="Ø­";e["ﺥ"]="Ø®";e["ﺦ"]="Ø®";e["ﺧ"]="Ø®";e["ﺨ"]="Ø®";e["ﺩ"]="د";e["ﺪ"]="د";e["ﺫ"]="Ø°";e["ﺬ"]="Ø°";e["ﺭ"]="ر";e["ﺮ"]="ر";e["ﺯ"]="ز";e["ﺰ"]="ز";e["ﺱ"]="س";e["ﺲ"]="س";e["ﺳ"]="س";e["ﺴ"]="س";e["ﺵ"]="Ø´";e["ﺶ"]="Ø´";e["ﺷ"]="Ø´";e["ﺸ"]="Ø´";e["ﺹ"]="ص";e["ﺺ"]="ص";e["ﺻ"]="ص";e["ﺼ"]="ص";e["ﺽ"]="ض";e["ﺾ"]="ض";e["ﺿ"]="ض";e["ﻀ"]="ض";e["ï»"]="Ø·";e["ﻂ"]="Ø·";e["ﻃ"]="Ø·";e["ﻄ"]="Ø·";e["ï»…"]="ظ";e["ﻆ"]="ظ";e["ﻇ"]="ظ";e["ﻈ"]="ظ";e["ﻉ"]="ع";e["ﻊ"]="ع";e["ﻋ"]="ع";e["ﻌ"]="ع";e["ï»"]="غ";e["ﻎ"]="غ";e["ï»"]="غ";e["ï»"]="غ";e["ﻑ"]="Ù";e["ï»’"]="Ù";e["ﻓ"]="Ù";e["ï»”"]="Ù";e["ﻕ"]="Ù‚";e["ï»–"]="Ù‚";e["ï»—"]="Ù‚";e["ﻘ"]="Ù‚";e["ï»™"]="Ùƒ";e["ﻚ"]="Ùƒ";e["ï»›"]="Ùƒ";e["ﻜ"]="Ùƒ";e["ï»"]="Ù„";e["ﻞ"]="Ù„";e["ﻟ"]="Ù„";e["ï» "]="Ù„";e["ﻡ"]="Ù…";e["ﻢ"]="Ù…";e["ﻣ"]="Ù…";e["ﻤ"]="Ù…";e["ﻥ"]="Ù†";e["ﻦ"]="Ù†";e["ﻧ"]="Ù†";e["ﻨ"]="Ù†";e["ﻩ"]="Ù‡";e["ﻪ"]="Ù‡";e["ﻫ"]="Ù‡";e["ﻬ"]="Ù‡";e["ï»­"]="Ùˆ";e["ï»®"]="Ùˆ";e["ﻯ"]="Ù‰";e["ï»°"]="Ù‰";e["ï»±"]="ÙŠ";e["ﻲ"]="ÙŠ";e["ﻳ"]="ÙŠ";e["ï»´"]="ÙŠ";e["ﻵ"]="لآ";e["ﻶ"]="لآ";e["ï»·"]="لأ";e["ﻸ"]="لأ";e["ﻹ"]="لإ";e["ﻺ"]="لإ";e["ï»»"]="لا";e["ﻼ"]="لا"});t.mapSpecialUnicodeValues=r;t.reverseIfRtl=o;t.getUnicodeRangeFor=n;t.getNormalizedUnicodes=f;t.getUnicodeForGlyph=i},function(e,t,a){"use strict";function r(e,t){this.url=e;t=t||{};this.isHttp=/^https?:/i.test(e);this.httpHeaders=this.isHttp&&t.httpHeaders||{};this.withCredentials=t.withCredentials||!1;this.getXhr=t.getXhr||function(){return new XMLHttpRequest};this.currXhrId=0;this.pendingRequests=Object.create(null);this.loadedRequests=Object.create(null)}function i(e){var t=e.response;if("string"!=typeof t)return t;for(var a=t.length,r=new Uint8Array(a),i=0;i<a;i++)r[i]=255&t.charCodeAt(i);return r.buffer}function n(e){this._options=e;var t=e.source;this._manager=new r(t.url,{httpHeaders:t.httpHeaders,withCredentials:t.withCredentials});this._rangeChunkSize=t.rangeChunkSize;this._fullRequestReader=null;this._rangeRequestReaders=[]}function s(e,t){this._manager=e;var a=t.source,r={onHeadersReceived:this._onHeadersReceived.bind(this),onProgressiveData:a.disableStream?null:this._onProgressiveData.bind(this),onDone:this._onDone.bind(this),onError:this._onError.bind(this),onProgress:this._onProgress.bind(this)};this._url=a.url;this._fullRequestId=e.requestFull(r);this._headersReceivedCapability=d();this._disableRange=t.disableRange||!1;this._contentLength=a.length;this._rangeChunkSize=a.rangeChunkSize;this._rangeChunkSize||this._disableRange||(this._disableRange=!0);this._isStreamingSupported=!1;this._isRangeSupported=!1;this._cachedChunks=[];this._requests=[];this._done=!1;this._storedError=void 0;this.onProgress=null}function o(e,t,a){this._manager=e;var r={onDone:this._onDone.bind(this),onProgress:this._onProgress.bind(this)};this._requestId=e.requestRange(t,a,r);this._requests=[];this._queuedChunk=null;this._done=!1;this.onProgress=null;this.onClosed=null}var c=a(0),l=a(8),h=c.globalScope,u=function(){try{var e=new XMLHttpRequest;e.open("GET",h.location.href);e.responseType="moz-chunked-arraybuffer";return"moz-chunked-arraybuffer"===e.responseType}catch(e){return!1}}();r.prototype={requestRange:function(e,t,a){var r={begin:e,end:t};for(var i in a)r[i]=a[i];return this.request(r)},requestFull:function(e){return this.request(e)},request:function(e){var t=this.getXhr(),a=this.currXhrId++,r=this.pendingRequests[a]={xhr:t};t.open("GET",this.url);t.withCredentials=this.withCredentials;for(var i in this.httpHeaders){var n=this.httpHeaders[i];void 0!==n&&t.setRequestHeader(i,n)}if(this.isHttp&&"begin"in e&&"end"in e){var s=e.begin+"-"+(e.end-1);t.setRequestHeader("Range","bytes="+s);r.expectedStatus=206}else r.expectedStatus=200;if(u&&!!e.onProgressiveData){t.responseType="moz-chunked-arraybuffer";r.onProgressiveData=e.onProgressiveData;r.mozChunked=!0}else t.responseType="arraybuffer";e.onError&&(t.onerror=function(a){e.onError(t.status)});t.onreadystatechange=this.onStateChange.bind(this,a);t.onprogress=this.onProgress.bind(this,a);r.onHeadersReceived=e.onHeadersReceived;r.onDone=e.onDone;r.onError=e.onError;r.onProgress=e.onProgress;t.send(null);return a},onProgress:function(e,t){var a=this.pendingRequests[e];if(a){if(a.mozChunked){var r=i(a.xhr);a.onProgressiveData(r)}var n=a.onProgress;n&&n(t)}},onStateChange:function(e,t){var a=this.pendingRequests[e];if(a){var r=a.xhr;if(r.readyState>=2&&a.onHeadersReceived){a.onHeadersReceived();delete a.onHeadersReceived}if(4===r.readyState&&e in this.pendingRequests){delete this.pendingRequests[e];if(0===r.status&&this.isHttp)a.onError&&a.onError(r.status);else{var n=r.status||200;if(200===n&&206===a.expectedStatus||n===a.expectedStatus){this.loadedRequests[e]=!0;var s=i(r);if(206===n){var o=r.getResponseHeader("Content-Range"),c=/bytes (\d+)-(\d+)\/(\d+)/.exec(o),l=parseInt(c[1],10);a.onDone({begin:l,chunk:s})}else a.onProgressiveData?a.onDone(null):s?a.onDone({begin:0,chunk:s}):a.onError&&a.onError(r.status)}else a.onError&&a.onError(r.status)}}}},hasPendingRequests:function(){for(var e in this.pendingRequests)return!0;return!1},getRequestXhr:function(e){return this.pendingRequests[e].xhr},isStreamingRequest:function(e){return!!this.pendingRequests[e].onProgressiveData},isPendingRequest:function(e){return e in this.pendingRequests},isLoadedRequest:function(e){return e in this.loadedRequests},abortAllRequests:function(){for(var e in this.pendingRequests)this.abortRequest(0|e)},abortRequest:function(e){var t=this.pendingRequests[e].xhr;delete this.pendingRequests[e];t.abort()}};var f=c.assert,d=c.createPromiseCapability,g=c.isInt,p=c.MissingPDFException,m=c.UnexpectedResponseException;n.prototype={_onRangeRequestReaderClosed:function(e){var t=this._rangeRequestReaders.indexOf(e);t>=0&&this._rangeRequestReaders.splice(t,1)},getFullReader:function(){f(!this._fullRequestReader);this._fullRequestReader=new s(this._manager,this._options);return this._fullRequestReader},getRangeReader:function(e,t){var a=new o(this._manager,e,t);a.onClosed=this._onRangeRequestReaderClosed.bind(this);this._rangeRequestReaders.push(a);return a},cancelAllRequests:function(e){this._fullRequestReader&&this._fullRequestReader.cancel(e);this._rangeRequestReaders.slice(0).forEach(function(t){t.cancel(e)})}};s.prototype={_validateRangeRequestCapabilities:function(){if(this._disableRange)return!1;var e=this._manager;if(!e.isHttp)return!1;var t=this._fullRequestId,a=e.getRequestXhr(t);if("bytes"!==a.getResponseHeader("Accept-Ranges"))return!1;if("identity"!==(a.getResponseHeader("Content-Encoding")||"identity"))return!1;var r=a.getResponseHeader("Content-Length");r=parseInt(r,10);if(!g(r))return!1;this._contentLength=r;return!(r<=2*this._rangeChunkSize)},_onHeadersReceived:function(){this._validateRangeRequestCapabilities()&&(this._isRangeSupported=!0);var e=this._manager,t=this._fullRequestId;e.isStreamingRequest(t)?this._isStreamingSupported=!0:this._isRangeSupported&&e.abortRequest(t);this._headersReceivedCapability.resolve()},_onProgressiveData:function(e){if(this._requests.length>0){this._requests.shift().resolve({value:e,done:!1})}else this._cachedChunks.push(e)},_onDone:function(e){e&&this._onProgressiveData(e.chunk);this._done=!0;if(!(this._cachedChunks.length>0)){this._requests.forEach(function(e){e.resolve({value:void 0,done:!0})});this._requests=[]}},_onError:function(e){var t,a=this._url;t=404===e||0===e&&/^file:/.test(a)?new p('Missing PDF "'+a+'".'):new m("Unexpected server response ("+e+') while retrieving PDF "'+a+'".',e);this._storedError=t;this._headersReceivedCapability.reject(t);this._requests.forEach(function(e){e.reject(t)});this._requests=[];this._cachedChunks=[]},_onProgress:function(e){this.onProgress&&this.onProgress({loaded:e.loaded,total:e.lengthComputable?e.total:this._contentLength})},get isRangeSupported(){return this._isRangeSupported},get isStreamingSupported(){return this._isStreamingSupported},get contentLength(){return this._contentLength},get headersReady(){return this._headersReceivedCapability.promise},read:function(){if(this._storedError)return Promise.reject(this._storedError);if(this._cachedChunks.length>0){var e=this._cachedChunks.shift();return Promise.resolve(e)}if(this._done)return Promise.resolve({value:void 0,done:!0});var t=d();this._requests.push(t);return t.promise},cancel:function(e){this._done=!0;this._headersReceivedCapability.reject(e);this._requests.forEach(function(e){e.resolve({value:void 0,done:!0})});this._requests=[];this._manager.isPendingRequest(this._fullRequestId)&&this._manager.abortRequest(this._fullRequestId);this._fullRequestReader=null}};o.prototype={_close:function(){this.onClosed&&this.onClosed(this)},_onDone:function(e){var t=e.chunk;if(this._requests.length>0){this._requests.shift().resolve({value:t,done:!1})}else this._queuedChunk=t;this._done=!0;this._requests.forEach(function(e){e.resolve({value:void 0,done:!0})});this._requests=[];this._close()},_onProgress:function(e){!this.isStreamingSupported&&this.onProgress&&this.onProgress({loaded:e.loaded})},get isStreamingSupported(){return!1},read:function(){if(null!==this._queuedChunk){var e=this._queuedChunk;this._queuedChunk=null;return Promise.resolve({value:e,done:!1})}if(this._done)return Promise.resolve({value:void 0,done:!0});var t=d();this._requests.push(t);return t.promise},cancel:function(e){this._done=!0;this._requests.forEach(function(e){e.resolve({value:void 0,done:!0})});this._requests=[];this._manager.isPendingRequest(this._requestId)&&this._manager.abortRequest(this._requestId);this._close()}};l.setPDFNetworkStreamClass(n);t.PDFNetworkStream=n;t.NetworkManager=r},function(e,t,a){"use strict";function r(){}var i=a(0),n=a(1),s=a(2),o=a(3),c=a(16),l=a(14),h=i.AnnotationBorderStyleType,u=i.AnnotationFieldFlag,f=i.AnnotationFlag,d=i.AnnotationType,g=i.OPS,p=i.Util,m=i.isArray,b=i.isInt,v=i.stringToBytes,y=i.stringToPDFString,k=i.warn,w=n.Dict,C=n.isDict,x=n.isName,S=n.isRef,A=n.isStream,I=s.Stream,B=o.ColorSpace,R=c.Catalog,T=c.ObjectLoader,O=c.FileSpec,P=l.OperatorList;r.prototype={create:function(e,t,a,r){var i=e.fetchIfRef(t);if(C(i)){var n=S(t)?t.toString():"annot_"+r.createObjId(),s=i.get("Subtype");s=x(s)?s.name:null;var o={xref:e,dict:i,ref:S(t)?t:null,subtype:s,id:n,pdfManager:a};switch(s){case"Link":return new N(o);case"Text":return new U(o);case"Widget":var c=p.getInheritableProperty(i,"FT");c=x(c)?c.name:null;switch(c){case"Tx":return new D(o);case"Btn":return new F(o);case"Ch":return new q(o)}k('Unimplemented widget field type "'+c+'", falling back to base field type.');return new L(o);case"Popup":return new j(o);case"Highlight":return new _(o);case"Underline":return new z(o);case"Squiggly":return new H(o);case"StrikeOut":return new G(o);case"FileAttachment":return new X(o);default:k(s?'Unimplemented annotation type "'+s+'", falling back to base annotation.':"Annotation is missing the required /Subtype.");return new M(o)}}}};var M=function(){function e(e,t,a){var r=p.getAxialAlignedBoundingBox(t,a),i=r[0],n=r[1],s=r[2],o=r[3];if(i===s||n===o)return[1,0,0,1,e[0],e[1]];var c=(e[2]-e[0])/(s-i),l=(e[3]-e[1])/(o-n);return[c,0,0,l,e[0]-i*c,e[1]-n*l]}function t(e){var t=e.dict;this.setFlags(t.get("F"));this.setRectangle(t.getArray("Rect"));this.setColor(t.getArray("C"));this.setBorderStyle(t);this.setAppearance(t);this.data={};this.data.id=e.id;this.data.subtype=e.subtype;this.data.annotationFlags=this.flags;this.data.rect=this.rectangle;this.data.color=this.color;this.data.borderStyle=this.borderStyle;this.data.hasAppearance=!!this.appearance}t.prototype={_hasFlag:function(e,t){return!!(e&t)},_isViewable:function(e){return!this._hasFlag(e,f.INVISIBLE)&&!this._hasFlag(e,f.HIDDEN)&&!this._hasFlag(e,f.NOVIEW)},_isPrintable:function(e){return this._hasFlag(e,f.PRINT)&&!this._hasFlag(e,f.INVISIBLE)&&!this._hasFlag(e,f.HIDDEN)},get viewable(){return 0===this.flags||this._isViewable(this.flags)},get printable(){return 0!==this.flags&&this._isPrintable(this.flags)},setFlags:function(e){this.flags=b(e)&&e>0?e:0},hasFlag:function(e){return this._hasFlag(this.flags,e)},setRectangle:function(e){m(e)&&4===e.length?this.rectangle=p.normalizeRect(e):this.rectangle=[0,0,0,0]},setColor:function(e){var t=new Uint8Array(3);if(m(e))switch(e.length){case 0:this.color=null;break;case 1:B.singletons.gray.getRgbItem(e,0,t,0);this.color=t;break;case 3:B.singletons.rgb.getRgbItem(e,0,t,0);this.color=t;break;case 4:B.singletons.cmyk.getRgbItem(e,0,t,0);this.color=t;break;default:this.color=t}else this.color=t},setBorderStyle:function(e){this.borderStyle=new E;if(C(e))if(e.has("BS")){var t=e.get("BS"),a=t.get("Type");if(!a||x(a,"Border")){this.borderStyle.setWidth(t.get("W"));this.borderStyle.setStyle(t.get("S"));this.borderStyle.setDashArray(t.getArray("D"))}}else if(e.has("Border")){var r=e.getArray("Border");if(m(r)&&r.length>=3){this.borderStyle.setHorizontalCornerRadius(r[0]);this.borderStyle.setVerticalCornerRadius(r[1]);this.borderStyle.setWidth(r[2]);4===r.length&&this.borderStyle.setDashArray(r[3])}}else this.borderStyle.setWidth(0)},setAppearance:function(e){this.appearance=null;var t=e.get("AP");if(C(t)){var a=t.get("N");if(A(a))this.appearance=a;else if(C(a)){var r=e.get("AS");x(r)&&a.has(r.name)&&(this.appearance=a.get(r.name))}}},_preparePopup:function(e){e.has("C")||(this.data.color=null);this.data.hasPopup=e.has("Popup");this.data.title=y(e.get("T")||"");this.data.contents=y(e.get("Contents")||"")},loadResources:function(e){return new Promise(function(t,a){this.appearance.dict.getAsync("Resources").then(function(r){if(r){new T(r.map,e,r.xref).load().then(function(){t(r)},a)}else t()},a)}.bind(this))},getOperatorList:function(t,a,r){if(!this.appearance)return Promise.resolve(new P);var i=this.data,n=this.appearance.dict,s=this.loadResources(["ExtGState","ColorSpace","Pattern","Shading","XObject","Font"]),o=n.getArray("BBox")||[0,0,1,1],c=n.getArray("Matrix")||[1,0,0,1,0,0],l=e(i.rect,o,c),h=this;return s.then(function(e){var r=new P;r.addOp(g.beginAnnotation,[i.rect,l,c]);return t.getOperatorList(h.appearance,a,e,r).then(function(){r.addOp(g.endAnnotation,[]);h.appearance.reset();return r})})}};return t}(),E=function(){function e(){this.width=1;this.style=h.SOLID;this.dashArray=[3];this.horizontalCornerRadius=0;this.verticalCornerRadius=0}e.prototype={setWidth:function(e){e===(0|e)&&(this.width=e)},setStyle:function(e){if(e)switch(e.name){case"S":this.style=h.SOLID;break;case"D":this.style=h.DASHED;break;case"B":this.style=h.BEVELED;break;case"I":this.style=h.INSET;break;case"U":this.style=h.UNDERLINE}},setDashArray:function(e){if(m(e)&&e.length>0){for(var t=!0,a=!0,r=0,i=e.length;r<i;r++){var n=e[r];if(!(+n>=0)){t=!1;break}n>0&&(a=!1)}t&&!a?this.dashArray=e:this.width=0}else e&&(this.width=0)},setHorizontalCornerRadius:function(e){e===(0|e)&&(this.horizontalCornerRadius=e)},setVerticalCornerRadius:function(e){e===(0|e)&&(this.verticalCornerRadius=e)}};return e}(),L=function(){function e(e){M.call(this,e);var t=e.dict,a=this.data;a.annotationType=d.WIDGET;a.fieldName=this._constructFieldName(t);a.fieldValue=p.getInheritableProperty(t,"V",!0);a.alternativeText=y(t.get("TU")||"");a.defaultAppearance=p.getInheritableProperty(t,"DA")||"";var r=p.getInheritableProperty(t,"FT");a.fieldType=x(r)?r.name:null;this.fieldResources=p.getInheritableProperty(t,"DR")||w.empty;a.fieldFlags=p.getInheritableProperty(t,"Ff");(!b(a.fieldFlags)||a.fieldFlags<0)&&(a.fieldFlags=0);a.readOnly=this.hasFieldFlag(u.READONLY);"Sig"===a.fieldType&&this.setFlags(f.HIDDEN)}p.inherit(e,M,{_constructFieldName:function(e){if(!e.has("T")&&!e.has("Parent")){k("Unknown field name, falling back to empty field name.");return""}if(!e.has("Parent"))return y(e.get("T"));var t=[];e.has("T")&&t.unshift(y(e.get("T")));for(var a=e;a.has("Parent");){a=a.get("Parent");if(!C(a))break;a.has("T")&&t.unshift(y(a.get("T")))}return t.join(".")},hasFieldFlag:function(e){return!!(this.data.fieldFlags&e)}});return e}(),D=function(){function e(e){L.call(this,e);this.data.fieldValue=y(this.data.fieldValue||"");var t=p.getInheritableProperty(e.dict,"Q");(!b(t)||t<0||t>2)&&(t=null);this.data.textAlignment=t;var a=p.getInheritableProperty(e.dict,"MaxLen");(!b(a)||a<0)&&(a=null);this.data.maxLen=a;this.data.multiLine=this.hasFieldFlag(u.MULTILINE);this.data.comb=this.hasFieldFlag(u.COMB)&&!this.hasFieldFlag(u.MULTILINE)&&!this.hasFieldFlag(u.PASSWORD)&&!this.hasFieldFlag(u.FILESELECT)&&null!==this.data.maxLen}p.inherit(e,L,{getOperatorList:function(e,t,a){var r=new P;if(a)return Promise.resolve(r);if(this.appearance)return M.prototype.getOperatorList.call(this,e,t,a);if(!this.data.defaultAppearance)return Promise.resolve(r);var i=new I(v(this.data.defaultAppearance));return e.getOperatorList(i,t,this.fieldResources,r).then(function(){return r})}});return e}(),F=function(){function e(e){L.call(this,e);this.data.checkBox=!this.hasFieldFlag(u.RADIO)&&!this.hasFieldFlag(u.PUSHBUTTON);if(this.data.checkBox){if(!x(this.data.fieldValue))return;this.data.fieldValue=this.data.fieldValue.name}this.data.radioButton=this.hasFieldFlag(u.RADIO)&&!this.hasFieldFlag(u.PUSHBUTTON);if(this.data.radioButton){this.data.fieldValue=this.data.buttonValue=null;var t=e.dict.get("Parent");if(C(t)&&t.has("V")){var a=t.get("V");x(a)&&(this.data.fieldValue=a.name)}var r=e.dict.get("AP");if(!C(r))return;var i=r.get("N");if(!C(i))return;for(var n=i.getKeys(),s=0,o=n.length;s<o;s++)if("Off"!==n[s]){this.data.buttonValue=n[s];break}}}p.inherit(e,L,{getOperatorList:function(e,t,a){var r=new P;return a?Promise.resolve(r):this.appearance?M.prototype.getOperatorList.call(this,e,t,a):Promise.resolve(r)}});return e}(),q=function(){function e(e){L.call(this,e);this.data.options=[];var t=p.getInheritableProperty(e.dict,"Opt");if(m(t))for(var a=e.xref,r=0,i=t.length;r<i;r++){var n=a.fetchIfRef(t[r]),s=m(n);this.data.options[r]={exportValue:s?a.fetchIfRef(n[0]):n,displayValue:s?a.fetchIfRef(n[1]):n}}m(this.data.fieldValue)||(this.data.fieldValue=[this.data.fieldValue]);this.data.combo=this.hasFieldFlag(u.COMBO);this.data.multiSelect=this.hasFieldFlag(u.MULTISELECT)}p.inherit(e,L,{getOperatorList:function(e,t,a){var r=new P;return a?Promise.resolve(r):M.prototype.getOperatorList.call(this,e,t,a)}});return e}(),U=function(){function e(e){M.call(this,e);this.data.annotationType=d.TEXT;if(this.data.hasAppearance)this.data.name="NoIcon";else{this.data.rect[1]=this.data.rect[3]-t;this.data.rect[2]=this.data.rect[0]+t;this.data.name=e.dict.has("Name")?e.dict.get("Name").name:"Note"}this._preparePopup(e.dict)}var t=22;p.inherit(e,M,{});return e}(),N=function(){function e(e){M.call(this,e);var t=this.data;t.annotationType=d.LINK;R.parseDestDictionary({destDict:e.dict,resultObj:t,docBaseUrl:e.pdfManager.docBaseUrl})}p.inherit(e,M,{});return e}(),j=function(){function e(e){M.call(this,e);this.data.annotationType=d.POPUP;var t=e.dict,a=t.get("Parent");if(a){this.data.parentId=t.getRaw("Parent").toString();this.data.title=y(a.get("T")||"");this.data.contents=y(a.get("Contents")||"");if(a.has("C")){this.setColor(a.getArray("C"));this.data.color=this.color}else this.data.color=null;if(!this.viewable){var r=a.get("F");this._isViewable(r)&&this.setFlags(r)}}else k("Popup annotation has a missing or invalid parent annotation.")}p.inherit(e,M,{});return e}(),_=function(){function e(e){M.call(this,e);this.data.annotationType=d.HIGHLIGHT;this._preparePopup(e.dict);this.data.borderStyle.setWidth(0)}p.inherit(e,M,{});return e}(),z=function(){function e(e){M.call(this,e);this.data.annotationType=d.UNDERLINE;this._preparePopup(e.dict);this.data.borderStyle.setWidth(0)}p.inherit(e,M,{});return e}(),H=function(){function e(e){M.call(this,e);this.data.annotationType=d.SQUIGGLY;this._preparePopup(e.dict);this.data.borderStyle.setWidth(0)}p.inherit(e,M,{});return e}(),G=function(){function e(e){M.call(this,e);this.data.annotationType=d.STRIKEOUT;this._preparePopup(e.dict);this.data.borderStyle.setWidth(0)}p.inherit(e,M,{});return e}(),X=function(){function e(e){M.call(this,e);var t=new O(e.dict.get("FS"),e.xref);this.data.annotationType=d.FILEATTACHMENT;this.data.file=t.serializable;this._preparePopup(e.dict)}p.inherit(e,M,{});return e}();t.Annotation=M;t.AnnotationBorderStyle=E;t.AnnotationFactory=r},function(e,t,a){"use strict";function r(e){return 0!=(1&e)}function i(e){return 0==(1&e)}function n(e,t,a){for(var r=t,i=e.length;r<i;++r)if(e[r]!==a)return r;return r}function s(e,t,a,r){for(var i=t;i<a;++i)e[i]=r}function o(e,t,a){for(var r=t,i=a-1;r<i;++r,--i){var n=e[r];e[r]=e[i];e[i]=n}}function c(e,t,a){return{str:e,dir:a?"ttb":t?"ltr":"rtl"}}function l(e,t,a){var l=!0,h=e.length;if(0===h||a)return c(e,l,a);g.length=h;p.length=h;var m,b,v=0;for(m=0;m<h;++m){g[m]=e.charAt(m);var y=e.charCodeAt(m),k="L";if(y<=255)k=f[y];else if(1424<=y&&y<=1524)k="R";else if(1536<=y&&y<=1791){k=d[255&y];k||u("Bidi: invalid Unicode character "+y.toString(16))}else 1792<=y&&y<=2220&&(k="AL");"R"!==k&&"AL"!==k&&"AN"!==k||v++;p[m]=k}if(0===v){l=!0;return c(e,l)}if(-1===t)if(v/h<.3){l=!0;t=0}else{l=!1;t=1}var w=[];for(m=0;m<h;++m)w[m]=t;var C=r(t)?"R":"L",x=C,S=x,A=x;for(m=0;m<h;++m)"NSM"===p[m]?p[m]=A:A=p[m];A=x;var I;for(m=0;m<h;++m){I=p[m];"EN"===I?p[m]="AL"===A?"AN":"EN":"R"!==I&&"L"!==I&&"AL"!==I||(A=I)}for(m=0;m<h;++m){I=p[m];"AL"===I&&(p[m]="R")}for(m=1;m<h-1;++m){"ES"===p[m]&&"EN"===p[m-1]&&"EN"===p[m+1]&&(p[m]="EN");"CS"!==p[m]||"EN"!==p[m-1]&&"AN"!==p[m-1]||p[m+1]!==p[m-1]||(p[m]=p[m-1])}for(m=0;m<h;++m)if("EN"===p[m]){var B;for(B=m-1;B>=0&&"ET"===p[B];--B)p[B]="EN";for(B=m+1;B<h&&"ET"===p[B];++B)p[B]="EN"}for(m=0;m<h;++m){I=p[m];"WS"!==I&&"ES"!==I&&"ET"!==I&&"CS"!==I||(p[m]="ON")}A=x;for(m=0;m<h;++m){I=p[m];"EN"===I?p[m]="L"===A?"L":"EN":"R"!==I&&"L"!==I||(A=I)}for(m=0;m<h;++m)if("ON"===p[m]){var R=n(p,m+1,"ON"),T=x;m>0&&(T=p[m-1]);var O=S;R+1<h&&(O=p[R+1]);"L"!==T&&(T="R");"L"!==O&&(O="R");T===O&&s(p,m,R,T);m=R-1}for(m=0;m<h;++m)"ON"===p[m]&&(p[m]=C);for(m=0;m<h;++m){I=p[m];i(w[m])?"R"===I?w[m]+=1:"AN"!==I&&"EN"!==I||(w[m]+=2):"L"!==I&&"AN"!==I&&"EN"!==I||(w[m]+=1)}var P,M=-1,E=99;for(m=0,b=w.length;m<b;++m){P=w[m];M<P&&(M=P);E>P&&r(P)&&(E=P)}for(P=M;P>=E;--P){var L=-1;for(m=0,b=w.length;m<b;++m)if(w[m]<P){if(L>=0){o(g,L,m);L=-1}}else L<0&&(L=m);L>=0&&o(g,L,w.length)}for(m=0,b=g.length;m<b;++m){var D=g[m];"<"!==D&&">"!==D||(g[m]="")}return c(g.join(""),l)}
+var h=a(0),u=h.warn,f=["BN","BN","BN","BN","BN","BN","BN","BN","BN","S","B","S","WS","B","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","B","B","B","S","WS","ON","ON","ET","ET","ET","ON","ON","ON","ON","ON","ES","CS","ES","CS","CS","EN","EN","EN","EN","EN","EN","EN","EN","EN","EN","CS","ON","ON","ON","ON","ON","ON","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","ON","ON","ON","ON","ON","ON","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","ON","ON","ON","ON","BN","BN","BN","BN","BN","BN","B","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","CS","ON","ET","ET","ET","ET","ON","ON","ON","ON","L","ON","ON","BN","ON","ON","ET","ET","EN","EN","ON","L","ON","ON","ON","EN","L","ON","ON","ON","ON","ON","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","ON","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","ON","L","L","L","L","L","L","L","L"],d=["AN","AN","AN","AN","AN","AN","ON","ON","AL","ET","ET","AL","CS","AL","ON","ON","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","AL","AL","","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","AN","AN","AN","AN","AN","AN","AN","AN","AN","AN","ET","AN","AN","AL","AL","AL","NSM","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","NSM","NSM","NSM","NSM","NSM","NSM","NSM","AN","ON","NSM","NSM","NSM","NSM","NSM","NSM","AL","AL","NSM","NSM","ON","NSM","NSM","NSM","NSM","AL","AL","EN","EN","EN","EN","EN","EN","EN","EN","EN","EN","AL","AL","AL","AL","AL","AL"],g=[],p=[];t.bidi=l},function(e,t,a){"use strict";var r=[".notdef","space","exclam","quotedbl","numbersign","dollar","percent","ampersand","quoteright","parenleft","parenright","asterisk","plus","comma","hyphen","period","slash","zero","one","two","three","four","five","six","seven","eight","nine","colon","semicolon","less","equal","greater","question","at","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","bracketleft","backslash","bracketright","asciicircum","underscore","quoteleft","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","braceleft","bar","braceright","asciitilde","exclamdown","cent","sterling","fraction","yen","florin","section","currency","quotesingle","quotedblleft","guillemotleft","guilsinglleft","guilsinglright","fi","fl","endash","dagger","daggerdbl","periodcentered","paragraph","bullet","quotesinglbase","quotedblbase","quotedblright","guillemotright","ellipsis","perthousand","questiondown","grave","acute","circumflex","tilde","macron","breve","dotaccent","dieresis","ring","cedilla","hungarumlaut","ogonek","caron","emdash","AE","ordfeminine","Lslash","Oslash","OE","ordmasculine","ae","dotlessi","lslash","oslash","oe","germandbls","onesuperior","logicalnot","mu","trademark","Eth","onehalf","plusminus","Thorn","onequarter","divide","brokenbar","degree","thorn","threequarters","twosuperior","registered","minus","eth","multiply","threesuperior","copyright","Aacute","Acircumflex","Adieresis","Agrave","Aring","Atilde","Ccedilla","Eacute","Ecircumflex","Edieresis","Egrave","Iacute","Icircumflex","Idieresis","Igrave","Ntilde","Oacute","Ocircumflex","Odieresis","Ograve","Otilde","Scaron","Uacute","Ucircumflex","Udieresis","Ugrave","Yacute","Ydieresis","Zcaron","aacute","acircumflex","adieresis","agrave","aring","atilde","ccedilla","eacute","ecircumflex","edieresis","egrave","iacute","icircumflex","idieresis","igrave","ntilde","oacute","ocircumflex","odieresis","ograve","otilde","scaron","uacute","ucircumflex","udieresis","ugrave","yacute","ydieresis","zcaron"],i=[".notdef","space","exclamsmall","Hungarumlautsmall","dollaroldstyle","dollarsuperior","ampersandsmall","Acutesmall","parenleftsuperior","parenrightsuperior","twodotenleader","onedotenleader","comma","hyphen","period","fraction","zerooldstyle","oneoldstyle","twooldstyle","threeoldstyle","fouroldstyle","fiveoldstyle","sixoldstyle","sevenoldstyle","eightoldstyle","nineoldstyle","colon","semicolon","commasuperior","threequartersemdash","periodsuperior","questionsmall","asuperior","bsuperior","centsuperior","dsuperior","esuperior","isuperior","lsuperior","msuperior","nsuperior","osuperior","rsuperior","ssuperior","tsuperior","ff","fi","fl","ffi","ffl","parenleftinferior","parenrightinferior","Circumflexsmall","hyphensuperior","Gravesmall","Asmall","Bsmall","Csmall","Dsmall","Esmall","Fsmall","Gsmall","Hsmall","Ismall","Jsmall","Ksmall","Lsmall","Msmall","Nsmall","Osmall","Psmall","Qsmall","Rsmall","Ssmall","Tsmall","Usmall","Vsmall","Wsmall","Xsmall","Ysmall","Zsmall","colonmonetary","onefitted","rupiah","Tildesmall","exclamdownsmall","centoldstyle","Lslashsmall","Scaronsmall","Zcaronsmall","Dieresissmall","Brevesmall","Caronsmall","Dotaccentsmall","Macronsmall","figuredash","hypheninferior","Ogoneksmall","Ringsmall","Cedillasmall","onequarter","onehalf","threequarters","questiondownsmall","oneeighth","threeeighths","fiveeighths","seveneighths","onethird","twothirds","zerosuperior","onesuperior","twosuperior","threesuperior","foursuperior","fivesuperior","sixsuperior","sevensuperior","eightsuperior","ninesuperior","zeroinferior","oneinferior","twoinferior","threeinferior","fourinferior","fiveinferior","sixinferior","seveninferior","eightinferior","nineinferior","centinferior","dollarinferior","periodinferior","commainferior","Agravesmall","Aacutesmall","Acircumflexsmall","Atildesmall","Adieresissmall","Aringsmall","AEsmall","Ccedillasmall","Egravesmall","Eacutesmall","Ecircumflexsmall","Edieresissmall","Igravesmall","Iacutesmall","Icircumflexsmall","Idieresissmall","Ethsmall","Ntildesmall","Ogravesmall","Oacutesmall","Ocircumflexsmall","Otildesmall","Odieresissmall","OEsmall","Oslashsmall","Ugravesmall","Uacutesmall","Ucircumflexsmall","Udieresissmall","Yacutesmall","Thornsmall","Ydieresissmall"],n=[".notdef","space","dollaroldstyle","dollarsuperior","parenleftsuperior","parenrightsuperior","twodotenleader","onedotenleader","comma","hyphen","period","fraction","zerooldstyle","oneoldstyle","twooldstyle","threeoldstyle","fouroldstyle","fiveoldstyle","sixoldstyle","sevenoldstyle","eightoldstyle","nineoldstyle","colon","semicolon","commasuperior","threequartersemdash","periodsuperior","asuperior","bsuperior","centsuperior","dsuperior","esuperior","isuperior","lsuperior","msuperior","nsuperior","osuperior","rsuperior","ssuperior","tsuperior","ff","fi","fl","ffi","ffl","parenleftinferior","parenrightinferior","hyphensuperior","colonmonetary","onefitted","rupiah","centoldstyle","figuredash","hypheninferior","onequarter","onehalf","threequarters","oneeighth","threeeighths","fiveeighths","seveneighths","onethird","twothirds","zerosuperior","onesuperior","twosuperior","threesuperior","foursuperior","fivesuperior","sixsuperior","sevensuperior","eightsuperior","ninesuperior","zeroinferior","oneinferior","twoinferior","threeinferior","fourinferior","fiveinferior","sixinferior","seveninferior","eightinferior","nineinferior","centinferior","dollarinferior","periodinferior","commainferior"];t.ISOAdobeCharset=r;t.ExpertCharset=i;t.ExpertSubsetCharset=n},function(e,t,a){"use strict";var r=a(0),i=a(1),n=a(2),s=a(5),o=r.Util,c=r.assert,l=r.warn,h=r.error,u=r.isInt,f=r.isString,d=r.MissingDataException,g=r.CMapCompressionType,p=i.isEOF,m=i.isName,b=i.isCmd,v=i.isStream,y=n.Stream,k=s.Lexer,w=["Adobe-GB1-UCS2","Adobe-CNS1-UCS2","Adobe-Japan1-UCS2","Adobe-Korea1-UCS2","78-EUC-H","78-EUC-V","78-H","78-RKSJ-H","78-RKSJ-V","78-V","78ms-RKSJ-H","78ms-RKSJ-V","83pv-RKSJ-H","90ms-RKSJ-H","90ms-RKSJ-V","90msp-RKSJ-H","90msp-RKSJ-V","90pv-RKSJ-H","90pv-RKSJ-V","Add-H","Add-RKSJ-H","Add-RKSJ-V","Add-V","Adobe-CNS1-0","Adobe-CNS1-1","Adobe-CNS1-2","Adobe-CNS1-3","Adobe-CNS1-4","Adobe-CNS1-5","Adobe-CNS1-6","Adobe-GB1-0","Adobe-GB1-1","Adobe-GB1-2","Adobe-GB1-3","Adobe-GB1-4","Adobe-GB1-5","Adobe-Japan1-0","Adobe-Japan1-1","Adobe-Japan1-2","Adobe-Japan1-3","Adobe-Japan1-4","Adobe-Japan1-5","Adobe-Japan1-6","Adobe-Korea1-0","Adobe-Korea1-1","Adobe-Korea1-2","B5-H","B5-V","B5pc-H","B5pc-V","CNS-EUC-H","CNS-EUC-V","CNS1-H","CNS1-V","CNS2-H","CNS2-V","ETHK-B5-H","ETHK-B5-V","ETen-B5-H","ETen-B5-V","ETenms-B5-H","ETenms-B5-V","EUC-H","EUC-V","Ext-H","Ext-RKSJ-H","Ext-RKSJ-V","Ext-V","GB-EUC-H","GB-EUC-V","GB-H","GB-V","GBK-EUC-H","GBK-EUC-V","GBK2K-H","GBK2K-V","GBKp-EUC-H","GBKp-EUC-V","GBT-EUC-H","GBT-EUC-V","GBT-H","GBT-V","GBTpc-EUC-H","GBTpc-EUC-V","GBpc-EUC-H","GBpc-EUC-V","H","HKdla-B5-H","HKdla-B5-V","HKdlb-B5-H","HKdlb-B5-V","HKgccs-B5-H","HKgccs-B5-V","HKm314-B5-H","HKm314-B5-V","HKm471-B5-H","HKm471-B5-V","HKscs-B5-H","HKscs-B5-V","Hankaku","Hiragana","KSC-EUC-H","KSC-EUC-V","KSC-H","KSC-Johab-H","KSC-Johab-V","KSC-V","KSCms-UHC-H","KSCms-UHC-HW-H","KSCms-UHC-HW-V","KSCms-UHC-V","KSCpc-EUC-H","KSCpc-EUC-V","Katakana","NWP-H","NWP-V","RKSJ-H","RKSJ-V","Roman","UniCNS-UCS2-H","UniCNS-UCS2-V","UniCNS-UTF16-H","UniCNS-UTF16-V","UniCNS-UTF32-H","UniCNS-UTF32-V","UniCNS-UTF8-H","UniCNS-UTF8-V","UniGB-UCS2-H","UniGB-UCS2-V","UniGB-UTF16-H","UniGB-UTF16-V","UniGB-UTF32-H","UniGB-UTF32-V","UniGB-UTF8-H","UniGB-UTF8-V","UniJIS-UCS2-H","UniJIS-UCS2-HW-H","UniJIS-UCS2-HW-V","UniJIS-UCS2-V","UniJIS-UTF16-H","UniJIS-UTF16-V","UniJIS-UTF32-H","UniJIS-UTF32-V","UniJIS-UTF8-H","UniJIS-UTF8-V","UniJIS2004-UTF16-H","UniJIS2004-UTF16-V","UniJIS2004-UTF32-H","UniJIS2004-UTF32-V","UniJIS2004-UTF8-H","UniJIS2004-UTF8-V","UniJISPro-UCS2-HW-V","UniJISPro-UCS2-V","UniJISPro-UTF8-V","UniJISX0213-UTF32-H","UniJISX0213-UTF32-V","UniJISX02132004-UTF32-H","UniJISX02132004-UTF32-V","UniKS-UCS2-H","UniKS-UCS2-V","UniKS-UTF16-H","UniKS-UTF16-V","UniKS-UTF32-H","UniKS-UTF32-V","UniKS-UTF8-H","UniKS-UTF8-V","V","WP-Symbol"],C=function(){function e(e){this.codespaceRanges=[[],[],[],[]];this.numCodespaceRanges=0;this._map=[];this.name="";this.vertical=!1;this.useCMap=null;this.builtInCMap=e}e.prototype={addCodespaceRange:function(e,t,a){this.codespaceRanges[e-1].push(t,a);this.numCodespaceRanges++},mapCidRange:function(e,t,a){for(;e<=t;)this._map[e++]=a++},mapBfRange:function(e,t,a){for(var r=a.length-1;e<=t;){this._map[e++]=a;a=a.substr(0,r)+String.fromCharCode(a.charCodeAt(r)+1)}},mapBfRangeToArray:function(e,t,a){for(var r=0,i=a.length;e<=t&&r<i;){this._map[e]=a[r++];++e}},mapOne:function(e,t){this._map[e]=t},lookup:function(e){return this._map[e]},contains:function(e){return void 0!==this._map[e]},forEach:function(e){var t,a=this._map,r=a.length;if(r<=65536)for(t=0;t<r;t++)void 0!==a[t]&&e(t,a[t]);else for(t in this._map)e(t,a[t])},charCodeOf:function(e){return this._map.indexOf(e)},getMap:function(){return this._map},readCharCode:function(e,t,a){for(var r=0,i=this.codespaceRanges,n=this.codespaceRanges.length,s=0;s<n;s++){r=(r<<8|e.charCodeAt(t+s))>>>0;for(var o=i[s],c=0,l=o.length;c<l;){var h=o[c++],u=o[c++];if(r>=h&&r<=u){a.charcode=r;a.length=s+1;return}}}a.charcode=0;a.length=1},get length(){return this._map.length},get isIdentityCMap(){if("Identity-H"!==this.name&&"Identity-V"!==this.name)return!1;if(65536!==this._map.length)return!1;for(var e=0;e<65536;e++)if(this._map[e]!==e)return!1;return!0}};return e}(),x=function(){function e(e,t){C.call(this);this.vertical=e;this.addCodespaceRange(t,0,65535)}o.inherit(e,C,{});e.prototype={addCodespaceRange:C.prototype.addCodespaceRange,mapCidRange:function(e,t,a){h("should not call mapCidRange")},mapBfRange:function(e,t,a){h("should not call mapBfRange")},mapBfRangeToArray:function(e,t,a){h("should not call mapBfRangeToArray")},mapOne:function(e,t){h("should not call mapCidOne")},lookup:function(e){return u(e)&&e<=65535?e:void 0},contains:function(e){return u(e)&&e<=65535},forEach:function(e){for(var t=0;t<=65535;t++)e(t,t)},charCodeOf:function(e){return u(e)&&e<=65535?e:-1},getMap:function(){for(var e=new Array(65536),t=0;t<=65535;t++)e[t]=t;return e},readCharCode:C.prototype.readCharCode,get length(){return 65536},get isIdentityCMap(){h("should not access .isIdentityCMap")}};return e}(),S=function(){function e(e,t){for(var a=0,r=0;r<=t;r++)a=a<<8|e[r];return a>>>0}function t(e,t){return 1===t?String.fromCharCode(e[0],e[1]):3===t?String.fromCharCode(e[0],e[1],e[2],e[3]):String.fromCharCode.apply(null,e.subarray(0,t+1))}function a(e,t,a){for(var r=0,i=a;i>=0;i--){r+=e[i]+t[i];e[i]=255&r;r>>=8}}function r(e,t){for(var a=1,r=t;r>=0&&a>0;r--){a+=e[r];e[r]=255&a;a>>=8}}function i(e){this.buffer=e;this.pos=0;this.end=e.length;this.tmpBuf=new Uint8Array(l)}function n(n,s,l){return new Promise(function(h,u){var f=new i(n),d=f.readByte();s.vertical=!!(1&d);for(var g,p,m=null,b=new Uint8Array(o),v=new Uint8Array(o),y=new Uint8Array(o),k=new Uint8Array(o),w=new Uint8Array(o);(p=f.readByte())>=0;){var C=p>>5;if(7!==C){var x=!!(16&p),S=15&p;c(S+1<=o);var A,I=f.readNumber();switch(C){case 0:f.readHex(b,S);f.readHexNumber(v,S);a(v,b,S);s.addCodespaceRange(S+1,e(b,S),e(v,S));for(A=1;A<I;A++){r(v,S);f.readHexNumber(b,S);a(b,v,S);f.readHexNumber(v,S);a(v,b,S);s.addCodespaceRange(S+1,e(b,S),e(v,S))}break;case 1:f.readHex(b,S);f.readHexNumber(v,S);a(v,b,S);g=f.readNumber();for(A=1;A<I;A++){r(v,S);f.readHexNumber(b,S);a(b,v,S);f.readHexNumber(v,S);a(v,b,S);g=f.readNumber()}break;case 2:f.readHex(y,S);g=f.readNumber();s.mapOne(e(y,S),g);for(A=1;A<I;A++){r(y,S);if(!x){f.readHexNumber(w,S);a(y,w,S)}g=f.readSigned()+(g+1);s.mapOne(e(y,S),g)}break;case 3:f.readHex(b,S);f.readHexNumber(v,S);a(v,b,S);g=f.readNumber();s.mapCidRange(e(b,S),e(v,S),g);for(A=1;A<I;A++){r(v,S);if(x)b.set(v);else{f.readHexNumber(b,S);a(b,v,S)}f.readHexNumber(v,S);a(v,b,S);g=f.readNumber();s.mapCidRange(e(b,S),e(v,S),g)}break;case 4:f.readHex(y,1);f.readHex(k,S);s.mapOne(e(y,1),t(k,S));for(A=1;A<I;A++){r(y,1);if(!x){f.readHexNumber(w,1);a(y,w,1)}r(k,S);f.readHexSigned(w,S);a(k,w,S);s.mapOne(e(y,1),t(k,S))}break;case 5:f.readHex(b,1);f.readHexNumber(v,1);a(v,b,1);f.readHex(k,S);s.mapBfRange(e(b,1),e(v,1),t(k,S));for(A=1;A<I;A++){r(v,1);if(x)b.set(v);else{f.readHexNumber(b,1);a(b,v,1)}f.readHexNumber(v,1);a(v,b,1);f.readHex(k,S);s.mapBfRange(e(b,1),e(v,1),t(k,S))}break;default:u(new Error("processBinaryCMap: Unknown type: "+C));return}}else switch(31&p){case 0:f.readString();break;case 1:m=f.readString()}}h(m?l(m):s)})}function s(){}var o=16,l=19;i.prototype={readByte:function(){return this.pos>=this.end?-1:this.buffer[this.pos++]},readNumber:function(){var e,t=0;do{var a=this.readByte();a<0&&h("unexpected EOF in bcmap");e=!(128&a);t=t<<7|127&a}while(!e);return t},readSigned:function(){var e=this.readNumber();return 1&e?~(e>>>1):e>>>1},readHex:function(e,t){e.set(this.buffer.subarray(this.pos,this.pos+t+1));this.pos+=t+1},readHexNumber:function(e,t){var a,r=this.tmpBuf,i=0;do{var n=this.readByte();n<0&&h("unexpected EOF in bcmap");a=!(128&n);r[i++]=127&n}while(!a);for(var s=t,o=0,c=0;s>=0;){for(;c<8&&r.length>0;){o=r[--i]<<c|o;c+=7}e[s]=255&o;s--;o>>=8;c-=8}},readHexSigned:function(e,t){this.readHexNumber(e,t);for(var a=1&e[t]?255:0,r=0,i=0;i<=t;i++){r=(1&r)<<8|e[i];e[i]=r>>1^a}},readString:function(){for(var e=this.readNumber(),t="",a=0;a<e;a++)t+=String.fromCharCode(this.readNumber());return t}};s.prototype={process:n};return s}(),A=function(){function e(e){for(var t=0,a=0;a<e.length;a++)t=t<<8|e.charCodeAt(a);return t>>>0}function t(e){f(e)||h("Malformed CMap: expected string.")}function a(e){u(e)||h("Malformed CMap: expected int.")}function r(a,r){for(;;){var i=r.getObj();if(p(i))break;if(b(i,"endbfchar"))return;t(i);var n=e(i);i=r.getObj();t(i);var s=i;a.mapOne(n,s)}}function i(a,r){for(;;){var i=r.getObj();if(p(i))break;if(b(i,"endbfrange"))return;t(i);var n=e(i);i=r.getObj();t(i);var s=e(i);i=r.getObj();if(u(i)||f(i)){var o=u(i)?String.fromCharCode(i):i;a.mapBfRange(n,s,o)}else{if(!b(i,"["))break;i=r.getObj();for(var c=[];!b(i,"]")&&!p(i);){c.push(i);i=r.getObj()}a.mapBfRangeToArray(n,s,c)}}h("Invalid bf range.")}function n(r,i){for(;;){var n=i.getObj();if(p(n))break;if(b(n,"endcidchar"))return;t(n);var s=e(n);n=i.getObj();a(n);var o=n;r.mapOne(s,o)}}function s(r,i){for(;;){var n=i.getObj();if(p(n))break;if(b(n,"endcidrange"))return;t(n);var s=e(n);n=i.getObj();t(n);var o=e(n);n=i.getObj();a(n);var c=n;r.mapCidRange(s,o,c)}}function o(t,a){for(;;){var r=a.getObj();if(p(r))break;if(b(r,"endcodespacerange"))return;if(!f(r))break;var i=e(r);r=a.getObj();if(!f(r))break;var n=e(r);t.addCodespaceRange(r.length,i,n)}h("Invalid codespace range.")}function A(e,t){var a=t.getObj();u(a)&&(e.vertical=!!a)}function I(e,t){var a=t.getObj();m(a)&&f(a.name)&&(e.name=a.name)}function B(e,t,a,c){var h,u;e:for(;;)try{var f=t.getObj();if(p(f))break;if(m(f)){"WMode"===f.name?A(e,t):"CMapName"===f.name&&I(e,t);h=f}else if(b(f))switch(f.cmd){case"endcmap":break e;case"usecmap":m(h)&&(u=h.name);break;case"begincodespacerange":o(e,t);break;case"beginbfchar":r(e,t);break;case"begincidchar":n(e,t);break;case"beginbfrange":i(e,t);break;case"begincidrange":s(e,t)}}catch(e){if(e instanceof d)throw e;l("Invalid cMap data: "+e);continue}!c&&u&&(c=u);return c?R(e,a,c):Promise.resolve(e)}function R(e,t,a){return T(a,t).then(function(t){e.useCMap=t;if(0===e.numCodespaceRanges){for(var a=e.useCMap.codespaceRanges,r=0;r<a.length;r++)e.codespaceRanges[r]=a[r].slice();e.numCodespaceRanges=e.useCMap.numCodespaceRanges}e.useCMap.forEach(function(t,a){e.contains(t)||e.mapOne(t,e.useCMap.lookup(t))});return e})}function T(e,t){if("Identity-H"===e)return Promise.resolve(new x(!1,2));if("Identity-V"===e)return Promise.resolve(new x(!0,2));if(-1===w.indexOf(e))return Promise.reject(new Error("Unknown CMap name: "+e));c(t,"Built-in CMap parameters are not provided.");return t(e).then(function(e){var a=e.cMapData,r=e.compressionType,i=new C(!0);if(r===g.BINARY)return(new S).process(a,i,function(e){return R(i,t,e)});c(r===g.NONE,"TODO: Only BINARY/NONE CMap compression is currently supported.");var n=new k(new y(a));return B(i,n,t,null)})}return{create:function(e){var t=e.encoding,a=e.fetchBuiltInCMap,r=e.useCMap;if(m(t))return T(t.name,a);if(v(t)){return B(new C,new k(t),a,r).then(function(e){return e.isIdentityCMap?T(e.name,a):e})}return Promise.reject(new Error("Encoding required."))}}}();t.CMap=C;t.CMapFactory=A;t.IdentityCMap=x},function(e,t,a){"use strict";var r=a(0),i=a(1),n=a(2),s=a(16),o=a(5),c=a(13),l=a(14),h=a(20),u=r.OPS,f=r.MissingDataException,d=r.Util,g=r.assert,p=r.error,m=r.info,b=r.isArray,v=r.isArrayBuffer,y=r.isNum,k=r.isString,w=r.shadow,C=r.stringToBytes,x=r.stringToPDFString,S=r.warn,A=r.isSpace,I=i.Dict,B=i.isDict,R=i.isName,T=i.isStream,O=n.NullStream,P=n.Stream,M=n.StreamsSequenceStream,E=s.Catalog,L=s.ObjectLoader,D=s.XRef,F=o.Linearization,q=c.calculateMD5,U=l.OperatorList,N=l.PartialEvaluator,j=h.AnnotationFactory,_=function(){function e(e,t){return"display"===t&&e.viewable||"print"===t&&e.printable}function t(e,t,a,r,i,n,s){this.pdfManager=e;this.pageIndex=a;this.pageDict=r;this.xref=t;this.ref=i;this.fontCache=n;this.builtInCMapCache=s;this.evaluatorOptions=e.evaluatorOptions;this.resourcesPromise=null;var o="p"+this.pageIndex+"_",c={obj:0};this.idFactory={createObjId:function(){return o+ ++c.obj}}}var a=[0,0,612,792];t.prototype={getPageProp:function(e){return this.pageDict.get(e)},getInheritedPageProp:function(e,t){var a=this.pageDict,r=null,i=0;t=t||!1;for(;a;){var n=t?a.getArray(e):a.get(e);if(void 0!==n){r||(r=[]);r.push(n)}if(++i>100){S("getInheritedPageProp: maximum loop count exceeded for "+e);return r?r[0]:void 0}a=a.get("Parent")}if(r)return 1!==r.length&&B(r[0])?I.merge(this.xref,r):r[0]},get content(){return this.getPageProp("Contents")},get resources(){return w(this,"resources",this.getInheritedPageProp("Resources")||I.empty)},get mediaBox(){var e=this.getInheritedPageProp("MediaBox",!0);return b(e)&&4===e.length?w(this,"mediaBox",e):w(this,"mediaBox",a)},get cropBox(){var e=this.getInheritedPageProp("CropBox",!0);return b(e)&&4===e.length?w(this,"cropBox",e):w(this,"cropBox",this.mediaBox)},get userUnit(){var e=this.getPageProp("UserUnit");(!y(e)||e<=0)&&(e=1);return w(this,"userUnit",e)},get view(){var e=this.mediaBox,t=this.cropBox;if(e===t)return w(this,"view",e);var a=d.intersect(t,e);return w(this,"view",a||e)},get rotate(){var e=this.getInheritedPageProp("Rotate")||0;e%90!=0?e=0:e>=360?e%=360:e<0&&(e=(e%360+360)%360);return w(this,"rotate",e)},getContentStream:function(){var e,t=this.content;if(b(t)){var a,r=this.xref,i=t.length,n=[];for(a=0;a<i;++a)n.push(r.fetchIfRef(t[a]));e=new M(n)}else e=T(t)?t:new O;return e},loadResources:function(e){this.resourcesPromise||(this.resourcesPromise=this.pdfManager.ensure(this,"resources"));return this.resourcesPromise.then(function(){return new L(this.resources.map,e,this.xref).load()}.bind(this))},getOperatorList:function(t,a,r,i){var n=this,s=this.pdfManager,o=s.ensure(this,"getContentStream",[]),c=this.loadResources(["ExtGState","ColorSpace","Pattern","Shading","XObject","Font"]),l=new N(s,this.xref,t,this.pageIndex,this.idFactory,this.fontCache,this.builtInCMapCache,this.evaluatorOptions),h=Promise.all([o,c]),f=h.then(function(e){var i=e[0],s=new U(r,t,n.pageIndex);t.send("StartRenderPage",{transparency:l.hasBlendModes(n.resources),pageIndex:n.pageIndex,intent:r});return l.getOperatorList(i,a,n.resources,s).then(function(){return s})}),d=s.ensure(this,"annotations");return Promise.all([f,d]).then(function(t){var n=t[0],s=t[1];if(0===s.length){n.flush(!0);return n}var o,c,h=[];for(o=0,c=s.length;o<c;o++)e(s[o],r)&&h.push(s[o].getOperatorList(l,a,i));return Promise.all(h).then(function(e){n.addOp(u.beginAnnotations,[]);for(o=0,c=e.length;o<c;o++)n.addOpList(e[o]);n.addOp(u.endAnnotations,[]);n.flush(!0);return n})})},extractTextContent:function(e,t,a,r){var i=this,n=this.pdfManager,s=n.ensure(this,"getContentStream",[]),o=this.loadResources(["ExtGState","XObject","Font"]);return Promise.all([s,o]).then(function(s){var o=s[0];return new N(n,i.xref,e,i.pageIndex,i.idFactory,i.fontCache,i.builtInCMapCache,i.evaluatorOptions).getTextContent(o,t,i.resources,null,a,r)})},getAnnotationsData:function(t){for(var a=this.annotations,r=[],i=0,n=a.length;i<n;++i)t&&!e(a[i],t)||r.push(a[i].data);return r},get annotations(){for(var e=[],t=this.getInheritedPageProp("Annots")||[],a=new j,r=0,i=t.length;r<i;++r){var n=t[r],s=a.create(this.xref,n,this.pdfManager,this.idFactory);s&&e.push(s)}return w(this,"annotations",e)}};return t}(),z=function(){function e(e,t){var a;T(t)?a=t:v(t)?a=new P(t):p("PDFDocument: Unknown argument type");g(a.length>0,"stream must have data");this.pdfManager=e;this.stream=a;this.xref=new D(a,e)}function t(e,t,a,r){var i=e.pos,n=e.end,s=[];i+a>n&&(a=n-i);for(var o=0;o<a;++o)s.push(String.fromCharCode(e.getByte()));var c=s.join("");e.pos=i;var l=r?c.lastIndexOf(t):c.indexOf(t);if(-1===l)return!1;e.pos+=l;return!0}var a={get entries(){return w(this,"entries",{Title:k,Author:k,Subject:k,Keywords:k,Creator:k,Producer:k,CreationDate:k,ModDate:k,Trapped:R})}};e.prototype={parse:function(e){this.setup(e);var t=this.catalog.catDict.get("Version");R(t)&&(this.pdfFormatVersion=t.name);try{this.acroForm=this.catalog.catDict.get("AcroForm");if(this.acroForm){this.xfa=this.acroForm.get("XFA");var a=this.acroForm.get("Fields");a&&b(a)&&0!==a.length||this.xfa||(this.acroForm=null)}}catch(e){if(e instanceof f)throw e;m("Something wrong with AcroForm entry");this.acroForm=null}},get linearization(){var e=null;if(this.stream.length)try{e=F.create(this.stream)}catch(e){if(e instanceof f)throw e;m(e)}return w(this,"linearization",e)},get startXRef(){var e=this.stream,a=0;if(this.linearization){e.reset();t(e,"endobj",1024)&&(a=e.pos+6)}else{for(var r=!1,i=e.end;!r&&i>0;){i-=1024-"startxref".length;i<0&&(i=0);e.pos=i;r=t(e,"startxref",1024,!0)}if(r){e.skip(9);var n;do{n=e.getByte()}while(A(n));for(var s="";n>=32&&n<=57;){s+=String.fromCharCode(n);n=e.getByte()}a=parseInt(s,10);isNaN(a)&&(a=0)}}return w(this,"startXRef",a)},get mainXRefEntriesOffset(){var e=0,t=this.linearization;t&&(e=t.mainXRefEntriesOffset);return w(this,"mainXRefEntriesOffset",e)},checkHeader:function(){var e=this.stream;e.reset();if(t(e,"%PDF-",1024)){e.moveStart();for(var a,r="";(a=e.getByte())>32&&!(r.length>=12);)r+=String.fromCharCode(a);this.pdfFormatVersion||(this.pdfFormatVersion=r.substring(5))}else;},parseStartXRef:function(){var e=this.startXRef;this.xref.setStartXRef(e)},setup:function(e){this.xref.parse(e);var t=this,a={createPage:function(e,a,r,i,n){return new _(t.pdfManager,t.xref,e,a,r,i,n)}};this.catalog=new E(this.pdfManager,this.xref,a)},get numPages(){var e=this.linearization,t=e?e.numPages:this.catalog.numPages;return w(this,"numPages",t)},get documentInfo(){var e,t={PDFFormatVersion:this.pdfFormatVersion,IsAcroFormPresent:!!this.acroForm,IsXFAPresent:!!this.xfa};try{e=this.xref.trailer.get("Info")}catch(e){if(e instanceof f)throw e;m("The document information dictionary is invalid.")}if(e){var r=a.entries;for(var i in r)if(e.has(i)){var n=e.get(i);r[i](n)?t[i]="string"!=typeof n?n:x(n):m('Bad value in document info for "'+i+'"')}}return w(this,"documentInfo",t)},get fingerprint(){var e,t=this.xref,a="",r=t.trailer.get("ID");if(r&&b(r)&&r[0]&&k(r[0])&&"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"!==r[0])e=C(r[0]);else{this.stream.ensureRange&&this.stream.ensureRange(0,Math.min(1024,this.stream.end));e=q(this.stream.bytes.subarray(0,1024),0,1024)}for(var i=0,n=e.length;i<n;i++){var s=e[i].toString(16);a+=1===s.length?"0"+s:s}return w(this,"fingerprint",a)},getPage:function(e){return this.catalog.getPage(e)},cleanup:function(){return this.catalog.cleanup()}};return e}();t.Page=_;t.PDFDocument=z},function(e,t,a){"use strict";var r=a(0),i=a(2),n=a(7),s=a(4),o=a(11),c=r.Util,l=r.bytesToString,h=r.error,u=i.Stream,f=n.getGlyphsUnicode,d=s.StandardEncoding,g=o.CFFParser,p=function(){function e(e,t){return e[t]<<24|e[t+1]<<16|e[t+2]<<8|e[t+3]}function t(e,t){return e[t]<<8|e[t+1]}function a(a,r,i){var n,s,o,c=1===t(a,r+2)?e(a,r+8):e(a,r+16),l=t(a,r+c);if(4===l){t(a,r+c+2);var u=t(a,r+c+6)>>1;s=r+c+14;n=[];for(o=0;o<u;o++,s+=2)n[o]={end:t(a,s)};s+=2;for(o=0;o<u;o++,s+=2)n[o].start=t(a,s);for(o=0;o<u;o++,s+=2)n[o].idDelta=t(a,s);for(o=0;o<u;o++,s+=2){var f=t(a,s);if(0!==f){n[o].ids=[];for(var d=0,g=n[o].end-n[o].start+1;d<g;d++){n[o].ids[d]=t(a,s+f);f+=2}}}return n}if(12===l){e(a,r+c+4);var p=e(a,r+c+12);s=r+c+16;n=[];for(o=0;o<p;o++){n.push({start:e(a,s),end:e(a,s+4),idDelta:e(a,s+8)-e(a,s)});s+=12}return n}h("not supported cmap: "+l)}function r(e,t,a,r){var i={},n=new g(new u(e,t,a-t),i,r),s=n.parse();return{glyphs:s.charStrings.objects,subrs:s.topDict.privateDict&&s.topDict.privateDict.subrsIndex&&s.topDict.privateDict.subrsIndex.objects,gsubrs:s.globalSubrIndex&&s.globalSubrIndex.objects}}function i(e,t,a){var r,i;if(a){r=4;i=function(e,t){return e[t]<<24|e[t+1]<<16|e[t+2]<<8|e[t+3]}}else{r=2;i=function(e,t){return e[t]<<9|e[t+1]<<1}}for(var n=[],s=i(t,0),o=r;o<t.length;o+=r){var c=i(t,o);n.push(e.subarray(s,c));s=c}return n}function n(e,t){for(var a=t.charCodeAt(0),r=0,i=0,n=e.length-1;i<n;){var s=i+n+1>>1;a<e[s].start?n=s-1:i=s}e[i].start<=a&&a<=e[i].end&&(r=e[i].idDelta+(e[i].ids?e[i].ids[a-e[i].start]:a)&65535);return{charCode:a,glyphId:r}}function s(e,t,a){function r(e,a,r,i){t.push({cmd:"quadraticCurveTo",args:[e,a,r,i]})}var i,n=0,o=(e[n]<<24|e[n+1]<<16)>>16,c=0,l=0;n+=10;if(o<0)do{i=e[n]<<8|e[n+1];var h=e[n+2]<<8|e[n+3];n+=4;var u,f;if(1&i){u=(e[n]<<24|e[n+1]<<16)>>16;f=(e[n+2]<<24|e[n+3]<<16)>>16;n+=4}else{u=e[n++];f=e[n++]}if(2&i){c=u;l=f}else{c=0;l=0}var d=1,g=1,p=0,m=0;if(8&i){d=g=(e[n]<<24|e[n+1]<<16)/1073741824;n+=2}else if(64&i){d=(e[n]<<24|e[n+1]<<16)/1073741824;g=(e[n+2]<<24|e[n+3]<<16)/1073741824;n+=4}else if(128&i){d=(e[n]<<24|e[n+1]<<16)/1073741824;p=(e[n+2]<<24|e[n+3]<<16)/1073741824;m=(e[n+4]<<24|e[n+5]<<16)/1073741824;g=(e[n+6]<<24|e[n+7]<<16)/1073741824;n+=8}var b=a.glyphs[h];if(b){t.push({cmd:"save"});t.push({cmd:"transform",args:[d,p,m,g,c,l]});s(b,t,a);t.push({cmd:"restore"})}}while(32&i);else{var v,y,k=[];for(v=0;v<o;v++){k.push(e[n]<<8|e[n+1]);n+=2}n+=2+(e[n]<<8|e[n+1]);for(var w=k[k.length-1]+1,C=[];C.length<w;){i=e[n++];var x=1;8&i&&(x+=e[n++]);for(;x-- >0;)C.push({flags:i})}for(v=0;v<w;v++){switch(18&C[v].flags){case 0:c+=(e[n]<<24|e[n+1]<<16)>>16;n+=2;break;case 2:c-=e[n++];break;case 18:c+=e[n++]}C[v].x=c}for(v=0;v<w;v++){switch(36&C[v].flags){case 0:l+=(e[n]<<24|e[n+1]<<16)>>16;n+=2;break;case 4:l-=e[n++];break;case 36:l+=e[n++]}C[v].y=l}var S=0;for(n=0;n<o;n++){var A=k[n],I=C.slice(S,A+1);if(1&I[0].flags)I.push(I[0]);else if(1&I[I.length-1].flags)I.unshift(I[I.length-1]);else{var B={flags:1,x:(I[0].x+I[I.length-1].x)/2,y:(I[0].y+I[I.length-1].y)/2};I.unshift(B);I.push(B)}!function(e,a){t.push({cmd:"moveTo",args:[e,a]})}(I[0].x,I[0].y);for(v=1,y=I.length;v<y;v++)if(1&I[v].flags)!function(e,a){t.push({cmd:"lineTo",args:[e,a]})}(I[v].x,I[v].y);else if(1&I[v+1].flags){r(I[v].x,I[v].y,I[v+1].x,I[v+1].y);v++}else r(I[v].x,I[v].y,(I[v].x+I[v+1].x)/2,(I[v].y+I[v+1].y)/2);S=A+1}}}function o(e,t,a){function r(e,a){t.push({cmd:"moveTo",args:[e,a]})}function i(e,a){t.push({cmd:"lineTo",args:[e,a]})}function s(e,a,r,i,n,s){t.push({cmd:"bezierCurveTo",args:[e,a,r,i,n,s]})}function c(e){for(var p=0;p<e.length;){var m,b,v,y,k,w,C,x,S,A=!1,I=e[p++];switch(I){case 1:case 3:g+=l.length>>1;A=!0;break;case 4:f+=l.pop();r(u,f);A=!0;break;case 5:for(;l.length>0;){u+=l.shift();f+=l.shift();i(u,f)}break;case 6:for(;l.length>0;){u+=l.shift();i(u,f);if(0===l.length)break;f+=l.shift();i(u,f)}break;case 7:for(;l.length>0;){f+=l.shift();i(u,f);if(0===l.length)break;u+=l.shift();i(u,f)}break;case 8:for(;l.length>0;){m=u+l.shift();v=f+l.shift();b=m+l.shift();y=v+l.shift();u=b+l.shift();f=y+l.shift();s(m,v,b,y,u,f)}break;case 10:x=l.pop()+a.subrsBias;S=a.subrs[x];S&&c(S);break;case 11:return;case 12:I=e[p++];switch(I){case 34:m=u+l.shift();b=m+l.shift();k=f+l.shift();u=b+l.shift();s(m,f,b,k,u,k);m=u+l.shift();b=m+l.shift();u=b+l.shift();s(m,k,b,f,u,f);break;case 35:m=u+l.shift();v=f+l.shift();b=m+l.shift();y=v+l.shift();u=b+l.shift();f=y+l.shift();s(m,v,b,y,u,f);m=u+l.shift();v=f+l.shift();b=m+l.shift();y=v+l.shift();u=b+l.shift();f=y+l.shift();s(m,v,b,y,u,f);l.pop();break;case 36:m=u+l.shift();k=f+l.shift();b=m+l.shift();w=k+l.shift();u=b+l.shift();s(m,k,b,w,u,w);m=u+l.shift();b=m+l.shift();C=w+l.shift();u=b+l.shift();s(m,w,b,C,u,f);break;case 37:var B=u,R=f;m=u+l.shift();v=f+l.shift();b=m+l.shift();y=v+l.shift();u=b+l.shift();f=y+l.shift();s(m,v,b,y,u,f);m=u+l.shift();v=f+l.shift();b=m+l.shift();y=v+l.shift();u=b;f=y;Math.abs(u-B)>Math.abs(f-R)?u+=l.shift():f+=l.shift();s(m,v,b,y,u,f);break;default:h("unknown operator: 12 "+I)}break;case 14:if(l.length>=4){var T=l.pop(),O=l.pop();f=l.pop();u=l.pop();t.push({cmd:"save"});t.push({cmd:"translate",args:[u,f]});var P=n(a.cmap,String.fromCharCode(a.glyphNameMap[d[T]]));o(a.glyphs[P.glyphId],t,a);t.push({cmd:"restore"});P=n(a.cmap,String.fromCharCode(a.glyphNameMap[d[O]]));o(a.glyphs[P.glyphId],t,a)}return;case 18:g+=l.length>>1;A=!0;break;case 19:case 20:g+=l.length>>1;p+=g+7>>3;A=!0;break;case 21:f+=l.pop();u+=l.pop();r(u,f);A=!0;break;case 22:u+=l.pop()
+;r(u,f);A=!0;break;case 23:g+=l.length>>1;A=!0;break;case 24:for(;l.length>2;){m=u+l.shift();v=f+l.shift();b=m+l.shift();y=v+l.shift();u=b+l.shift();f=y+l.shift();s(m,v,b,y,u,f)}u+=l.shift();f+=l.shift();i(u,f);break;case 25:for(;l.length>6;){u+=l.shift();f+=l.shift();i(u,f)}m=u+l.shift();v=f+l.shift();b=m+l.shift();y=v+l.shift();u=b+l.shift();f=y+l.shift();s(m,v,b,y,u,f);break;case 26:l.length%2&&(u+=l.shift());for(;l.length>0;){m=u;v=f+l.shift();b=m+l.shift();y=v+l.shift();u=b;f=y+l.shift();s(m,v,b,y,u,f)}break;case 27:l.length%2&&(f+=l.shift());for(;l.length>0;){m=u+l.shift();v=f;b=m+l.shift();y=v+l.shift();u=b+l.shift();f=y;s(m,v,b,y,u,f)}break;case 28:l.push((e[p]<<24|e[p+1]<<16)>>16);p+=2;break;case 29:x=l.pop()+a.gsubrsBias;S=a.gsubrs[x];S&&c(S);break;case 30:for(;l.length>0;){m=u;v=f+l.shift();b=m+l.shift();y=v+l.shift();u=b+l.shift();f=y+(1===l.length?l.shift():0);s(m,v,b,y,u,f);if(0===l.length)break;m=u+l.shift();v=f;b=m+l.shift();y=v+l.shift();f=y+l.shift();u=b+(1===l.length?l.shift():0);s(m,v,b,y,u,f)}break;case 31:for(;l.length>0;){m=u+l.shift();v=f;b=m+l.shift();y=v+l.shift();f=y+l.shift();u=b+(1===l.length?l.shift():0);s(m,v,b,y,u,f);if(0===l.length)break;m=u;v=f+l.shift();b=m+l.shift();y=v+l.shift();u=b+l.shift();f=y+(1===l.length?l.shift():0);s(m,v,b,y,u,f)}break;default:I<32&&h("unknown operator: "+I);if(I<247)l.push(I-139);else if(I<251)l.push(256*(I-247)+e[p++]+108);else if(I<255)l.push(256*-(I-251)-e[p++]-108);else{l.push((e[p]<<24|e[p+1]<<16|e[p+2]<<8|e[p+3])/65536);p+=4}}A&&(l.length=0)}}var l=[],u=0,f=0,g=0;c(e)}function p(e){this.compiledGlyphs=Object.create(null);this.compiledCharCodeToGlyphId=Object.create(null);this.fontMatrix=e}function m(e,t,a){a=a||[488e-6,0,0,488e-6,0,0];p.call(this,a);this.glyphs=e;this.cmap=t}function b(e,t,a,r){a=a||[.001,0,0,.001,0,0];p.call(this,a);this.glyphs=e.glyphs;this.gsubrs=e.gsubrs||[];this.subrs=e.subrs||[];this.cmap=t;this.glyphNameMap=r||f();this.gsubrsBias=this.gsubrs.length<1240?107:this.gsubrs.length<33900?1131:32768;this.subrsBias=this.subrs.length<1240?107:this.subrs.length<33900?1131:32768}p.prototype={getPathJs:function(e){var t=n(this.cmap,e),a=this.compiledGlyphs[t.glyphId];if(!a){a=this.compileGlyph(this.glyphs[t.glyphId]);this.compiledGlyphs[t.glyphId]=a}void 0===this.compiledCharCodeToGlyphId[t.charCode]&&(this.compiledCharCodeToGlyphId[t.charCode]=t.glyphId);return a},compileGlyph:function(e){if(!e||0===e.length||14===e[0])return"";var t=[];t.push({cmd:"save"});t.push({cmd:"transform",args:this.fontMatrix.slice()});t.push({cmd:"scale",args:["size","-size"]});this.compileGlyphImpl(e,t);t.push({cmd:"restore"});return t},compileGlyphImpl:function(){h("Children classes should implement this.")},hasBuiltPath:function(e){var t=n(this.cmap,e);return void 0!==this.compiledGlyphs[t.glyphId]&&void 0!==this.compiledCharCodeToGlyphId[t.charCode]}};c.inherit(m,p,{compileGlyphImpl:function(e,t){s(e,t,this)}});c.inherit(b,p,{compileGlyphImpl:function(e,t){o(e,t,this)}});return{create:function(n,s){for(var o,c,h,u,f,d,g=new Uint8Array(n.data),p=t(g,4),v=0,y=12;v<p;v++,y+=16){var k=l(g.subarray(y,y+4)),w=e(g,y+8),C=e(g,y+12);switch(k){case"cmap":o=a(g,w,w+C);break;case"glyf":c=g.subarray(w,w+C);break;case"loca":h=g.subarray(w,w+C);break;case"head":d=t(g,w+18);f=t(g,w+50);break;case"CFF ":u=r(g,w,w+C,s)}}if(c){var x=d?[1/d,0,0,1/d,0,0]:n.fontMatrix;return new m(i(c,h,f),o,x)}return new b(u,o,n.fontMatrix,n.glyphNameMap)}}}();t.FontRendererFactory=p},function(e,t,a){"use strict";function r(e){if(e.fontMatrix&&e.fontMatrix[0]!==b[0]){var t=.001/e.fontMatrix[0],a=e.widths;for(var r in a)a[r]*=t;e.defaultWidth*=t}}function i(e,t){if(!e.hasIncludedToUnicodeMap&&!(e.hasEncoding||t===e.defaultEncoding||e.toUnicode instanceof ge)){var a=[],r=E();for(var i in t){var n=t[i],s=W(n,r);-1!==s&&(a[i]=String.fromCharCode(s))}e.toUnicode.amend(a)}}function n(e,t){switch(e){case"Type1":return"Type1C"===t?v.TYPE1C:v.TYPE1;case"CIDFontType0":return"CIDFontType0C"===t?v.CIDFONTTYPE0C:v.CIDFONTTYPE0;case"OpenType":return v.OPENTYPE;case"TrueType":return v.TRUETYPE;case"CIDFontType2":return v.CIDFONTTYPE2;case"MMType1":return v.MMTYPE1;case"Type0":return v.TYPE0;default:return v.UNKNOWN}}function s(e,t){if(void 0!==t[e])return e;var a=W(e,t);if(-1!==a)for(var r in t)if(t[r]===a)return r;C("Unable to recover a standard glyph name for: "+e);return e}function o(e,t,a){var r,i,n,o=Object.create(null),c=!!(e.flags&he.Symbolic);if(e.baseEncodingName){n=j(e.baseEncodingName);for(i=0;i<n.length;i++){r=a.indexOf(n[i]);o[i]=r>=0?r:0}}else if(c)for(i in t)o[i]=t[i];else{n=F;for(i=0;i<n.length;i++){r=a.indexOf(n[i]);o[i]=r>=0?r:0}}var l,h=e.differences;if(h)for(i in h){var u=h[i];r=a.indexOf(u);if(-1===r){l||(l=E());var f=s(u,l);f!==u&&(r=a.indexOf(f))}o[i]=r>=0?r:0}return o}var c=a(0),l=(a(1),a(2)),h=a(7),u=a(25),f=a(4),d=a(17),g=a(18),p=a(35),m=a(11),b=c.FONT_IDENTITY_MATRIX,v=c.FontType,y=c.assert,k=c.bytesToString,w=c.error,C=c.info,x=c.isArray,S=c.isInt,A=c.isNum,I=c.readUint32,B=c.shadow,R=c.string32,T=c.warn,O=c.MissingDataException,P=c.isSpace,M=l.Stream,E=h.getGlyphsUnicode,L=h.getDingbatsGlyphsUnicode,D=u.FontRendererFactory,F=f.StandardEncoding,q=f.MacRomanEncoding,U=f.SymbolSetEncoding,N=f.ZapfDingbatsEncoding,j=f.getEncoding,_=d.getStdFontMap,z=d.getNonStdFontMap,H=d.getGlyphMapForStandardFonts,G=d.getSupplementalGlyphMapForArialBlack,X=g.getUnicodeRangeFor,V=g.mapSpecialUnicodeValues,W=g.getUnicodeForGlyph,K=p.Type1Parser,Y=m.CFFStandardStrings,J=m.CFFParser,Z=m.CFFCompiler,Q=m.CFF,$=m.CFFHeader,ee=m.CFFTopDict,te=m.CFFPrivateDict,ae=m.CFFStrings,re=m.CFFIndex,ie=m.CFFCharset,ne=57344,se=63743,oe=!1,ce=1e3,le=!1,he={FixedPitch:1,Serif:2,Symbolic:4,Script:8,Nonsymbolic:32,Italic:64,AllCap:65536,SmallCap:131072,ForceBold:262144},ue=[".notdef",".null","nonmarkingreturn","space","exclam","quotedbl","numbersign","dollar","percent","ampersand","quotesingle","parenleft","parenright","asterisk","plus","comma","hyphen","period","slash","zero","one","two","three","four","five","six","seven","eight","nine","colon","semicolon","less","equal","greater","question","at","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","bracketleft","backslash","bracketright","asciicircum","underscore","grave","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","braceleft","bar","braceright","asciitilde","Adieresis","Aring","Ccedilla","Eacute","Ntilde","Odieresis","Udieresis","aacute","agrave","acircumflex","adieresis","atilde","aring","ccedilla","eacute","egrave","ecircumflex","edieresis","iacute","igrave","icircumflex","idieresis","ntilde","oacute","ograve","ocircumflex","odieresis","otilde","uacute","ugrave","ucircumflex","udieresis","dagger","degree","cent","sterling","section","bullet","paragraph","germandbls","registered","copyright","trademark","acute","dieresis","notequal","AE","Oslash","infinity","plusminus","lessequal","greaterequal","yen","mu","partialdiff","summation","product","pi","integral","ordfeminine","ordmasculine","Omega","ae","oslash","questiondown","exclamdown","logicalnot","radical","florin","approxequal","Delta","guillemotleft","guillemotright","ellipsis","nonbreakingspace","Agrave","Atilde","Otilde","OE","oe","endash","emdash","quotedblleft","quotedblright","quoteleft","quoteright","divide","lozenge","ydieresis","Ydieresis","fraction","currency","guilsinglleft","guilsinglright","fi","fl","daggerdbl","periodcentered","quotesinglbase","quotedblbase","perthousand","Acircumflex","Ecircumflex","Aacute","Edieresis","Egrave","Iacute","Icircumflex","Idieresis","Igrave","Oacute","Ocircumflex","apple","Ograve","Uacute","Ucircumflex","Ugrave","dotlessi","circumflex","tilde","macron","breve","dotaccent","ring","cedilla","hungarumlaut","ogonek","caron","Lslash","lslash","Scaron","scaron","Zcaron","zcaron","brokenbar","Eth","eth","Yacute","yacute","Thorn","thorn","minus","multiply","onesuperior","twosuperior","threesuperior","onehalf","onequarter","threequarters","franc","Gbreve","gbreve","Idotaccent","Scedilla","scedilla","Cacute","cacute","Ccaron","ccaron","dcroat"],fe=function(){function e(e,t,a,r,i,n,s,o){this.fontChar=e;this.unicode=t;this.accent=a;this.width=r;this.vmetric=i;this.operatorListId=n;this.isSpace=s;this.isInFont=o}e.prototype.matchesForCache=function(e,t,a,r,i,n,s,o){return this.fontChar===e&&this.unicode===t&&this.accent===a&&this.width===r&&this.vmetric===i&&this.operatorListId===n&&this.isSpace===s&&this.isInFont===o};return e}(),de=function(){function e(e){this._map=e}e.prototype={get length(){return this._map.length},forEach:function(e){for(var t in this._map)e(t,this._map[t].charCodeAt(0))},has:function(e){return void 0!==this._map[e]},get:function(e){return this._map[e]},charCodeOf:function(e){return this._map.indexOf(e)},amend:function(e){for(var t in e)this._map[t]=e[t]}};return e}(),ge=function(){function e(e,t){this.firstChar=e;this.lastChar=t}e.prototype={get length(){return this.lastChar+1-this.firstChar},forEach:function(e){for(var t=this.firstChar,a=this.lastChar;t<=a;t++)e(t,t)},has:function(e){return this.firstChar<=e&&e<=this.lastChar},get:function(e){if(this.firstChar<=e&&e<=this.lastChar)return String.fromCharCode(e)},charCodeOf:function(e){return S(e)&&e>=this.firstChar&&e<=this.lastChar?e:-1},amend:function(e){w("Should not call amend()")}};return e}(),pe=function(){function e(e,t,a){e[t]=a>>8&255;e[t+1]=255&a}function t(e,t,a){e[t]=a>>24&255;e[t+1]=a>>16&255;e[t+2]=a>>8&255;e[t+3]=255&a}function a(e,t,a){var r,i;if(a instanceof Uint8Array)e.set(a,t);else if("string"==typeof a)for(r=0,i=a.length;r<i;r++)e[t++]=255&a.charCodeAt(r);else for(r=0,i=a.length;r<i;r++)e[t++]=255&a[r]}function r(e){this.sfnt=e;this.tables=Object.create(null)}r.getSearchParams=function(e,t){for(var a=1,r=0;(a^e)>a;){a<<=1;r++}var i=a*t;return{range:i,entry:r,rangeShift:t*e-i}};r.prototype={toArray:function(){var i=this.sfnt,n=this.tables,s=Object.keys(n);s.sort();var o,c,l,h,u,f=s.length,d=12+16*f,g=[d];for(o=0;o<f;o++){h=n[s[o]];d+=(h.length+3&-4)>>>0;g.push(d)}var p=new Uint8Array(d);for(o=0;o<f;o++){h=n[s[o]];a(p,g[o],h)}"true"===i&&(i=R(65536));p[0]=255&i.charCodeAt(0);p[1]=255&i.charCodeAt(1);p[2]=255&i.charCodeAt(2);p[3]=255&i.charCodeAt(3);e(p,4,f);var m=r.getSearchParams(f,16);e(p,6,m.range);e(p,8,m.entry);e(p,10,m.rangeShift);d=12;for(o=0;o<f;o++){u=s[o];p[d]=255&u.charCodeAt(0);p[d+1]=255&u.charCodeAt(1);p[d+2]=255&u.charCodeAt(2);p[d+3]=255&u.charCodeAt(3);var b=0;for(c=g[o],l=g[o+1];c<l;c+=4){b=b+I(p,c)>>>0}t(p,d+4,b);t(p,d+8,g[o]);t(p,d+12,n[u].length);d+=16}return p},addTable:function(e,t){if(e in this.tables)throw new Error("Table "+e+" already exists");this.tables[e]=t}};return r}(),me=new Int32Array([0,32,127,161,173,174,1536,1920,2208,4256,6016,6144,7168,7248,8192,8208,8209,8210,8232,8240,8287,8304,9676,9677,12288,12289,43616,43648,65520,65536]),be=function(){function e(e,t,a){var i,s,o;this.name=e;this.loadedName=a.loadedName;this.isType3Font=a.isType3Font;this.sizes=[];this.missingFile=!1;this.glyphCache=Object.create(null);this.isSerifFont=!!(a.flags&he.Serif);this.isSymbolicFont=!!(a.flags&he.Symbolic);this.isMonospace=!!(a.flags&he.FixedPitch);var c=a.type,l=a.subtype;this.type=c;this.fallbackName=this.isMonospace?"monospace":this.isSerifFont?"serif":"sans-serif";this.differences=a.differences;this.widths=a.widths;this.defaultWidth=a.defaultWidth;this.composite=a.composite;this.wideChars=a.wideChars;this.cMap=a.cMap;this.ascent=a.ascent/ce;this.descent=a.descent/ce;this.fontMatrix=a.fontMatrix;this.bbox=a.bbox;this.toUnicode=a.toUnicode;this.toFontChar=[];if("Type3"!==a.type){this.cidEncoding=a.cidEncoding;this.vertical=a.vertical;if(this.vertical){this.vmetrics=a.vmetrics;this.defaultVMetrics=a.defaultVMetrics}var g;if(t&&!t.isEmpty){"Type1C"===l&&("Type1"!==c&&"MMType1"!==c?h(t)?l="TrueType":c="Type1":u(t)&&(c=l="OpenType"));"CIDFontType0C"===l&&"CIDFontType0"!==c&&(c="CIDFontType0");"OpenType"===l&&(c="OpenType");"CIDFontType0"===c&&(f(t)?l="CIDFontType0":u(t)?c=l="OpenType":l="CIDFontType0C");var p;switch(c){case"MMType1":C("MMType1 font ("+e+"), falling back to Type1.");case"Type1":case"CIDFontType0":this.mimetype="font/opentype";var m="Type1C"===l||"CIDFontType0C"===l?new ke(t,a):new ye(e,t,a);r(a);p=this.convert(e,m,a);break;case"OpenType":case"TrueType":case"CIDFontType2":this.mimetype="font/opentype";p=this.checkAndRepair(e,t,a);if(this.isOpenType){r(a);c="OpenType"}break;default:w("Font "+c+" is not supported")}this.data=p;this.fontType=n(c,l);this.fontMatrix=a.fontMatrix;this.widths=a.widths;this.defaultWidth=a.defaultWidth;this.toUnicode=a.toUnicode;this.encoding=a.baseEncoding;this.seacMap=a.seacMap;this.loading=!0}else{t&&T('Font file is empty in "'+e+'" ('+this.loadedName+")");this.missingFile=!0;var b=e.replace(/[,_]/g,"-"),y=_(),k=z(),x=!!y[b]||!(!k[b]||!y[k[b]]);b=y[b]||k[b]||b;this.bold=-1!==b.search(/bold/gi);this.italic=-1!==b.search(/oblique/gi)||-1!==b.search(/italic/gi);this.black=-1!==e.search(/Black/g);this.remeasure=Object.keys(this.widths).length>0;if(x&&"CIDFontType2"===c&&0===a.cidEncoding.indexOf("Identity-")){var S=H(),A=[];for(i in S)A[+i]=S[i];if(/Arial-?Black/i.test(e)){var I=G();for(i in I)A[+i]=I[i]}this.toUnicode instanceof ge||this.toUnicode.forEach(function(e,t){A[+e]=t});this.toFontChar=A;this.toUnicode=new de(A)}else if(/Symbol/i.test(b))this.toFontChar=d(U,E(),a.differences);else if(/Dingbats/i.test(b)){/Wingdings/i.test(e)&&T("Non-embedded Wingdings font, falling back to ZapfDingbats.");this.toFontChar=d(N,L(),a.differences)}else if(x)this.toFontChar=d(a.defaultEncoding,E(),a.differences);else{g=E();this.toUnicode.forEach(function(e,t){if(!this.composite){s=a.differences[e]||a.defaultEncoding[e];o=W(s,g);-1!==o&&(t=o)}this.toFontChar[e]=t}.bind(this))}this.loadedName=b.split("-")[0];this.loading=!1;this.fontType=n(c,l)}}else{for(i=0;i<256;i++)this.toFontChar[i]=this.differences[i]||a.defaultEncoding[i];this.fontType=v.TYPE3}}function t(e,t){return(e<<8)+t}function a(e,t){var a=(e<<8)+t;return 32768&a?a-65536:a}function o(e,t,a,r){return(e<<24)+(t<<16)+(a<<8)+r}function c(e){return String.fromCharCode(e>>8&255,255&e)}function l(e){e=e>32767?32767:e<-32768?-32768:e;return String.fromCharCode(e>>8&255,255&e)}function h(e){var t=e.peekBytes(4);return 65536===I(t,0)}function u(e){var t=e.peekBytes(4);return"OTTO"===k(t)}function f(e){var t=e.peekBytes(2);return 37===t[0]&&33===t[1]||128===t[0]&&1===t[1]}function d(e,t,a){for(var r,i=[],n=0,s=e.length;n<s;n++){r=W(e[n],t);-1!==r&&(i[n]=r)}for(var o in a){r=W(a[o],t);-1!==r&&(i[+o]=r)}return i}function g(e){for(var t=0,a=me.length-1;t<a;){var r=t+a+1>>1;e<me[r]?a=r-1:t=r}return!(1&t)}function p(e,t){var a=t.toUnicode,r=!!(t.flags&he.Symbolic),i=t.toUnicode instanceof ge,n=Object.create(null),s=[],o=[],c=ne;for(var l in e){l|=0;var h=e[l],u=l,f=!1;if(!i&&a.has(l)){f=!0;var d=a.get(u);1===d.length&&(u=d.charCodeAt(0))}if((void 0!==o[u]||g(u)||r&&!f)&&c<=se)do{u=c++;if(oe&&61440===u){u=61472;c=u+1}}while(void 0!==o[u]&&c<=se);n[u]=h;s[l]=u;o[u]=!0}return{toFontChar:s,charCodeToGlyphId:n,nextAvailableFontCharCode:c}}function m(e,t){var a=[];for(var r in e)e[r]>=t||a.push({fontCharCode:0|r,glyphId:e[r]});a.sort(function(e,t){return e.fontCharCode-t.fontCharCode});for(var i=[],n=a.length,s=0;s<n;){var o=a[s].fontCharCode,c=[a[s].glyphId];++s;for(var l=o;s<n&&l+1===a[s].fontCharCode;){c.push(a[s].glyphId);++l;++s;if(65535===l)break}i.push([o,l,c])}return i}function x(e,t){var a,r,i,n,s=m(e,t),o=s[s.length-1][1]>65535?2:1,l="\0\0"+c(o)+"\0\0"+R(4+8*o);for(a=s.length-1;a>=0&&!(s[a][0]<=65535);--a);var h=a+1;s[a][0]<65535&&65535===s[a][1]&&(s[a][1]=65534);var u,f,d,g,p=s[a][1]<65535?1:0,b=h+p,v=pe.getSearchParams(b,2),y="",k="",w="",C="",x="",S=0;for(a=0,r=h;a<r;a++){u=s[a];f=u[0];d=u[1];y+=c(f);k+=c(d);g=u[2];var A=!0;for(i=1,n=g.length;i<n;++i)if(g[i]!==g[i-1]+1){A=!1;break}if(A){w+=c(g[0]-f&65535);C+=c(0)}else{var I=2*(b-a)+2*S;S+=d-f+1;w+=c(0);C+=c(I);for(i=0,n=g.length;i<n;++i)x+=c(g[i])}}if(p>0){k+="ÿÿ";y+="ÿÿ";w+="\0";C+="\0\0"}var B="\0\0"+c(2*b)+c(v.range)+c(v.entry)+c(v.rangeShift)+k+"\0\0"+y+w+C+x,T="",O="";if(o>1){l+="\0\0\n"+R(4+8*o+4+B.length);T="";for(a=0,r=s.length;a<r;a++){u=s[a];f=u[0];g=u[2];var P=g[0];for(i=1,n=g.length;i<n;++i)if(g[i]!==g[i-1]+1){d=u[0]+i-1;T+=R(f)+R(d)+R(P);f=d+1;P=g[i]}T+=R(f)+R(u[1])+R(P)}O="\0\f\0\0"+R(T.length+16)+"\0\0\0\0"+R(T.length/12)}return l+"\0"+c(B.length+4)+B+O+T}function S(e){var t=new M(e.data),a=t.getUint16();t.getBytes(60);var r=t.getUint16();if(a<4&&768&r)return!1;if(t.getUint16()>t.getUint16())return!1;t.getBytes(6);if(0===t.getUint16())return!1;e.data[8]=e.data[9]=0;return!0}function O(e,t,a){a=a||{unitsPerEm:0,yMax:0,yMin:0,ascent:0,descent:0};var r=0,i=0,n=0,s=0,o=null,l=0;if(t)for(var h in t){h|=0;(o>h||!o)&&(o=h);l<h&&(l=h);var u=X(h);u<32?r|=1<<u:u<64?i|=1<<u-32:u<96?n|=1<<u-64:u<123?s|=1<<u-96:w("Unicode ranges Bits > 123 are reserved for internal usage")}else{o=0;l=255}var f=e.bbox||[0,0,0,0],d=a.unitsPerEm||1/(e.fontMatrix||b)[0],g=e.ascentScaled?1:d/ce,p=a.ascent||Math.round(g*(e.ascent||f[3])),m=a.descent||Math.round(g*(e.descent||f[1]));m>0&&e.descent>0&&f[1]<0&&(m=-m);var v=a.yMax||p,y=-a.yMin||-m;return"\0$ô\0\0\0Š»\0\0\0ŒŠ»\0\0ß\x001\0\0\0\0"+String.fromCharCode(e.fixedPitch?9:0)+"\0\0\0\0\0\0"+R(r)+R(i)+R(n)+R(s)+"*21*"+c(e.italicAngle?1:0)+c(o||e.firstChar)+c(l||e.lastChar)+c(p)+c(m)+"\0d"+c(v)+c(y)+"\0\0\0\0\0\0\0\0"+c(e.xHeight)+c(e.capHeight)+c(0)+c(o||e.firstChar)+"\0"}function P(e){var t=Math.floor(e.italicAngle*Math.pow(2,16));return"\0\0\0"+R(t)+"\0\0\0\0"+R(e.fixedPitch)+"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"}function K(e,t){t||(t=[[],[]]);var a,r,i,n,s,o=[t[0][0]||"Original licence",t[0][1]||e,t[0][2]||"Unknown",t[0][3]||"uniqueID",t[0][4]||e,t[0][5]||"Version 0.11",t[0][6]||"",t[0][7]||"Unknown",t[0][8]||"Unknown",t[0][9]||"Unknown"],l=[];for(a=0,r=o.length;a<r;a++){s=t[1][a]||o[a];var h=[];for(i=0,n=s.length;i<n;i++)h.push(c(s.charCodeAt(i)));l.push(h.join(""))}var u=[o,l],f=["\0","\0"],d=["\0\0","\0"],g=["\0\0","\t"],p=o.length*f.length,m="\0\0"+c(p)+c(12*p+6),b=0;for(a=0,r=f.length;a<r;a++){var v=u[a];for(i=0,n=v.length;i<n;i++){s=v[i];m+=f[a]+d[a]+g[a]+c(i)+c(s.length)+c(b);b+=s.length}}m+=o.join("")+l.join("");return m}e.getFontID=function(){var e=1;return function(){return String(e++)}}();e.prototype={name:null,font:null,mimetype:null,encoding:null,get renderer(){var e=D.create(this,le);return B(this,"renderer",e)},exportData:function(){var e={};for(var t in this)this.hasOwnProperty(t)&&(e[t]=this[t]);return e},checkAndRepair:function(e,i,n){function c(e,t,a,r,i,n){if(a-t<=12)return 0;var s=e.subarray(t,a),o=s[0]<<8|s[1];if(32768&o){r.set(s,i);return s.length}var c,l=10,h=0;for(c=0;c<o;c++){h=(s[l]<<8|s[l+1])+1;l+=2}var u=l,f=s[l]<<8|s[l+1];l+=2+f;var d=l,g=0;for(c=0;c<h;c++){var p=s[l++];192&p&&(s[l-1]=63&p);var m=(2&p?1:16&p?0:2)+(4&p?1:32&p?0:2);g+=m;if(8&p){var b=s[l++];c+=b;g+=b*m}}if(0===g)return 0;var v=l+g;if(v>s.length)return 0;if(!n&&f>0){r.set(s.subarray(0,u),i);r.set([0,0],i+u);r.set(s.subarray(d,v),i+u+2);v-=f;s.length-v>3&&(v=v+3&-4);return v}if(s.length-v>3){v=v+3&-4;r.set(s.subarray(0,v),i);return v}r.set(s,i);return s.length}function l(e,t){for(var a,r,i,n,s,o=e.data,c=0,l=0,h=0,f=[],g=[],p=[],m=t.tooComplexToFollowFunctions,b=!1,v=0,y=0,k=o.length;c<k;){var w=o[c++];if(64===w){r=o[c++];if(b||y)c+=r;else for(a=0;a<r;a++)f.push(o[c++])}else if(65===w){r=o[c++];if(b||y)c+=2*r;else for(a=0;a<r;a++){i=o[c++];f.push(i<<8|o[c++])}}else if(176==(248&w)){r=w-176+1;if(b||y)c+=r;else for(a=0;a<r;a++)f.push(o[c++])}else if(184==(248&w)){r=w-184+1;if(b||y)c+=2*r;else for(a=0;a<r;a++){i=o[c++];f.push(i<<8|o[c++])}}else if(43!==w||m)if(44!==w||m){if(45===w)if(b){b=!1;l=c}else{s=g.pop();if(!s){T("TT: ENDF bad stack");t.hintsValid=!1;return}n=p.pop();o=s.data;c=s.i;t.functionsStackDeltas[n]=f.length-s.stackTop}else if(137===w){if(b||y){T("TT: nested IDEFs not allowed");m=!0}b=!0;h=c}else if(88===w)++v;else if(27===w)y=v;else if(89===w){y===v&&(y=0);--v}else if(28===w&&!b&&!y){var C=f[f.length-1];C>0&&(c+=C-1)}}else{if(b||y){T("TT: nested FDEFs not allowed");m=!0}b=!0;h=c;n=f.pop();t.functionsDefined[n]={data:o,i:c}}else if(!b&&!y){n=f[f.length-1];t.functionsUsed[n]=!0;if(n in t.functionsStackDeltas)f.length+=t.functionsStackDeltas[n];else if(n in t.functionsDefined&&p.indexOf(n)<0){g.push({data:o,i:c,stackTop:f.length-1});p.push(n);s=t.functionsDefined[n];if(!s){T("TT: CALL non-existent function");t.hintsValid=!1;return}o=s.data;c=s.i}}if(!b&&!y){var x=w<=142?d[w]:w>=192&&w<=223?-1:w>=224?-2:0;if(w>=113&&w<=117){r=f.pop();isNaN(r)||(x=2*-r)}for(;x<0&&f.length>0;){f.pop();x++}for(;x>0;){f.push(NaN);x--}}}t.tooComplexToFollowFunctions=m;var S=[o];c>o.length&&S.push(new Uint8Array(c-o.length));if(h>l){T("TT: complementing a missing function tail");S.push(new Uint8Array([34,45]))}u(e,S)}function h(e,t){if(!e.tooComplexToFollowFunctions)if(e.functionsDefined.length>t){T("TT: more functions defined than expected");e.hintsValid=!1}else for(var a=0,r=e.functionsUsed.length;a<r;a++){if(a>t){T("TT: invalid function id: "+a);e.hintsValid=!1;return}if(e.functionsUsed[a]&&!e.functionsDefined[a]){T("TT: undefined function: "+a);e.hintsValid=!1;return}}}function u(e,t){if(t.length>1){var a,r,i=0;for(a=0,r=t.length;a<r;a++)i+=t[a].length;i=i+3&-4;var n=new Uint8Array(i),s=0;for(a=0,r=t.length;a<r;a++){n.set(t[a],s);s+=t[a].length}e.data=n;e.length=i}}function f(e,t,a){return!G[e]||(!!(!ee&&t>=0&&Q.has(t))||!!($&&a>=0&&A($[a])))}var d=[0,0,0,0,0,0,0,0,-2,-2,-2,-2,0,0,-2,-5,-1,-1,-1,-1,-1,-1,-1,-1,0,0,-1,0,-1,-1,-1,-1,1,-1,-999,0,1,0,-1,-2,0,-1,-2,-1,-1,0,-1,-1,0,0,-999,-999,-1,-1,-1,-1,-2,-999,-2,-2,-999,0,-2,-2,0,0,-2,0,-2,0,0,0,-2,-1,-1,1,1,0,0,-1,-1,-1,-1,-1,-1,-1,0,0,-1,0,-1,-1,0,-999,-1,-1,-1,-1,-1,-1,0,0,0,0,0,0,0,0,0,0,0,0,-2,-999,-999,-999,-999,-999,-1,-1,-2,-2,0,0,0,0,-1,-1,-999,-2,-2,0,0,-1,-2,-2,0,0,0,-1,-1,-1,-2];i=new M(new Uint8Array(i.getBytes()));var g,m,b=["OS/2","cmap","head","hhea","hmtx","maxp","name","post","loca","glyf","fpgm","prep","cvt ","CFF "],v=function(e){return{version:k(e.getBytes(4)),numTables:e.getUint16(),searchRange:e.getUint16(),entrySelector:e.getUint16(),rangeShift:e.getUint16()}}(i),I=v.numTables,B=Object.create(null);B["OS/2"]=null;B.cmap=null;B.head=null;B.hhea=null;B.hmtx=null;B.maxp=null;B.name=null;B.post=null;for(var R,L=0;L<I;L++){R=function(e){var t=k(e.getBytes(4)),a=e.getInt32()>>>0,r=e.getInt32()>>>0,i=e.getInt32()>>>0,n=e.pos;e.pos=e.start?e.start:0;e.skip(r);var s=e.getBytes(i);e.pos=n;if("head"===t){s[8]=s[9]=s[10]=s[11]=0;s[17]|=32}return{tag:t,checksum:a,length:i,offset:r,data:s}}(i);b.indexOf(R.tag)<0||0!==R.length&&(B[R.tag]=R)}var D=!B["CFF "];if(D){B.loca||w('Required "loca" table is not found');if(!B.glyf){T('Required "glyf" table is not found -- trying to recover.');B.glyf={tag:"glyf",data:new Uint8Array(0)}}this.isOpenType=!1}else{if("OTTO"===v.version&&!n.composite||!B.head||!B.hhea||!B.maxp||!B.post){m=new M(B["CFF "].data);g=new ke(m,n);r(n);return this.convert(e,g,n)}delete B.glyf;delete B.loca;delete B.fpgm;delete B.prep;delete B["cvt "];this.isOpenType=!0}B.maxp||w('Required "maxp" table is not found');i.pos=(i.start||0)+B.maxp.offset;var U=i.getInt32(),N=i.getUint16(),_=0;if(U>=65536&&B.maxp.length>=22){i.pos+=8;if(i.getUint16()>2){B.maxp.data[14]=0;B.maxp.data[15]=2}i.pos+=4;_=i.getUint16()}var z=!1;if("CIDFontType2"===n.type&&n.toUnicode&&n.toUnicode.get(0)>"\0"){z=!0;N++;B.maxp.data[4]=N>>8;B.maxp.data[5]=255&N}var H=function(e,t,a,r){var i={functionsDefined:[],functionsUsed:[],functionsStackDeltas:[],tooComplexToFollowFunctions:!1,hintsValid:!0};e&&l(e,i);t&&l(t,i);e&&h(i,r);if(a&&1&a.length){var n=new Uint8Array(a.length+1);n.set(a.data);a.data=n}return i.hintsValid}(B.fpgm,B.prep,B["cvt "],_);if(!H){delete B.fpgm;delete B.prep;delete B["cvt "]}!function(e,t,a,r){if(t){e.pos=(e.start?e.start:0)+t.offset;e.pos+=t.length-2;var i=e.getUint16();if(i>r){C("The numOfMetrics ("+i+") should not be greater than the numGlyphs ("+r+")");i=r;t.data[34]=(65280&i)>>8;t.data[35]=255&i}var n=r-i,s=n-(a.length-4*i>>1);if(s>0){var o=new Uint8Array(a.length+2*s);o.set(a.data);a.data=o}}else a&&(a.data=null)}(i,B.hhea,B.hmtx,N);B.head||w('Required "head" table is not found');!function(e,a,r){var i=e.data,n=o(i[0],i[1],i[2],i[3]);if(n>>16!=1){C("Attempting to fix invalid version in head table: "+n);i[0]=0;i[1]=1;i[2]=0;i[3]=0}var s=t(i[50],i[51]);if(s<0||s>1){C("Attempting to fix invalid indexToLocFormat in head table: "+s);var c=a+1;if(r===c<<1){i[50]=0;i[51]=0}else if(r===c<<2){i[50]=0;i[51]=1}else T("Could not fix indexToLocFormat: "+s)}}(B.head,N,D?B.loca.length:0);var G=Object.create(null);if(D){var X=t(B.head.data[50],B.head.data[51]);G=function(e,t,a,r,i,n){var s,o,l;if(r){s=4;o=function(e,t){return e[t]<<24|e[t+1]<<16|e[t+2]<<8|e[t+3]};l=function(e,t,a){e[t]=a>>>24&255;e[t+1]=a>>16&255;e[t+2]=a>>8&255;e[t+3]=255&a}}else{s=2;o=function(e,t){return e[t]<<9|e[t+1]<<1};l=function(e,t,a){e[t]=a>>9&255;e[t+1]=a>>1&255}}var h=e.data,u=s*(1+a);if(h.length!==u){h=new Uint8Array(u);h.set(e.data.subarray(0,u));e.data=h}var f=t.data,d=f.length,g=new Uint8Array(d),p=o(h,0),m=0,b=Object.create(null);l(h,0,m);var v,y;for(v=0,y=s;v<a;v++,y+=s){var k=o(h,y);k>d&&(d+3&-4)===k&&(k=d);if(k>d){l(h,y,m);p=k}else{p===k&&(b[v]=!0);m+=c(f,p,k,g,m,i);l(h,y,m);p=k}}if(0===m){var w=new Uint8Array([0,1,0,0,0,0,0,0,0,0,0,0,0,0,49,0]);for(v=0,y=s;v<a;v++,y+=s)l(h,y,w.length);t.data=w;return b}if(n){var C=o(h,s);if(g.length>C+m)t.data=g.subarray(0,C+m);else{t.data=new Uint8Array(C+m);t.data.set(g.subarray(0,m))}t.data.set(g.subarray(0,C),m);l(e.data,h.length-s,m+C)}else t.data=g.subarray(0,m);return b}(B.loca,B.glyf,N,X,H,z)}B.hhea||w('Required "hhea" table is not found');if(0===B.hhea.data[10]&&0===B.hhea.data[11]){B.hhea.data[10]=255;B.hhea.data[11]=255}var V={unitsPerEm:t(B.head.data[18],B.head.data[19]),yMax:t(B.head.data[42],B.head.data[43]),yMin:a(B.head.data[38],B.head.data[39]),ascent:t(B.hhea.data[4],B.hhea.data[5]),descent:a(B.hhea.data[6],B.hhea.data[7])};this.ascent=V.ascent/V.unitsPerEm;this.descent=V.descent/V.unitsPerEm;if(B.post){(function(e,t,a){var r=(i.start?i.start:0)+e.offset;i.pos=r;var n=e.length,s=r+n,o=i.getInt32();i.getBytes(28);var c,l,h=!0;switch(o){case 65536:c=ue;break;case 131072:var u=i.getUint16();if(u!==a){h=!1;break}var f=[];for(l=0;l<u;++l){var d=i.getUint16();if(d>=32768){h=!1;break}f.push(d)}if(!h)break;for(var g=[],p=[];i.pos<s;){var m=i.getByte();p.length=m;for(l=0;l<m;++l)p[l]=String.fromCharCode(i.getByte());g.push(p.join(""))}c=[];for(l=0;l<u;++l){var b=f[l];b<258?c.push(ue[b]):c.push(g[b-258])}break;case 196608:break;default:T("Unknown/unsupported post table version "+o);h=!1;t.defaultEncoding&&(c=t.defaultEncoding)}t.glyphNames=c;return h})(B.post,n,N)||(B.post=null)}var W,Y=[],Q=n.toUnicode,$=n.widths,ee=Q instanceof ge||65536===Q.length;if(n.composite){var te=n.cidToGidMap||[],ae=0===te.length;n.cMap.forEach(function(e,t){y(t<=65535,"Max size of CID is 65,535");var a=-1;ae?a=t:void 0!==te[t]&&(a=te[t]);a>=0&&a<N&&f(a,e,t)&&(Y[e]=a)});!z||!ae&&Y[0]||(Y[0]=N-1)}else{var re=function(e,t,a,r){if(!e){T("No cmap table available.");return{platformId:-1,encodingId:-1,mappings:[],hasShortCmap:!1}}var i,n=(t.start?t.start:0)+e.offset;t.pos=n;t.getUint16();for(var s,o=t.getUint16(),c=!1,l=0;l<o;l++){var h=t.getUint16(),u=t.getUint16(),f=t.getInt32()>>>0,d=!1;if(0===h&&0===u)d=!0;else if(1===h&&0===u)d=!0;else if(3!==h||1!==u||!r&&s){if(a&&3===h&&0===u){d=!0;c=!0}}else{d=!0;a||(c=!0)}d&&(s={platformId:h,encodingId:u,offset:f});if(c)break}s&&(t.pos=n+s.offset);if(!s||-1===t.peekByte()){T("Could not find a preferred cmap table.");return{platformId:-1,encodingId:-1,mappings:[],hasShortCmap:!1}}var g=t.getUint16();t.getUint16();t.getUint16();var p,m,b=!1,v=[];if(0===g){for(p=0;p<256;p++){var y=t.getByte();y&&v.push({charCode:p,glyphId:y})}b=!0}else if(4===g){var k=t.getUint16()>>1;t.getBytes(6);var w,C=[];for(w=0;w<k;w++)C.push({end:t.getUint16()});t.getUint16();for(w=0;w<k;w++)C[w].start=t.getUint16();for(w=0;w<k;w++)C[w].delta=t.getUint16();var x=0;for(w=0;w<k;w++){i=C[w];var S=t.getUint16();if(S){var A=(S>>1)-(k-w);i.offsetIndex=A;x=Math.max(x,A+i.end-i.start+1)}else i.offsetIndex=-1}var I=[];for(p=0;p<x;p++)I.push(t.getUint16());for(w=0;w<k;w++){i=C[w];n=i.start;var B=i.end,R=i.delta;A=i.offsetIndex;for(p=n;p<=B;p++)if(65535!==p){m=A<0?p:I[A+p-n];m=m+R&65535;v.push({charCode:p,glyphId:m})}}}else{if(6!==g){T("cmap table has unsupported format: "+g);return{platformId:-1,encodingId:-1,mappings:[],hasShortCmap:!1}}var O=t.getUint16(),P=t.getUint16();for(p=0;p<P;p++){m=t.getUint16();var M=O+p;v.push({charCode:M,glyphId:m})}}v.sort(function(e,t){return e.charCode-t.charCode});for(l=1;l<v.length;l++)if(v[l-1].charCode===v[l].charCode){v.splice(l,1);l--}return{platformId:s.platformId,encodingId:s.encodingId,mappings:v,hasShortCmap:b}}(B.cmap,i,this.isSymbolicFont,n.hasEncoding),ie=re.platformId,ne=re.encodingId,se=re.mappings,oe=se.length;if(n.hasEncoding&&(3===ie&&1===ne||1===ie&&0===ne)||-1===ie&&-1===ne&&j(n.baseEncodingName)){var ce=[];"MacRomanEncoding"!==n.baseEncodingName&&"WinAnsiEncoding"!==n.baseEncodingName||(ce=j(n.baseEncodingName));var he=E();for(W=0;W<256;W++){var fe,de;fe=this.differences&&W in this.differences?this.differences[W]:W in ce&&""!==ce[W]?ce[W]:F[W];if(fe){de=s(fe,he);var me,be=!1;if(3===ie&&1===ne){me=he[de];be=!0}else 1===ie&&0===ne&&(me=q.indexOf(de));var ve=!1;for(L=0;L<oe;++L)if(se[L].charCode===me){var ye=be?W:me;if(f(se[L].glyphId,ye,-1)){Y[W]=se[L].glyphId;ve=!0;break}}if(!ve&&n.glyphNames){var we=n.glyphNames.indexOf(fe);-1===we&&de!==fe&&(we=n.glyphNames.indexOf(de));if(we>0&&f(we,-1,-1)){Y[W]=we;ve=!0}}ve||(Y[W]=0)}}}else if(0===ie&&0===ne)for(L=0;L<oe;++L)Y[se[L].charCode]=se[L].glyphId;else for(L=0;L<oe;++L){W=255&se[L].charCode;Y[W]=se[L].glyphId}}0===Y.length&&(Y[0]=0);var Ce=p(Y,n);this.toFontChar=Ce.toFontChar;B.cmap={tag:"cmap",data:x(Ce.charCodeToGlyphId,N)};B["OS/2"]&&S(B["OS/2"])||(B["OS/2"]={tag:"OS/2",data:O(n,Ce.charCodeToGlyphId,V)});B.post||(B.post={tag:"post",data:P(n)});if(!D)try{m=new M(B["CFF "].data);g=new J(m,n,le).parse();var xe=new Z(g);B["CFF "].data=xe.compile()}catch(e){T("Failed to compile font "+n.loadedName)}if(B.name){var Se=function(e){var t=(i.start?i.start:0)+e.offset;i.pos=t;var a=[[],[]],r=e.length,n=t+r;if(0!==i.getUint16()||r<6)return a;var s,o,c=i.getUint16(),l=i.getUint16(),h=[];for(s=0;s<c&&i.pos+12<=n;s++){var u={platform:i.getUint16(),encoding:i.getUint16(),language:i.getUint16(),name:i.getUint16(),length:i.getUint16(),offset:i.getUint16()};(1===u.platform&&0===u.encoding&&0===u.language||3===u.platform&&1===u.encoding&&1033===u.language)&&h.push(u)}for(s=0,o=h.length;s<o;s++){var f=h[s];if(!(f.length<=0)){var d=t+l+f.offset;if(!(d+f.length>n)){i.pos=d;var g=f.name;if(f.encoding){for(var p="",m=0,b=f.length;m<b;m+=2)p+=String.fromCharCode(i.getUint16());a[1][g]=p}else a[0][g]=k(i.getBytes(f.length))}}}return a}(B.name);B.name.data=K(e,Se)}else B.name={tag:"name",data:K(this.name)};var Ae=new pe(v.version);for(var Ie in B)Ae.addTable(Ie,B[Ie].data);return Ae.toArray()},convert:function(e,t,a){function r(e,t){for(var a in e)if(t===e[a])return 0|a;s.charCodeToGlyphId[s.nextAvailableFontCharCode]=t;return s.nextAvailableFontCharCode++}a.fixedPitch=!1;a.builtInEncoding&&i(a,a.builtInEncoding);var n=t.getGlyphMapping(a),s=p(n,a);this.toFontChar=s.toFontChar;var o=t.numGlyphs,h=t.seacs;if(le&&h&&h.length){var u=a.fontMatrix||b,f=t.getCharset(),d=Object.create(null);for(var g in h){g|=0;var m=h[g],v=F[m[2]],y=F[m[3]],k=f.indexOf(v),w=f.indexOf(y);if(!(k<0||w<0)){var C={x:m[0]*u[0]+m[1]*u[2]+u[4],y:m[0]*u[1]+m[1]*u[3]+u[5]},S=function(e,t){var a=null;for(var r in e)if(t===e[r]){a||(a=[]);a.push(0|r)}return a}(n,g);if(S)for(var A=0,I=S.length;A<I;A++){var B=S[A],R=s.charCodeToGlyphId,T=r(R,k),M=r(R,w);d[B]={
+baseFontCharCode:T,accentFontCharCode:M,accentOffset:C}}}}a.seacMap=d}var E=1/(a.fontMatrix||b)[0],L=new pe("OTTO");L.addTable("CFF ",t.data);L.addTable("OS/2",O(a,s.charCodeToGlyphId));L.addTable("cmap",x(s.charCodeToGlyphId,o));L.addTable("head","\0\0\0\0\0\0\0\0\0\0_<õ\0\0"+l(E)+"\0\0\0\0ž\v~'\0\0\0\0ž\v~'\0\0"+l(a.descent)+"ÿ"+l(a.ascent)+c(a.italicAngle?2:0)+"\0\0\0\0\0\0\0");L.addTable("hhea","\0\0\0"+l(a.ascent)+l(a.descent)+"\0\0ÿÿ\0\0\0\0\0\0"+l(a.capHeight)+l(Math.tan(a.italicAngle)*a.xHeight)+"\0\0\0\0\0\0\0\0\0\0\0\0"+c(o));L.addTable("hmtx",function(){for(var e=t.charstrings,a=t.cff?t.cff.widths:null,r="\0\0\0\0",i=1,n=o;i<n;i++){var s=0;if(e){var l=e[i-1];s="width"in l?l.width:0}else a&&(s=Math.ceil(a[i]||0));r+=c(s)+c(0)}return r}());L.addTable("maxp","\0\0P\0"+c(o));L.addTable("name",K(e));L.addTable("post",P(a));return L.toArray()},get spaceWidth(){if("_shadowWidth"in this)return this._shadowWidth;for(var e,t=["space","minus","one","i","I"],a=0,r=t.length;a<r;a++){var i=t[a];if(i in this.widths){e=this.widths[i];break}var n=E(),s=n[i],o=0;this.composite&&this.cMap.contains(s)&&(o=this.cMap.lookup(s));!o&&this.toUnicode&&(o=this.toUnicode.charCodeOf(s));o<=0&&(o=s);e=this.widths[o];if(e)break}e=e||this.defaultWidth;this._shadowWidth=e;return e},charToGlyph:function(e,t){var a,r,i,n=e;this.cMap&&this.cMap.contains(e)&&(n=this.cMap.lookup(e));r=this.widths[n];r=A(r)?r:this.defaultWidth;var s=this.vmetrics&&this.vmetrics[n],o=this.toUnicode.get(e)||e;"number"==typeof o&&(o=String.fromCharCode(o));var c=e in this.toFontChar;a=this.toFontChar[e]||e;this.missingFile&&(a=V(a));this.isType3Font&&(i=a);var l=null;if(this.seacMap&&this.seacMap[e]){c=!0;var h=this.seacMap[e];a=h.baseFontCharCode;l={fontChar:String.fromCharCode(h.accentFontCharCode),offset:h.accentOffset}}var u=String.fromCharCode(a),f=this.glyphCache[e];if(!f||!f.matchesForCache(u,o,l,r,s,i,t,c)){f=new fe(u,o,l,r,s,i,t,c);this.glyphCache[e]=f}return f},charsToGlyphs:function(e){var t,a,r,i=this.charsCache;if(i){t=i[e];if(t)return t}i||(i=this.charsCache=Object.create(null));t=[];var n,s=e,o=0;if(this.cMap)for(var c=Object.create(null);o<e.length;){this.cMap.readCharCode(e,o,c);r=c.charcode;var l=c.length;o+=l;var h=1===l&&32===e.charCodeAt(o-1);a=this.charToGlyph(r,h);t.push(a)}else for(o=0,n=e.length;o<n;++o){r=e.charCodeAt(o);a=this.charToGlyph(r,32===r);t.push(a)}return i[s]=t}};return e}(),ve=function(){function e(e){this.error=e;this.loadedName="g_font_error";this.loading=!1}e.prototype={charsToGlyphs:function(){return[]},exportData:function(){return{error:this.error}}};return e}(),ye=function(){function e(e,t,a){for(var r,i=e.length,n=t.length,s=i-n,o=a,c=!1;o<s;){r=0;for(;r<n&&e[o+r]===t[r];)r++;if(r>=n){o+=r;for(;o<i&&P(e[o]);)o++;c=!0;break}o++}return{found:c,length:o}}function t(t,a){var r,i,n,s=[101,101,120,101,99],o=t.pos;try{r=t.getBytes(a);i=r.length}catch(e){if(e instanceof O)throw e}if(i===a){n=e(r,s,a-2*s.length);if(n.found&&n.length===a)return{stream:new M(r),length:a}}T('Invalid "Length1" property in Type1 font -- trying to recover.');t.pos=o;for(var c;;){n=e(t.peekBytes(2048),s,0);if(0===n.length)break;t.pos+=n.length;if(n.found){c=t.pos-o;break}}t.pos=o;if(c)return{stream:new M(t.getBytes(c)),length:c};T('Unable to recover "Length1" property in Type1 font -- using as is.');return{stream:new M(t.getBytes(a)),length:a}}function a(e,t){var a=e.getBytes();return{stream:new M(a),length:a.length}}function r(e,r,i){var n=i.length1,s=i.length2,o=r.peekBytes(6),c=128===o[0]&&1===o[1];if(c){r.skip(6);n=o[5]<<24|o[4]<<16|o[3]<<8|o[2]}var l=t(r,n);n=l.length;new K(l.stream,!1,le).extractFontHeader(i);if(c){o=r.getBytes(6);s=o[5]<<24|o[4]<<16|o[3]<<8|o[2]}var h=a(r,s);s=h.length;var u=new K(h.stream,!0,le),f=u.extractFontProgram();for(var d in f.properties)i[d]=f.properties[d];var g=f.charstrings,p=this.getType2Charstrings(g),m=this.getType2Subrs(f.subrs);this.charstrings=g;this.data=this.wrap(e,p,this.charstrings,m,i);this.seacs=this.getSeacs(f.charstrings)}r.prototype={get numGlyphs(){return this.charstrings.length+1},getCharset:function(){for(var e=[".notdef"],t=this.charstrings,a=0;a<t.length;a++)e.push(t[a].glyphName);return e},getGlyphMapping:function(e){var t,a=this.charstrings,r=[".notdef"];for(t=0;t<a.length;t++)r.push(a[t].glyphName);var i=e.builtInEncoding;if(i){var n=Object.create(null);for(var s in i){t=r.indexOf(i[s]);t>=0&&(n[s]=t)}}return o(e,n,r)},getSeacs:function(e){var t,a,r=[];for(t=0,a=e.length;t<a;t++){var i=e[t];i.seac&&(r[t+1]=i.seac)}return r},getType2Charstrings:function(e){for(var t=[],a=0,r=e.length;a<r;a++)t.push(e[a].charstring);return t},getType2Subrs:function(e){var t=0,a=e.length;t=a<1133?107:a<33769?1131:32768;var r,i=[];for(r=0;r<t;r++)i.push([11]);for(r=0;r<a;r++)i.push(e[r]);return i},wrap:function(e,t,a,r,i){var n=new Q;n.header=new $(1,0,4,4);n.names=[e];var s=new ee;s.setByName("version",391);s.setByName("Notice",392);s.setByName("FullName",393);s.setByName("FamilyName",394);s.setByName("Weight",395);s.setByName("Encoding",null);s.setByName("FontMatrix",i.fontMatrix);s.setByName("FontBBox",i.bbox);s.setByName("charset",null);s.setByName("CharStrings",null);s.setByName("Private",null);n.topDict=s;var o=new ae;o.add("Version 0.11");o.add("See original notice");o.add(e);o.add(e);o.add("Medium");n.strings=o;n.globalSubrIndex=new re;var c,l,h=t.length,u=[0];for(c=0;c<h;c++){var f=Y.indexOf(a[c].glyphName);-1===f&&(f=0);u.push(f>>8&255,255&f)}n.charset=new ie(!1,0,[],u);var d=new re;d.add([139,14]);for(c=0;c<h;c++){var g=t[c];0!==g.length?d.add(g):d.add([139,14])}n.charStrings=d;var p=new te;p.setByName("Subrs",null);var m=["BlueValues","OtherBlues","FamilyBlues","FamilyOtherBlues","StemSnapH","StemSnapV","BlueShift","BlueFuzz","BlueScale","LanguageGroup","ExpansionFactor","ForceBold","StdHW","StdVW"];for(c=0,l=m.length;c<l;c++){var b=m[c];if(b in i.privateData){var v=i.privateData[b];if(x(v))for(var y=v.length-1;y>0;y--)v[y]-=v[y-1];p.setByName(b,v)}}n.topDict.privateDict=p;var k=new re;for(c=0,l=r.length;c<l;c++)k.add(r[c]);p.subrsIndex=k;return new Z(n).compile()}};return r}(),ke=function(){function e(e,t){this.properties=t;var a=new J(e,t,le);this.cff=a.parse();var r=new Z(this.cff);this.seacs=this.cff.seacs;try{this.data=r.compile()}catch(a){T("Failed to compile font "+t.loadedName);this.data=e}}e.prototype={get numGlyphs(){return this.cff.charStrings.count},getCharset:function(){return this.cff.charset.charset},getGlyphMapping:function(){var e,t,a=this.cff,r=this.properties,i=a.charset.charset;if(r.composite){e=Object.create(null);if(a.isCIDFont)for(t=0;t<i.length;t++){var n=i[t],s=r.cMap.charCodeOf(n);e[s]=t}else for(t=0;t<a.charStrings.count;t++)e[t]=t;return e}e=o(r,a.encoding?a.encoding.encoding:null,i);return e}};return e}();!function(){"undefined"!=typeof navigator&&/Windows/.test(navigator.userAgent)&&(le=!0)}();!function(){"undefined"!=typeof navigator&&/Windows.*Chrome/.test(navigator.userAgent)&&(oe=!0)}();t.SEAC_ANALYSIS_ENABLED=le;t.PRIVATE_USE_OFFSET_START=ne;t.PRIVATE_USE_OFFSET_END=se;t.ErrorFont=ve;t.Font=be;t.FontFlags=he;t.IdentityToUnicodeMap=ge;t.ProblematicCharRanges=me;t.ToUnicodeMap=de;t.getFontType=n},function(e,t,a){"use strict";var r=a(0),i=a(1),n=a(3),s=a(2),o=a(15),c=r.ImageKind,l=r.assert,h=r.error,u=r.info,f=r.isArray,d=r.warn,g=i.Name,p=i.isStream,m=n.ColorSpace,b=s.DecodeStream,v=s.JpegStream,y=o.JpxImage,k=function(){function e(e,t){return t&&t.canDecode(e)?t.decode(e):Promise.resolve(e)}function t(e,t,a,r){e=t+e*a;return e<0?0:e>r?r:e}function a(e,t,a,r,i,n){var s,o,c,l,h=i*n,u=t<=8?new Uint8Array(h):t<=16?new Uint16Array(h):new Uint32Array(h),f=a/i,d=r/n,g=0,p=new Uint16Array(i),m=a;for(s=0;s<i;s++)p[s]=Math.floor(s*f);for(s=0;s<n;s++){c=Math.floor(s*d)*m;for(o=0;o<i;o++){l=c+p[o];u[g++]=e[l]}}return u}function r(e,t,a,i,n,s,o){this.image=a;var c=a.dict;if(c.has("Filter")){var l=c.get("Filter").name;if("JPXDecode"===l){var f=new y;f.parseImageProperties(a.stream);a.stream.reset();a.bitsPerComponent=f.bitsPerComponent;a.numComps=f.componentsCount}else if("JBIG2Decode"===l){a.bitsPerComponent=1;a.numComps=1}}this.width=c.get("Width","W");this.height=c.get("Height","H");(this.width<1||this.height<1)&&h("Invalid image width: "+this.width+" or height: "+this.height);this.interpolate=c.get("Interpolate","I")||!1;this.imageMask=c.get("ImageMask","IM")||!1;this.matte=c.get("Matte")||!1;var b=a.bitsPerComponent;if(!b){b=c.get("BitsPerComponent","BPC");b||(this.imageMask?b=1:h("Bits per component missing in image: "+this.imageMask))}this.bpc=b;if(!this.imageMask){var v=c.get("ColorSpace","CS");if(!v){u("JPX images (which do not require color spaces)");switch(a.numComps){case 1:v=g.get("DeviceGray");break;case 3:v=g.get("DeviceRGB");break;case 4:v=g.get("DeviceCMYK");break;default:h("JPX images with "+this.numComps+" color components not supported.")}}this.colorSpace=m.parse(v,e,t);this.numComps=this.colorSpace.numComps}this.decode=c.getArray("Decode","D");this.needsDecode=!1;if(this.decode&&(this.colorSpace&&!this.colorSpace.isDefaultDecode(this.decode)||o&&!m.isDefaultDecode(this.decode,1))){this.needsDecode=!0;var k=(1<<b)-1;this.decodeCoefficients=[];this.decodeAddends=[];for(var w=0,C=0;w<this.decode.length;w+=2,++C){var x=this.decode[w],S=this.decode[w+1];this.decodeCoefficients[C]=S-x;this.decodeAddends[C]=k*x}}if(n)this.smask=new r(e,t,n,!1);else if(s)if(p(s)){var A=s.dict,I=A.get("ImageMask","IM");I?this.mask=new r(e,t,s,!1,null,null,!0):d("Ignoring /Mask in image without /ImageMask.")}else this.mask=s}r.buildImage=function(t,a,i,n,s,o){var c,l,h=e(n,o),u=n.dict.get("SMask"),g=n.dict.get("Mask");if(u){c=e(u,o);l=Promise.resolve(null)}else{c=Promise.resolve(null);if(g)if(p(g))l=e(g,o);else if(f(g))l=Promise.resolve(g);else{d("Unsupported mask format.");l=Promise.resolve(null)}else l=Promise.resolve(null)}return Promise.all([h,c,l]).then(function(e){var t=e[0],n=e[1],o=e[2];return new r(a,i,t,s,n,o)})};r.createMask=function(e,t,a,r,i){var n,s,o=(t+7>>3)*a,c=e.byteLength,l=o===c;if(!r||i&&!l)if(i){n=new Uint8Array(o);n.set(e);for(s=c;s<o;s++)n[s]=255}else{n=new Uint8Array(c);n.set(e)}else n=e;if(i)for(s=0;s<c;s++)n[s]=~n[s];return{data:n,width:t,height:a}};r.prototype={get drawWidth(){return Math.max(this.width,this.smask&&this.smask.width||0,this.mask&&this.mask.width||0)},get drawHeight(){return Math.max(this.height,this.smask&&this.smask.height||0,this.mask&&this.mask.height||0)},decodeBuffer:function(e){var a,r,i=this.bpc,n=this.numComps,s=this.decodeAddends,o=this.decodeCoefficients,c=(1<<i)-1;if(1!==i){var l=0;for(a=0,r=this.width*this.height;a<r;a++)for(var h=0;h<n;h++){e[l]=t(e[l],s[h],o[h],c);l++}}else for(a=0,r=e.length;a<r;a++)e[a]=+!e[a]},getComponents:function(e){var t=this.bpc;if(8===t)return e;var a,r,i=this.width,n=this.height,s=this.numComps,o=i*n*s,c=0,l=t<=8?new Uint8Array(o):t<=16?new Uint16Array(o):new Uint32Array(o),h=i*s,u=(1<<t)-1,f=0;if(1===t)for(var d,g,p,m=0;m<n;m++){g=f+(-8&h);p=f+h;for(;f<g;){r=e[c++];l[f]=r>>7&1;l[f+1]=r>>6&1;l[f+2]=r>>5&1;l[f+3]=r>>4&1;l[f+4]=r>>3&1;l[f+5]=r>>2&1;l[f+6]=r>>1&1;l[f+7]=1&r;f+=8}if(f<p){r=e[c++];d=128;for(;f<p;){l[f++]=+!!(r&d);d>>=1}}}else{var b=0;r=0;for(f=0,a=o;f<a;++f){if(f%h==0){r=0;b=0}for(;b<t;){r=r<<8|e[c++];b+=8}var v=b-t,y=r>>v;l[f]=y<0?0:y>u?u:y;r&=(1<<v)-1;b=v}}return l},fillOpacity:function(e,t,i,n,s){var o,c,l,u,d,g,p=this.smask,m=this.mask;if(p){c=p.width;l=p.height;o=new Uint8Array(c*l);p.fillGrayBuffer(o);c===t&&l===i||(o=a(o,p.bpc,c,l,t,i))}else if(m)if(m instanceof r){c=m.width;l=m.height;o=new Uint8Array(c*l);m.numComps=1;m.fillGrayBuffer(o);for(u=0,d=c*l;u<d;++u)o[u]=255-o[u];c===t&&l===i||(o=a(o,m.bpc,c,l,t,i))}else if(f(m)){o=new Uint8Array(t*i);var b=this.numComps;for(u=0,d=t*i;u<d;++u){var v=0,y=u*b;for(g=0;g<b;++g){var k=s[y+g],w=2*g;if(k<m[w]||k>m[w+1]){v=255;break}}o[u]=v}}else h("Unknown mask format.");if(o)for(u=0,g=3,d=t*n;u<d;++u,g+=4)e[g]=o[u];else for(u=0,g=3,d=t*n;u<d;++u,g+=4)e[g]=255},undoPreblend:function(e,t,a){var r=this.smask&&this.smask.matte;if(r)for(var i,n,s,o=this.colorSpace.getRgb(r,0),c=o[0],l=o[1],h=o[2],u=t*a*4,f=0;f<u;f+=4){var d=e[f+3];if(0!==d){var g=255/d;i=(e[f]-c)*g+c;n=(e[f+1]-l)*g+l;s=(e[f+2]-h)*g+h;e[f]=i<=0?0:i>=255?255:0|i;e[f+1]=n<=0?0:n>=255?255:0|n;e[f+2]=s<=0?0:s>=255?255:0|s}else{e[f]=255;e[f+1]=255;e[f+2]=255}}},createImageData:function(e){var t,a=this.drawWidth,r=this.drawHeight,i={width:a,height:r},n=this.numComps,s=this.width,o=this.height,h=this.bpc,u=s*n*h+7>>3;if(!e){var f;"DeviceGray"===this.colorSpace.name&&1===h?f=c.GRAYSCALE_1BPP:"DeviceRGB"!==this.colorSpace.name||8!==h||this.needsDecode||(f=c.RGB_24BPP);if(f&&!this.smask&&!this.mask&&a===s&&r===o){i.kind=f;t=this.getImageBytes(o*u);if(this.image instanceof b)i.data=t;else{var d=new Uint8Array(t.length);d.set(t);i.data=d}if(this.needsDecode){l(f===c.GRAYSCALE_1BPP);for(var g=i.data,p=0,m=g.length;p<m;p++)g[p]^=255}return i}if(this.image instanceof v&&!this.smask&&!this.mask&&("DeviceGray"===this.colorSpace.name||"DeviceRGB"===this.colorSpace.name||"DeviceCMYK"===this.colorSpace.name)){i.kind=c.RGB_24BPP;i.data=this.getImageBytes(o*u,a,r,!0);return i}}t=this.getImageBytes(o*u);var y,k,w=0|t.length/u*r/o,C=this.getComponents(t);if(e||this.smask||this.mask){i.kind=c.RGBA_32BPP;i.data=new Uint8Array(a*r*4);y=1;k=!0;this.fillOpacity(i.data,a,r,w,C)}else{i.kind=c.RGB_24BPP;i.data=new Uint8Array(a*r*3);y=0;k=!1}this.needsDecode&&this.decodeBuffer(C);this.colorSpace.fillRgb(i.data,s,o,a,r,w,h,C,y);k&&this.undoPreblend(i.data,a,w);return i},fillGrayBuffer:function(e){var t=this.numComps;1!==t&&h("Reading gray scale from a color image: "+t);var a,r,i=this.width,n=this.height,s=this.bpc,o=i*t*s+7>>3,c=this.getImageBytes(n*o),l=this.getComponents(c);if(1!==s){this.needsDecode&&this.decodeBuffer(l);r=i*n;var u=255/((1<<s)-1);for(a=0;a<r;++a)e[a]=u*l[a]|0}else{r=i*n;if(this.needsDecode)for(a=0;a<r;++a)e[a]=l[a]-1&255;else for(a=0;a<r;++a)e[a]=255&-l[a]}},getImageBytes:function(e,t,a,r){this.image.reset();this.image.drawWidth=t||this.width;this.image.drawHeight=a||this.height;this.image.forceRGB=!!r;return this.image.getBytes(e)}};return r}();t.PDFImage=k},function(e,t,a){"use strict";var r=a(0),i=a(10),n=r.error,s=r.log2,o=r.readInt8,c=r.readUint16,l=r.readUint32,h=r.shadow,u=i.ArithmeticDecoder,f=function(){function e(){}function t(e,t,a){this.data=e;this.start=t;this.end=a}function a(e,t,a){function r(e){for(var t=0,r=0;r<e;r++){var s=a.readBit(i,n);n=n<256?n<<1|s:511&(n<<1|s)|256;t=t<<1|s}return t>>>0}var i=e.getContexts(t),n=1,s=r(1),o=r(1)?r(1)?r(1)?r(1)?r(1)?r(32)+4436:r(12)+340:r(8)+84:r(6)+20:r(4)+4:r(2);return 0===s?o:o>0?-o:null}function r(e,t,a){for(var r=e.getContexts("IAID"),i=1,n=0;n<a;n++){i=i<<1|t.readBit(r,i)}return a<31?i&(1<<a)-1:2147483647&i}function i(e,t,a){var r,i,n,s,o,c,l,h=a.decoder,u=a.contextCache.getContexts("GB"),f=[];for(i=0;i<t;i++){o=f[i]=new Uint8Array(e);c=i<1?o:f[i-1];l=i<2?o:f[i-2];r=l[0]<<13|l[1]<<12|l[2]<<11|c[0]<<7|c[1]<<6|c[2]<<5|c[3]<<4;for(n=0;n<e;n++){o[n]=s=h.readBit(u,r);r=(31735&r)<<1|(n+3<e?l[n+3]<<11:0)|(n+4<e?c[n+4]<<4:0)|s}}return f}function f(e,t,a,r,s,o,c,l){e&&n("JBIG2 error: MMR encoding is not supported");if(0===r&&!o&&!s&&4===c.length&&3===c[0].x&&-1===c[0].y&&-3===c[1].x&&-1===c[1].y&&2===c[2].x&&-2===c[2].y&&-2===c[3].x&&-2===c[3].y)return i(t,a,l);var h=!!o,u=A[r].concat(c);u.sort(function(e,t){return e.y-t.y||e.x-t.x});var f,d,g=u.length,p=new Int8Array(g),m=new Int8Array(g),b=[],v=0,y=0,k=0,w=0;for(d=0;d<g;d++){p[d]=u[d].x;m[d]=u[d].y;y=Math.min(y,u[d].x);k=Math.max(k,u[d].x);w=Math.min(w,u[d].y);d<g-1&&u[d].y===u[d+1].y&&u[d].x===u[d+1].x-1?v|=1<<g-1-d:b.push(d)}var C=b.length,x=new Int8Array(C),S=new Int8Array(C),I=new Uint16Array(C);for(f=0;f<C;f++){d=b[f];x[f]=u[d].x;S[f]=u[d].y;I[f]=1<<g-1-d}for(var R,T,O,P,M,E=-y,L=-w,D=t-k,F=B[r],q=new Uint8Array(t),U=[],N=l.decoder,j=l.contextCache.getContexts("GB"),_=0,z=0,H=0;H<a;H++){if(s){_^=N.readBit(j,F);if(_){U.push(q);continue}}q=new Uint8Array(q);U.push(q);for(R=0;R<t;R++)if(h&&o[H][R])q[R]=0;else{if(R>=E&&R<D&&H>=L){z=z<<1&v;for(d=0;d<C;d++){T=H+S[d];O=R+x[d];P=U[T][O];if(P){P=I[d];z|=P}}}else{z=0;M=g-1;for(d=0;d<g;d++,M--){O=R+p[d];if(O>=0&&O<t){T=H+m[d];if(T>=0){P=U[T][O];P&&(z|=P<<M)}}}}var G=N.readBit(j,z);q[R]=G}}return U}function d(e,t,a,r,i,s,o,c,l){var h=I[a].coding;0===a&&(h=h.concat([c[0]]));var u,f=h.length,d=new Int32Array(f),g=new Int32Array(f);for(u=0;u<f;u++){d[u]=h[u].x;g[u]=h[u].y}var p=I[a].reference;0===a&&(p=p.concat([c[1]]));var m=p.length,b=new Int32Array(m),v=new Int32Array(m);for(u=0;u<m;u++){b[u]=p[u].x;v[u]=p[u].y}for(var y=r[0].length,k=r.length,w=R[a],C=[],x=l.decoder,S=l.contextCache.getContexts("GR"),A=0,B=0;B<t;B++){if(o){A^=x.readBit(S,w);A&&n("JBIG2 error: prediction is not supported")}var T=new Uint8Array(e);C.push(T);for(var O=0;O<e;O++){var P,M,E=0;for(u=0;u<f;u++){P=B+g[u];M=O+d[u];P<0||M<0||M>=e?E<<=1:E=E<<1|C[P][M]}for(u=0;u<m;u++){P=B+v[u]+s;M=O+b[u]+i;P<0||P>=k||M<0||M>=y?E<<=1:E=E<<1|r[P][M]}var L=x.readBit(S,E);T[O]=L}}return C}function g(e,t,i,o,c,l,h,u,g,m,b){e&&n("JBIG2 error: huffman is not supported");for(var v=[],y=0,k=s(i.length+o),w=b.decoder,C=b.contextCache;v.length<o;){y+=a(C,"IADH",w);for(var x=0;;){var S=a(C,"IADW",w);if(null===S)break;x+=S;var A;if(t){var I=a(C,"IAAI",w);if(I>1)A=p(e,t,x,y,0,I,1,i.concat(v),k,0,0,1,0,l,g,m,b);else{var B=r(C,w,k),R=a(C,"IARDX",w),T=a(C,"IARDY",w);A=d(x,y,g,B<i.length?i[B]:v[B-i.length],R,T,!1,m,b)}}else A=f(!1,x,y,h,!1,null,u,b);v.push(A)}}for(var O=[],P=[],M=!1,E=i.length+o;P.length<E;){for(var L=a(C,"IAEX",w);L--;)P.push(M);M=!M}for(var D=0,F=i.length;D<F;D++)P[D]&&O.push(i[D]);for(var q=0;q<o;D++,q++)P[D]&&O.push(v[q]);return O}function p(e,t,i,s,o,c,l,h,u,f,g,p,m,b,v,y,k){e&&n("JBIG2 error: huffman is not supported");var w,C,x=[];for(w=0;w<s;w++){C=new Uint8Array(i);if(o)for(var S=0;S<i;S++)C[S]=o;x.push(C)}var A=k.decoder,I=k.contextCache,B=-a(I,"IADT",A),R=0;w=0;for(;w<c;){B+=a(I,"IADT",A);R+=a(I,"IAFS",A);for(var T=R;;){var O=1===l?0:a(I,"IAIT",A),P=l*B+O,M=r(I,A,u),E=t&&a(I,"IARI",A),L=h[M],D=L[0].length,F=L.length;if(E){var q=a(I,"IARDW",A),U=a(I,"IARDH",A),N=a(I,"IARDX",A),j=a(I,"IARDY",A);D+=q;F+=U;L=d(D,F,v,L,(q>>1)+N,(U>>1)+j,!1,y,k)}var _,z,H,G=P-(1&p?0:F),X=T-(2&p?D:0);if(f){for(_=0;_<F;_++){C=x[X+_];if(C){H=L[_];var V=Math.min(i-G,D);switch(m){case 0:for(z=0;z<V;z++)C[G+z]|=H[z];break;case 2:for(z=0;z<V;z++)C[G+z]^=H[z];break;default:n("JBIG2 error: operator "+m+" is not supported")}}}T+=F-1}else{for(z=0;z<F;z++){C=x[G+z];if(C){H=L[z];switch(m){case 0:for(_=0;_<D;_++)C[X+_]|=H[_];break;case 2:for(_=0;_<D;_++)C[X+_]^=H[_];break;default:n("JBIG2 error: operator "+m+" is not supported")}}}T+=D-1}w++;var W=a(I,"IADS",A);if(null===W)break;T+=W+g}}return x}function m(e,t){var a={};a.number=l(e,t);var r=e[t+4],i=63&r;S[i]||n("JBIG2 error: invalid segment type: "+i);a.type=i;a.typeName=S[i];a.deferredNonRetain=!!(128&r);var s=!!(64&r),o=e[t+5],h=o>>5&7,u=[31&o],f=t+6;if(7===o){h=536870911&l(e,f-1);f+=3;var d=h+7>>3;u[0]=e[f++];for(;--d>0;)u.push(e[f++])}else 5!==o&&6!==o||n("JBIG2 error: invalid referred-to flags");a.retainBits=u;var g,p,m=a.number<=256?1:a.number<=65536?2:4,b=[];for(g=0;g<h;g++){var y=1===m?e[f]:2===m?c(e,f):l(e,f);b.push(y);f+=m}a.referredTo=b;if(s){a.pageAssociation=l(e,f);f+=4}else a.pageAssociation=e[f++];a.length=l(e,f);f+=4;if(4294967295===a.length)if(38===i){var k=v(e,f),w=e[f+T],C=!!(1&w),x=new Uint8Array(6);if(!C){x[0]=255;x[1]=172}x[2]=k.height>>>24&255;x[3]=k.height>>16&255;x[4]=k.height>>8&255;x[5]=255&k.height;for(g=f,p=e.length;g<p;g++){for(var A=0;A<6&&x[A]===e[g+A];)A++;if(6===A){a.length=g+6;break}}4294967295===a.length&&n("JBIG2 error: segment end was not found")}else n("JBIG2 error: invalid unknown segment length");a.headerEnd=f;return a}function b(e,t,a,r){for(var i=[],n=a;n<r;){var s=m(t,n);n=s.headerEnd;var o={header:s,data:t};if(!e.randomAccess){o.start=n;n+=s.length;o.end=n}i.push(o);if(51===s.type)break}if(e.randomAccess)for(var c=0,l=i.length;c<l;c++){i[c].start=n;n+=i[c].header.length;i[c].end=n}return i}function v(e,t){return{width:l(e,t),height:l(e,t+4),x:l(e,t+8),y:l(e,t+12),combinationOperator:7&e[t+16]}}function y(e,t){var a,r,i,s,h=e.header,u=e.data,f=e.start,d=e.end;switch(h.type){case 0:var g={},p=c(u,f);g.huffman=!!(1&p);g.refinement=!!(2&p);g.huffmanDHSelector=p>>2&3;g.huffmanDWSelector=p>>4&3;g.bitmapSizeSelector=p>>6&1;g.aggregationInstancesSelector=p>>7&1;g.bitmapCodingContextUsed=!!(256&p);g.bitmapCodingContextRetained=!!(512&p);g.template=p>>10&3;g.refinementTemplate=p>>12&1;f+=2;if(!g.huffman){s=0===g.template?4:1;r=[];for(i=0;i<s;i++){r.push({x:o(u,f),y:o(u,f+1)});f+=2}g.at=r}if(g.refinement&&!g.refinementTemplate){r=[];for(i=0;i<2;i++){r.push({x:o(u,f),y:o(u,f+1)});f+=2}g.refinementAt=r}g.numberOfExportedSymbols=l(u,f);f+=4;g.numberOfNewSymbols=l(u,f);f+=4;a=[g,h.number,h.referredTo,u,f,d];break;case 6:case 7:var m={};m.info=v(u,f);f+=T;var b=c(u,f);f+=2;m.huffman=!!(1&b);m.refinement=!!(2&b);m.stripSize=1<<(b>>2&3);m.referenceCorner=b>>4&3;m.transposed=!!(64&b);m.combinationOperator=b>>7&3;m.defaultPixelValue=b>>9&1;m.dsOffset=b<<17>>27;m.refinementTemplate=b>>15&1;if(m.huffman){var y=c(u,f);f+=2;m.huffmanFS=3&y;m.huffmanDS=y>>2&3;m.huffmanDT=y>>4&3;m.huffmanRefinementDW=y>>6&3;m.huffmanRefinementDH=y>>8&3;m.huffmanRefinementDX=y>>10&3;m.huffmanRefinementDY=y>>12&3;m.huffmanRefinementSizeSelector=!!(14&y)}if(m.refinement&&!m.refinementTemplate){r=[];for(i=0;i<2;i++){r.push({x:o(u,f),y:o(u,f+1)});f+=2}m.refinementAt=r}m.numberOfSymbolInstances=l(u,f);f+=4;m.huffman&&n("JBIG2 error: huffman is not supported");a=[m,h.referredTo,u,f,d];break;case 38:case 39:var k={};k.info=v(u,f);f+=T;var w=u[f++];k.mmr=!!(1&w);k.template=w>>1&3;k.prediction=!!(8&w);if(!k.mmr){s=0===k.template?4:1;r=[];for(i=0;i<s;i++){r.push({x:o(u,f),y:o(u,f+1)});f+=2}k.at=r}a=[k,u,f,d];break;case 48:var C={width:l(u,f),height:l(u,f+4),resolutionX:l(u,f+8),resolutionY:l(u,f+12)};4294967295===C.height&&delete C.height;var x=u[f+16];c(u,f+17);C.lossless=!!(1&x);C.refinement=!!(2&x);C.defaultPixelValue=x>>2&1;C.combinationOperator=x>>3&3;C.requiresBuffer=!!(32&x);C.combinationOperatorOverride=!!(64&x);a=[C];break;case 49:case 50:case 51:case 62:break;default:n("JBIG2 error: segment type "+h.typeName+"("+h.type+") is not implemented")}var S="on"+h.typeName;S in t&&t[S].apply(t,a)}function k(e,t){for(var a=0,r=e.length;a<r;a++)y(e[a],t)}function w(e){for(var t=new C,a=0,r=e.length;a<r;a++){var i=e[a];k(b({},i.data,i.start,i.end),t)}return t.buffer}function C(){}function x(){}e.prototype={getContexts:function(e){return e in this?this[e]:this[e]=new Int8Array(65536)}};t.prototype={get decoder(){var e=new u(this.data,this.start,this.end);return h(this,"decoder",e)},get contextCache(){var t=new e;return h(this,"contextCache",t)}};var S=["SymbolDictionary",null,null,null,"IntermediateTextRegion",null,"ImmediateTextRegion","ImmediateLosslessTextRegion",null,null,null,null,null,null,null,null,"patternDictionary",null,null,null,"IntermediateHalftoneRegion",null,"ImmediateHalftoneRegion","ImmediateLosslessHalftoneRegion",null,null,null,null,null,null,null,null,null,null,null,null,"IntermediateGenericRegion",null,"ImmediateGenericRegion","ImmediateLosslessGenericRegion","IntermediateGenericRefinementRegion",null,"ImmediateGenericRefinementRegion","ImmediateLosslessGenericRefinementRegion",null,null,null,null,"PageInformation","EndOfPage","EndOfStripe","EndOfFile","Profiles","Tables",null,null,null,null,null,null,null,null,"Extension"],A=[[{x:-1,y:-2},{x:0,y:-2},{x:1,y:-2},{x:-2,y:-1},{x:-1,y:-1},{x:0,y:-1},{x:1,y:-1},{x:2,y:-1},{x:-4,y:0},{x:-3,y:0},{x:-2,y:0},{x:-1,y:0}],[{x:-1,y:-2},{x:0,y:-2},{x:1,y:-2},{x:2,y:-2},{x:-2,y:-1},{x:-1,y:-1},{x:0,y:-1},{x:1,y:-1},{x:2,y:-1},{x:-3,y:0},{x:-2,y:0},{x:-1,y:0}],[{x:-1,y:-2},{x:0,y:-2},{x:1,y:-2},{x:-2,y:-1},{x:-1,y:-1},{x:0,y:-1},{x:1,y:-1},{x:-2,y:0},{x:-1,y:0}],[{x:-3,y:-1},{x:-2,y:-1},{x:-1,y:-1},{x:0,y:-1},{x:1,y:-1},{x:-4,y:0},{x:-3,y:0},{x:-2,y:0},{x:-1,y:0}]],I=[{coding:[{x:0,y:-1},{x:1,y:-1},{x:-1,y:0}],reference:[{x:0,y:-1},{x:1,y:-1},{x:-1,y:0},{x:0,y:0},{x:1,y:0},{x:-1,y:1},{x:0,y:1},{x:1,y:1}]},{coding:[{x:-1,y:-1},{x:0,y:-1},{x:1,y:-1},{x:-1,y:0}],reference:[{x:0,y:-1},{x:-1,y:0},{x:0,y:0},{x:1,y:0},{x:0,y:1},{x:1,y:1}]}],B=[39717,1941,229,405],R=[32,8],T=17;C.prototype={onPageInformation:function(e){this.currentPageInfo=e;var t=e.width+7>>3,a=new Uint8Array(t*e.height);if(e.defaultPixelValue)for(var r=0,i=a.length;r<i;r++)a[r]=255;this.buffer=a},drawBitmap:function(e,t){var a,r,i,s,o=this.currentPageInfo,c=e.width,l=e.height,h=o.width+7>>3,u=o.combinationOperatorOverride?e.combinationOperator:o.combinationOperator,f=this.buffer,d=128>>(7&e.x),g=e.y*h+(e.x>>3);switch(u){case 0:for(a=0;a<l;a++){i=d;s=g;for(r=0;r<c;r++){t[a][r]&&(f[s]|=i);i>>=1;if(!i){i=128;s++}}g+=h}break;case 2:for(a=0;a<l;a++){i=d;s=g;for(r=0;r<c;r++){t[a][r]&&(f[s]^=i);i>>=1;if(!i){i=128;s++}}g+=h}break;default:n("JBIG2 error: operator "+u+" is not supported")}},onImmediateGenericRegion:function(e,a,r,i){var n=e.info,s=new t(a,r,i),o=f(e.mmr,n.width,n.height,e.template,e.prediction,null,e.at,s);this.drawBitmap(n,o)},onImmediateLosslessGenericRegion:function(){this.onImmediateGenericRegion.apply(this,arguments)},onSymbolDictionary:function(e,a,r,i,s,o){e.huffman&&n("JBIG2 error: huffman is not supported");var c=this.symbols;c||(this.symbols=c={});for(var l=[],h=0,u=r.length;h<u;h++)l=l.concat(c[r[h]]);var f=new t(i,s,o);c[a]=g(e.huffman,e.refinement,l,e.numberOfNewSymbols,e.numberOfExportedSymbols,void 0,e.template,e.at,e.refinementTemplate,e.refinementAt,f)},onImmediateTextRegion:function(e,a,r,i,n){for(var o=e.info,c=this.symbols,l=[],h=0,u=a.length;h<u;h++)l=l.concat(c[a[h]]);var f=s(l.length),d=new t(r,i,n),g=p(e.huffman,e.refinement,o.width,o.height,e.defaultPixelValue,e.numberOfSymbolInstances,e.stripSize,l,f,e.transposed,e.dsOffset,e.referenceCorner,e.combinationOperator,void 0,e.refinementTemplate,e.refinementAt,d);this.drawBitmap(o,g)},onImmediateLosslessTextRegion:function(){this.onImmediateTextRegion.apply(this,arguments)}};x.prototype={parseChunks:function(e){return w(e)}};return x}();t.Jbig2Image=f},function(e,t,a){"use strict";var r=a(0),i=r.warn,n=r.error,s=function(){function e(){this.decodeTransform=null;this.colorTransform=-1}function t(e,t){for(var a,r,i=0,n=[],s=16;s>0&&!e[s-1];)s--;n.push({children:[],index:0});var o,c=n[0];for(a=0;a<s;a++){for(r=0;r<e[a];r++){c=n.pop();c.children[c.index]=t[i];for(;c.index>0;)c=n.pop();c.index++;n.push(c);for(;n.length<=a;){n.push(o={children:[],index:0});c.children[c.index]=o.children;c=o}i++}if(a+1<s){n.push(o={children:[],index:0});c.children[c.index]=o.children;c=o}}return n[0].children}function a(e,t,a){return 64*((e.blocksPerLine+1)*t+a)}function r(e,t,r,s,o,c,u,f,d){function g(){if(L>0){L--;return E>>L&1}E=e[t++];if(255===E){var a=e[t++];a&&n("JPEG error: unexpected marker "+(E<<8|a).toString(16))}L=7;return E>>>7}function p(e){for(var t=e;;){t=t[g()];if("number"==typeof t)return t;"object"!=typeof t&&n("JPEG error: invalid huffman sequence")}}function m(e){for(var t=0;e>0;){t=t<<1|g();e--}return t}function b(e){if(1===e)return 1===g()?1:-1;var t=m(e);return t>=1<<e-1?t:t+(-1<<e)+1}function v(e,t){var a=p(e.huffmanTableDC),r=0===a?0:b(a);e.blockData[t]=e.pred+=r;for(var i=1;i<64;){var n=p(e.huffmanTableAC),s=15&n,o=n>>4;if(0!==s){i+=o;var c=h[i];e.blockData[t+c]=b(s);i++}else{if(o<15)break;i+=16}}}function y(e,t){var a=p(e.huffmanTableDC),r=0===a?0:b(a)<<d;e.blockData[t]=e.pred+=r}function k(e,t){e.blockData[t]|=g()<<d}function w(e,t){if(D>0)D--;else for(var a=c,r=u;a<=r;){var i=p(e.huffmanTableAC),n=15&i,s=i>>4;if(0!==n){a+=s;var o=h[a];e.blockData[t+o]=b(n)*(1<<d);a++}else{if(s<15){D=m(s)+(1<<s)-1;break}a+=16}}}function C(e,t){for(var a,r,i=c,s=u,o=0;i<=s;){var l=h[i];switch(F){case 0:r=p(e.huffmanTableAC);a=15&r;o=r>>4;if(0===a)if(o<15){D=m(o)+(1<<o);F=4}else{o=16;F=1}else{1!==a&&n("JPEG error: invalid ACn encoding");x=b(a);F=o?2:3}continue;case 1:case 2:if(e.blockData[t+l])e.blockData[t+l]+=g()<<d;else{o--;0===o&&(F=2===F?3:0)}break;case 3:if(e.blockData[t+l])e.blockData[t+l]+=g()<<d;else{e.blockData[t+l]=x<<d;F=0}break;case 4:e.blockData[t+l]&&(e.blockData[t+l]+=g()<<d)}i++}if(4===F){D--;0===D&&(F=0)}}var x,S,A,I,B,R,T,O=r.mcusPerLine,P=r.progressive,M=t,E=0,L=0,D=0,F=0,q=s.length;T=P?0===c?0===f?y:k:0===f?w:C:v;var U,N,j=0;N=1===q?s[0].blocksPerLine*s[0].blocksPerColumn:O*r.mcusPerColumn;for(var _,z;j<N;){var H=o?Math.min(N-j,o):N;for(A=0;A<q;A++)s[A].pred=0;D=0;if(1===q){S=s[0];for(R=0;R<H;R++){!function(e,t,r){t(e,a(e,r/e.blocksPerLine|0,r%e.blocksPerLine))}(S,T,j);j++}}else for(R=0;R<H;R++){for(A=0;A<q;A++){S=s[A];_=S.h;z=S.v;for(I=0;I<z;I++)for(B=0;B<_;B++)!function(e,t,r,i,n){var s=r/O|0,o=r%O;t(e,a(e,s*e.v+i,o*e.h+n))}(S,T,j,I,B)}j++}L=0;U=l(e,t);if(U&&U.invalid){i("decodeScan - unexpected MCU data, next marker is: "+U.invalid);t=U.offset}var G=U&&U.marker;(!G||G<=65280)&&n("JPEG error: marker was not found");if(!(G>=65488&&G<=65495))break;t+=2}U=l(e,t);if(U&&U.invalid){i("decodeScan - unexpected Scan data, next marker is: "+U.invalid);t=U.offset}return t-M}function s(e,t,a){var r,i,s,o,c,l,h,y,k,w,C,x,S,A,I,B,R,T=e.quantizationTable,O=e.blockData;T||n("JPEG error: missing required Quantization Table.");for(var P=0;P<64;P+=8){k=O[t+P];w=O[t+P+1];C=O[t+P+2];x=O[t+P+3];S=O[t+P+4];A=O[t+P+5];I=O[t+P+6];B=O[t+P+7];k*=T[P];if(0!=(w|C|x|S|A|I|B)){w*=T[P+1];C*=T[P+2];x*=T[P+3];S*=T[P+4];A*=T[P+5];I*=T[P+6];B*=T[P+7];r=b*k+128>>8;i=b*S+128>>8;s=C;o=I;c=v*(w-B)+128>>8;y=v*(w+B)+128>>8;l=x<<4;h=A<<4;r=r+i+1>>1;i=r-i;R=s*m+o*p+128>>8;s=s*p-o*m+128>>8;o=R;c=c+h+1>>1;h=c-h;y=y+l+1>>1;l=y-l;r=r+o+1>>1;o=r-o;i=i+s+1>>1;s=i-s;R=c*g+y*d+2048>>12;c=c*d-y*g+2048>>12;y=R;R=l*f+h*u+2048>>12;l=l*u-h*f+2048>>12;h=R;a[P]=r+y;a[P+7]=r-y;a[P+1]=i+h;a[P+6]=i-h;a[P+2]=s+l;a[P+5]=s-l;a[P+3]=o+c;a[P+4]=o-c}else{R=b*k+512>>10;a[P]=R;a[P+1]=R;a[P+2]=R;a[P+3]=R;a[P+4]=R;a[P+5]=R;a[P+6]=R;a[P+7]=R}}for(var M=0;M<8;++M){k=a[M];w=a[M+8];C=a[M+16];x=a[M+24];S=a[M+32];A=a[M+40];I=a[M+48];B=a[M+56];if(0!=(w|C|x|S|A|I|B)){r=b*k+2048>>12;i=b*S+2048>>12;s=C;o=I;c=v*(w-B)+2048>>12;y=v*(w+B)+2048>>12;l=x;h=A;r=4112+(r+i+1>>1);i=r-i;R=s*m+o*p+2048>>12;s=s*p-o*m+2048>>12;o=R;c=c+h+1>>1;h=c-h;y=y+l+1>>1;l=y-l;r=r+o+1>>1;o=r-o;i=i+s+1>>1;s=i-s;R=c*g+y*d+2048>>12;c=c*d-y*g+2048>>12;y=R;R=l*f+h*u+2048>>12;l=l*u-h*f+2048>>12;h=R;k=r+y;B=r-y;w=i+h;I=i-h;C=s+l;A=s-l;x=o+c;S=o-c;k=k<16?0:k>=4080?255:k>>4;w=w<16?0:w>=4080?255:w>>4;C=C<16?0:C>=4080?255:C>>4;x=x<16?0:x>=4080?255:x>>4;S=S<16?0:S>=4080?255:S>>4;A=A<16?0:A>=4080?255:A>>4;I=I<16?0:I>=4080?255:I>>4;B=B<16?0:B>=4080?255:B>>4;O[t+M]=k;O[t+M+8]=w;O[t+M+16]=C;O[t+M+24]=x;O[t+M+32]=S;O[t+M+40]=A;O[t+M+48]=I;O[t+M+56]=B}else{R=b*k+8192>>14;R=R<-2040?0:R>=2024?255:R+2056>>4;O[t+M]=R;O[t+M+8]=R;O[t+M+16]=R;O[t+M+24]=R;O[t+M+32]=R;O[t+M+40]=R;O[t+M+48]=R;O[t+M+56]=R}}}function o(e,t){for(var r=t.blocksPerLine,i=t.blocksPerColumn,n=new Int16Array(64),o=0;o<i;o++)for(var c=0;c<r;c++){var l=a(t,o,c);s(t,l,n)}return t.blockData}function c(e){return e<=0?0:e>=255?255:e}function l(e,t,a){function r(t){return e[t]<<8|e[t+1]}var i=e.length-1,n=a<t?a:t;if(t>=i)return null;var s=r(t);if(s>=65472&&s<=65534)return{invalid:null,marker:s,offset:t};for(var o=r(n);!(o>=65472&&o<=65534);){if(++n>=i)return null;o=r(n)}return{invalid:s.toString(16),marker:o,offset:n}}var h=new Uint8Array([0,1,8,16,9,2,3,10,17,24,32,25,18,11,4,5,12,19,26,33,40,48,41,34,27,20,13,6,7,14,21,28,35,42,49,56,57,50,43,36,29,22,15,23,30,37,44,51,58,59,52,45,38,31,39,46,53,60,61,54,47,55,62,63]),u=4017,f=799,d=3406,g=2276,p=1567,m=3784,b=5793,v=2896;e.prototype={parse:function(e){function a(){var t=e[u]<<8|e[u+1];u+=2;return t}var s,c,u=0,f=null,d=null,g=[],p=[],m=[],b=a();65496!==b&&n("JPEG error: SOI not found");b=a();for(;65497!==b;){var v,y,k;switch(b){case 65504:case 65505:case 65506:case 65507:case 65508:case 65509:case 65510:case 65511:case 65512:case 65513:case 65514:
+case 65515:case 65516:case 65517:case 65518:case 65519:case 65534:var w=function(){var t=a(),r=u+t-2,n=l(e,r,u);if(n&&n.invalid){i("readDataBlock - incorrect length, next marker is: "+n.invalid);r=n.offset}var s=e.subarray(u,r);u+=s.length;return s}();65504===b&&74===w[0]&&70===w[1]&&73===w[2]&&70===w[3]&&0===w[4]&&(f={version:{major:w[5],minor:w[6]},densityUnits:w[7],xDensity:w[8]<<8|w[9],yDensity:w[10]<<8|w[11],thumbWidth:w[12],thumbHeight:w[13],thumbData:w.subarray(14,14+3*w[12]*w[13])});65518===b&&65===w[0]&&100===w[1]&&111===w[2]&&98===w[3]&&101===w[4]&&(d={version:w[5]<<8|w[6],flags0:w[7]<<8|w[8],flags1:w[9]<<8|w[10],transformCode:w[11]});break;case 65499:for(var C,x=a(),S=x+u-2;u<S;){var A=e[u++],I=new Uint16Array(64);if(A>>4==0)for(y=0;y<64;y++){C=h[y];I[C]=e[u++]}else if(A>>4==1)for(y=0;y<64;y++){C=h[y];I[C]=a()}else n("JPEG error: DQT - invalid table spec");g[15&A]=I}break;case 65472:case 65473:case 65474:s&&n("JPEG error: Only single frame JPEGs supported");a();s={};s.extended=65473===b;s.progressive=65474===b;s.precision=e[u++];s.scanLines=a();s.samplesPerLine=a();s.components=[];s.componentIds={};var B,R=e[u++],T=0,O=0;for(v=0;v<R;v++){B=e[u];var P=e[u+1]>>4,M=15&e[u+1];T<P&&(T=P);O<M&&(O=M);var E=e[u+2];k=s.components.push({h:P,v:M,quantizationId:E,quantizationTable:null});s.componentIds[B]=k-1;u+=3}s.maxH=T;s.maxV=O;!function(e){for(var t=Math.ceil(e.samplesPerLine/8/e.maxH),a=Math.ceil(e.scanLines/8/e.maxV),r=0;r<e.components.length;r++){N=e.components[r];var i=Math.ceil(Math.ceil(e.samplesPerLine/8)*N.h/e.maxH),n=Math.ceil(Math.ceil(e.scanLines/8)*N.v/e.maxV),s=t*N.h,o=a*N.v,c=64*o*(s+1);N.blockData=new Int16Array(c);N.blocksPerLine=i;N.blocksPerColumn=n}e.mcusPerLine=t;e.mcusPerColumn=a}(s);break;case 65476:var L=a();for(v=2;v<L;){var D=e[u++],F=new Uint8Array(16),q=0;for(y=0;y<16;y++,u++)q+=F[y]=e[u];var U=new Uint8Array(q);for(y=0;y<q;y++,u++)U[y]=e[u];v+=17+q;(D>>4==0?m:p)[15&D]=t(F,U)}break;case 65501:a();c=a();break;case 65498:a();var N,j=e[u++],_=[];for(v=0;v<j;v++){var z=s.componentIds[e[u++]];N=s.components[z];var H=e[u++];N.huffmanTableDC=m[H>>4];N.huffmanTableAC=p[15&H];_.push(N)}var G=e[u++],X=e[u++],V=e[u++],W=r(e,u,s,_,c,G,X,V>>4,15&V);u+=W;break;case 65535:255!==e[u]&&u--;break;default:if(255===e[u-3]&&e[u-2]>=192&&e[u-2]<=254){u-=3;break}n("JPEG error: unknown marker "+b.toString(16))}b=a()}this.width=s.samplesPerLine;this.height=s.scanLines;this.jfif=f;this.adobe=d;this.components=[];for(v=0;v<s.components.length;v++){N=s.components[v];var K=g[N.quantizationId];K&&(N.quantizationTable=K);this.components.push({output:o(s,N),scaleX:N.h/s.maxH,scaleY:N.v/s.maxV,blocksPerLine:N.blocksPerLine,blocksPerColumn:N.blocksPerColumn})}this.numComponents=this.components.length},_getLinearizedBlockData:function(e,t){var a,r,i,n,s,o,c,l,h,u,f,d=this.width/e,g=this.height/t,p=0,m=this.components.length,b=e*t*m,v=new Uint8Array(b),y=new Uint32Array(e);for(c=0;c<m;c++){a=this.components[c];r=a.scaleX*d;i=a.scaleY*g;p=c;f=a.output;n=a.blocksPerLine+1<<3;for(s=0;s<e;s++){l=0|s*r;y[s]=(4294967288&l)<<3|7&l}for(o=0;o<t;o++){l=0|o*i;u=n*(4294967288&l)|(7&l)<<3;for(s=0;s<e;s++){v[p]=f[u+y[s]];p+=m}}}var k=this.decodeTransform;if(k)for(c=0;c<b;)for(l=0,h=0;l<m;l++,c++,h+=2)v[c]=(v[c]*k[h]>>8)+k[h+1];return v},_isColorConversionNeeded:function(){return!(!this.adobe||!this.adobe.transformCode)||(3===this.numComponents?!(!this.adobe&&0===this.colorTransform):!this.adobe&&1===this.colorTransform)},_convertYccToRgb:function(e){for(var t,a,r,i=0,n=e.length;i<n;i+=3){t=e[i];a=e[i+1];r=e[i+2];e[i]=c(t-179.456+1.402*r);e[i+1]=c(t+135.459-.344*a-.714*r);e[i+2]=c(t-226.816+1.772*a)}return e},_convertYcckToRgb:function(e){for(var t,a,r,i,n=0,s=0,o=e.length;s<o;s+=4){t=e[s];a=e[s+1];r=e[s+2];i=e[s+3];var l=a*(-660635669420364e-19*a+.000437130475926232*r-54080610064599e-18*t+.00048449797120281*i-.154362151871126)-122.67195406894+r*(-.000957964378445773*r+.000817076911346625*t-.00477271405408747*i+1.53380253221734)+t*(.000961250184130688*t-.00266257332283933*i+.48357088451265)+i*(-.000336197177618394*i+.484791561490776),h=107.268039397724+a*(219927104525741e-19*a-.000640992018297945*r+.000659397001245577*t+.000426105652938837*i-.176491792462875)+r*(-.000778269941513683*r+.00130872261408275*t+.000770482631801132*i-.151051492775562)+t*(.00126935368114843*t-.00265090189010898*i+.25802910206845)+i*(-.000318913117588328*i-.213742400323665),u=a*(-.000570115196973677*a-263409051004589e-19*r+.0020741088115012*t-.00288260236853442*i+.814272968359295)-20.810012546947+r*(-153496057440975e-19*r-.000132689043961446*t+.000560833691242812*i-.195152027534049)+t*(.00174418132927582*t-.00255243321439347*i+.116935020465145)+i*(-.000343531996510555*i+.24165260232407);e[n++]=c(l);e[n++]=c(h);e[n++]=c(u)}return e},_convertYcckToCmyk:function(e){for(var t,a,r,i=0,n=e.length;i<n;i+=4){t=e[i];a=e[i+1];r=e[i+2];e[i]=c(434.456-t-1.402*r);e[i+1]=c(119.541-t+.344*a+.714*r);e[i+2]=c(481.816-t-1.772*a)}return e},_convertCmykToRgb:function(e){for(var t,a,r,i,n=0,s=-16581375,o=0,c=e.length;o<c;o+=4){t=e[o];a=e[o+1];r=e[o+2];i=e[o+3];var l=t*(-4.387332384609988*t+54.48615194189176*a+18.82290502165302*r+212.25662451639585*i-72734.4411664936)+a*(1.7149763477362134*a-5.6096736904047315*r-17.873870861415444*i-1401.7366389350734)+r*(-2.5217340131683033*r-21.248923337353073*i+4465.541406466231)-i*(21.86122147463605*i+48317.86113160301),h=t*(8.841041422036149*t+60.118027045597366*a+6.871425592049007*r+31.159100130055922*i-20220.756542821975)+a*(-15.310361306967817*a+17.575251261109482*r+131.35250912493976*i-48691.05921601825)+r*(4.444339102852739*r+9.8632861493405*i-6341.191035517494)-i*(20.737325471181034*i+47890.15695978492),u=t*(.8842522430003296*t+8.078677503112928*a+30.89978309703729*r-.23883238689178934*i-3616.812083916688)+a*(10.49593273432072*a+63.02378494754052*r+50.606957656360734*i-28620.90484698408)+r*(.03296041114873217*r+115.60384449646641*i-49363.43385999684)-i*(22.33816807309886*i+45932.16563550634);e[n++]=l>=0?255:l<=s?0:255+l*(1/255/255)|0;e[n++]=h>=0?255:h<=s?0:255+h*(1/255/255)|0;e[n++]=u>=0?255:u<=s?0:255+u*(1/255/255)|0}return e},getData:function(e,t,a){this.numComponents>4&&n("JPEG error: Unsupported color mode");var r=this._getLinearizedBlockData(e,t);if(1===this.numComponents&&a){for(var i=r.length,s=new Uint8Array(3*i),o=0,c=0;c<i;c++){var l=r[c];s[o++]=l;s[o++]=l;s[o++]=l}return s}if(3===this.numComponents&&this._isColorConversionNeeded())return this._convertYccToRgb(r);if(4===this.numComponents){if(this._isColorConversionNeeded())return a?this._convertYcckToRgb(r):this._convertYcckToCmyk(r);if(a)return this._convertCmykToRgb(r)}return r}};return e}();t.JpegImage=s},function(e,t,a){"use strict";var r=a(0),i=r.getLookupTableFactory,n=i(function(e){e.Courier=600;e["Courier-Bold"]=600;e["Courier-BoldOblique"]=600;e["Courier-Oblique"]=600;e.Helvetica=i(function(e){e.space=278;e.exclam=278;e.quotedbl=355;e.numbersign=556;e.dollar=556;e.percent=889;e.ampersand=667;e.quoteright=222;e.parenleft=333;e.parenright=333;e.asterisk=389;e.plus=584;e.comma=278;e.hyphen=333;e.period=278;e.slash=278;e.zero=556;e.one=556;e.two=556;e.three=556;e.four=556;e.five=556;e.six=556;e.seven=556;e.eight=556;e.nine=556;e.colon=278;e.semicolon=278;e.less=584;e.equal=584;e.greater=584;e.question=556;e.at=1015;e.A=667;e.B=667;e.C=722;e.D=722;e.E=667;e.F=611;e.G=778;e.H=722;e.I=278;e.J=500;e.K=667;e.L=556;e.M=833;e.N=722;e.O=778;e.P=667;e.Q=778;e.R=722;e.S=667;e.T=611;e.U=722;e.V=667;e.W=944;e.X=667;e.Y=667;e.Z=611;e.bracketleft=278;e.backslash=278;e.bracketright=278;e.asciicircum=469;e.underscore=556;e.quoteleft=222;e.a=556;e.b=556;e.c=500;e.d=556;e.e=556;e.f=278;e.g=556;e.h=556;e.i=222;e.j=222;e.k=500;e.l=222;e.m=833;e.n=556;e.o=556;e.p=556;e.q=556;e.r=333;e.s=500;e.t=278;e.u=556;e.v=500;e.w=722;e.x=500;e.y=500;e.z=500;e.braceleft=334;e.bar=260;e.braceright=334;e.asciitilde=584;e.exclamdown=333;e.cent=556;e.sterling=556;e.fraction=167;e.yen=556;e.florin=556;e.section=556;e.currency=556;e.quotesingle=191;e.quotedblleft=333;e.guillemotleft=556;e.guilsinglleft=333;e.guilsinglright=333;e.fi=500;e.fl=500;e.endash=556;e.dagger=556;e.daggerdbl=556;e.periodcentered=278;e.paragraph=537;e.bullet=350;e.quotesinglbase=222;e.quotedblbase=333;e.quotedblright=333;e.guillemotright=556;e.ellipsis=1e3;e.perthousand=1e3;e.questiondown=611;e.grave=333;e.acute=333;e.circumflex=333;e.tilde=333;e.macron=333;e.breve=333;e.dotaccent=333;e.dieresis=333;e.ring=333;e.cedilla=333;e.hungarumlaut=333;e.ogonek=333;e.caron=333;e.emdash=1e3;e.AE=1e3;e.ordfeminine=370;e.Lslash=556;e.Oslash=778;e.OE=1e3;e.ordmasculine=365;e.ae=889;e.dotlessi=278;e.lslash=222;e.oslash=611;e.oe=944;e.germandbls=611;e.Idieresis=278;e.eacute=556;e.abreve=556;e.uhungarumlaut=556;e.ecaron=556;e.Ydieresis=667;e.divide=584;e.Yacute=667;e.Acircumflex=667;e.aacute=556;e.Ucircumflex=722;e.yacute=500;e.scommaaccent=500;e.ecircumflex=556;e.Uring=722;e.Udieresis=722;e.aogonek=556;e.Uacute=722;e.uogonek=556;e.Edieresis=667;e.Dcroat=722;e.commaaccent=250;e.copyright=737;e.Emacron=667;e.ccaron=500;e.aring=556;e.Ncommaaccent=722;e.lacute=222;e.agrave=556;e.Tcommaaccent=611;e.Cacute=722;e.atilde=556;e.Edotaccent=667;e.scaron=500;e.scedilla=500;e.iacute=278;e.lozenge=471;e.Rcaron=722;e.Gcommaaccent=778;e.ucircumflex=556;e.acircumflex=556;e.Amacron=667;e.rcaron=333;e.ccedilla=500;e.Zdotaccent=611;e.Thorn=667;e.Omacron=778;e.Racute=722;e.Sacute=667;e.dcaron=643;e.Umacron=722;e.uring=556;e.threesuperior=333;e.Ograve=778;e.Agrave=667;e.Abreve=667;e.multiply=584;e.uacute=556;e.Tcaron=611;e.partialdiff=476;e.ydieresis=500;e.Nacute=722;e.icircumflex=278;e.Ecircumflex=667;e.adieresis=556;e.edieresis=556;e.cacute=500;e.nacute=556;e.umacron=556;e.Ncaron=722;e.Iacute=278;e.plusminus=584;e.brokenbar=260;e.registered=737;e.Gbreve=778;e.Idotaccent=278;e.summation=600;e.Egrave=667;e.racute=333;e.omacron=556;e.Zacute=611;e.Zcaron=611;e.greaterequal=549;e.Eth=722;e.Ccedilla=722;e.lcommaaccent=222;e.tcaron=317;e.eogonek=556;e.Uogonek=722;e.Aacute=667;e.Adieresis=667;e.egrave=556;e.zacute=500;e.iogonek=222;e.Oacute=778;e.oacute=556;e.amacron=556;e.sacute=500;e.idieresis=278;e.Ocircumflex=778;e.Ugrave=722;e.Delta=612;e.thorn=556;e.twosuperior=333;e.Odieresis=778;e.mu=556;e.igrave=278;e.ohungarumlaut=556;e.Eogonek=667;e.dcroat=556;e.threequarters=834;e.Scedilla=667;e.lcaron=299;e.Kcommaaccent=667;e.Lacute=556;e.trademark=1e3;e.edotaccent=556;e.Igrave=278;e.Imacron=278;e.Lcaron=556;e.onehalf=834;e.lessequal=549;e.ocircumflex=556;e.ntilde=556;e.Uhungarumlaut=722;e.Eacute=667;e.emacron=556;e.gbreve=556;e.onequarter=834;e.Scaron=667;e.Scommaaccent=667;e.Ohungarumlaut=778;e.degree=400;e.ograve=556;e.Ccaron=722;e.ugrave=556;e.radical=453;e.Dcaron=722;e.rcommaaccent=333;e.Ntilde=722;e.otilde=556;e.Rcommaaccent=722;e.Lcommaaccent=556;e.Atilde=667;e.Aogonek=667;e.Aring=667;e.Otilde=778;e.zdotaccent=500;e.Ecaron=667;e.Iogonek=278;e.kcommaaccent=500;e.minus=584;e.Icircumflex=278;e.ncaron=556;e.tcommaaccent=278;e.logicalnot=584;e.odieresis=556;e.udieresis=556;e.notequal=549;e.gcommaaccent=556;e.eth=556;e.zcaron=500;e.ncommaaccent=556;e.onesuperior=333;e.imacron=278;e.Euro=556});e["Helvetica-Bold"]=i(function(e){e.space=278;e.exclam=333;e.quotedbl=474;e.numbersign=556;e.dollar=556;e.percent=889;e.ampersand=722;e.quoteright=278;e.parenleft=333;e.parenright=333;e.asterisk=389;e.plus=584;e.comma=278;e.hyphen=333;e.period=278;e.slash=278;e.zero=556;e.one=556;e.two=556;e.three=556;e.four=556;e.five=556;e.six=556;e.seven=556;e.eight=556;e.nine=556;e.colon=333;e.semicolon=333;e.less=584;e.equal=584;e.greater=584;e.question=611;e.at=975;e.A=722;e.B=722;e.C=722;e.D=722;e.E=667;e.F=611;e.G=778;e.H=722;e.I=278;e.J=556;e.K=722;e.L=611;e.M=833;e.N=722;e.O=778;e.P=667;e.Q=778;e.R=722;e.S=667;e.T=611;e.U=722;e.V=667;e.W=944;e.X=667;e.Y=667;e.Z=611;e.bracketleft=333;e.backslash=278;e.bracketright=333;e.asciicircum=584;e.underscore=556;e.quoteleft=278;e.a=556;e.b=611;e.c=556;e.d=611;e.e=556;e.f=333;e.g=611;e.h=611;e.i=278;e.j=278;e.k=556;e.l=278;e.m=889;e.n=611;e.o=611;e.p=611;e.q=611;e.r=389;e.s=556;e.t=333;e.u=611;e.v=556;e.w=778;e.x=556;e.y=556;e.z=500;e.braceleft=389;e.bar=280;e.braceright=389;e.asciitilde=584;e.exclamdown=333;e.cent=556;e.sterling=556;e.fraction=167;e.yen=556;e.florin=556;e.section=556;e.currency=556;e.quotesingle=238;e.quotedblleft=500;e.guillemotleft=556;e.guilsinglleft=333;e.guilsinglright=333;e.fi=611;e.fl=611;e.endash=556;e.dagger=556;e.daggerdbl=556;e.periodcentered=278;e.paragraph=556;e.bullet=350;e.quotesinglbase=278;e.quotedblbase=500;e.quotedblright=500;e.guillemotright=556;e.ellipsis=1e3;e.perthousand=1e3;e.questiondown=611;e.grave=333;e.acute=333;e.circumflex=333;e.tilde=333;e.macron=333;e.breve=333;e.dotaccent=333;e.dieresis=333;e.ring=333;e.cedilla=333;e.hungarumlaut=333;e.ogonek=333;e.caron=333;e.emdash=1e3;e.AE=1e3;e.ordfeminine=370;e.Lslash=611;e.Oslash=778;e.OE=1e3;e.ordmasculine=365;e.ae=889;e.dotlessi=278;e.lslash=278;e.oslash=611;e.oe=944;e.germandbls=611;e.Idieresis=278;e.eacute=556;e.abreve=556;e.uhungarumlaut=611;e.ecaron=556;e.Ydieresis=667;e.divide=584;e.Yacute=667;e.Acircumflex=722;e.aacute=556;e.Ucircumflex=722;e.yacute=556;e.scommaaccent=556;e.ecircumflex=556;e.Uring=722;e.Udieresis=722;e.aogonek=556;e.Uacute=722;e.uogonek=611;e.Edieresis=667;e.Dcroat=722;e.commaaccent=250;e.copyright=737;e.Emacron=667;e.ccaron=556;e.aring=556;e.Ncommaaccent=722;e.lacute=278;e.agrave=556;e.Tcommaaccent=611;e.Cacute=722;e.atilde=556;e.Edotaccent=667;e.scaron=556;e.scedilla=556;e.iacute=278;e.lozenge=494;e.Rcaron=722;e.Gcommaaccent=778;e.ucircumflex=611;e.acircumflex=556;e.Amacron=722;e.rcaron=389;e.ccedilla=556;e.Zdotaccent=611;e.Thorn=667;e.Omacron=778;e.Racute=722;e.Sacute=667;e.dcaron=743;e.Umacron=722;e.uring=611;e.threesuperior=333;e.Ograve=778;e.Agrave=722;e.Abreve=722;e.multiply=584;e.uacute=611;e.Tcaron=611;e.partialdiff=494;e.ydieresis=556;e.Nacute=722;e.icircumflex=278;e.Ecircumflex=667;e.adieresis=556;e.edieresis=556;e.cacute=556;e.nacute=611;e.umacron=611;e.Ncaron=722;e.Iacute=278;e.plusminus=584;e.brokenbar=280;e.registered=737;e.Gbreve=778;e.Idotaccent=278;e.summation=600;e.Egrave=667;e.racute=389;e.omacron=611;e.Zacute=611;e.Zcaron=611;e.greaterequal=549;e.Eth=722;e.Ccedilla=722;e.lcommaaccent=278;e.tcaron=389;e.eogonek=556;e.Uogonek=722;e.Aacute=722;e.Adieresis=722;e.egrave=556;e.zacute=500;e.iogonek=278;e.Oacute=778;e.oacute=611;e.amacron=556;e.sacute=556;e.idieresis=278;e.Ocircumflex=778;e.Ugrave=722;e.Delta=612;e.thorn=611;e.twosuperior=333;e.Odieresis=778;e.mu=611;e.igrave=278;e.ohungarumlaut=611;e.Eogonek=667;e.dcroat=611;e.threequarters=834;e.Scedilla=667;e.lcaron=400;e.Kcommaaccent=722;e.Lacute=611;e.trademark=1e3;e.edotaccent=556;e.Igrave=278;e.Imacron=278;e.Lcaron=611;e.onehalf=834;e.lessequal=549;e.ocircumflex=611;e.ntilde=611;e.Uhungarumlaut=722;e.Eacute=667;e.emacron=556;e.gbreve=611;e.onequarter=834;e.Scaron=667;e.Scommaaccent=667;e.Ohungarumlaut=778;e.degree=400;e.ograve=611;e.Ccaron=722;e.ugrave=611;e.radical=549;e.Dcaron=722;e.rcommaaccent=389;e.Ntilde=722;e.otilde=611;e.Rcommaaccent=722;e.Lcommaaccent=611;e.Atilde=722;e.Aogonek=722;e.Aring=722;e.Otilde=778;e.zdotaccent=500;e.Ecaron=667;e.Iogonek=278;e.kcommaaccent=556;e.minus=584;e.Icircumflex=278;e.ncaron=611;e.tcommaaccent=333;e.logicalnot=584;e.odieresis=611;e.udieresis=611;e.notequal=549;e.gcommaaccent=611;e.eth=611;e.zcaron=500;e.ncommaaccent=611;e.onesuperior=333;e.imacron=278;e.Euro=556});e["Helvetica-BoldOblique"]=i(function(e){e.space=278;e.exclam=333;e.quotedbl=474;e.numbersign=556;e.dollar=556;e.percent=889;e.ampersand=722;e.quoteright=278;e.parenleft=333;e.parenright=333;e.asterisk=389;e.plus=584;e.comma=278;e.hyphen=333;e.period=278;e.slash=278;e.zero=556;e.one=556;e.two=556;e.three=556;e.four=556;e.five=556;e.six=556;e.seven=556;e.eight=556;e.nine=556;e.colon=333;e.semicolon=333;e.less=584;e.equal=584;e.greater=584;e.question=611;e.at=975;e.A=722;e.B=722;e.C=722;e.D=722;e.E=667;e.F=611;e.G=778;e.H=722;e.I=278;e.J=556;e.K=722;e.L=611;e.M=833;e.N=722;e.O=778;e.P=667;e.Q=778;e.R=722;e.S=667;e.T=611;e.U=722;e.V=667;e.W=944;e.X=667;e.Y=667;e.Z=611;e.bracketleft=333;e.backslash=278;e.bracketright=333;e.asciicircum=584;e.underscore=556;e.quoteleft=278;e.a=556;e.b=611;e.c=556;e.d=611;e.e=556;e.f=333;e.g=611;e.h=611;e.i=278;e.j=278;e.k=556;e.l=278;e.m=889;e.n=611;e.o=611;e.p=611;e.q=611;e.r=389;e.s=556;e.t=333;e.u=611;e.v=556;e.w=778;e.x=556;e.y=556;e.z=500;e.braceleft=389;e.bar=280;e.braceright=389;e.asciitilde=584;e.exclamdown=333;e.cent=556;e.sterling=556;e.fraction=167;e.yen=556;e.florin=556;e.section=556;e.currency=556;e.quotesingle=238;e.quotedblleft=500;e.guillemotleft=556;e.guilsinglleft=333;e.guilsinglright=333;e.fi=611;e.fl=611;e.endash=556;e.dagger=556;e.daggerdbl=556;e.periodcentered=278;e.paragraph=556;e.bullet=350;e.quotesinglbase=278;e.quotedblbase=500;e.quotedblright=500;e.guillemotright=556;e.ellipsis=1e3;e.perthousand=1e3;e.questiondown=611;e.grave=333;e.acute=333;e.circumflex=333;e.tilde=333;e.macron=333;e.breve=333;e.dotaccent=333;e.dieresis=333;e.ring=333;e.cedilla=333;e.hungarumlaut=333;e.ogonek=333;e.caron=333;e.emdash=1e3;e.AE=1e3;e.ordfeminine=370;e.Lslash=611;e.Oslash=778;e.OE=1e3;e.ordmasculine=365;e.ae=889;e.dotlessi=278;e.lslash=278;e.oslash=611;e.oe=944;e.germandbls=611;e.Idieresis=278;e.eacute=556;e.abreve=556;e.uhungarumlaut=611;e.ecaron=556;e.Ydieresis=667;e.divide=584;e.Yacute=667;e.Acircumflex=722;e.aacute=556;e.Ucircumflex=722;e.yacute=556;e.scommaaccent=556;e.ecircumflex=556;e.Uring=722;e.Udieresis=722;e.aogonek=556;e.Uacute=722;e.uogonek=611;e.Edieresis=667;e.Dcroat=722;e.commaaccent=250;e.copyright=737;e.Emacron=667;e.ccaron=556;e.aring=556;e.Ncommaaccent=722;e.lacute=278;e.agrave=556;e.Tcommaaccent=611;e.Cacute=722;e.atilde=556;e.Edotaccent=667;e.scaron=556;e.scedilla=556;e.iacute=278;e.lozenge=494;e.Rcaron=722;e.Gcommaaccent=778;e.ucircumflex=611;e.acircumflex=556;e.Amacron=722;e.rcaron=389;e.ccedilla=556;e.Zdotaccent=611;e.Thorn=667;e.Omacron=778;e.Racute=722;e.Sacute=667;e.dcaron=743;e.Umacron=722;e.uring=611;e.threesuperior=333;e.Ograve=778;e.Agrave=722;e.Abreve=722;e.multiply=584;e.uacute=611;e.Tcaron=611;e.partialdiff=494;e.ydieresis=556;e.Nacute=722;e.icircumflex=278;e.Ecircumflex=667;e.adieresis=556;e.edieresis=556;e.cacute=556;e.nacute=611;e.umacron=611;e.Ncaron=722;e.Iacute=278;e.plusminus=584;e.brokenbar=280;e.registered=737;e.Gbreve=778;e.Idotaccent=278;e.summation=600;e.Egrave=667;e.racute=389;e.omacron=611;e.Zacute=611;e.Zcaron=611;e.greaterequal=549;e.Eth=722;e.Ccedilla=722;e.lcommaaccent=278;e.tcaron=389;e.eogonek=556;e.Uogonek=722;e.Aacute=722;e.Adieresis=722;e.egrave=556;e.zacute=500;e.iogonek=278;e.Oacute=778;e.oacute=611;e.amacron=556;e.sacute=556;e.idieresis=278;e.Ocircumflex=778;e.Ugrave=722;e.Delta=612;e.thorn=611;e.twosuperior=333;e.Odieresis=778;e.mu=611;e.igrave=278;e.ohungarumlaut=611;e.Eogonek=667;e.dcroat=611;e.threequarters=834;e.Scedilla=667;e.lcaron=400;e.Kcommaaccent=722;e.Lacute=611;e.trademark=1e3;e.edotaccent=556;e.Igrave=278;e.Imacron=278;e.Lcaron=611;e.onehalf=834;e.lessequal=549;e.ocircumflex=611;e.ntilde=611;e.Uhungarumlaut=722;e.Eacute=667;e.emacron=556;e.gbreve=611;e.onequarter=834;e.Scaron=667;e.Scommaaccent=667;e.Ohungarumlaut=778;e.degree=400;e.ograve=611;e.Ccaron=722;e.ugrave=611;e.radical=549;e.Dcaron=722;e.rcommaaccent=389;e.Ntilde=722;e.otilde=611;e.Rcommaaccent=722;e.Lcommaaccent=611;e.Atilde=722;e.Aogonek=722;e.Aring=722;e.Otilde=778;e.zdotaccent=500;e.Ecaron=667;e.Iogonek=278;e.kcommaaccent=556;e.minus=584;e.Icircumflex=278;e.ncaron=611;e.tcommaaccent=333;e.logicalnot=584;e.odieresis=611;e.udieresis=611;e.notequal=549;e.gcommaaccent=611;e.eth=611;e.zcaron=500;e.ncommaaccent=611;e.onesuperior=333;e.imacron=278;e.Euro=556});e["Helvetica-Oblique"]=i(function(e){e.space=278;e.exclam=278;e.quotedbl=355;e.numbersign=556;e.dollar=556;e.percent=889;e.ampersand=667;e.quoteright=222;e.parenleft=333;e.parenright=333;e.asterisk=389;e.plus=584;e.comma=278;e.hyphen=333;e.period=278;e.slash=278;e.zero=556;e.one=556;e.two=556;e.three=556;e.four=556;e.five=556;e.six=556;e.seven=556;e.eight=556;e.nine=556;e.colon=278;e.semicolon=278;e.less=584;e.equal=584;e.greater=584;e.question=556;e.at=1015;e.A=667;e.B=667;e.C=722;e.D=722;e.E=667;e.F=611;e.G=778;e.H=722;e.I=278;e.J=500;e.K=667;e.L=556;e.M=833;e.N=722;e.O=778;e.P=667;e.Q=778;e.R=722;e.S=667;e.T=611;e.U=722;e.V=667;e.W=944;e.X=667;e.Y=667;e.Z=611;e.bracketleft=278;e.backslash=278;e.bracketright=278;e.asciicircum=469;e.underscore=556;e.quoteleft=222;e.a=556;e.b=556;e.c=500;e.d=556;e.e=556;e.f=278;e.g=556;e.h=556;e.i=222;e.j=222;e.k=500;e.l=222;e.m=833;e.n=556;e.o=556;e.p=556;e.q=556;e.r=333;e.s=500;e.t=278;e.u=556;e.v=500;e.w=722;e.x=500;e.y=500;e.z=500;e.braceleft=334;e.bar=260;e.braceright=334;e.asciitilde=584;e.exclamdown=333;e.cent=556;e.sterling=556;e.fraction=167;e.yen=556;e.florin=556;e.section=556;e.currency=556;e.quotesingle=191;e.quotedblleft=333;e.guillemotleft=556;e.guilsinglleft=333;e.guilsinglright=333;e.fi=500;e.fl=500;e.endash=556;e.dagger=556;e.daggerdbl=556;e.periodcentered=278;e.paragraph=537;e.bullet=350;e.quotesinglbase=222;e.quotedblbase=333;e.quotedblright=333;e.guillemotright=556;e.ellipsis=1e3;e.perthousand=1e3;e.questiondown=611;e.grave=333;e.acute=333;e.circumflex=333;e.tilde=333;e.macron=333;e.breve=333;e.dotaccent=333;e.dieresis=333;e.ring=333;e.cedilla=333;e.hungarumlaut=333;e.ogonek=333;e.caron=333;e.emdash=1e3;e.AE=1e3;e.ordfeminine=370;e.Lslash=556;e.Oslash=778;e.OE=1e3;e.ordmasculine=365;e.ae=889;e.dotlessi=278;e.lslash=222;e.oslash=611;e.oe=944;e.germandbls=611;e.Idieresis=278;e.eacute=556;e.abreve=556;e.uhungarumlaut=556;e.ecaron=556;e.Ydieresis=667;e.divide=584;e.Yacute=667;e.Acircumflex=667;e.aacute=556;e.Ucircumflex=722;e.yacute=500;e.scommaaccent=500;e.ecircumflex=556;e.Uring=722;e.Udieresis=722;e.aogonek=556;e.Uacute=722;e.uogonek=556;e.Edieresis=667;e.Dcroat=722;e.commaaccent=250;e.copyright=737;e.Emacron=667;e.ccaron=500;e.aring=556;e.Ncommaaccent=722;e.lacute=222;e.agrave=556;e.Tcommaaccent=611;e.Cacute=722;e.atilde=556;e.Edotaccent=667;e.scaron=500;e.scedilla=500;e.iacute=278;e.lozenge=471;e.Rcaron=722;e.Gcommaaccent=778;e.ucircumflex=556;e.acircumflex=556;e.Amacron=667;e.rcaron=333;e.ccedilla=500;e.Zdotaccent=611;e.Thorn=667;e.Omacron=778;e.Racute=722;e.Sacute=667;e.dcaron=643;e.Umacron=722;e.uring=556;e.threesuperior=333;e.Ograve=778;e.Agrave=667;e.Abreve=667;e.multiply=584;e.uacute=556;e.Tcaron=611;e.partialdiff=476;e.ydieresis=500;e.Nacute=722;e.icircumflex=278;e.Ecircumflex=667;e.adieresis=556;e.edieresis=556;e.cacute=500;e.nacute=556;e.umacron=556;e.Ncaron=722;e.Iacute=278;e.plusminus=584;e.brokenbar=260;e.registered=737;e.Gbreve=778;e.Idotaccent=278;e.summation=600;e.Egrave=667;e.racute=333;e.omacron=556;e.Zacute=611;e.Zcaron=611;e.greaterequal=549;e.Eth=722;e.Ccedilla=722;e.lcommaaccent=222;e.tcaron=317;e.eogonek=556;e.Uogonek=722;e.Aacute=667;e.Adieresis=667;e.egrave=556;e.zacute=500;e.iogonek=222;e.Oacute=778;e.oacute=556;e.amacron=556;e.sacute=500;e.idieresis=278;e.Ocircumflex=778;e.Ugrave=722;e.Delta=612;e.thorn=556;e.twosuperior=333;e.Odieresis=778;e.mu=556;e.igrave=278;e.ohungarumlaut=556;e.Eogonek=667;e.dcroat=556;e.threequarters=834;e.Scedilla=667;e.lcaron=299;e.Kcommaaccent=667;e.Lacute=556;e.trademark=1e3;e.edotaccent=556;e.Igrave=278;e.Imacron=278;e.Lcaron=556;e.onehalf=834;e.lessequal=549;e.ocircumflex=556;e.ntilde=556;e.Uhungarumlaut=722;e.Eacute=667;e.emacron=556;e.gbreve=556;e.onequarter=834;e.Scaron=667;e.Scommaaccent=667;e.Ohungarumlaut=778;e.degree=400;e.ograve=556;e.Ccaron=722;e.ugrave=556;e.radical=453;e.Dcaron=722;e.rcommaaccent=333;e.Ntilde=722;e.otilde=556;e.Rcommaaccent=722;e.Lcommaaccent=556;e.Atilde=667;e.Aogonek=667;e.Aring=667;e.Otilde=778;e.zdotaccent=500;e.Ecaron=667;e.Iogonek=278;e.kcommaaccent=500;e.minus=584;e.Icircumflex=278;e.ncaron=556;e.tcommaaccent=278;e.logicalnot=584;e.odieresis=556;e.udieresis=556;e.notequal=549;e.gcommaaccent=556;e.eth=556;e.zcaron=500;e.ncommaaccent=556;e.onesuperior=333;e.imacron=278;e.Euro=556});e.Symbol=i(function(e){e.space=250;e.exclam=333;e.universal=713;e.numbersign=500;e.existential=549;e.percent=833;e.ampersand=778;e.suchthat=439;e.parenleft=333;e.parenright=333;e.asteriskmath=500;e.plus=549;e.comma=250;e.minus=549;e.period=250;e.slash=278;e.zero=500;e.one=500;e.two=500;e.three=500;e.four=500;e.five=500;e.six=500;e.seven=500;e.eight=500;e.nine=500;e.colon=278;e.semicolon=278;e.less=549;e.equal=549;e.greater=549;e.question=444;e.congruent=549;e.Alpha=722;e.Beta=667;e.Chi=722;e.Delta=612;e.Epsilon=611;e.Phi=763;e.Gamma=603;e.Eta=722;e.Iota=333;e.theta1=631;e.Kappa=722;e.Lambda=686;e.Mu=889;e.Nu=722;e.Omicron=722;e.Pi=768;e.Theta=741;e.Rho=556;e.Sigma=592;e.Tau=611;e.Upsilon=690;e.sigma1=439;e.Omega=768;e.Xi=645;e.Psi=795;e.Zeta=611;e.bracketleft=333;e.therefore=863;e.bracketright=333;e.perpendicular=658;e.underscore=500;e.radicalex=500;e.alpha=631;e.beta=549;e.chi=549;e.delta=494;e.epsilon=439;e.phi=521;e.gamma=411;e.eta=603;e.iota=329;e.phi1=603;e.kappa=549;e.lambda=549;e.mu=576;e.nu=521;e.omicron=549;e.pi=549;e.theta=521;e.rho=549;e.sigma=603;e.tau=439;e.upsilon=576;e.omega1=713;e.omega=686;e.xi=493;e.psi=686;e.zeta=494;e.braceleft=480;e.bar=200;e.braceright=480;e.similar=549;e.Euro=750;e.Upsilon1=620;e.minute=247;e.lessequal=549;e.fraction=167;e.infinity=713;e.florin=500;e.club=753;e.diamond=753;e.heart=753;e.spade=753;e.arrowboth=1042;e.arrowleft=987;e.arrowup=603;e.arrowright=987;e.arrowdown=603;e.degree=400;e.plusminus=549;e.second=411;e.greaterequal=549;e.multiply=549;e.proportional=713;e.partialdiff=494;e.bullet=460;e.divide=549;e.notequal=549;e.equivalence=549;e.approxequal=549;e.ellipsis=1e3;e.arrowvertex=603;e.arrowhorizex=1e3;e.carriagereturn=658;e.aleph=823;e.Ifraktur=686;e.Rfraktur=795;e.weierstrass=987;e.circlemultiply=768;e.circleplus=768;e.emptyset=823;e.intersection=768;e.union=768;e.propersuperset=713;e.reflexsuperset=713;e.notsubset=713;e.propersubset=713;e.reflexsubset=713;e.element=713;e.notelement=713;e.angle=768;e.gradient=713;e.registerserif=790;e.copyrightserif=790;e.trademarkserif=890;e.product=823;e.radical=549;e.dotmath=250;e.logicalnot=713;e.logicaland=603;e.logicalor=603;e.arrowdblboth=1042;e.arrowdblleft=987;e.arrowdblup=603;e.arrowdblright=987;e.arrowdbldown=603;e.lozenge=494;e.angleleft=329;e.registersans=790;e.copyrightsans=790;e.trademarksans=786;e.summation=713;e.parenlefttp=384;e.parenleftex=384;e.parenleftbt=384;e.bracketlefttp=384;e.bracketleftex=384;e.bracketleftbt=384;e.bracelefttp=494;e.braceleftmid=494;e.braceleftbt=494;e.braceex=494;e.angleright=329;e.integral=274;e.integraltp=686;e.integralex=686;e.integralbt=686;e.parenrighttp=384;e.parenrightex=384;e.parenrightbt=384;e.bracketrighttp=384;e.bracketrightex=384;e.bracketrightbt=384;e.bracerighttp=494;e.bracerightmid=494;e.bracerightbt=494;e.apple=790});e["Times-Roman"]=i(function(e){e.space=250;e.exclam=333;e.quotedbl=408;e.numbersign=500;e.dollar=500;e.percent=833;e.ampersand=778;e.quoteright=333;e.parenleft=333;e.parenright=333;e.asterisk=500;e.plus=564;e.comma=250;e.hyphen=333;e.period=250;e.slash=278;e.zero=500;e.one=500;e.two=500;e.three=500;e.four=500;e.five=500;e.six=500;e.seven=500;e.eight=500;e.nine=500;e.colon=278;e.semicolon=278;e.less=564;e.equal=564;e.greater=564;e.question=444;e.at=921;e.A=722;e.B=667;e.C=667;e.D=722;e.E=611;e.F=556;e.G=722;e.H=722;e.I=333;e.J=389;e.K=722;e.L=611;e.M=889;e.N=722;e.O=722;e.P=556;e.Q=722;e.R=667;e.S=556;e.T=611;e.U=722;e.V=722;e.W=944;e.X=722;e.Y=722;e.Z=611;e.bracketleft=333;e.backslash=278;e.bracketright=333;e.asciicircum=469;e.underscore=500;e.quoteleft=333;e.a=444;e.b=500;e.c=444;e.d=500;e.e=444;e.f=333;e.g=500;e.h=500;e.i=278;e.j=278;e.k=500;e.l=278;e.m=778;e.n=500;e.o=500;e.p=500;e.q=500;e.r=333;e.s=389;e.t=278;e.u=500;e.v=500;e.w=722;e.x=500;e.y=500;e.z=444;e.braceleft=480;e.bar=200;e.braceright=480;e.asciitilde=541;e.exclamdown=333;e.cent=500;e.sterling=500;e.fraction=167;e.yen=500;e.florin=500;e.section=500;e.currency=500;e.quotesingle=180;e.quotedblleft=444;e.guillemotleft=500;e.guilsinglleft=333;e.guilsinglright=333;e.fi=556;e.fl=556;e.endash=500;e.dagger=500;e.daggerdbl=500;e.periodcentered=250;e.paragraph=453;e.bullet=350;e.quotesinglbase=333;e.quotedblbase=444;e.quotedblright=444;e.guillemotright=500;e.ellipsis=1e3;e.perthousand=1e3;e.questiondown=444;e.grave=333;e.acute=333;e.circumflex=333;e.tilde=333;e.macron=333;e.breve=333;e.dotaccent=333;e.dieresis=333;e.ring=333;e.cedilla=333;e.hungarumlaut=333;e.ogonek=333;e.caron=333;e.emdash=1e3;e.AE=889;e.ordfeminine=276;e.Lslash=611;e.Oslash=722;e.OE=889;e.ordmasculine=310;e.ae=667;e.dotlessi=278;e.lslash=278;e.oslash=500;e.oe=722;e.germandbls=500;e.Idieresis=333;e.eacute=444;e.abreve=444;e.uhungarumlaut=500;e.ecaron=444;e.Ydieresis=722;e.divide=564;e.Yacute=722;e.Acircumflex=722;e.aacute=444;e.Ucircumflex=722;e.yacute=500;e.scommaaccent=389;e.ecircumflex=444;e.Uring=722;e.Udieresis=722;e.aogonek=444;e.Uacute=722;e.uogonek=500;e.Edieresis=611;e.Dcroat=722;e.commaaccent=250;e.copyright=760;e.Emacron=611;e.ccaron=444;e.aring=444;e.Ncommaaccent=722;e.lacute=278;e.agrave=444;e.Tcommaaccent=611;e.Cacute=667;e.atilde=444;e.Edotaccent=611;e.scaron=389;e.scedilla=389;e.iacute=278;e.lozenge=471;e.Rcaron=667;e.Gcommaaccent=722;e.ucircumflex=500;e.acircumflex=444;e.Amacron=722;e.rcaron=333;e.ccedilla=444;e.Zdotaccent=611;e.Thorn=556;e.Omacron=722;e.Racute=667;e.Sacute=556;e.dcaron=588;e.Umacron=722;e.uring=500;e.threesuperior=300;e.Ograve=722;e.Agrave=722;e.Abreve=722;e.multiply=564;e.uacute=500;e.Tcaron=611;e.partialdiff=476;e.ydieresis=500;e.Nacute=722;e.icircumflex=278;e.Ecircumflex=611;e.adieresis=444;e.edieresis=444;e.cacute=444;e.nacute=500;e.umacron=500;e.Ncaron=722;e.Iacute=333;e.plusminus=564;e.brokenbar=200;e.registered=760;e.Gbreve=722;e.Idotaccent=333;e.summation=600;e.Egrave=611;e.racute=333;e.omacron=500;e.Zacute=611;e.Zcaron=611;e.greaterequal=549;e.Eth=722;e.Ccedilla=667;e.lcommaaccent=278;e.tcaron=326;e.eogonek=444;e.Uogonek=722;e.Aacute=722;e.Adieresis=722;e.egrave=444;e.zacute=444;e.iogonek=278;e.Oacute=722;e.oacute=500;e.amacron=444;e.sacute=389;e.idieresis=278;e.Ocircumflex=722;e.Ugrave=722;e.Delta=612;e.thorn=500;e.twosuperior=300;e.Odieresis=722;e.mu=500;e.igrave=278;e.ohungarumlaut=500;e.Eogonek=611;e.dcroat=500;e.threequarters=750;e.Scedilla=556;e.lcaron=344;e.Kcommaaccent=722;e.Lacute=611;e.trademark=980;e.edotaccent=444;e.Igrave=333;e.Imacron=333;e.Lcaron=611;e.onehalf=750;e.lessequal=549;e.ocircumflex=500;e.ntilde=500;e.Uhungarumlaut=722;e.Eacute=611;e.emacron=444;e.gbreve=500;e.onequarter=750;e.Scaron=556;e.Scommaaccent=556;e.Ohungarumlaut=722;e.degree=400;e.ograve=500;e.Ccaron=667;e.ugrave=500;e.radical=453;e.Dcaron=722;e.rcommaaccent=333;e.Ntilde=722;e.otilde=500;e.Rcommaaccent=667;e.Lcommaaccent=611;e.Atilde=722;e.Aogonek=722;e.Aring=722;e.Otilde=722;e.zdotaccent=444;e.Ecaron=611;e.Iogonek=333;e.kcommaaccent=500;e.minus=564;e.Icircumflex=333;e.ncaron=500;e.tcommaaccent=278;e.logicalnot=564;e.odieresis=500;e.udieresis=500;e.notequal=549;e.gcommaaccent=500;e.eth=500;e.zcaron=444;e.ncommaaccent=500;e.onesuperior=300;e.imacron=278;e.Euro=500});e["Times-Bold"]=i(function(e){e.space=250;e.exclam=333;e.quotedbl=555;e.numbersign=500;e.dollar=500;e.percent=1e3;e.ampersand=833;e.quoteright=333;e.parenleft=333;e.parenright=333;e.asterisk=500;e.plus=570;e.comma=250;e.hyphen=333;e.period=250;e.slash=278;e.zero=500;e.one=500;e.two=500;e.three=500;e.four=500;e.five=500;e.six=500;e.seven=500;e.eight=500;e.nine=500;e.colon=333;e.semicolon=333;e.less=570;e.equal=570;e.greater=570;e.question=500;e.at=930;e.A=722;e.B=667;e.C=722;e.D=722;e.E=667;e.F=611;e.G=778;e.H=778;e.I=389;e.J=500;e.K=778;e.L=667;e.M=944;e.N=722;e.O=778;e.P=611;e.Q=778;e.R=722;e.S=556;e.T=667;e.U=722;e.V=722;e.W=1e3;e.X=722;e.Y=722;e.Z=667;e.bracketleft=333;e.backslash=278;e.bracketright=333;e.asciicircum=581;e.underscore=500;e.quoteleft=333;e.a=500;e.b=556;e.c=444;e.d=556;e.e=444;e.f=333;e.g=500;e.h=556;e.i=278;e.j=333
+;e.k=556;e.l=278;e.m=833;e.n=556;e.o=500;e.p=556;e.q=556;e.r=444;e.s=389;e.t=333;e.u=556;e.v=500;e.w=722;e.x=500;e.y=500;e.z=444;e.braceleft=394;e.bar=220;e.braceright=394;e.asciitilde=520;e.exclamdown=333;e.cent=500;e.sterling=500;e.fraction=167;e.yen=500;e.florin=500;e.section=500;e.currency=500;e.quotesingle=278;e.quotedblleft=500;e.guillemotleft=500;e.guilsinglleft=333;e.guilsinglright=333;e.fi=556;e.fl=556;e.endash=500;e.dagger=500;e.daggerdbl=500;e.periodcentered=250;e.paragraph=540;e.bullet=350;e.quotesinglbase=333;e.quotedblbase=500;e.quotedblright=500;e.guillemotright=500;e.ellipsis=1e3;e.perthousand=1e3;e.questiondown=500;e.grave=333;e.acute=333;e.circumflex=333;e.tilde=333;e.macron=333;e.breve=333;e.dotaccent=333;e.dieresis=333;e.ring=333;e.cedilla=333;e.hungarumlaut=333;e.ogonek=333;e.caron=333;e.emdash=1e3;e.AE=1e3;e.ordfeminine=300;e.Lslash=667;e.Oslash=778;e.OE=1e3;e.ordmasculine=330;e.ae=722;e.dotlessi=278;e.lslash=278;e.oslash=500;e.oe=722;e.germandbls=556;e.Idieresis=389;e.eacute=444;e.abreve=500;e.uhungarumlaut=556;e.ecaron=444;e.Ydieresis=722;e.divide=570;e.Yacute=722;e.Acircumflex=722;e.aacute=500;e.Ucircumflex=722;e.yacute=500;e.scommaaccent=389;e.ecircumflex=444;e.Uring=722;e.Udieresis=722;e.aogonek=500;e.Uacute=722;e.uogonek=556;e.Edieresis=667;e.Dcroat=722;e.commaaccent=250;e.copyright=747;e.Emacron=667;e.ccaron=444;e.aring=500;e.Ncommaaccent=722;e.lacute=278;e.agrave=500;e.Tcommaaccent=667;e.Cacute=722;e.atilde=500;e.Edotaccent=667;e.scaron=389;e.scedilla=389;e.iacute=278;e.lozenge=494;e.Rcaron=722;e.Gcommaaccent=778;e.ucircumflex=556;e.acircumflex=500;e.Amacron=722;e.rcaron=444;e.ccedilla=444;e.Zdotaccent=667;e.Thorn=611;e.Omacron=778;e.Racute=722;e.Sacute=556;e.dcaron=672;e.Umacron=722;e.uring=556;e.threesuperior=300;e.Ograve=778;e.Agrave=722;e.Abreve=722;e.multiply=570;e.uacute=556;e.Tcaron=667;e.partialdiff=494;e.ydieresis=500;e.Nacute=722;e.icircumflex=278;e.Ecircumflex=667;e.adieresis=500;e.edieresis=444;e.cacute=444;e.nacute=556;e.umacron=556;e.Ncaron=722;e.Iacute=389;e.plusminus=570;e.brokenbar=220;e.registered=747;e.Gbreve=778;e.Idotaccent=389;e.summation=600;e.Egrave=667;e.racute=444;e.omacron=500;e.Zacute=667;e.Zcaron=667;e.greaterequal=549;e.Eth=722;e.Ccedilla=722;e.lcommaaccent=278;e.tcaron=416;e.eogonek=444;e.Uogonek=722;e.Aacute=722;e.Adieresis=722;e.egrave=444;e.zacute=444;e.iogonek=278;e.Oacute=778;e.oacute=500;e.amacron=500;e.sacute=389;e.idieresis=278;e.Ocircumflex=778;e.Ugrave=722;e.Delta=612;e.thorn=556;e.twosuperior=300;e.Odieresis=778;e.mu=556;e.igrave=278;e.ohungarumlaut=500;e.Eogonek=667;e.dcroat=556;e.threequarters=750;e.Scedilla=556;e.lcaron=394;e.Kcommaaccent=778;e.Lacute=667;e.trademark=1e3;e.edotaccent=444;e.Igrave=389;e.Imacron=389;e.Lcaron=667;e.onehalf=750;e.lessequal=549;e.ocircumflex=500;e.ntilde=556;e.Uhungarumlaut=722;e.Eacute=667;e.emacron=444;e.gbreve=500;e.onequarter=750;e.Scaron=556;e.Scommaaccent=556;e.Ohungarumlaut=778;e.degree=400;e.ograve=500;e.Ccaron=722;e.ugrave=556;e.radical=549;e.Dcaron=722;e.rcommaaccent=444;e.Ntilde=722;e.otilde=500;e.Rcommaaccent=722;e.Lcommaaccent=667;e.Atilde=722;e.Aogonek=722;e.Aring=722;e.Otilde=778;e.zdotaccent=444;e.Ecaron=667;e.Iogonek=389;e.kcommaaccent=556;e.minus=570;e.Icircumflex=389;e.ncaron=556;e.tcommaaccent=333;e.logicalnot=570;e.odieresis=500;e.udieresis=556;e.notequal=549;e.gcommaaccent=500;e.eth=500;e.zcaron=444;e.ncommaaccent=556;e.onesuperior=300;e.imacron=278;e.Euro=500});e["Times-BoldItalic"]=i(function(e){e.space=250;e.exclam=389;e.quotedbl=555;e.numbersign=500;e.dollar=500;e.percent=833;e.ampersand=778;e.quoteright=333;e.parenleft=333;e.parenright=333;e.asterisk=500;e.plus=570;e.comma=250;e.hyphen=333;e.period=250;e.slash=278;e.zero=500;e.one=500;e.two=500;e.three=500;e.four=500;e.five=500;e.six=500;e.seven=500;e.eight=500;e.nine=500;e.colon=333;e.semicolon=333;e.less=570;e.equal=570;e.greater=570;e.question=500;e.at=832;e.A=667;e.B=667;e.C=667;e.D=722;e.E=667;e.F=667;e.G=722;e.H=778;e.I=389;e.J=500;e.K=667;e.L=611;e.M=889;e.N=722;e.O=722;e.P=611;e.Q=722;e.R=667;e.S=556;e.T=611;e.U=722;e.V=667;e.W=889;e.X=667;e.Y=611;e.Z=611;e.bracketleft=333;e.backslash=278;e.bracketright=333;e.asciicircum=570;e.underscore=500;e.quoteleft=333;e.a=500;e.b=500;e.c=444;e.d=500;e.e=444;e.f=333;e.g=500;e.h=556;e.i=278;e.j=278;e.k=500;e.l=278;e.m=778;e.n=556;e.o=500;e.p=500;e.q=500;e.r=389;e.s=389;e.t=278;e.u=556;e.v=444;e.w=667;e.x=500;e.y=444;e.z=389;e.braceleft=348;e.bar=220;e.braceright=348;e.asciitilde=570;e.exclamdown=389;e.cent=500;e.sterling=500;e.fraction=167;e.yen=500;e.florin=500;e.section=500;e.currency=500;e.quotesingle=278;e.quotedblleft=500;e.guillemotleft=500;e.guilsinglleft=333;e.guilsinglright=333;e.fi=556;e.fl=556;e.endash=500;e.dagger=500;e.daggerdbl=500;e.periodcentered=250;e.paragraph=500;e.bullet=350;e.quotesinglbase=333;e.quotedblbase=500;e.quotedblright=500;e.guillemotright=500;e.ellipsis=1e3;e.perthousand=1e3;e.questiondown=500;e.grave=333;e.acute=333;e.circumflex=333;e.tilde=333;e.macron=333;e.breve=333;e.dotaccent=333;e.dieresis=333;e.ring=333;e.cedilla=333;e.hungarumlaut=333;e.ogonek=333;e.caron=333;e.emdash=1e3;e.AE=944;e.ordfeminine=266;e.Lslash=611;e.Oslash=722;e.OE=944;e.ordmasculine=300;e.ae=722;e.dotlessi=278;e.lslash=278;e.oslash=500;e.oe=722;e.germandbls=500;e.Idieresis=389;e.eacute=444;e.abreve=500;e.uhungarumlaut=556;e.ecaron=444;e.Ydieresis=611;e.divide=570;e.Yacute=611;e.Acircumflex=667;e.aacute=500;e.Ucircumflex=722;e.yacute=444;e.scommaaccent=389;e.ecircumflex=444;e.Uring=722;e.Udieresis=722;e.aogonek=500;e.Uacute=722;e.uogonek=556;e.Edieresis=667;e.Dcroat=722;e.commaaccent=250;e.copyright=747;e.Emacron=667;e.ccaron=444;e.aring=500;e.Ncommaaccent=722;e.lacute=278;e.agrave=500;e.Tcommaaccent=611;e.Cacute=667;e.atilde=500;e.Edotaccent=667;e.scaron=389;e.scedilla=389;e.iacute=278;e.lozenge=494;e.Rcaron=667;e.Gcommaaccent=722;e.ucircumflex=556;e.acircumflex=500;e.Amacron=667;e.rcaron=389;e.ccedilla=444;e.Zdotaccent=611;e.Thorn=611;e.Omacron=722;e.Racute=667;e.Sacute=556;e.dcaron=608;e.Umacron=722;e.uring=556;e.threesuperior=300;e.Ograve=722;e.Agrave=667;e.Abreve=667;e.multiply=570;e.uacute=556;e.Tcaron=611;e.partialdiff=494;e.ydieresis=444;e.Nacute=722;e.icircumflex=278;e.Ecircumflex=667;e.adieresis=500;e.edieresis=444;e.cacute=444;e.nacute=556;e.umacron=556;e.Ncaron=722;e.Iacute=389;e.plusminus=570;e.brokenbar=220;e.registered=747;e.Gbreve=722;e.Idotaccent=389;e.summation=600;e.Egrave=667;e.racute=389;e.omacron=500;e.Zacute=611;e.Zcaron=611;e.greaterequal=549;e.Eth=722;e.Ccedilla=667;e.lcommaaccent=278;e.tcaron=366;e.eogonek=444;e.Uogonek=722;e.Aacute=667;e.Adieresis=667;e.egrave=444;e.zacute=389;e.iogonek=278;e.Oacute=722;e.oacute=500;e.amacron=500;e.sacute=389;e.idieresis=278;e.Ocircumflex=722;e.Ugrave=722;e.Delta=612;e.thorn=500;e.twosuperior=300;e.Odieresis=722;e.mu=576;e.igrave=278;e.ohungarumlaut=500;e.Eogonek=667;e.dcroat=500;e.threequarters=750;e.Scedilla=556;e.lcaron=382;e.Kcommaaccent=667;e.Lacute=611;e.trademark=1e3;e.edotaccent=444;e.Igrave=389;e.Imacron=389;e.Lcaron=611;e.onehalf=750;e.lessequal=549;e.ocircumflex=500;e.ntilde=556;e.Uhungarumlaut=722;e.Eacute=667;e.emacron=444;e.gbreve=500;e.onequarter=750;e.Scaron=556;e.Scommaaccent=556;e.Ohungarumlaut=722;e.degree=400;e.ograve=500;e.Ccaron=667;e.ugrave=556;e.radical=549;e.Dcaron=722;e.rcommaaccent=389;e.Ntilde=722;e.otilde=500;e.Rcommaaccent=667;e.Lcommaaccent=611;e.Atilde=667;e.Aogonek=667;e.Aring=667;e.Otilde=722;e.zdotaccent=389;e.Ecaron=667;e.Iogonek=389;e.kcommaaccent=500;e.minus=606;e.Icircumflex=389;e.ncaron=556;e.tcommaaccent=278;e.logicalnot=606;e.odieresis=500;e.udieresis=556;e.notequal=549;e.gcommaaccent=500;e.eth=500;e.zcaron=389;e.ncommaaccent=556;e.onesuperior=300;e.imacron=278;e.Euro=500});e["Times-Italic"]=i(function(e){e.space=250;e.exclam=333;e.quotedbl=420;e.numbersign=500;e.dollar=500;e.percent=833;e.ampersand=778;e.quoteright=333;e.parenleft=333;e.parenright=333;e.asterisk=500;e.plus=675;e.comma=250;e.hyphen=333;e.period=250;e.slash=278;e.zero=500;e.one=500;e.two=500;e.three=500;e.four=500;e.five=500;e.six=500;e.seven=500;e.eight=500;e.nine=500;e.colon=333;e.semicolon=333;e.less=675;e.equal=675;e.greater=675;e.question=500;e.at=920;e.A=611;e.B=611;e.C=667;e.D=722;e.E=611;e.F=611;e.G=722;e.H=722;e.I=333;e.J=444;e.K=667;e.L=556;e.M=833;e.N=667;e.O=722;e.P=611;e.Q=722;e.R=611;e.S=500;e.T=556;e.U=722;e.V=611;e.W=833;e.X=611;e.Y=556;e.Z=556;e.bracketleft=389;e.backslash=278;e.bracketright=389;e.asciicircum=422;e.underscore=500;e.quoteleft=333;e.a=500;e.b=500;e.c=444;e.d=500;e.e=444;e.f=278;e.g=500;e.h=500;e.i=278;e.j=278;e.k=444;e.l=278;e.m=722;e.n=500;e.o=500;e.p=500;e.q=500;e.r=389;e.s=389;e.t=278;e.u=500;e.v=444;e.w=667;e.x=444;e.y=444;e.z=389;e.braceleft=400;e.bar=275;e.braceright=400;e.asciitilde=541;e.exclamdown=389;e.cent=500;e.sterling=500;e.fraction=167;e.yen=500;e.florin=500;e.section=500;e.currency=500;e.quotesingle=214;e.quotedblleft=556;e.guillemotleft=500;e.guilsinglleft=333;e.guilsinglright=333;e.fi=500;e.fl=500;e.endash=500;e.dagger=500;e.daggerdbl=500;e.periodcentered=250;e.paragraph=523;e.bullet=350;e.quotesinglbase=333;e.quotedblbase=556;e.quotedblright=556;e.guillemotright=500;e.ellipsis=889;e.perthousand=1e3;e.questiondown=500;e.grave=333;e.acute=333;e.circumflex=333;e.tilde=333;e.macron=333;e.breve=333;e.dotaccent=333;e.dieresis=333;e.ring=333;e.cedilla=333;e.hungarumlaut=333;e.ogonek=333;e.caron=333;e.emdash=889;e.AE=889;e.ordfeminine=276;e.Lslash=556;e.Oslash=722;e.OE=944;e.ordmasculine=310;e.ae=667;e.dotlessi=278;e.lslash=278;e.oslash=500;e.oe=667;e.germandbls=500;e.Idieresis=333;e.eacute=444;e.abreve=500;e.uhungarumlaut=500;e.ecaron=444;e.Ydieresis=556;e.divide=675;e.Yacute=556;e.Acircumflex=611;e.aacute=500;e.Ucircumflex=722;e.yacute=444;e.scommaaccent=389;e.ecircumflex=444;e.Uring=722;e.Udieresis=722;e.aogonek=500;e.Uacute=722;e.uogonek=500;e.Edieresis=611;e.Dcroat=722;e.commaaccent=250;e.copyright=760;e.Emacron=611;e.ccaron=444;e.aring=500;e.Ncommaaccent=667;e.lacute=278;e.agrave=500;e.Tcommaaccent=556;e.Cacute=667;e.atilde=500;e.Edotaccent=611;e.scaron=389;e.scedilla=389;e.iacute=278;e.lozenge=471;e.Rcaron=611;e.Gcommaaccent=722;e.ucircumflex=500;e.acircumflex=500;e.Amacron=611;e.rcaron=389;e.ccedilla=444;e.Zdotaccent=556;e.Thorn=611;e.Omacron=722;e.Racute=611;e.Sacute=500;e.dcaron=544;e.Umacron=722;e.uring=500;e.threesuperior=300;e.Ograve=722;e.Agrave=611;e.Abreve=611;e.multiply=675;e.uacute=500;e.Tcaron=556;e.partialdiff=476;e.ydieresis=444;e.Nacute=667;e.icircumflex=278;e.Ecircumflex=611;e.adieresis=500;e.edieresis=444;e.cacute=444;e.nacute=500;e.umacron=500;e.Ncaron=667;e.Iacute=333;e.plusminus=675;e.brokenbar=275;e.registered=760;e.Gbreve=722;e.Idotaccent=333;e.summation=600;e.Egrave=611;e.racute=389;e.omacron=500;e.Zacute=556;e.Zcaron=556;e.greaterequal=549;e.Eth=722;e.Ccedilla=667;e.lcommaaccent=278;e.tcaron=300;e.eogonek=444;e.Uogonek=722;e.Aacute=611;e.Adieresis=611;e.egrave=444;e.zacute=389;e.iogonek=278;e.Oacute=722;e.oacute=500;e.amacron=500;e.sacute=389;e.idieresis=278;e.Ocircumflex=722;e.Ugrave=722;e.Delta=612;e.thorn=500;e.twosuperior=300;e.Odieresis=722;e.mu=500;e.igrave=278;e.ohungarumlaut=500;e.Eogonek=611;e.dcroat=500;e.threequarters=750;e.Scedilla=500;e.lcaron=300;e.Kcommaaccent=667;e.Lacute=556;e.trademark=980;e.edotaccent=444;e.Igrave=333;e.Imacron=333;e.Lcaron=611;e.onehalf=750;e.lessequal=549;e.ocircumflex=500;e.ntilde=500;e.Uhungarumlaut=722;e.Eacute=611;e.emacron=444;e.gbreve=500;e.onequarter=750;e.Scaron=500;e.Scommaaccent=500;e.Ohungarumlaut=722;e.degree=400;e.ograve=500;e.Ccaron=667;e.ugrave=500;e.radical=453;e.Dcaron=722;e.rcommaaccent=389;e.Ntilde=667;e.otilde=500;e.Rcommaaccent=611;e.Lcommaaccent=556;e.Atilde=611;e.Aogonek=611;e.Aring=611;e.Otilde=722;e.zdotaccent=389;e.Ecaron=611;e.Iogonek=333;e.kcommaaccent=444;e.minus=675;e.Icircumflex=333;e.ncaron=500;e.tcommaaccent=278;e.logicalnot=675;e.odieresis=500;e.udieresis=500;e.notequal=549;e.gcommaaccent=500;e.eth=500;e.zcaron=389;e.ncommaaccent=500;e.onesuperior=300;e.imacron=278;e.Euro=500});e.ZapfDingbats=i(function(e){e.space=278;e.a1=974;e.a2=961;e.a202=974;e.a3=980;e.a4=719;e.a5=789;e.a119=790;e.a118=791;e.a117=690;e.a11=960;e.a12=939;e.a13=549;e.a14=855;e.a15=911;e.a16=933;e.a105=911;e.a17=945;e.a18=974;e.a19=755;e.a20=846;e.a21=762;e.a22=761;e.a23=571;e.a24=677;e.a25=763;e.a26=760;e.a27=759;e.a28=754;e.a6=494;e.a7=552;e.a8=537;e.a9=577;e.a10=692;e.a29=786;e.a30=788;e.a31=788;e.a32=790;e.a33=793;e.a34=794;e.a35=816;e.a36=823;e.a37=789;e.a38=841;e.a39=823;e.a40=833;e.a41=816;e.a42=831;e.a43=923;e.a44=744;e.a45=723;e.a46=749;e.a47=790;e.a48=792;e.a49=695;e.a50=776;e.a51=768;e.a52=792;e.a53=759;e.a54=707;e.a55=708;e.a56=682;e.a57=701;e.a58=826;e.a59=815;e.a60=789;e.a61=789;e.a62=707;e.a63=687;e.a64=696;e.a65=689;e.a66=786;e.a67=787;e.a68=713;e.a69=791;e.a70=785;e.a71=791;e.a72=873;e.a73=761;e.a74=762;e.a203=762;e.a75=759;e.a204=759;e.a76=892;e.a77=892;e.a78=788;e.a79=784;e.a81=438;e.a82=138;e.a83=277;e.a84=415;e.a97=392;e.a98=392;e.a99=668;e.a100=668;e.a89=390;e.a90=390;e.a93=317;e.a94=317;e.a91=276;e.a92=276;e.a205=509;e.a85=509;e.a206=410;e.a86=410;e.a87=234;e.a88=234;e.a95=334;e.a96=334;e.a101=732;e.a102=544;e.a103=544;e.a104=910;e.a106=667;e.a107=760;e.a108=760;e.a112=776;e.a111=595;e.a110=694;e.a109=626;e.a120=788;e.a121=788;e.a122=788;e.a123=788;e.a124=788;e.a125=788;e.a126=788;e.a127=788;e.a128=788;e.a129=788;e.a130=788;e.a131=788;e.a132=788;e.a133=788;e.a134=788;e.a135=788;e.a136=788;e.a137=788;e.a138=788;e.a139=788;e.a140=788;e.a141=788;e.a142=788;e.a143=788;e.a144=788;e.a145=788;e.a146=788;e.a147=788;e.a148=788;e.a149=788;e.a150=788;e.a151=788;e.a152=788;e.a153=788;e.a154=788;e.a155=788;e.a156=788;e.a157=788;e.a158=788;e.a159=788;e.a160=894;e.a161=838;e.a163=1016;e.a164=458;e.a196=748;e.a165=924;e.a192=748;e.a166=918;e.a167=927;e.a168=928;e.a169=928;e.a170=834;e.a171=873;e.a172=828;e.a173=924;e.a162=924;e.a174=917;e.a175=930;e.a176=931;e.a177=463;e.a178=883;e.a179=836;e.a193=836;e.a180=867;e.a199=867;e.a181=696;e.a200=696;e.a182=874;e.a201=874;e.a183=760;e.a184=946;e.a197=771;e.a185=865;e.a194=771;e.a198=888;e.a186=967;e.a195=888;e.a187=831;e.a188=873;e.a189=927;e.a190=970;e.a191=918})});t.getMetrics=n},function(e,t,a){"use strict";var r=a(0),i=r.Uint32ArrayView,n=function(e){function t(e){this.h1=e?4294967295&e:3285377520;this.h2=e?4294967295&e:3285377520}var a=!1;try{new Uint32Array(new Uint8Array(5).buffer,0,1)}catch(e){a=!0}t.prototype={update:function(e){var t,r=a;if("string"==typeof e){var n=new Uint8Array(2*e.length),s=0;for(t=0;t<e.length;t++){var o=e.charCodeAt(t);if(o<=255)n[s++]=o;else{n[s++]=o>>>8;n[s++]=255&o}}}else if(e instanceof Uint8Array){n=e;s=n.length}else{if(!("object"==typeof e&&"length"in e))throw new Error("Wrong data format in MurmurHash3_64_update. Input must be a string or array.");n=e;s=n.length;r=!0}var c=s>>2,l=s-4*c,h=r?new i(n,c):new Uint32Array(n.buffer,0,c),u=0,f=0,d=this.h1,g=this.h2,p=3432918353,m=461845907;for(t=0;t<c;t++)if(1&t){u=h[t];u=u*p&4294901760|11601*u&65535;u=u<<15|u>>>17;u=u*m&4294901760|13715*u&65535;d^=u;d=d<<13|d>>>19;d=5*d+3864292196}else{f=h[t];f=f*p&4294901760|11601*f&65535;f=f<<15|f>>>17;f=f*m&4294901760|13715*f&65535;g^=f;g=g<<13|g>>>19;g=5*g+3864292196}u=0;switch(l){case 3:u^=n[4*c+2]<<16;case 2:u^=n[4*c+1]<<8;case 1:u^=n[4*c];u=u*p&4294901760|11601*u&65535;u=u<<15|u>>>17;u=u*m&4294901760|13715*u&65535;1&c?d^=u:g^=u}this.h1=d;this.h2=g;return this},hexdigest:function(){var e=this.h1,t=this.h2;e^=t>>>1;e=3981806797*e&4294901760|36045*e&65535;t=4283543511*t&4294901760|(2950163797*(t<<16|e>>>16)&4294901760)>>>16;e^=t>>>1;e=444984403*e&4294901760|60499*e&65535;t=3301882366*t&4294901760|(3120437893*(t<<16|e>>>16)&4294901760)>>>16;e^=t>>>1;for(var a=0,r=[e,t],i="";a<r.length;a++){for(var n=(r[a]>>>0).toString(16);n.length<8;)n="0"+n;i+=n}return i}};return t}();t.MurmurHash3_64=n},function(e,t,a){"use strict";function r(e,t,a){return["TilingPattern",a,e,t.getArray("Matrix"),t.getArray("BBox"),t.get("XStep"),t.get("YStep"),t.get("PaintType"),t.get("TilingType")]}var i=a(0),n=a(1),s=a(6),o=a(3),c=i.UNSUPPORTED_FEATURES,l=i.MissingDataException,h=i.Util,u=i.assert,f=i.error,d=i.info,g=i.warn,p=n.isStream,m=s.PDFFunction,b=o.ColorSpace,v={FUNCTION_BASED:1,AXIAL:2,RADIAL:3,FREE_FORM_MESH:4,LATTICE_FORM_MESH:5,COONS_PATCH_MESH:6,TENSOR_PATCH_MESH:7},y=function(){function e(){f("should not call Pattern constructor")}e.prototype={getPattern:function(e){f("Should not call Pattern.getStyle: "+e)}};e.parseShading=function(e,t,a,r,i){var n=p(e)?e.dict:e,s=n.get("ShadingType");try{switch(s){case v.AXIAL:case v.RADIAL:return new k.RadialAxial(n,t,a,r);case v.FREE_FORM_MESH:case v.LATTICE_FORM_MESH:case v.COONS_PATCH_MESH:case v.TENSOR_PATCH_MESH:return new k.Mesh(e,t,a,r);default:throw new Error("Unsupported ShadingType: "+s)}}catch(e){if(e instanceof l)throw e;i.send("UnsupportedFeature",{featureId:c.shadingPattern});g(e);return new k.Dummy}};return e}(),k={};k.SMALL_NUMBER=1e-6;k.RadialAxial=function(){function e(e,t,a,r){this.matrix=t;this.coordsArr=e.getArray("Coords");this.shadingType=e.get("ShadingType");this.type="Pattern";var i=e.get("ColorSpace","CS");i=b.parse(i,a,r);this.cs=i;var n=0,s=1;if(e.has("Domain")){var o=e.getArray("Domain");n=o[0];s=o[1]}var c=!1,l=!1;if(e.has("Extend")){var u=e.getArray("Extend");c=u[0];l=u[1]}if(!(this.shadingType!==v.RADIAL||c&&l)){var f=this.coordsArr[0],p=this.coordsArr[1],y=this.coordsArr[2],w=this.coordsArr[3],C=this.coordsArr[4],x=this.coordsArr[5],S=Math.sqrt((f-w)*(f-w)+(p-C)*(p-C));y<=x+S&&x<=y+S&&g("Unsupported radial gradient.")}this.extendStart=c;this.extendEnd=l;var A=e.get("Function"),I=m.parseArray(a,A),B=s-n,R=B/10,T=this.colorStops=[];if(n>=s||R<=0)d("Bad shading domain.");else{for(var O,P=new Float32Array(i.numComps),M=new Float32Array(1),E=n;E<=s;E+=R){M[0]=E;I(M,0,P,0);O=i.getRgb(P,0);var L=h.makeCssRgb(O[0],O[1],O[2]);T.push([(E-n)/B,L])}var D="transparent";if(e.has("Background")){O=i.getRgb(e.get("Background"),0);D=h.makeCssRgb(O[0],O[1],O[2])}if(!c){T.unshift([0,D]);T[1][0]+=k.SMALL_NUMBER}if(!l){T[T.length-1][0]-=k.SMALL_NUMBER;T.push([1,D])}this.colorStops=T}}e.prototype={getIR:function(){var e,t,a,r,i,n=this.coordsArr,s=this.shadingType;if(s===v.AXIAL){t=[n[0],n[1]];a=[n[2],n[3]];r=null;i=null;e="axial"}else if(s===v.RADIAL){t=[n[0],n[1]];a=[n[3],n[4]];r=n[2];i=n[5];e="radial"}else f("getPattern type unknown: "+s);var o=this.matrix;if(o){t=h.applyTransform(t,o);a=h.applyTransform(a,o);if(s===v.RADIAL){var c=h.singularValueDecompose2dScale(o);r*=c[0];i*=c[1]}}return["RadialAxial",e,this.colorStops,t,a,r,i]}};return e}();k.Mesh=function(){function e(e,t){this.stream=e;this.context=t;this.buffer=0;this.bufferLength=0;var a=t.numComps;this.tmpCompsBuf=new Float32Array(a);var r=t.colorSpace.numComps;this.tmpCsCompsBuf=t.colorFn?new Float32Array(r):this.tmpCompsBuf}function t(e,t){for(var a=e.coords,r=e.colors,i=[],n=[],s=0;t.hasData;){var o=t.readFlag(),c=t.readCoordinate(),l=t.readComponents();if(0===s){u(0<=o&&o<=2,"Unknown type4 flag");switch(o){case 0:s=3;break;case 1:n.push(n[n.length-2],n[n.length-1]);s=1;break;case 2:n.push(n[n.length-3],n[n.length-1]);s=1}i.push(o)}n.push(a.length);a.push(c);r.push(l);s--;t.align()}e.figures.push({type:"triangles",coords:new Int32Array(n),colors:new Int32Array(n)})}function a(e,t,a){for(var r=e.coords,i=e.colors,n=[];t.hasData;){var s=t.readCoordinate(),o=t.readComponents();n.push(r.length);r.push(s);i.push(o)}e.figures.push({type:"lattice",coords:new Int32Array(n),colors:new Int32Array(n),verticesPerRow:a})}function r(e,t){var a=e.figures[t];u("patch"===a.type,"Unexpected patch mesh figure");var r=e.coords,i=e.colors,n=a.coords,s=a.colors,o=Math.min(r[n[0]][0],r[n[3]][0],r[n[12]][0],r[n[15]][0]),c=Math.min(r[n[0]][1],r[n[3]][1],r[n[12]][1],r[n[15]][1]),f=Math.max(r[n[0]][0],r[n[3]][0],r[n[12]][0],r[n[15]][0]),p=Math.max(r[n[0]][1],r[n[3]][1],r[n[12]][1],r[n[15]][1]),m=Math.ceil((f-o)*d/(e.bounds[2]-e.bounds[0]));m=Math.max(l,Math.min(h,m));var b=Math.ceil((p-c)*d/(e.bounds[3]-e.bounds[1]));b=Math.max(l,Math.min(h,b));for(var v=m+1,y=new Int32Array((b+1)*v),k=new Int32Array((b+1)*v),w=0,C=new Uint8Array(3),x=new Uint8Array(3),S=i[s[0]],A=i[s[1]],I=i[s[2]],B=i[s[3]],R=g(b),T=g(m),O=0;O<=b;O++){C[0]=(S[0]*(b-O)+I[0]*O)/b|0;C[1]=(S[1]*(b-O)+I[1]*O)/b|0;C[2]=(S[2]*(b-O)+I[2]*O)/b|0;x[0]=(A[0]*(b-O)+B[0]*O)/b|0;x[1]=(A[1]*(b-O)+B[1]*O)/b|0;x[2]=(A[2]*(b-O)+B[2]*O)/b|0;for(var P=0;P<=m;P++,w++)if(0!==O&&O!==b||0!==P&&P!==m){for(var M=0,E=0,L=0,D=0;D<=3;D++)for(var F=0;F<=3;F++,L++){var q=R[O][D]*T[P][F];M+=r[n[L]][0]*q;E+=r[n[L]][1]*q}y[w]=r.length;r.push([M,E]);k[w]=i.length;var U=new Uint8Array(3);U[0]=(C[0]*(m-P)+x[0]*P)/m|0;U[1]=(C[1]*(m-P)+x[1]*P)/m|0;U[2]=(C[2]*(m-P)+x[2]*P)/m|0;i.push(U)}}y[0]=n[0];k[0]=s[0];y[m]=n[3];k[m]=s[1];y[v*b]=n[12];k[v*b]=s[2];y[v*b+m]=n[15];k[v*b+m]=s[3];e.figures[t]={type:"lattice",coords:y,colors:k,verticesPerRow:v}}function i(e,t){for(var a=e.coords,r=e.colors,i=new Int32Array(16),n=new Int32Array(4);t.hasData;){var s=t.readFlag();u(0<=s&&s<=3,"Unknown type6 flag");var o,c,l=a.length;for(o=0,c=0!==s?8:12;o<c;o++)a.push(t.readCoordinate());var h=r.length;for(o=0,c=0!==s?2:4;o<c;o++)r.push(t.readComponents());var f,d,g,p;switch(s){case 0:i[12]=l+3;i[13]=l+4;i[14]=l+5;i[15]=l+6;i[8]=l+2;i[11]=l+7;i[4]=l+1;i[7]=l+8;i[0]=l;i[1]=l+11;i[2]=l+10;i[3]=l+9;n[2]=h+1;n[3]=h+2;n[0]=h;n[1]=h+3;break;case 1:f=i[12];d=i[13];g=i[14];p=i[15];i[12]=p;i[13]=l+0;i[14]=l+1;i[15]=l+2;i[8]=g;i[11]=l+3;i[4]=d;i[7]=l+4;i[0]=f;i[1]=l+7;i[2]=l+6;i[3]=l+5;f=n[2];d=n[3];n[2]=d;n[3]=h;n[0]=f;n[1]=h+1;break;case 2:f=i[15];d=i[11];i[12]=i[3];i[13]=l+0;i[14]=l+1;i[15]=l+2;i[8]=i[7];i[11]=l+3;i[4]=d;i[7]=l+4;i[0]=f;i[1]=l+7;i[2]=l+6;i[3]=l+5;f=n[3];n[2]=n[1];n[3]=h;n[0]=f;n[1]=h+1;break;case 3:i[12]=i[0];i[13]=l+0;i[14]=l+1;i[15]=l+2;i[8]=i[1];i[11]=l+3;i[4]=i[2];i[7]=l+4;i[0]=i[3];i[1]=l+7;i[2]=l+6;i[3]=l+5;n[2]=n[0];n[3]=h;n[0]=n[1];n[1]=h+1}i[5]=a.length;a.push([(-4*a[i[0]][0]-a[i[15]][0]+6*(a[i[4]][0]+a[i[1]][0])-2*(a[i[12]][0]+a[i[3]][0])+3*(a[i[13]][0]+a[i[7]][0]))/9,(-4*a[i[0]][1]-a[i[15]][1]+6*(a[i[4]][1]+a[i[1]][1])-2*(a[i[12]][1]+a[i[3]][1])+3*(a[i[13]][1]+a[i[7]][1]))/9]);i[6]=a.length;a.push([(-4*a[i[3]][0]-a[i[12]][0]+6*(a[i[2]][0]+a[i[7]][0])-2*(a[i[0]][0]+a[i[15]][0])+3*(a[i[4]][0]+a[i[14]][0]))/9,(-4*a[i[3]][1]-a[i[12]][1]+6*(a[i[2]][1]+a[i[7]][1])-2*(a[i[0]][1]+a[i[15]][1])+3*(a[i[4]][1]+a[i[14]][1]))/9]);i[9]=a.length;a.push([(-4*a[i[12]][0]-a[i[3]][0]+6*(a[i[8]][0]+a[i[13]][0])-2*(a[i[0]][0]+a[i[15]][0])+3*(a[i[11]][0]+a[i[1]][0]))/9,(-4*a[i[12]][1]-a[i[3]][1]+6*(a[i[8]][1]+a[i[13]][1])-2*(a[i[0]][1]+a[i[15]][1])+3*(a[i[11]][1]+a[i[1]][1]))/9]);i[10]=a.length;a.push([(-4*a[i[15]][0]-a[i[0]][0]+6*(a[i[11]][0]+a[i[14]][0])-2*(a[i[12]][0]+a[i[3]][0])+3*(a[i[2]][0]+a[i[8]][0]))/9,(-4*a[i[15]][1]-a[i[0]][1]+6*(a[i[11]][1]+a[i[14]][1])-2*(a[i[12]][1]+a[i[3]][1])+3*(a[i[2]][1]+a[i[8]][1]))/9]);e.figures.push({type:"patch",coords:new Int32Array(i),colors:new Int32Array(n)})}}function n(e,t){for(var a=e.coords,r=e.colors,i=new Int32Array(16),n=new Int32Array(4);t.hasData;){var s=t.readFlag();u(0<=s&&s<=3,"Unknown type7 flag");var o,c,l=a.length;for(o=0,c=0!==s?12:16;o<c;o++)a.push(t.readCoordinate());var h=r.length;for(o=0,c=0!==s?2:4;o<c;o++)r.push(t.readComponents());var f,d,g,p;switch(s){case 0:i[12]=l+3;i[13]=l+4;i[14]=l+5;i[15]=l+6;i[8]=l+2;i[9]=l+13;i[10]=l+14;i[11]=l+7;i[4]=l+1;i[5]=l+12;i[6]=l+15;i[7]=l+8;i[0]=l;i[1]=l+11;i[2]=l+10;i[3]=l+9;n[2]=h+1;n[3]=h+2;n[0]=h;n[1]=h+3;break;case 1:f=i[12];d=i[13];g=i[14];p=i[15];i[12]=p;i[13]=l+0;i[14]=l+1;i[15]=l+2;i[8]=g;i[9]=l+9;i[10]=l+10;i[11]=l+3;i[4]=d;i[5]=l+8;i[6]=l+11;i[7]=l+4;i[0]=f;i[1]=l+7;i[2]=l+6;i[3]=l+5;f=n[2];d=n[3];n[2]=d;n[3]=h;n[0]=f;n[1]=h+1;break;case 2:f=i[15];d=i[11];i[12]=i[3];i[13]=l+0;i[14]=l+1;i[15]=l+2;i[8]=i[7];i[9]=l+9;i[10]=l+10;i[11]=l+3;i[4]=d;i[5]=l+8;i[6]=l+11;i[7]=l+4;i[0]=f;i[1]=l+7;i[2]=l+6;i[3]=l+5;f=n[3];n[2]=n[1];n[3]=h;n[0]=f;n[1]=h+1;break;case 3:i[12]=i[0];i[13]=l+0;i[14]=l+1;i[15]=l+2;i[8]=i[1];i[9]=l+9;i[10]=l+10;i[11]=l+3;i[4]=i[2];i[5]=l+8;i[6]=l+11;i[7]=l+4;i[0]=i[3];i[1]=l+7;i[2]=l+6;i[3]=l+5;n[2]=n[0];n[3]=h;n[0]=n[1];n[1]=h+1}e.figures.push({type:"patch",coords:new Int32Array(i),colors:new Int32Array(n)})}}function s(e){for(var t=e.coords[0][0],a=e.coords[0][1],r=t,i=a,n=1,s=e.coords.length;n<s;n++){var o=e.coords[n][0],c=e.coords[n][1];t=t>o?o:t;a=a>c?c:a;r=r<o?o:r;i=i<c?c:i}e.bounds=[t,a,r,i]}function o(e){var t,a,r,i,n=e.coords,s=new Float32Array(2*n.length);for(t=0,r=0,a=n.length;t<a;t++){var o=n[t];s[r++]=o[0];s[r++]=o[1]}e.coords=s;var c=e.colors,l=new Uint8Array(3*c.length);for(t=0,r=0,a=c.length;t<a;t++){var h=c[t];l[r++]=h[0];l[r++]=h[1];l[r++]=h[2]}e.colors=l;var u=e.figures;for(t=0,a=u.length;t<a;t++){var f=u[t],d=f.coords,g=f.colors;for(r=0,i=d.length;r<i;r++){d[r]*=2;g[r]*=3}}}function c(c,l,h,d){u(p(c),"Mesh data is not a stream");var g=c.dict;this.matrix=l;this.shadingType=g.get("ShadingType");this.type="Pattern";this.bbox=g.getArray("BBox");var y=g.get("ColorSpace","CS");y=b.parse(y,h,d);this.cs=y;this.background=g.has("Background")?y.getRgb(g.get("Background"),0):null;var k=g.get("Function"),w=k?m.parseArray(h,k):null;this.coords=[];this.colors=[];this.figures=[];var C={bitsPerCoordinate:g.get("BitsPerCoordinate"),bitsPerComponent:g.get("BitsPerComponent"),bitsPerFlag:g.get("BitsPerFlag"),decode:g.getArray("Decode"),colorFn:w,colorSpace:y,numComps:w?1:y.numComps},x=new e(c,C),S=!1;switch(this.shadingType){case v.FREE_FORM_MESH:t(this,x);break;case v.LATTICE_FORM_MESH:var A=0|g.get("VerticesPerRow");u(A>=2,"Invalid VerticesPerRow");a(this,x,A);break;case v.COONS_PATCH_MESH:i(this,x);S=!0;break;case v.TENSOR_PATCH_MESH:n(this,x);S=!0;break;default:f("Unsupported mesh type.")}if(S){s(this);for(var I=0,B=this.figures.length;I<B;I++)r(this,I)}s(this);o(this)}e.prototype={get hasData(){if(this.stream.end)return this.stream.pos<this.stream.end;if(this.bufferLength>0)return!0;var e=this.stream.getByte();if(e<0)return!1;this.buffer=e;this.bufferLength=8;return!0},readBits:function(e){var t=this.buffer,a=this.bufferLength;if(32===e){if(0===a)return(this.stream.getByte()<<24|this.stream.getByte()<<16|this.stream.getByte()<<8|this.stream.getByte())>>>0;t=t<<24|this.stream.getByte()<<16|this.stream.getByte()<<8|this.stream.getByte();var r=this.stream.getByte();this.buffer=r&(1<<a)-1;return(t<<8-a|(255&r)>>a)>>>0}if(8===e&&0===a)return this.stream.getByte();for(;a<e;){t=t<<8|this.stream.getByte();a+=8}a-=e;this.bufferLength=a;this.buffer=t&(1<<a)-1;return t>>a},align:function(){this.buffer=0;this.bufferLength=0},readFlag:function(){return this.readBits(this.context.bitsPerFlag)},readCoordinate:function(){var e=this.context.bitsPerCoordinate,t=this.readBits(e),a=this.readBits(e),r=this.context.decode,i=e<32?1/((1<<e)-1):2.3283064365386963e-10;return[t*i*(r[1]-r[0])+r[0],a*i*(r[3]-r[2])+r[2]]},readComponents:function(){for(var e=this.context.numComps,t=this.context.bitsPerComponent,a=t<32?1/((1<<t)-1):2.3283064365386963e-10,r=this.context.decode,i=this.tmpCompsBuf,n=0,s=4;n<e;n++,s+=2){var o=this.readBits(t);i[n]=o*a*(r[s+1]-r[s])+r[s]}var c=this.tmpCsCompsBuf;this.context.colorFn&&this.context.colorFn(i,0,c,0);return this.context.colorSpace.getRgb(c,0)}};var l=3,h=20,d=20,g=function(){function e(e){for(var t=[],a=0;a<=e;a++){var r=a/e,i=1-r;t.push(new Float32Array([i*i*i,3*r*i*i,3*r*r*i,r*r*r]))}return t}var t=[];return function(a){t[a]||(t[a]=e(a));return t[a]}}();c.prototype={getIR:function(){return["Mesh",this.shadingType,this.coords,this.colors,this.figures,this.bounds,this.matrix,this.bbox,this.background]}};return c}();k.Dummy=function(){function e(){this.type="Pattern"}e.prototype={getIR:function(){return["Dummy"]}};return e}();t.Pattern=y;t.getTilingPatternIR=r},function(e,t,a){"use strict";var r=a(0),i=a(2),n=a(12),s=a(24),o=r.warn,c=r.createValidAbsoluteUrl,l=r.shadow,h=r.NotImplementedException,u=r.MissingDataException,f=r.createPromiseCapability,d=r.Util,g=i.Stream,p=n.ChunkedStreamManager,m=s.PDFDocument,b=function(){function e(){throw new Error("Cannot initialize BaseManagerManager")}e.prototype={get docId(){return this._docId},get password(){return this._password},get docBaseUrl(){var e=null;if(this._docBaseUrl){var t=c(this._docBaseUrl);t?e=t.href:o('Invalid absolute docBaseUrl: "'+this._docBaseUrl+'".')}return l(this,"docBaseUrl",e)},onLoadedStream:function(){throw new h},ensureDoc:function(e,t){return this.ensure(this.pdfDocument,e,t)},ensureXRef:function(e,t){return this.ensure(this.pdfDocument.xref,e,t)},ensureCatalog:function(e,t){return this.ensure(this.pdfDocument.catalog,e,t)},getPage:function(e){return this.pdfDocument.getPage(e)},cleanup:function(){return this.pdfDocument.cleanup()},ensure:function(e,t,a){return new h},requestRange:function(e,t){return new h},requestLoadedStream:function(){return new h},sendProgressiveData:function(e){return new h},updatePassword:function(e){this._password=e},terminate:function(){return new h}};return e}(),v=function(){function e(e,t,a,r,i){this._docId=e;this._password=a;this._docBaseUrl=i;this.evaluatorOptions=r;var n=new g(t);this.pdfDocument=new m(this,n);this._loadedStreamCapability=f();this._loadedStreamCapability.resolve(n)}d.inherit(e,b,{ensure:function(e,t,a){return new Promise(function(r,i){try{var n,s=e[t];n="function"==typeof s?s.apply(e,a):s;r(n)}catch(e){i(e)}})},requestRange:function(e,t){return Promise.resolve()},requestLoadedStream:function(){},onLoadedStream:function(){return this._loadedStreamCapability.promise},terminate:function(){}});return e}(),y=function(){function e(e,t,a,r,i){this._docId=e;this._password=a.password;this._docBaseUrl=i;this.msgHandler=a.msgHandler;this.evaluatorOptions=r;var n={msgHandler:a.msgHandler,url:a.url,length:a.length,disableAutoFetch:a.disableAutoFetch,rangeChunkSize:a.rangeChunkSize};this.streamManager=new p(t,n);this.pdfDocument=new m(this,this.streamManager.getStream())}d.inherit(e,b,{ensure:function(e,t,a){var r=this;return new Promise(function(i,n){function s(){try{var o,c=e[t];o="function"==typeof c?c.apply(e,a):c;i(o)}catch(e){if(!(e instanceof u)){n(e);return}r.streamManager.requestRange(e.begin,e.end).then(s,n)}}s()})},requestRange:function(e,t){return this.streamManager.requestRange(e,t)},requestLoadedStream:function(){this.streamManager.requestAllChunks()},sendProgressiveData:function(e){this.streamManager.onReceiveData({chunk:e})},onLoadedStream:function(){return this.streamManager.onLoadedStream()},terminate:function(){this.streamManager.abort()}});return e}();t.LocalPdfManager=v;t.NetworkPdfManager=y},function(e,t,a){"use strict";var r=a(0),i=a(1),n=r.error,s=r.isSpace,o=i.EOF,c=function(){function e(e){this.lexer=e;this.operators=[];this.token=null;this.prev=null}e.prototype={nextToken:function(){this.prev=this.token;this.token=this.lexer.getToken()},accept:function(e){if(this.token.type===e){this.nextToken();return!0}return!1},expect:function(e){if(this.accept(e))return!0;n("Unexpected symbol: found "+this.token.type+" expected "+e+".")},parse:function(){this.nextToken();this.expect(l.LBRACE);this.parseBlock();this.expect(l.RBRACE);return this.operators},parseBlock:function(){for(;;)if(this.accept(l.NUMBER))this.operators.push(this.prev.value);else if(this.accept(l.OPERATOR))this.operators.push(this.prev.value);else{if(!this.accept(l.LBRACE))return;this.parseCondition()}},parseCondition:function(){var e=this.operators.length;this.operators.push(null,null);this.parseBlock();this.expect(l.RBRACE);if(this.accept(l.IF)){this.operators[e]=this.operators.length;this.operators[e+1]="jz"}else if(this.accept(l.LBRACE)){var t=this.operators.length;this.operators.push(null,null);var a=this.operators.length;this.parseBlock();this.expect(l.RBRACE);this.expect(l.IFELSE);this.operators[t]=this.operators.length;this.operators[t+1]="j";this.operators[e]=a;this.operators[e+1]="jz"}else n("PS Function: error parsing conditional.")}};return e}(),l={LBRACE:0,RBRACE:1,NUMBER:2,OPERATOR:3,IF:4,
+IFELSE:5},h=function(){function e(e,t){this.type=e;this.value=t}var t=Object.create(null);e.getOperator=function(a){var r=t[a];return r||(t[a]=new e(l.OPERATOR,a))};e.LBRACE=new e(l.LBRACE,"{");e.RBRACE=new e(l.RBRACE,"}");e.IF=new e(l.IF,"IF");e.IFELSE=new e(l.IFELSE,"IFELSE");return e}(),u=function(){function e(e){this.stream=e;this.nextChar();this.strBuf=[]}e.prototype={nextChar:function(){return this.currentChar=this.stream.getByte()},getToken:function(){for(var e=!1,t=this.currentChar;;){if(t<0)return o;if(e)10!==t&&13!==t||(e=!1);else if(37===t)e=!0;else if(!s(t))break;t=this.nextChar()}switch(0|t){case 48:case 49:case 50:case 51:case 52:case 53:case 54:case 55:case 56:case 57:case 43:case 45:case 46:return new h(l.NUMBER,this.getNumber());case 123:this.nextChar();return h.LBRACE;case 125:this.nextChar();return h.RBRACE}var a=this.strBuf;a.length=0;a[0]=String.fromCharCode(t);for(;(t=this.nextChar())>=0&&(t>=65&&t<=90||t>=97&&t<=122);)a.push(String.fromCharCode(t));var r=a.join("");switch(r.toLowerCase()){case"if":return h.IF;case"ifelse":return h.IFELSE;default:return h.getOperator(r)}},getNumber:function(){var e=this.currentChar,t=this.strBuf;t.length=0;t[0]=String.fromCharCode(e);for(;(e=this.nextChar())>=0&&(e>=48&&e<=57||45===e||46===e);)t.push(String.fromCharCode(e));var a=parseFloat(t.join(""));isNaN(a)&&n("Invalid floating point number: "+a);return a}};return e}();t.PostScriptLexer=u;t.PostScriptParser=c},function(e,t,a){"use strict";var r=a(0),i=a(2),n=a(4),s=r.warn,o=r.isSpace,c=i.Stream,l=n.getEncoding,h=function(){function e(){this.width=0;this.lsb=0;this.flexing=!1;this.output=[];this.stack=[]}var t={hstem:[1],vstem:[3],vmoveto:[4],rlineto:[5],hlineto:[6],vlineto:[7],rrcurveto:[8],callsubr:[10],flex:[12,35],drop:[12,18],endchar:[14],rmoveto:[21],hmoveto:[22],vhcurveto:[30],hvcurveto:[31]};e.prototype={convert:function(e,a,r){for(var i,n,o,c=e.length,l=!1,h=0;h<c;h++){var u=e[h];if(u<32){12===u&&(u=(u<<8)+e[++h]);switch(u){case 1:case 3:this.stack=[];break;case 4:if(this.flexing){if(this.stack.length<1){l=!0;break}var f=this.stack.pop();this.stack.push(0,f);break}l=this.executeCommand(1,t.vmoveto);break;case 5:l=this.executeCommand(2,t.rlineto);break;case 6:l=this.executeCommand(1,t.hlineto);break;case 7:l=this.executeCommand(1,t.vlineto);break;case 8:l=this.executeCommand(6,t.rrcurveto);break;case 9:this.stack=[];break;case 10:if(this.stack.length<1){l=!0;break}o=this.stack.pop();l=this.convert(a[o],a,r);break;case 11:return l;case 13:if(this.stack.length<2){l=!0;break}i=this.stack.pop();n=this.stack.pop();this.lsb=n;this.width=i;this.stack.push(i,n);l=this.executeCommand(2,t.hmoveto);break;case 14:this.output.push(t.endchar[0]);break;case 21:if(this.flexing)break;l=this.executeCommand(2,t.rmoveto);break;case 22:if(this.flexing){this.stack.push(0);break}l=this.executeCommand(1,t.hmoveto);break;case 30:l=this.executeCommand(4,t.vhcurveto);break;case 31:l=this.executeCommand(4,t.hvcurveto);break;case 3072:case 3073:case 3074:this.stack=[];break;case 3078:if(r){this.seac=this.stack.splice(-4,4);l=this.executeCommand(0,t.endchar)}else l=this.executeCommand(4,t.endchar);break;case 3079:if(this.stack.length<4){l=!0;break}this.stack.pop();i=this.stack.pop();var d=this.stack.pop();n=this.stack.pop();this.lsb=n;this.width=i;this.stack.push(i,n,d);l=this.executeCommand(3,t.rmoveto);break;case 3084:if(this.stack.length<2){l=!0;break}var g=this.stack.pop(),p=this.stack.pop();this.stack.push(p/g);break;case 3088:if(this.stack.length<2){l=!0;break}o=this.stack.pop();var m=this.stack.pop();if(0===o&&3===m){var b=this.stack.splice(this.stack.length-17,17);this.stack.push(b[2]+b[0],b[3]+b[1],b[4],b[5],b[6],b[7],b[8],b[9],b[10],b[11],b[12],b[13],b[14]);l=this.executeCommand(13,t.flex,!0);this.flexing=!1;this.stack.push(b[15],b[16])}else 1===o&&0===m&&(this.flexing=!0);break;case 3089:break;case 3105:this.stack=[];break;default:s('Unknown type 1 charstring command of "'+u+'"')}if(l)break}else{u<=246?u-=139:u=u<=250?256*(u-247)+e[++h]+108:u<=254?-256*(u-251)-e[++h]-108:(255&e[++h])<<24|(255&e[++h])<<16|(255&e[++h])<<8|(255&e[++h])<<0;this.stack.push(u)}}return l},executeCommand:function(e,t,a){var r=this.stack.length;if(e>r)return!0;for(var i=r-e,n=i;n<r;n++){var s=this.stack[n];if(s===(0|s))this.output.push(28,s>>8&255,255&s);else{s=65536*s|0;this.output.push(255,s>>24&255,s>>16&255,s>>8&255,255&s)}}this.output.push.apply(this.output,t);a?this.stack.splice(i,e):this.stack.length=0;return!1}};return e}(),u=function(){function e(e){return e>=48&&e<=57||e>=65&&e<=70||e>=97&&e<=102}function t(e,t,a){if(a>=e.length)return new Uint8Array(0);var r,i,n=0|t;for(r=0;r<a;r++)n=52845*(e[r]+n)+22719&65535;var s=e.length-a,o=new Uint8Array(s);for(r=a,i=0;i<s;r++,i++){var c=e[r];o[i]=c^n>>8;n=52845*(c+n)+22719&65535}return o}function a(t,a,r){var i,n,s=0|a,o=t.length,c=o>>>1,l=new Uint8Array(c);for(i=0,n=0;i<o;i++){var h=t[i];if(e(h)){i++;for(var u;i<o&&!e(u=t[i]);)i++;if(i<o){var f=parseInt(String.fromCharCode(h,u),16);l[n++]=f^s>>8;s=52845*(f+s)+22719&65535}}}return Array.prototype.slice.call(l,r,n)}function r(e){return 47===e||91===e||93===e||123===e||125===e||40===e||41===e}function i(r,i,s){if(i){var o=r.getBytes(),l=!(e(o[0])&&e(o[1])&&e(o[2])&&e(o[3]));r=new c(l?t(o,n,4):a(o,n,4))}this.seacAnalysisEnabled=!!s;this.stream=r;this.nextChar()}var n=55665;i.prototype={readNumberArray:function(){this.getToken();for(var e=[];;){var t=this.getToken();if(null===t||"]"===t||"}"===t)break;e.push(parseFloat(t||0))}return e},readNumber:function(){var e=this.getToken();return parseFloat(e||0)},readInt:function(){var e=this.getToken();return 0|parseInt(e||0,10)},readBoolean:function(){return"true"===this.getToken()?1:0},nextChar:function(){return this.currentChar=this.stream.getByte()},getToken:function(){for(var e=!1,t=this.currentChar;;){if(-1===t)return null;if(e)10!==t&&13!==t||(e=!1);else if(37===t)e=!0;else if(!o(t))break;t=this.nextChar()}if(r(t)){this.nextChar();return String.fromCharCode(t)}var a="";do{a+=String.fromCharCode(t);t=this.nextChar()}while(t>=0&&!o(t)&&!r(t));return a},extractFontProgram:function(){var e=this.stream,a=[],r=[],i=Object.create(null);i.lenIV=4;for(var n,s,o,c,l,u={subrs:[],charstrings:[],properties:{privateData:i}};null!==(n=this.getToken());)if("/"===n){n=this.getToken();switch(n){case"CharStrings":this.getToken();this.getToken();this.getToken();this.getToken();for(;;){n=this.getToken();if(null===n||"end"===n)break;if("/"===n){var f=this.getToken();s=this.readInt();this.getToken();o=e.makeSubStream(e.pos,s);c=u.properties.privateData.lenIV;l=t(o.getBytes(),4330,c);e.skip(s);this.nextChar();n=this.getToken();"noaccess"===n&&this.getToken();r.push({glyph:f,encoded:l})}}break;case"Subrs":this.readInt();this.getToken();for(;"dup"===(n=this.getToken());){var d=this.readInt();s=this.readInt();this.getToken();o=e.makeSubStream(e.pos,s);c=u.properties.privateData.lenIV;l=t(o.getBytes(),4330,c);e.skip(s);this.nextChar();n=this.getToken();"noaccess"===n&&this.getToken();a[d]=l}break;case"BlueValues":case"OtherBlues":case"FamilyBlues":case"FamilyOtherBlues":var g=this.readNumberArray();(g.length>0&&g.length,1)||(u.properties.privateData[n]=g);break;case"StemSnapH":case"StemSnapV":u.properties.privateData[n]=this.readNumberArray();break;case"StdHW":case"StdVW":u.properties.privateData[n]=this.readNumberArray()[0];break;case"BlueShift":case"lenIV":case"BlueFuzz":case"BlueScale":case"LanguageGroup":case"ExpansionFactor":u.properties.privateData[n]=this.readNumber();break;case"ForceBold":u.properties.privateData[n]=this.readBoolean()}}for(var p=0;p<r.length;p++){f=r[p].glyph;l=r[p].encoded;var m=new h,b=m.convert(l,a,this.seacAnalysisEnabled),v=m.output;b&&(v=[14]);u.charstrings.push({glyphName:f,charstring:v,width:m.width,lsb:m.lsb,seac:m.seac})}return u},extractFontHeader:function(e){for(var t;null!==(t=this.getToken());)if("/"===t){t=this.getToken();switch(t){case"FontMatrix":var a=this.readNumberArray();e.fontMatrix=a;break;case"Encoding":var r,i=this.getToken();if(/^\d+$/.test(i)){r=[];var n=0|parseInt(i,10);this.getToken();for(var s=0;s<n;s++){t=this.getToken();for(;"dup"!==t&&"def"!==t;){t=this.getToken();if(null===t)return}if("def"===t)break;var o=this.readInt();this.getToken();var c=this.getToken();r[o]=c;this.getToken()}}else r=l(i);e.builtInEncoding=r;break;case"FontBBox":var h=this.readNumberArray();e.ascent=Math.max(h[3],h[1]);e.descent=Math.min(h[1],h[3]);e.ascentScaled=!0}}}};return i}();t.Type1Parser=u},function(e,t,a){"use strict";var r=a(8);a(19);t.WorkerMessageHandler=r.WorkerMessageHandler},function(e,t,a){"use strict";(function(e){if("undefined"==typeof PDFJS||!PDFJS.compatibilityChecked){var t="undefined"!=typeof window?window:void 0!==e?e:"undefined"!=typeof self?self:void 0,a="undefined"!=typeof navigator&&navigator.userAgent||"",r=/Android/.test(a),i=/Android\s[0-2][^\d]/.test(a),n=/Android\s[0-4][^\d]/.test(a),s=a.indexOf("Chrom")>=0,o=/Chrome\/(39|40)\./.test(a),c=a.indexOf("CriOS")>=0,l=a.indexOf("Trident")>=0,h=/\b(iPad|iPhone|iPod)(?=;)/.test(a),u=a.indexOf("Opera")>=0,f=/Safari\//.test(a)&&!/(Chrome\/|Android\s)/.test(a),d="object"==typeof window&&"object"==typeof document;"undefined"==typeof PDFJS&&(t.PDFJS={});PDFJS.compatibilityChecked=!0;!function(){function e(e,t){return new r(this.slice(e,t))}function a(e,t){arguments.length<2&&(t=0);for(var a=0,r=e.length;a<r;++a,++t)this[t]=255&e[a]}function r(t){var r,i,n;if("number"==typeof t){r=[];for(i=0;i<t;++i)r[i]=0}else if("slice"in t)r=t.slice(0);else{r=[];for(i=0,n=t.length;i<n;++i)r[i]=t[i]}r.subarray=e;r.buffer=r;r.byteLength=r.length;r.set=a;"object"==typeof t&&t.buffer&&(r.buffer=t.buffer);return r}if("undefined"==typeof Uint8Array){t.Uint8Array=r;t.Int8Array=r;t.Uint32Array=r;t.Int32Array=r;t.Uint16Array=r;t.Float32Array=r;t.Float64Array=r}else{if(void 0===Uint8Array.prototype.subarray){Uint8Array.prototype.subarray=function(e,t){return new Uint8Array(this.slice(e,t))};Float32Array.prototype.subarray=function(e,t){return new Float32Array(this.slice(e,t))}}"undefined"==typeof Float64Array&&(t.Float64Array=Float32Array)}}();!function(){t.URL||(t.URL=t.webkitURL)}();!function(){if(void 0!==Object.defineProperty){var e=!0;try{d&&Object.defineProperty(new Image,"id",{value:"test"});var t=function(){};t.prototype={get id(){}};Object.defineProperty(new t,"id",{value:"",configurable:!0,enumerable:!0,writable:!1})}catch(t){e=!1}if(e)return}Object.defineProperty=function(e,t,a){delete e[t];"get"in a&&e.__defineGetter__(t,a.get);"set"in a&&e.__defineSetter__(t,a.set);if("value"in a){e.__defineSetter__(t,function(e){this.__defineGetter__(t,function(){return e});return e});e[t]=a.value}}}();!function(){if("undefined"!=typeof XMLHttpRequest){var e=XMLHttpRequest.prototype,t=new XMLHttpRequest;"overrideMimeType"in t||Object.defineProperty(e,"overrideMimeType",{value:function(e){}});if(!("responseType"in t)){Object.defineProperty(e,"responseType",{get:function(){return this._responseType||"text"},set:function(e){if("text"===e||"arraybuffer"===e){this._responseType=e;"arraybuffer"===e&&"function"==typeof this.overrideMimeType&&this.overrideMimeType("text/plain; charset=x-user-defined")}}});"undefined"==typeof VBArray?Object.defineProperty(e,"response",{get:function(){if("arraybuffer"!==this.responseType)return this.responseText;var e,t=this.responseText,a=t.length,r=new Uint8Array(a);for(e=0;e<a;++e)r[e]=255&t.charCodeAt(e);return r.buffer}}):Object.defineProperty(e,"response",{get:function(){return"arraybuffer"===this.responseType?new Uint8Array(new VBArray(this.responseBody).toArray()):this.responseText}})}}}();!function(){if(!("btoa"in t)){var e="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";t.btoa=function(t){var a,r,i="";for(a=0,r=t.length;a<r;a+=3){var n=255&t.charCodeAt(a),s=255&t.charCodeAt(a+1),o=255&t.charCodeAt(a+2),c=n>>2,l=(3&n)<<4|s>>4,h=a+1<r?(15&s)<<2|o>>6:64,u=a+2<r?63&o:64;i+=e.charAt(c)+e.charAt(l)+e.charAt(h)+e.charAt(u)}return i}}}();!function(){if(!("atob"in t)){t.atob=function(e){e=e.replace(/=+$/,"");if(e.length%4==1)throw new Error("bad atob input");for(var t,a,r=0,i=0,n="";a=e.charAt(i++);~a&&(t=r%4?64*t+a:a,r++%4)?n+=String.fromCharCode(255&t>>(-2*r&6)):0)a="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".indexOf(a);return n}}}();!function(){void 0===Function.prototype.bind&&(Function.prototype.bind=function(e){var t=this,a=Array.prototype.slice.call(arguments,1);return function(){var r=a.concat(Array.prototype.slice.call(arguments));return t.apply(e,r)}})}();!function(){if(d){"dataset"in document.createElement("div")||Object.defineProperty(HTMLElement.prototype,"dataset",{get:function(){if(this._dataset)return this._dataset;for(var e={},t=0,a=this.attributes.length;t<a;t++){var r=this.attributes[t];if("data-"===r.name.substring(0,5)){e[r.name.substring(5).replace(/\-([a-z])/g,function(e,t){return t.toUpperCase()})]=r.value}}Object.defineProperty(this,"_dataset",{value:e,writable:!1,enumerable:!1});return e},enumerable:!0})}}();!function(){function e(e,t,a,r){var i=e.className||"",n=i.split(/\s+/g);""===n[0]&&n.shift();var s=n.indexOf(t);s<0&&a&&n.push(t);s>=0&&r&&n.splice(s,1);e.className=n.join(" ");return s>=0}if(d){if(!("classList"in document.createElement("div"))){var t={add:function(t){e(this.element,t,!0,!1)},contains:function(t){return e(this.element,t,!1,!1)},remove:function(t){e(this.element,t,!1,!0)},toggle:function(t){e(this.element,t,!0,!0)}};Object.defineProperty(HTMLElement.prototype,"classList",{get:function(){if(this._classList)return this._classList;var e=Object.create(t,{element:{value:this,writable:!1,enumerable:!0}});Object.defineProperty(this,"_classList",{value:e,writable:!1,enumerable:!1});return e},enumerable:!0})}}}();!function(){if(!("undefined"==typeof importScripts||"console"in t)){var e={},a={log:function(){var e=Array.prototype.slice.call(arguments);t.postMessage({targetName:"main",action:"console_log",data:e})},error:function(){var e=Array.prototype.slice.call(arguments);t.postMessage({targetName:"main",action:"console_error",data:e})},time:function(t){e[t]=Date.now()},timeEnd:function(t){var a=e[t];if(!a)throw new Error("Unknown timer name "+t);this.log("Timer:",t,Date.now()-a)}};t.console=a}}();!function(){if(d)if("console"in window)if("bind"in console.log);else{console.log=function(e){return function(t){return e(t)}}(console.log);console.error=function(e){return function(t){return e(t)}}(console.error);console.warn=function(e){return function(t){return e(t)}}(console.warn)}else window.console={log:function(){},error:function(){},warn:function(){}}}();!function(){function e(e){t(e.target)&&e.stopPropagation()}function t(e){return e.disabled||e.parentNode&&t(e.parentNode)}u&&document.addEventListener("click",e,!0)}();!function(){(l||c)&&(PDFJS.disableCreateObjectURL=!0)}();!function(){"undefined"!=typeof navigator&&("language"in navigator||(PDFJS.locale=navigator.userLanguage||"en-US"))}();!function(){if(f||i||o||h){PDFJS.disableRange=!0;PDFJS.disableStream=!0}}();!function(){d&&(history.pushState&&!i||(PDFJS.disableHistory=!0))}();!function(){if(d)if(window.CanvasPixelArray)"function"!=typeof window.CanvasPixelArray.prototype.set&&(window.CanvasPixelArray.prototype.set=function(e){for(var t=0,a=this.length;t<a;t++)this[t]=e[t]});else{var e,t=!1;if(s){e=a.match(/Chrom(e|ium)\/([0-9]+)\./);t=e&&parseInt(e[2])<21}else if(r)t=n;else if(f){e=a.match(/Version\/([0-9]+)\.([0-9]+)\.([0-9]+) Safari\//);t=e&&parseInt(e[1])<6}if(t){var i=window.CanvasRenderingContext2D.prototype,o=i.createImageData;i.createImageData=function(e,t){var a=o.call(this,e,t);a.data.set=function(e){for(var t=0,a=this.length;t<a;t++)this[t]=e[t]};return a};i=null}}}();!function(){function e(){window.requestAnimationFrame=function(e){return window.setTimeout(e,20)};window.cancelAnimationFrame=function(e){window.clearTimeout(e)}}if(d)if(h)e();else if(!("requestAnimationFrame"in window)){window.requestAnimationFrame=window.mozRequestAnimationFrame||window.webkitRequestAnimationFrame;"requestAnimationFrame"in window||e()}}();!function(){(h||r)&&(PDFJS.maxCanvasPixels=5242880)}();!function(){d&&l&&window.parent!==window&&(PDFJS.disableFullscreen=!0)}();!function(){d&&("currentScript"in document||Object.defineProperty(document,"currentScript",{get:function(){var e=document.getElementsByTagName("script");return e[e.length-1]},enumerable:!0,configurable:!0}))}();!function(){if(d){var e=document.createElement("input");try{e.type="number"}catch(r){var t=e.constructor.prototype,a=Object.getOwnPropertyDescriptor(t,"type");Object.defineProperty(t,"type",{get:function(){return a.get.call(this)},set:function(e){a.set.call(this,"number"===e?"text":e)},enumerable:!0,configurable:!0})}}}();!function(){if(d&&document.attachEvent){var e=document.constructor.prototype,t=Object.getOwnPropertyDescriptor(e,"readyState");Object.defineProperty(e,"readyState",{get:function(){var e=t.get.call(this);return"interactive"===e?"loading":e},set:function(e){t.set.call(this,e)},enumerable:!0,configurable:!0})}}();!function(){d&&void 0===Element.prototype.remove&&(Element.prototype.remove=function(){this.parentNode&&this.parentNode.removeChild(this)})}();!function(){if(t.Promise){"function"!=typeof t.Promise.all&&(t.Promise.all=function(e){var a,r,i=0,n=[],s=new t.Promise(function(e,t){a=e;r=t});e.forEach(function(e,t){i++;e.then(function(e){n[t]=e;i--;0===i&&a(n)},r)});0===i&&a(n);return s});"function"!=typeof t.Promise.resolve&&(t.Promise.resolve=function(e){return new t.Promise(function(t){t(e)})});"function"!=typeof t.Promise.reject&&(t.Promise.reject=function(e){return new t.Promise(function(t,a){a(e)})});"function"!=typeof t.Promise.prototype.catch&&(t.Promise.prototype.catch=function(e){return t.Promise.prototype.then(void 0,e)})}else{var e=2,a={handlers:[],running:!1,unhandledRejections:[],pendingRejectionCheck:!1,scheduleHandlers:function(e){if(0!==e._status){this.handlers=this.handlers.concat(e._handlers);e._handlers=[];if(!this.running){this.running=!0;setTimeout(this.runHandlers.bind(this),0)}}},runHandlers:function(){for(var t=Date.now()+1;this.handlers.length>0;){var a=this.handlers.shift(),r=a.thisPromise._status,i=a.thisPromise._value;try{if(1===r)"function"==typeof a.onResolve&&(i=a.onResolve(i));else if("function"==typeof a.onReject){i=a.onReject(i);r=1;a.thisPromise._unhandledRejection&&this.removeUnhandeledRejection(a.thisPromise)}}catch(t){r=e;i=t}a.nextPromise._updateStatus(r,i);if(Date.now()>=t)break}this.handlers.length>0?setTimeout(this.runHandlers.bind(this),0):this.running=!1},addUnhandledRejection:function(e){this.unhandledRejections.push({promise:e,time:Date.now()});this.scheduleRejectionCheck()},removeUnhandeledRejection:function(e){e._unhandledRejection=!1;for(var t=0;t<this.unhandledRejections.length;t++)if(this.unhandledRejections[t].promise===e){this.unhandledRejections.splice(t);t--}},scheduleRejectionCheck:function(){if(!this.pendingRejectionCheck){this.pendingRejectionCheck=!0;setTimeout(function(){this.pendingRejectionCheck=!1;for(var e=Date.now(),t=0;t<this.unhandledRejections.length;t++)if(e-this.unhandledRejections[t].time>500){var a=this.unhandledRejections[t].promise._value,r="Unhandled rejection: "+a;a.stack&&(r+="\n"+a.stack);try{throw new Error(r)}catch(e){console.warn(r)}this.unhandledRejections.splice(t);t--}this.unhandledRejections.length&&this.scheduleRejectionCheck()}.bind(this),500)}}},r=function(e){this._status=0;this._handlers=[];try{e.call(this,this._resolve.bind(this),this._reject.bind(this))}catch(e){this._reject(e)}};r.all=function(t){function a(t){if(s._status!==e){c=[];n(t)}}var i,n,s=new r(function(e,t){i=e;n=t}),o=t.length,c=[];if(0===o){i(c);return s}for(var l=0,h=t.length;l<h;++l){var u=t[l],f=function(t){return function(a){if(s._status!==e){c[t]=a;o--;0===o&&i(c)}}}(l);r.isPromise(u)?u.then(f,a):f(u)}return s};r.isPromise=function(e){return e&&"function"==typeof e.then};r.resolve=function(e){return new r(function(t){t(e)})};r.reject=function(e){return new r(function(t,a){a(e)})};r.prototype={_status:null,_value:null,_handlers:null,_unhandledRejection:null,_updateStatus:function(t,i){if(1!==this._status&&this._status!==e)if(1===t&&r.isPromise(i))i.then(this._updateStatus.bind(this,1),this._updateStatus.bind(this,e));else{this._status=t;this._value=i;if(t===e&&0===this._handlers.length){this._unhandledRejection=!0;a.addUnhandledRejection(this)}a.scheduleHandlers(this)}},_resolve:function(e){this._updateStatus(1,e)},_reject:function(t){this._updateStatus(e,t)},then:function(e,t){var i=new r(function(e,t){this.resolve=e;this.reject=t});this._handlers.push({thisPromise:this,onResolve:e,onReject:t,nextPromise:i});a.scheduleHandlers(this);return i},catch:function(e){return this.then(void 0,e)}};t.Promise=r}}();!function(){function e(){this.id="$weakmap"+a++}if(!t.WeakMap){var a=0;e.prototype={has:function(e){return!!Object.getOwnPropertyDescriptor(e,this.id)},get:function(e,t){return this.has(e)?e[this.id]:t},set:function(e,t){Object.defineProperty(e,this.id,{value:t,enumerable:!1,configurable:!0})},delete:function(e){delete e[this.id]}};t.WeakMap=e}}();!function(){function e(e){return void 0!==u[e]}function a(){o.call(this);this._isInvalid=!0}function r(e){""===e&&a.call(this);return e.toLowerCase()}function i(e){var t=e.charCodeAt(0);return t>32&&t<127&&-1===[34,35,60,62,63,96].indexOf(t)?e:encodeURIComponent(e)}function n(e){var t=e.charCodeAt(0);return t>32&&t<127&&-1===[34,35,60,62,96].indexOf(t)?e:encodeURIComponent(e)}function s(t,s,o){function c(e){y.push(e)}var l=s||"scheme start",h=0,m="",b=!1,v=!1,y=[];e:for(;(t[h-1]!==d||0===h)&&!this._isInvalid;){var k=t[h];switch(l){case"scheme start":if(!k||!g.test(k)){if(s){c("Invalid scheme.");break e}m="";l="no scheme";continue}m+=k.toLowerCase();l="scheme";break;case"scheme":if(k&&p.test(k))m+=k.toLowerCase();else{if(":"!==k){if(s){if(k===d)break e;c("Code point not allowed in scheme: "+k);break e}m="";h=0;l="no scheme";continue}this._scheme=m;m="";if(s)break e;e(this._scheme)&&(this._isRelative=!0);l="file"===this._scheme?"relative":this._isRelative&&o&&o._scheme===this._scheme?"relative or authority":this._isRelative?"authority first slash":"scheme data"}break;case"scheme data":if("?"===k){this._query="?";l="query"}else if("#"===k){this._fragment="#";l="fragment"}else k!==d&&"\t"!==k&&"\n"!==k&&"\r"!==k&&(this._schemeData+=i(k));break;case"no scheme":if(o&&e(o._scheme)){l="relative";continue}c("Missing scheme.");a.call(this);break;case"relative or authority":if("/"!==k||"/"!==t[h+1]){c("Expected /, got: "+k);l="relative";continue}l="authority ignore slashes";break;case"relative":this._isRelative=!0;"file"!==this._scheme&&(this._scheme=o._scheme);if(k===d){this._host=o._host;this._port=o._port;this._path=o._path.slice();this._query=o._query;this._username=o._username;this._password=o._password;break e}if("/"===k||"\\"===k){"\\"===k&&c("\\ is an invalid code point.");l="relative slash"}else if("?"===k){this._host=o._host;this._port=o._port;this._path=o._path.slice();this._query="?";this._username=o._username;this._password=o._password;l="query"}else{if("#"!==k){var w=t[h+1],C=t[h+2];if("file"!==this._scheme||!g.test(k)||":"!==w&&"|"!==w||C!==d&&"/"!==C&&"\\"!==C&&"?"!==C&&"#"!==C){this._host=o._host;this._port=o._port;this._username=o._username;this._password=o._password;this._path=o._path.slice();this._path.pop()}l="relative path";continue}this._host=o._host;this._port=o._port;this._path=o._path.slice();this._query=o._query;this._fragment="#";this._username=o._username;this._password=o._password;l="fragment"}break;case"relative slash":if("/"!==k&&"\\"!==k){if("file"!==this._scheme){this._host=o._host;this._port=o._port;this._username=o._username;this._password=o._password}l="relative path";continue}"\\"===k&&c("\\ is an invalid code point.");l="file"===this._scheme?"file host":"authority ignore slashes";break;case"authority first slash":if("/"!==k){c("Expected '/', got: "+k);l="authority ignore slashes";continue}l="authority second slash";break;case"authority second slash":l="authority ignore slashes";if("/"!==k){c("Expected '/', got: "+k);continue}break;case"authority ignore slashes":if("/"!==k&&"\\"!==k){l="authority";continue}c("Expected authority, got: "+k);break;case"authority":if("@"===k){if(b){c("@ already seen.");m+="%40"}b=!0;for(var x=0;x<m.length;x++){var S=m[x];if("\t"!==S&&"\n"!==S&&"\r"!==S)if(":"!==S||null!==this._password){var A=i(S);null!==this._password?this._password+=A:this._username+=A}else this._password="";else c("Invalid whitespace in authority.")}m=""}else{if(k===d||"/"===k||"\\"===k||"?"===k||"#"===k){h-=m.length;m="";l="host";continue}m+=k}break;case"file host":if(k===d||"/"===k||"\\"===k||"?"===k||"#"===k){if(2!==m.length||!g.test(m[0])||":"!==m[1]&&"|"!==m[1])if(0===m.length)l="relative path start";else{this._host=r.call(this,m);m="";l="relative path start"}else l="relative path";continue}"\t"===k||"\n"===k||"\r"===k?c("Invalid whitespace in file host."):m+=k;break;case"host":case"hostname":if(":"!==k||v){if(k===d||"/"===k||"\\"===k||"?"===k||"#"===k){this._host=r.call(this,m);m="";l="relative path start";if(s)break e;continue}if("\t"!==k&&"\n"!==k&&"\r"!==k){"["===k?v=!0:"]"===k&&(v=!1);m+=k}else c("Invalid code point in host/hostname: "+k)}else{this._host=r.call(this,m);m="";l="port";if("hostname"===s)break e}break;case"port":if(/[0-9]/.test(k))m+=k;else{if(k===d||"/"===k||"\\"===k||"?"===k||"#"===k||s){if(""!==m){var I=parseInt(m,10);I!==u[this._scheme]&&(this._port=I+"");m=""}if(s)break e;l="relative path start";continue}"\t"===k||"\n"===k||"\r"===k?c("Invalid code point in port: "+k):a.call(this)}break;case"relative path start":"\\"===k&&c("'\\' not allowed in path.");l="relative path";if("/"!==k&&"\\"!==k)continue;break;case"relative path":if(k!==d&&"/"!==k&&"\\"!==k&&(s||"?"!==k&&"#"!==k))"\t"!==k&&"\n"!==k&&"\r"!==k&&(m+=i(k));else{"\\"===k&&c("\\ not allowed in relative path.");var B;(B=f[m.toLowerCase()])&&(m=B);if(".."===m){this._path.pop();"/"!==k&&"\\"!==k&&this._path.push("")}else if("."===m&&"/"!==k&&"\\"!==k)this._path.push("");else if("."!==m){"file"===this._scheme&&0===this._path.length&&2===m.length&&g.test(m[0])&&"|"===m[1]&&(m=m[0]+":");this._path.push(m)}m="";if("?"===k){this._query="?";l="query"}else if("#"===k){this._fragment="#";l="fragment"}}break;case"query":if(s||"#"!==k)k!==d&&"\t"!==k&&"\n"!==k&&"\r"!==k&&(this._query+=n(k));else{this._fragment="#";l="fragment"}break;case"fragment":k!==d&&"\t"!==k&&"\n"!==k&&"\r"!==k&&(this._fragment+=k)}h++}}function o(){this._scheme="";this._schemeData="";this._username="";this._password=null;this._host="";this._port="";this._path=[];this._query="";this._fragment="";this._isInvalid=!1;this._isRelative=!1}function c(e,t){void 0===t||t instanceof c||(t=new c(String(t)));this._url=e;o.call(this);var a=e.replace(/^[ \t\r\n\f]+|[ \t\r\n\f]+$/g,"");s.call(this,a,null,t)}var l=!1;try{if("function"==typeof URL&&"object"==typeof URL.prototype&&"origin"in URL.prototype){var h=new URL("b","http://a");h.pathname="c%20d";l="http://a/c%20d"===h.href}}catch(e){}if(!l){var u=Object.create(null);u.ftp=21;u.file=0;u.gopher=70;u.http=80;u.https=443;u.ws=80;u.wss=443;var f=Object.create(null);f["%2e"]=".";f[".%2e"]="..";f["%2e."]="..";f["%2e%2e"]="..";var d,g=/[a-zA-Z]/,p=/[a-zA-Z0-9\+\-\.]/;c.prototype={toString:function(){return this.href},get href(){if(this._isInvalid)return this._url;var e="";""===this._username&&null===this._password||(e=this._username+(null!==this._password?":"+this._password:"")+"@");return this.protocol+(this._isRelative?"//"+e+this.host:"")+this.pathname+this._query+this._fragment},set href(e){o.call(this);s.call(this,e)},get protocol(){return this._scheme+":"},set protocol(e){this._isInvalid||s.call(this,e+":","scheme start")},get host(){return this._isInvalid?"":this._port?this._host+":"+this._port:this._host},set host(e){!this._isInvalid&&this._isRelative&&s.call(this,e,"host")},get hostname(){return this._host},set hostname(e){!this._isInvalid&&this._isRelative&&s.call(this,e,"hostname")},get port(){return this._port},set port(e){!this._isInvalid&&this._isRelative&&s.call(this,e,"port")},get pathname(){return this._isInvalid?"":this._isRelative?"/"+this._path.join("/"):this._schemeData},set pathname(e){if(!this._isInvalid&&this._isRelative){this._path=[];s.call(this,e,"relative path start")}},get search(){return this._isInvalid||!this._query||"?"===this._query?"":this._query},set search(e){if(!this._isInvalid&&this._isRelative){this._query="?";"?"===e[0]&&(e=e.slice(1));s.call(this,e,"query")}},get hash(){return this._isInvalid||!this._fragment||"#"===this._fragment?"":this._fragment},set hash(e){if(!this._isInvalid){this._fragment="#";"#"===e[0]&&(e=e.slice(1));s.call(this,e,"fragment")}},get origin(){var e;if(this._isInvalid||!this._scheme)return"";switch(this._scheme){case"data":case"file":case"javascript":case"mailto":return"null"}e=this.host;return e?this._scheme+"://"+e:""}};var m=t.URL;if(m){c.createObjectURL=function(e){return m.createObjectURL.apply(m,arguments)};c.revokeObjectURL=function(e){m.revokeObjectURL(e)}}t.URL=c}}()}}).call(t,a(9))}])}); \ No newline at end of file
diff --git a/vendor/project_templates/rails.tar.gz b/vendor/project_templates/rails.tar.gz
new file mode 100644
index 00000000000..0509016f130
--- /dev/null
+++ b/vendor/project_templates/rails.tar.gz
Binary files differ
diff --git a/yarn.lock b/yarn.lock
index d56d7272fe2..c9e1b630a9e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -41,10 +41,14 @@ after@0.8.2:
version "0.8.2"
resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f"
-ajv-keywords@^1.0.0, ajv-keywords@^1.1.1:
+ajv-keywords@^1.0.0:
version "1.5.1"
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-1.5.1.tgz#314dd0a4b3368fad3dfcdc54ede6171b886daf3c"
+ajv-keywords@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-2.1.0.tgz#a296e17f7bfae7c1ce4f7e0de53d29cb32162df0"
+
ajv@^4.7.0:
version "4.11.2"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.2.tgz#f166c3c11cbc6cb9dcc102a5bcfe5b72c95287e6"
@@ -52,6 +56,15 @@ ajv@^4.7.0:
co "^4.6.0"
json-stable-stringify "^1.0.1"
+ajv@^5.1.5:
+ version "5.2.0"
+ resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.2.0.tgz#c1735024c5da2ef75cc190713073d44f098bf486"
+ dependencies:
+ co "^4.6.0"
+ fast-deep-equal "^0.1.0"
+ json-schema-traverse "^0.3.0"
+ json-stable-stringify "^1.0.1"
+
align-text@^0.1.1, align-text@^0.1.3:
version "0.1.4"
resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117"
@@ -128,6 +141,10 @@ arr-flatten@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.0.1.tgz#e5ffe54d45e19f32f216e91eb99c8ce892bb604b"
+array-find-index@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1"
+
array-find@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/array-find/-/array-find-1.0.0.tgz#6c8e286d11ed768327f8e62ecee87353ca3e78b8"
@@ -136,6 +153,10 @@ array-flatten@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
+array-flatten@^2.1.0:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.1.tgz#426bb9da84090c1838d812c8150af20a8331e296"
+
array-slice@^0.2.3:
version "0.2.3"
resolved "https://registry.yarnpkg.com/array-slice/-/array-slice-0.2.3.tgz#dd3cfb80ed7973a75117cdac69b0b99ec86186f5"
@@ -233,6 +254,13 @@ aws4@^1.2.1:
version "1.6.0"
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e"
+axios@^0.16.2:
+ version "0.16.2"
+ resolved "https://registry.yarnpkg.com/axios/-/axios-0.16.2.tgz#ba4f92f17167dfbab40983785454b9ac149c3c6d"
+ dependencies:
+ follow-redirects "^1.2.3"
+ is-buffer "^1.1.5"
+
babel-code-frame@^6.11.0, babel-code-frame@^6.16.0, babel-code-frame@^6.22.0:
version "6.22.0"
resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.22.0.tgz#027620bee567a88c32561574e7fd0801d33118e4"
@@ -405,14 +433,13 @@ babel-helpers@^6.23.0:
babel-runtime "^6.22.0"
babel-template "^6.23.0"
-babel-loader@^6.2.10:
- version "6.2.10"
- resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-6.2.10.tgz#adefc2b242320cd5d15e65b31cea0e8b1b02d4b0"
+babel-loader@^7.1.1:
+ version "7.1.1"
+ resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-7.1.1.tgz#b87134c8b12e3e4c2a94e0546085bc680a2b8488"
dependencies:
- find-cache-dir "^0.1.1"
- loader-utils "^0.2.11"
+ find-cache-dir "^1.0.0"
+ loader-utils "^1.0.2"
mkdirp "^0.5.1"
- object-assign "^4.0.1"
babel-messages@^6.23.0:
version "6.23.0"
@@ -825,11 +852,7 @@ babel-types@^6.18.0, babel-types@^6.19.0, babel-types@^6.22.0, babel-types@^6.23
lodash "^4.2.0"
to-fast-properties "^1.0.1"
-babylon@^6.11.0:
- version "6.15.0"
- resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.15.0.tgz#ba65cfa1a80e1759b0e89fb562e27dccae70348e"
-
-babylon@^6.13.0, babylon@^6.15.0, babylon@^6.16.1:
+babylon@^6.11.0, babylon@^6.13.0, babylon@^6.15.0, babylon@^6.16.1:
version "6.16.1"
resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.16.1.tgz#30c5a22f481978a9e7f8cdfdf496b11d94b404d3"
@@ -887,6 +910,10 @@ block-stream@*:
dependencies:
inherits "~2.0.0"
+bluebird@^2.10.2:
+ version "2.11.0"
+ resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-2.11.0.tgz#534b9033c022c9579c56ba3b3e5a5caafbb650e1"
+
bluebird@^3.0.5, bluebird@^3.1.1, bluebird@^3.3.0:
version "3.4.7"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.4.7.tgz#f72d760be09b7f76d08ed8fae98b289a8d05fab3"
@@ -910,6 +937,17 @@ body-parser@^1.16.1:
raw-body "~2.2.0"
type-is "~1.6.15"
+bonjour@^3.5.0:
+ version "3.5.0"
+ resolved "https://registry.yarnpkg.com/bonjour/-/bonjour-3.5.0.tgz#8e890a183d8ee9a2393b3844c691a42bcf7bc9f5"
+ dependencies:
+ array-flatten "^2.1.0"
+ deep-equal "^1.0.1"
+ dns-equal "^1.0.0"
+ dns-txt "^2.0.2"
+ multicast-dns "^6.0.1"
+ multicast-dns-service-types "^1.1.0"
+
boom@2.x.x:
version "2.10.1"
resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f"
@@ -1003,6 +1041,10 @@ browserslist@^1.3.6, browserslist@^1.5.2, browserslist@^1.7.6:
caniuse-db "^1.0.30000639"
electron-to-chromium "^1.2.7"
+buffer-indexof@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/buffer-indexof/-/buffer-indexof-1.1.0.tgz#f54f647c4f4e25228baa656a2e57e43d5f270982"
+
buffer-shims@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/buffer-shims/-/buffer-shims-1.0.0.tgz#9978ce317388c649ad8793028c3477ef044a8b51"
@@ -1049,14 +1091,29 @@ callsites@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/callsites/-/callsites-0.2.0.tgz#afab96262910a7f33c19a5775825c69f34e350ca"
+camelcase-keys@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7"
+ dependencies:
+ camelcase "^2.0.0"
+ map-obj "^1.0.0"
+
camelcase@^1.0.2:
version "1.2.1"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39"
+camelcase@^2.0.0:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f"
+
camelcase@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a"
+camelcase@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd"
+
caniuse-api@^1.5.2:
version "1.6.1"
resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-1.6.1.tgz#b534e7c734c4f81ec5fbe8aca2ad24354b962c6c"
@@ -1106,6 +1163,21 @@ chokidar@^1.4.1, chokidar@^1.4.3, chokidar@^1.6.0:
optionalDependencies:
fsevents "^1.0.0"
+chokidar@^1.7.0:
+ version "1.7.0"
+ resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468"
+ dependencies:
+ anymatch "^1.3.0"
+ async-each "^1.0.0"
+ glob-parent "^2.0.0"
+ inherits "^2.0.1"
+ is-binary-path "^1.0.0"
+ is-glob "^2.0.0"
+ path-is-absolute "^1.0.0"
+ readdirp "^2.0.0"
+ optionalDependencies:
+ fsevents "^1.0.0"
+
cipher-base@^1.0.0, cipher-base@^1.0.1:
version "1.0.3"
resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.3.tgz#eeabf194419ce900da3018c207d212f2a6df0a07"
@@ -1363,6 +1435,19 @@ cookie@0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb"
+copy-webpack-plugin@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-4.0.1.tgz#9728e383b94316050d0c7463958f2b85c0aa8200"
+ dependencies:
+ bluebird "^2.10.2"
+ fs-extra "^0.26.4"
+ glob "^6.0.4"
+ is-glob "^3.1.0"
+ loader-utils "^0.2.15"
+ lodash "^4.3.0"
+ minimatch "^3.0.0"
+ node-dir "^0.1.10"
+
core-js@^2.2.0, core-js@^2.4.0, core-js@^2.4.1:
version "2.4.1"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.4.1.tgz#4de911e667b0eae9124e34254b53aea6fc618d3e"
@@ -1415,6 +1500,14 @@ cropper@^2.3.0:
dependencies:
jquery ">= 1.9.1"
+cross-spawn@^5.0.1:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449"
+ dependencies:
+ lru-cache "^4.0.1"
+ shebang-command "^1.2.0"
+ which "^1.2.9"
+
cryptiles@2.x.x:
version "2.0.5"
resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8"
@@ -1521,6 +1614,12 @@ csso@~2.3.1:
clap "^1.0.9"
source-map "^0.5.3"
+currently-unhandled@^0.4.1:
+ version "0.4.1"
+ resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea"
+ dependencies:
+ array-find-index "^1.0.1"
+
custom-event@~1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/custom-event/-/custom-event-1.0.1.tgz#5d02a46850adf1b4a317946a3928fccb5bfd0425"
@@ -1529,7 +1628,13 @@ d3@^3.5.11:
version "3.5.11"
resolved "https://registry.yarnpkg.com/d3/-/d3-3.5.11.tgz#d130750eed0554db70e8432102f920a12407b69c"
-d@^0.1.1, d@~0.1.1:
+d@1:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/d/-/d-1.0.0.tgz#754bb5bfe55451da69a58b94d45f4c5b0462d58f"
+ dependencies:
+ es5-ext "^0.10.9"
+
+d@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/d/-/d-0.1.1.tgz#da184c535d18d8ee7ba2aa229b914009fae11309"
dependencies:
@@ -1567,11 +1672,11 @@ debug@2.6.7:
dependencies:
ms "2.0.0"
-debug@^2.1.0, debug@^2.1.1, debug@^2.2.0:
- version "2.6.0"
- resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.0.tgz#bc596bcabe7617f11d9fa15361eded5608b8499b"
+debug@^2.1.0, debug@^2.1.1, debug@^2.2.0, debug@^2.4.5, debug@^2.6.6, debug@^2.6.8:
+ version "2.6.8"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc"
dependencies:
- ms "0.7.2"
+ ms "2.0.0"
decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2:
version "1.2.0"
@@ -1587,6 +1692,10 @@ decompress-response@^3.2.0:
dependencies:
mimic-response "^1.0.0"
+deep-equal@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5"
+
deep-extend@~0.4.0:
version "0.4.1"
resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.1.tgz#efe4113d08085f4e6f9687759810f807469e2253"
@@ -1623,6 +1732,17 @@ del@^2.0.2:
pinkie-promise "^2.0.0"
rimraf "^2.2.8"
+del@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/del/-/del-3.0.0.tgz#53ecf699ffcbcb39637691ab13baf160819766e5"
+ dependencies:
+ globby "^6.1.0"
+ is-path-cwd "^1.0.0"
+ is-path-in-cwd "^1.0.0"
+ p-map "^1.1.1"
+ pify "^3.0.0"
+ rimraf "^2.2.8"
+
delayed-stream@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
@@ -1668,6 +1788,23 @@ diffie-hellman@^5.0.0:
miller-rabin "^4.0.0"
randombytes "^2.0.0"
+dns-equal@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d"
+
+dns-packet@^1.0.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-1.1.1.tgz#2369d45038af045f3898e6fa56862aed3f40296c"
+ dependencies:
+ ip "^1.1.0"
+ safe-buffer "^5.0.1"
+
+dns-txt@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/dns-txt/-/dns-txt-2.0.2.tgz#b91d806f5d27188e4ab3e7d107d881a1cc4642b6"
+ dependencies:
+ buffer-indexof "^1.0.0"
+
doctrine@1.5.0, doctrine@^1.2.2:
version "1.5.0"
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa"
@@ -1834,14 +1971,14 @@ engine.io@1.8.3:
engine.io-parser "1.3.2"
ws "1.1.2"
-enhanced-resolve@^3.0.0:
- version "3.1.0"
- resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-3.1.0.tgz#9f4b626f577245edcf4b2ad83d86e17f4f421dec"
+enhanced-resolve@^3.4.0:
+ version "3.4.1"
+ resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-3.4.1.tgz#0421e339fd71419b3da13d129b3979040230476e"
dependencies:
graceful-fs "^4.1.2"
memory-fs "^0.4.0"
object-assign "^4.0.1"
- tapable "^0.2.5"
+ tapable "^0.2.7"
enhanced-resolve@~0.9.0:
version "0.9.1"
@@ -1871,52 +2008,52 @@ error-ex@^1.2.0:
dependencies:
is-arrayish "^0.2.1"
-es5-ext@^0.10.7, es5-ext@^0.10.8, es5-ext@~0.10.11, es5-ext@~0.10.2, es5-ext@~0.10.7:
- version "0.10.12"
- resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.12.tgz#aa84641d4db76b62abba5e45fd805ecbab140047"
+es5-ext@^0.10.14, es5-ext@^0.10.8, es5-ext@^0.10.9, es5-ext@~0.10.14, es5-ext@~0.10.2:
+ version "0.10.24"
+ resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.24.tgz#a55877c9924bc0c8d9bd3c2cbe17495ac1709b14"
dependencies:
es6-iterator "2"
es6-symbol "~3.1"
-es6-iterator@2:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.0.tgz#bd968567d61635e33c0b80727613c9cb4b096bac"
+es6-iterator@2, es6-iterator@~2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.1.tgz#8e319c9f0453bf575d374940a655920e59ca5512"
dependencies:
- d "^0.1.1"
- es5-ext "^0.10.7"
- es6-symbol "3"
+ d "1"
+ es5-ext "^0.10.14"
+ es6-symbol "^3.1"
es6-map@^0.1.3:
- version "0.1.4"
- resolved "https://registry.yarnpkg.com/es6-map/-/es6-map-0.1.4.tgz#a34b147be224773a4d7da8072794cefa3632b897"
+ version "0.1.5"
+ resolved "https://registry.yarnpkg.com/es6-map/-/es6-map-0.1.5.tgz#9136e0503dcc06a301690f0bb14ff4e364e949f0"
dependencies:
- d "~0.1.1"
- es5-ext "~0.10.11"
- es6-iterator "2"
- es6-set "~0.1.3"
- es6-symbol "~3.1.0"
- event-emitter "~0.3.4"
+ d "1"
+ es5-ext "~0.10.14"
+ es6-iterator "~2.0.1"
+ es6-set "~0.1.5"
+ es6-symbol "~3.1.1"
+ event-emitter "~0.3.5"
es6-promise@^3.0.2, es6-promise@~3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-3.0.2.tgz#010d5858423a5f118979665f46486a95c6ee2bb6"
-es6-set@~0.1.3:
- version "0.1.4"
- resolved "https://registry.yarnpkg.com/es6-set/-/es6-set-0.1.4.tgz#9516b6761c2964b92ff479456233a247dc707ce8"
+es6-set@~0.1.5:
+ version "0.1.5"
+ resolved "https://registry.yarnpkg.com/es6-set/-/es6-set-0.1.5.tgz#d2b3ec5d4d800ced818db538d28974db0a73ccb1"
dependencies:
- d "~0.1.1"
- es5-ext "~0.10.11"
- es6-iterator "2"
- es6-symbol "3"
- event-emitter "~0.3.4"
+ d "1"
+ es5-ext "~0.10.14"
+ es6-iterator "~2.0.1"
+ es6-symbol "3.1.1"
+ event-emitter "~0.3.5"
-es6-symbol@3, es6-symbol@~3.1, es6-symbol@~3.1.0:
- version "3.1.0"
- resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.0.tgz#94481c655e7a7cad82eba832d97d5433496d7ffa"
+es6-symbol@3, es6-symbol@3.1.1, es6-symbol@^3.1, es6-symbol@~3.1, es6-symbol@~3.1.1:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.1.tgz#bf00ef4fdab6ba1b46ecb7b629b4c7ed5715cc77"
dependencies:
- d "~0.1.1"
- es5-ext "~0.10.11"
+ d "1"
+ es5-ext "~0.10.14"
es6-weak-map@^2.0.1:
version "2.0.1"
@@ -1967,12 +2104,12 @@ eslint-import-resolver-node@^0.2.0:
object-assign "^4.0.1"
resolve "^1.1.6"
-eslint-import-resolver-webpack@^0.8.1:
- version "0.8.1"
- resolved "https://registry.yarnpkg.com/eslint-import-resolver-webpack/-/eslint-import-resolver-webpack-0.8.1.tgz#c7f8b4d5bd3c5b489457e5728c5db1c4ffbac9aa"
+eslint-import-resolver-webpack@^0.8.3:
+ version "0.8.3"
+ resolved "https://registry.yarnpkg.com/eslint-import-resolver-webpack/-/eslint-import-resolver-webpack-0.8.3.tgz#ad61e28df378a474459d953f246fd43f92675385"
dependencies:
array-find "^1.0.0"
- debug "^2.2.0"
+ debug "^2.6.8"
enhanced-resolve "~0.9.0"
find-root "^0.1.1"
has "^1.0.1"
@@ -2112,12 +2249,12 @@ eve-raphael@0.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/eve-raphael/-/eve-raphael-0.5.0.tgz#17c754b792beef3fa6684d79cf5a47c63c4cda30"
-event-emitter@~0.3.4:
- version "0.3.4"
- resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.4.tgz#8d63ddfb4cfe1fae3b32ca265c4c720222080bb5"
+event-emitter@~0.3.5:
+ version "0.3.5"
+ resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39"
dependencies:
- d "~0.1.1"
- es5-ext "~0.10.7"
+ d "1"
+ es5-ext "~0.10.14"
event-stream@~3.3.0:
version "3.3.4"
@@ -2151,6 +2288,18 @@ evp_bytestokey@^1.0.0:
dependencies:
create-hash "^1.1.1"
+execa@^0.7.0:
+ version "0.7.0"
+ resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777"
+ dependencies:
+ cross-spawn "^5.0.1"
+ get-stream "^3.0.0"
+ is-stream "^1.1.0"
+ npm-run-path "^2.0.0"
+ p-finally "^1.0.0"
+ signal-exit "^3.0.0"
+ strip-eof "^1.0.0"
+
exit-hook@^1.0.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8"
@@ -2236,6 +2385,10 @@ extsprintf@1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.0.2.tgz#e1080e0658e300b06294990cc70e1502235fd550"
+fast-deep-equal@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-0.1.0.tgz#5c6f4599aba6b333ee3342e2ed978672f1001f8d"
+
fast-levenshtein@~2.0.4:
version "2.0.6"
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
@@ -2323,13 +2476,13 @@ finalhandler@1.0.3, finalhandler@~1.0.3:
statuses "~1.3.1"
unpipe "~1.0.0"
-find-cache-dir@^0.1.1:
- version "0.1.1"
- resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-0.1.1.tgz#c8defae57c8a52a8a784f9e31c57c742e993a0b9"
+find-cache-dir@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-1.0.0.tgz#9288e3e9e3cc3748717d39eade17cf71fc30ee6f"
dependencies:
commondir "^1.0.1"
- mkdirp "^0.5.1"
- pkg-dir "^1.0.0"
+ make-dir "^1.0.0"
+ pkg-dir "^2.0.0"
find-root@^0.1.1:
version "0.1.2"
@@ -2342,7 +2495,7 @@ find-up@^1.0.0:
path-exists "^2.0.0"
pinkie-promise "^2.0.0"
-find-up@^2.1.0:
+find-up@^2.0.0, find-up@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7"
dependencies:
@@ -2361,6 +2514,12 @@ flatten@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782"
+follow-redirects@^1.2.3:
+ version "1.2.3"
+ resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.2.3.tgz#01abaeca85e3609837d9fcda3167a7e42fdaca21"
+ dependencies:
+ debug "^2.4.5"
+
for-in@^0.1.5:
version "0.1.6"
resolved "https://registry.yarnpkg.com/for-in/-/for-in-0.1.6.tgz#c9f96e89bfad18a545af5ec3ed352a1d9e5b4dc8"
@@ -2401,6 +2560,16 @@ fs-access@^1.0.0:
dependencies:
null-check "^1.0.0"
+fs-extra@^0.26.4:
+ version "0.26.7"
+ resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-0.26.7.tgz#9ae1fdd94897798edab76d0918cf42d0c3184fa9"
+ dependencies:
+ graceful-fs "^4.1.2"
+ jsonfile "^2.1.0"
+ klaw "^1.0.0"
+ path-is-absolute "^1.0.0"
+ rimraf "^2.2.8"
+
fs.realpath@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
@@ -2461,6 +2630,10 @@ get-caller-file@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5"
+get-stdin@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe"
+
get-stream@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14"
@@ -2494,6 +2667,16 @@ glob@^5.0.15:
once "^1.3.0"
path-is-absolute "^1.0.0"
+glob@^6.0.4:
+ version "6.0.4"
+ resolved "https://registry.yarnpkg.com/glob/-/glob-6.0.4.tgz#0f08860f6a155127b2fadd4f9ce24b1aab6e4d22"
+ dependencies:
+ inflight "^1.0.4"
+ inherits "2"
+ minimatch "2 || 3"
+ once "^1.3.0"
+ path-is-absolute "^1.0.0"
+
glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1:
version "7.1.1"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8"
@@ -2520,6 +2703,16 @@ globby@^5.0.0:
pify "^2.0.0"
pinkie-promise "^2.0.0"
+globby@^6.1.0:
+ version "6.1.0"
+ resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c"
+ dependencies:
+ array-union "^1.0.1"
+ glob "^7.0.3"
+ object-assign "^4.0.1"
+ pify "^2.0.0"
+ pinkie-promise "^2.0.0"
+
good-listener@^1.2.0:
version "1.2.2"
resolved "https://registry.yarnpkg.com/good-listener/-/good-listener-1.2.2.tgz#d53b30cdf9313dffb7dc9a0d477096aa6d145c50"
@@ -2560,7 +2753,7 @@ got@^7.0.0:
url-parse-lax "^1.0.0"
url-to-options "^1.0.1"
-graceful-fs@^4.1.11, graceful-fs@^4.1.2:
+graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9:
version "4.1.11"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"
@@ -2617,6 +2810,10 @@ has-flag@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa"
+has-flag@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51"
+
has-symbol-support-x@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/has-symbol-support-x/-/has-symbol-support-x-1.3.0.tgz#588bd6927eaa0e296afae24160659167fc2be4f8"
@@ -2776,10 +2973,23 @@ immediate@~3.0.5:
version "3.0.6"
resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"
+imports-loader@^0.7.1:
+ version "0.7.1"
+ resolved "https://registry.yarnpkg.com/imports-loader/-/imports-loader-0.7.1.tgz#f204b5f34702a32c1db7d48d89d5e867a0441253"
+ dependencies:
+ loader-utils "^1.0.2"
+ source-map "^0.5.6"
+
imurmurhash@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
+indent-string@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80"
+ dependencies:
+ repeating "^2.0.0"
+
indexes-of@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607"
@@ -2829,6 +3039,12 @@ inquirer@^0.12.0:
strip-ansi "^3.0.0"
through "^2.3.6"
+internal-ip@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-1.2.0.tgz#ae9fbf93b984878785d50a8de1b356956058cf5c"
+ dependencies:
+ meow "^3.3.0"
+
interpret@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.0.1.tgz#d579fb7f693b858004947af39fa0db49f795602c"
@@ -2843,6 +3059,10 @@ invert-kv@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6"
+ip@^1.1.0:
+ version "1.1.5"
+ resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a"
+
ipaddr.js@1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.3.0.tgz#1e03a52fdad83a8bbb2b25cbf4998b4cffcd3dec"
@@ -2868,9 +3088,9 @@ is-binary-path@^1.0.0:
dependencies:
binary-extensions "^1.0.0"
-is-buffer@^1.0.2:
- version "1.1.4"
- resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.4.tgz#cfc86ccd5dc5a52fa80489111c6920c457e2d98b"
+is-buffer@^1.0.2, is-buffer@^1.1.5:
+ version "1.1.5"
+ resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.5.tgz#1f3b26ef613b214b88cbca23cc6c01d87961eecc"
is-builtin-module@^1.0.0:
version "1.0.0"
@@ -3007,7 +3227,7 @@ is-retry-allowed@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz#11a060568b67339444033d0125a61a20d564fb34"
-is-stream@^1.0.0:
+is-stream@^1.0.0, is-stream@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
@@ -3230,6 +3450,10 @@ json-loader@^0.5.4:
version "0.5.4"
resolved "https://registry.yarnpkg.com/json-loader/-/json-loader-0.5.4.tgz#8baa1365a632f58a3c46d20175fc6002c96e37de"
+json-schema-traverse@^0.3.0:
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.0.tgz#0016c0b1ca1efe46d44d37541bcdfc19dcfae0db"
+
json-schema@0.2.3:
version "0.2.3"
resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13"
@@ -3252,6 +3476,12 @@ json5@^0.5.0, json5@^0.5.1:
version "0.5.1"
resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821"
+jsonfile@^2.1.0:
+ version "2.4.0"
+ resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-2.4.0.tgz#3736a2b428b87bbda0cc83b53fa3d633a35c2ae8"
+ optionalDependencies:
+ graceful-fs "^4.1.6"
+
jsonify@~0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"
@@ -3311,9 +3541,9 @@ karma-sourcemap-loader@^0.3.7:
dependencies:
graceful-fs "^4.1.2"
-karma-webpack@^2.0.2:
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/karma-webpack/-/karma-webpack-2.0.2.tgz#bd38350af5645c9644090770939ebe7ce726f864"
+karma-webpack@^2.0.4:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/karma-webpack/-/karma-webpack-2.0.4.tgz#3e2d4f48ba94a878e1c66bb8e1ae6128987a175b"
dependencies:
async "~0.9.0"
loader-utils "^0.2.5"
@@ -3359,6 +3589,12 @@ kind-of@^3.0.2:
dependencies:
is-buffer "^1.0.2"
+klaw@^1.0.0:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/klaw/-/klaw-1.3.1.tgz#4088433b46b3b1ba259d78785d8e96f73ba02439"
+ optionalDependencies:
+ graceful-fs "^4.1.9"
+
latest-version@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-1.0.1.tgz#72cfc46e3e8d1be651e1ebb54ea9f6ea96f374bb"
@@ -3398,11 +3634,20 @@ load-json-file@^1.0.0:
pinkie-promise "^2.0.0"
strip-bom "^2.0.0"
+load-json-file@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8"
+ dependencies:
+ graceful-fs "^4.1.2"
+ parse-json "^2.2.0"
+ pify "^2.0.0"
+ strip-bom "^3.0.0"
+
loader-runner@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.3.0.tgz#f482aea82d543e07921700d5a46ef26fdac6b8a2"
-loader-utils@^0.2.11, loader-utils@^0.2.16, loader-utils@^0.2.5:
+loader-utils@^0.2.15, loader-utils@^0.2.5:
version "0.2.16"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-0.2.16.tgz#f08632066ed8282835dff88dfb52704765adee6d"
dependencies:
@@ -3578,6 +3823,10 @@ log4js@^0.6.31:
readable-stream "~1.0.2"
semver "~4.3.3"
+loglevel@^1.4.1:
+ version "1.4.1"
+ resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.4.1.tgz#95b383f91a3c2756fd4ab093667e4309161f2bcd"
+
longest@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097"
@@ -3588,6 +3837,13 @@ loose-envify@^1.0.0:
dependencies:
js-tokens "^3.0.0"
+loud-rejection@^1.0.0:
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f"
+ dependencies:
+ currently-unhandled "^0.4.1"
+ signal-exit "^3.0.0"
+
lowercase-keys@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306"
@@ -3613,6 +3869,16 @@ macaddress@^0.2.8:
version "0.2.8"
resolved "https://registry.yarnpkg.com/macaddress/-/macaddress-0.2.8.tgz#5904dc537c39ec6dbefeae902327135fa8511f12"
+make-dir@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.0.0.tgz#97a011751e91dd87cfadef58832ebb04936de978"
+ dependencies:
+ pify "^2.3.0"
+
+map-obj@^1.0.0, map-obj@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d"
+
map-stream@~0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/map-stream/-/map-stream-0.1.0.tgz#e56aa94c4c8055a16404a0674b78f215f7c8e194"
@@ -3629,6 +3895,12 @@ media-typer@0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
+mem@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/mem/-/mem-1.1.0.tgz#5edd52b485ca1d900fe64895505399a0dfa45f76"
+ dependencies:
+ mimic-fn "^1.0.0"
+
memory-fs@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.2.0.tgz#f2bb25368bc121e391c2520de92969caee0a0290"
@@ -3640,6 +3912,21 @@ memory-fs@^0.4.0, memory-fs@~0.4.1:
errno "^0.1.3"
readable-stream "^2.0.1"
+meow@^3.3.0:
+ version "3.7.0"
+ resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb"
+ dependencies:
+ camelcase-keys "^2.0.0"
+ decamelize "^1.1.2"
+ loud-rejection "^1.0.0"
+ map-obj "^1.0.1"
+ minimist "^1.1.3"
+ normalize-package-data "^2.3.4"
+ object-assign "^4.0.1"
+ read-pkg-up "^1.0.1"
+ redent "^1.0.0"
+ trim-newlines "^1.0.0"
+
merge-descriptors@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
@@ -3673,30 +3960,24 @@ miller-rabin@^4.0.0:
bn.js "^4.0.0"
brorand "^1.0.1"
-"mime-db@>= 1.24.0 < 2", mime-db@~1.26.0:
- version "1.26.0"
- resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.26.0.tgz#eaffcd0e4fc6935cf8134da246e2e6c35305adff"
-
-mime-db@~1.27.0:
+"mime-db@>= 1.24.0 < 2", mime-db@~1.27.0:
version "1.27.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.27.0.tgz#820f572296bbd20ec25ed55e5b5de869e5436eb1"
-mime-types@^2.1.12, mime-types@~2.1.15, mime-types@~2.1.7:
+mime-types@^2.1.12, mime-types@~2.1.11, mime-types@~2.1.15, mime-types@~2.1.7:
version "2.1.15"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.15.tgz#a4ebf5064094569237b8cf70046776d09fc92aed"
dependencies:
mime-db "~1.27.0"
-mime-types@~2.1.11:
- version "2.1.14"
- resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.14.tgz#f7ef7d97583fcaf3b7d282b6f8b5679dab1e94ee"
- dependencies:
- mime-db "~1.26.0"
-
mime@1.3.4, mime@1.3.x, mime@^1.3.4:
version "1.3.4"
resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.4.tgz#115f9e3b6b3daf2959983cb38f149a2d40eb5d53"
+mimic-fn@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.1.0.tgz#e667783d92e89dbd342818b5230b9d62a672ad18"
+
mimic-response@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.0.tgz#df3d3652a73fded6b9b0b24146e6fd052353458e"
@@ -3715,7 +3996,7 @@ minimist@0.0.8, minimist@~0.0.1:
version "0.0.8"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
-minimist@^1.2.0:
+minimist@^1.1.3, minimist@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
@@ -3729,6 +4010,10 @@ moment@2.x:
version "2.17.1"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.17.1.tgz#fed9506063f36b10f066c8b59a144d7faebe1d82"
+monaco-editor@0.8.3:
+ version "0.8.3"
+ resolved "https://registry.yarnpkg.com/monaco-editor/-/monaco-editor-0.8.3.tgz#523bdf2d1524db2c2dfc3cae0a7b6edc48d6dea6"
+
mousetrap@^1.4.6:
version "1.4.6"
resolved "https://registry.yarnpkg.com/mousetrap/-/mousetrap-1.4.6.tgz#eaca72e22e56d5b769b7555873b688c3332e390a"
@@ -3745,6 +4030,17 @@ ms@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
+multicast-dns-service-types@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz#899f11d9686e5e05cb91b35d5f0e63b773cfc901"
+
+multicast-dns@^6.0.1:
+ version "6.1.1"
+ resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-6.1.1.tgz#6e7de86a570872ab17058adea7160bbeca814dde"
+ dependencies:
+ dns-packet "^1.0.1"
+ thunky "^0.1.0"
+
mute-stream@0.0.5:
version "0.0.5"
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.5.tgz#8fbfabb0a98a253d3184331f9e8deb7372fac6c0"
@@ -3771,9 +4067,15 @@ nested-error-stacks@^1.0.0:
dependencies:
inherits "~2.0.1"
-node-ensure@^0.0.0:
- version "0.0.0"
- resolved "https://registry.yarnpkg.com/node-ensure/-/node-ensure-0.0.0.tgz#ecae764150de99861ec5c810fd5d096b183932a7"
+node-dir@^0.1.10:
+ version "0.1.17"
+ resolved "https://registry.yarnpkg.com/node-dir/-/node-dir-0.1.17.tgz#5f5665d93351335caabef8f1c554516cf5f1e4e5"
+ dependencies:
+ minimatch "^3.0.2"
+
+node-forge@0.6.33:
+ version "0.6.33"
+ resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.6.33.tgz#463811879f573d45155ad6a9f43dc296e8e85ebc"
node-libs-browser@^1.0.0:
version "1.1.1"
@@ -3881,7 +4183,7 @@ nopt@~1.0.10:
dependencies:
abbrev "1"
-normalize-package-data@^2.3.2:
+normalize-package-data@^2.3.2, normalize-package-data@^2.3.4:
version "2.3.5"
resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.3.5.tgz#8d924f142960e1777e7ffe170543631cc7cb02df"
dependencies:
@@ -3907,6 +4209,12 @@ normalize-url@^1.4.0:
query-string "^4.1.0"
sort-keys "^1.0.0"
+npm-run-path@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f"
+ dependencies:
+ path-key "^2.0.0"
+
npmlog@^4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.0.2.tgz#d03950e0e78ce1527ba26d2a7592e9348ac3e75f"
@@ -4038,6 +4346,14 @@ os-locale@^1.4.0:
dependencies:
lcid "^1.0.0"
+os-locale@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-2.1.0.tgz#42bc2900a6b5b8bd17376c8e882b65afccf24bf2"
+ dependencies:
+ execa "^0.7.0"
+ lcid "^1.0.0"
+ mem "^1.1.0"
+
os-tmpdir@^1.0.0, os-tmpdir@^1.0.1, os-tmpdir@~1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
@@ -4067,6 +4383,10 @@ p-locate@^2.0.0:
dependencies:
p-limit "^1.1.0"
+p-map@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.1.1.tgz#05f5e4ae97a068371bc2a5cc86bfbdbc19c4ae7a"
+
p-timeout@^1.1.1:
version "1.2.0"
resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-1.2.0.tgz#9820f99434c5817868b4f34809ee5291660d5b6c"
@@ -4157,6 +4477,10 @@ path-is-inside@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53"
+path-key@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40"
+
path-parse@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1"
@@ -4173,6 +4497,12 @@ path-type@^1.0.0:
pify "^2.0.0"
pinkie-promise "^2.0.0"
+path-type@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73"
+ dependencies:
+ pify "^2.0.0"
+
pause-stream@0.0.11:
version "0.0.11"
resolved "https://registry.yarnpkg.com/pause-stream/-/pause-stream-0.0.11.tgz#fe5a34b0cbce12b5aa6a2b403ee2e73b602f1445"
@@ -4185,17 +4515,14 @@ pbkdf2@^3.0.3:
dependencies:
create-hmac "^1.1.2"
-pdfjs-dist@^1.8.252:
- version "1.8.252"
- resolved "https://registry.yarnpkg.com/pdfjs-dist/-/pdfjs-dist-1.8.252.tgz#2477245695341f7fe096824dacf327bc324c0f52"
- dependencies:
- node-ensure "^0.0.0"
- worker-loader "^0.8.0"
-
-pify@^2.0.0:
+pify@^2.0.0, pify@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
+pify@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176"
+
pikaday@^1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/pikaday/-/pikaday-1.5.1.tgz#0a48549bc1a14ea1d08c44074d761bc2f2bfcfd3"
@@ -4218,6 +4545,12 @@ pkg-dir@^1.0.0:
dependencies:
find-up "^1.0.0"
+pkg-dir@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b"
+ dependencies:
+ find-up "^2.1.0"
+
pkg-up@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-1.0.0.tgz#3e08fb461525c4421624a33b9f7e6d0af5b05a26"
@@ -4609,6 +4942,10 @@ querystringify@0.0.x:
version "0.0.4"
resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-0.0.4.tgz#0cf7f84f9463ff0ae51c4c4b142d95be37724d9c"
+querystringify@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-1.0.0.tgz#6286242112c5b712fa654e526652bf6a13ff05cb"
+
randomatic@^1.1.3:
version "1.1.6"
resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-1.1.6.tgz#110dcabff397e9dcff7c0789ccc0a49adf1ec5bb"
@@ -4686,6 +5023,13 @@ read-pkg-up@^1.0.1:
find-up "^1.0.0"
read-pkg "^1.0.0"
+read-pkg-up@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be"
+ dependencies:
+ find-up "^2.0.0"
+ read-pkg "^2.0.0"
+
read-pkg@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28"
@@ -4694,6 +5038,14 @@ read-pkg@^1.0.0:
normalize-package-data "^2.3.2"
path-type "^1.0.0"
+read-pkg@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8"
+ dependencies:
+ load-json-file "^2.0.0"
+ normalize-package-data "^2.3.2"
+ path-type "^2.0.0"
+
readable-stream@^2.0.0, "readable-stream@^2.0.0 || ^1.1.13", readable-stream@^2.1.0, readable-stream@^2.2.2:
version "2.2.2"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.2.2.tgz#a9e6fec3c7dda85f8bb1b3ba7028604556fc825e"
@@ -4767,6 +5119,13 @@ recursive-readdir@2.1.1:
dependencies:
minimatch "3.0.3"
+redent@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde"
+ dependencies:
+ indent-string "^2.1.0"
+ strip-indent "^1.0.1"
+
reduce-css-calc@^1.2.6:
version "1.3.0"
resolved "https://registry.yarnpkg.com/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz#747c914e049614a4c9cfbba629871ad1d2927716"
@@ -4979,6 +5338,12 @@ select@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/select/-/select-1.1.2.tgz#0e7350acdec80b1108528786ec1d4418d11b396d"
+selfsigned@^1.9.1:
+ version "1.9.1"
+ resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.9.1.tgz#cdda4492d70d486570f87c65546023558e1dfa5a"
+ dependencies:
+ node-forge "0.6.33"
+
semver-diff@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36"
@@ -5058,6 +5423,16 @@ sha.js@^2.3.6:
dependencies:
inherits "^2.0.1"
+shebang-command@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
+ dependencies:
+ shebang-regex "^1.0.0"
+
+shebang-regex@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3"
+
shelljs@^0.7.5:
version "0.7.6"
resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.7.6.tgz#379cccfb56b91c8601e4793356eb5382924de9ad"
@@ -5147,16 +5522,16 @@ sockjs-client@1.0.1:
json3 "^3.3.2"
url-parse "^1.0.1"
-sockjs-client@1.1.2:
- version "1.1.2"
- resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.1.2.tgz#f0212a8550e4c9468c8cceaeefd2e3493c033ad5"
+sockjs-client@1.1.4:
+ version "1.1.4"
+ resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.1.4.tgz#5babe386b775e4cf14e7520911452654016c8b12"
dependencies:
- debug "^2.2.0"
+ debug "^2.6.6"
eventsource "0.1.6"
faye-websocket "~0.11.0"
inherits "^2.0.1"
json3 "^3.3.2"
- url-parse "^1.1.1"
+ url-parse "^1.1.8"
sockjs@0.3.18:
version "0.3.18"
@@ -5175,9 +5550,9 @@ source-list-map@^0.1.7, source-list-map@~0.1.7:
version "0.1.8"
resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-0.1.8.tgz#c550b2ab5427f6b3f21f5afead88c4f5587b2106"
-source-list-map@^1.1.1:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-1.1.1.tgz#1a33ac210ca144d1e561f906ebccab5669ff4cb4"
+source-list-map@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.0.tgz#aaa47403f7b245a92fbc97ea08f250d6087ed085"
source-map-support@^0.4.2:
version "0.4.11"
@@ -5270,10 +5645,6 @@ sshpk@^1.7.0:
jsbn "~0.1.0"
tweetnacl "~0.14.0"
-stats-webpack-plugin@^0.4.3:
- version "0.4.3"
- resolved "https://registry.yarnpkg.com/stats-webpack-plugin/-/stats-webpack-plugin-0.4.3.tgz#b2f618202f28dd04ab47d7ecf54ab846137b7aea"
-
"statuses@>= 1.3.1 < 2", statuses@~1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e"
@@ -5354,6 +5725,16 @@ strip-bom@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
+strip-eof@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"
+
+strip-indent@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2"
+ dependencies:
+ get-stdin "^4.0.1"
+
strip-json-comments@~1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-1.0.4.tgz#1e15fbcac97d3ee99bf2d73b4c656b082bbafb91"
@@ -5376,6 +5757,12 @@ supports-color@^3.1.0, supports-color@^3.1.1, supports-color@^3.1.2, supports-co
dependencies:
has-flag "^1.0.0"
+supports-color@^4.2.1:
+ version "4.2.1"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.2.1.tgz#65a4bb2631e90e02420dba5554c375a4754bb836"
+ dependencies:
+ has-flag "^2.0.0"
+
svgo@^0.7.0:
version "0.7.2"
resolved "https://registry.yarnpkg.com/svgo/-/svgo-0.7.2.tgz#9f5772413952135c6fefbf40afe6a4faa88b4bb5"
@@ -5403,9 +5790,9 @@ tapable@^0.1.8:
version "0.1.10"
resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.1.10.tgz#29c35707c2b70e50d07482b5d202e8ed446dafd4"
-tapable@^0.2.5, tapable@~0.2.5:
- version "0.2.6"
- resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.2.6.tgz#206be8e188860b514425375e6f1ae89bfb01fd8d"
+tapable@^0.2.7:
+ version "0.2.7"
+ resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.2.7.tgz#e46c0daacbb2b8a98b9b0cea0f4052105817ed5c"
tar-pack@~3.3.0:
version "3.3.0"
@@ -5458,6 +5845,10 @@ through@2, through@^2.3.6, through@~2.3, through@~2.3.1:
version "2.3.8"
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
+thunky@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/thunky/-/thunky-0.1.0.tgz#bf30146824e2b6e67b0f2d7a4ac8beb26908684e"
+
timeago.js@^2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/timeago.js/-/timeago.js-2.0.5.tgz#730c74fbdb0b0917a553675a4460e3a7f80db86c"
@@ -5520,6 +5911,10 @@ traverse@0.6.6:
version "0.6.6"
resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.6.6.tgz#cbdf560fd7b9af632502fed40f918c157ea97137"
+trim-newlines@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613"
+
trim-right@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003"
@@ -5557,9 +5952,9 @@ typedarray@^0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
-uglify-js@^2.6, uglify-js@^2.8.27:
- version "2.8.27"
- resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.27.tgz#47787f912b0f242e5b984343be8e35e95f694c9c"
+uglify-js@^2.6, uglify-js@^2.8.29:
+ version "2.8.29"
+ resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.29.tgz#29c5733148057bb4e1f75df35b7a9cb72e6a59dd"
dependencies:
source-map "~0.5.1"
yargs "~3.10.0"
@@ -5570,6 +5965,14 @@ uglify-to-browserify@~1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7"
+uglifyjs-webpack-plugin@^0.4.6:
+ version "0.4.6"
+ resolved "https://registry.yarnpkg.com/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-0.4.6.tgz#b951f4abb6bd617e66f63eb891498e391763e309"
+ dependencies:
+ source-map "^0.5.6"
+ uglify-js "^2.8.29"
+ webpack-sources "^1.0.1"
+
uid-number@~0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81"
@@ -5644,13 +6047,20 @@ url-parse@1.0.x:
querystringify "0.0.x"
requires-port "1.0.x"
-url-parse@^1.0.1, url-parse@^1.1.1:
+url-parse@^1.0.1:
version "1.1.7"
resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.1.7.tgz#025cff999653a459ab34232147d89514cc87d74a"
dependencies:
querystringify "0.0.x"
requires-port "1.0.x"
+url-parse@^1.1.8:
+ version "1.1.9"
+ resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.1.9.tgz#c67f1d775d51f0a18911dd7b3ffad27bb9e5bd19"
+ dependencies:
+ querystringify "~1.0.0"
+ requires-port "1.0.x"
+
url-to-options@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/url-to-options/-/url-to-options-1.0.1.tgz#1505a03a289a48cbd7a434efbaeec5055f5633a9"
@@ -5781,12 +6191,12 @@ vue@^2.2.6:
version "2.2.6"
resolved "https://registry.yarnpkg.com/vue/-/vue-2.2.6.tgz#451714b394dd6d4eae7b773c40c2034a59621aed"
-watchpack@^1.3.1:
- version "1.3.1"
- resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.3.1.tgz#7d8693907b28ce6013e7f3610aa2a1acf07dad87"
+watchpack@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.4.0.tgz#4a1472bcbb952bd0a9bb4036801f954dfb39faac"
dependencies:
async "^2.1.2"
- chokidar "^1.4.3"
+ chokidar "^1.7.0"
graceful-fs "^4.1.2"
wbuf@^1.1.0, wbuf@^1.4.0:
@@ -5811,35 +6221,40 @@ webpack-bundle-analyzer@^2.8.2:
opener "^1.4.3"
ws "^2.3.1"
-webpack-dev-middleware@^1.0.11, webpack-dev-middleware@^1.9.0:
- version "1.10.0"
- resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-1.10.0.tgz#7d5be2651e692fddfafd8aaed177c16ff51f0eb8"
+webpack-dev-middleware@^1.0.11, webpack-dev-middleware@^1.11.0:
+ version "1.11.0"
+ resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-1.11.0.tgz#09691d0973a30ad1f82ac73a12e2087f0a4754f9"
dependencies:
memory-fs "~0.4.1"
mime "^1.3.4"
path-is-absolute "^1.0.0"
range-parser "^1.0.3"
-webpack-dev-server@^2.4.2:
- version "2.4.2"
- resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-2.4.2.tgz#cf595d6b40878452b6d2ad7229056b686f8a16be"
+webpack-dev-server@^2.6.1:
+ version "2.6.1"
+ resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-2.6.1.tgz#0b292a9da96daf80a65988f69f87b4166e5defe7"
dependencies:
ansi-html "0.0.7"
+ bonjour "^3.5.0"
chokidar "^1.6.0"
compression "^1.5.2"
connect-history-api-fallback "^1.3.0"
+ del "^3.0.0"
express "^4.13.3"
html-entities "^1.2.0"
http-proxy-middleware "~0.17.4"
+ internal-ip "^1.2.0"
+ loglevel "^1.4.1"
opn "4.0.2"
portfinder "^1.0.9"
+ selfsigned "^1.9.1"
serve-index "^1.7.2"
sockjs "0.3.18"
- sockjs-client "1.1.2"
+ sockjs-client "1.1.4"
spdy "^3.4.1"
strip-ansi "^3.0.0"
supports-color "^3.1.1"
- webpack-dev-middleware "^1.9.0"
+ webpack-dev-middleware "^1.11.0"
yargs "^6.0.0"
webpack-sources@^0.1.0:
@@ -5849,38 +6264,43 @@ webpack-sources@^0.1.0:
source-list-map "~0.1.7"
source-map "~0.5.3"
-webpack-sources@^0.2.3:
- version "0.2.3"
- resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-0.2.3.tgz#17c62bfaf13c707f9d02c479e0dcdde8380697fb"
+webpack-sources@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.0.1.tgz#c7356436a4d13123be2e2426a05d1dad9cbe65cf"
dependencies:
- source-list-map "^1.1.1"
+ source-list-map "^2.0.0"
source-map "~0.5.3"
-webpack@^2.6.1:
- version "2.6.1"
- resolved "https://registry.yarnpkg.com/webpack/-/webpack-2.6.1.tgz#2e0457f0abb1ac5df3ab106c69c672f236785f07"
+webpack-stats-plugin@^0.1.5:
+ version "0.1.5"
+ resolved "https://registry.yarnpkg.com/webpack-stats-plugin/-/webpack-stats-plugin-0.1.5.tgz#29e5f12ebfd53158d31d656a113ac1f7b86179d9"
+
+webpack@^3.4.0:
+ version "3.4.0"
+ resolved "https://registry.yarnpkg.com/webpack/-/webpack-3.4.0.tgz#e9465b660ad79dd2d33874d968b31746ea9a8e63"
dependencies:
acorn "^5.0.0"
acorn-dynamic-import "^2.0.0"
- ajv "^4.7.0"
- ajv-keywords "^1.1.1"
+ ajv "^5.1.5"
+ ajv-keywords "^2.0.0"
async "^2.1.2"
- enhanced-resolve "^3.0.0"
+ enhanced-resolve "^3.4.0"
+ escope "^3.6.0"
interpret "^1.0.0"
json-loader "^0.5.4"
json5 "^0.5.1"
loader-runner "^2.3.0"
- loader-utils "^0.2.16"
+ loader-utils "^1.1.0"
memory-fs "~0.4.1"
mkdirp "~0.5.0"
node-libs-browser "^2.0.0"
source-map "^0.5.3"
- supports-color "^3.1.0"
- tapable "~0.2.5"
- uglify-js "^2.8.27"
- watchpack "^1.3.1"
- webpack-sources "^0.2.3"
- yargs "^6.0.0"
+ supports-color "^4.2.1"
+ tapable "^0.2.7"
+ uglifyjs-webpack-plugin "^0.4.6"
+ watchpack "^1.4.0"
+ webpack-sources "^1.0.1"
+ yargs "^8.0.2"
websocket-driver@>=0.3.6, websocket-driver@>=0.5.1:
version "0.6.5"
@@ -5900,7 +6320,11 @@ which-module@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f"
-which@^1.1.1, which@^1.2.1:
+which-module@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
+
+which@^1.1.1, which@^1.2.1, which@^1.2.9:
version "1.2.12"
resolved "https://registry.yarnpkg.com/which/-/which-1.2.12.tgz#de67b5e450269f194909ef23ece4ebe416fa1192"
dependencies:
@@ -5928,12 +6352,6 @@ wordwrap@~0.0.2:
version "0.0.3"
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107"
-worker-loader@^0.8.0:
- version "0.8.0"
- resolved "https://registry.yarnpkg.com/worker-loader/-/worker-loader-0.8.0.tgz#13582960dcd7d700dc829d3fd252a7561696167e"
- dependencies:
- loader-utils "^1.0.2"
-
wrap-ansi@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85"
@@ -6005,6 +6423,12 @@ yargs-parser@^4.2.0:
dependencies:
camelcase "^3.0.0"
+yargs-parser@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-7.0.0.tgz#8d0ac42f16ea55debd332caf4c4038b3e3f5dfd9"
+ dependencies:
+ camelcase "^4.1.0"
+
yargs@^6.0.0:
version "6.6.0"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-6.6.0.tgz#782ec21ef403345f830a808ca3d513af56065208"
@@ -6023,6 +6447,24 @@ yargs@^6.0.0:
y18n "^3.2.1"
yargs-parser "^4.2.0"
+yargs@^8.0.2:
+ version "8.0.2"
+ resolved "https://registry.yarnpkg.com/yargs/-/yargs-8.0.2.tgz#6299a9055b1cefc969ff7e79c1d918dceb22c360"
+ dependencies:
+ camelcase "^4.1.0"
+ cliui "^3.2.0"
+ decamelize "^1.1.1"
+ get-caller-file "^1.0.1"
+ os-locale "^2.0.0"
+ read-pkg-up "^2.0.0"
+ require-directory "^2.1.1"
+ require-main-filename "^1.0.1"
+ set-blocking "^2.0.0"
+ string-width "^2.0.0"
+ which-module "^2.0.0"
+ y18n "^3.2.1"
+ yargs-parser "^7.0.0"
+
yargs@~3.10.0:
version "3.10.0"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1"